diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9d7e950..1d5d4456 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,6 +181,196 @@ jobs: if-no-files-found: error retention-days: 7 + xbox-uwp-changes: + name: detect Xbox UWP changes + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.paths.outputs.changed }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - id: paths + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.sha }} + run: | + if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(ports/uwp/|scripts/build_xbox_uwp\.sh$|scripts/xbox-uwp/|scripts/pack_love\.sh$|\.github/workflows/(ci|release)\.yml$|src/core/Platform\.lua$|src/import/(CacheFs|LauncherView|RomImporter)\.lua$|src/update/Check\.lua$|tests/engine/(platform_nx|uwp_baseroms|uwp_native_picker)_test\.lua$|tests/rom_importer_double_pick_test\.lua$)'; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + xbox-uwp-selftest: + name: Xbox UWP offline selftest + needs: xbox-uwp-changes + if: needs.xbox-uwp-changes.outputs.changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Xbox UWP offline selftest + run: bash scripts/xbox-uwp/selftest_build_xbox_uwp.sh + - name: Build shared payload + run: | + scripts/pack_love.sh \ + --output .bazinga/work/ci-game.love \ + --listing .bazinga/work/ci-love-listing.txt \ + --version 0.0.0 + - name: Upload shared payload + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-xbox-uwp-payload + path: .bazinga/work/ci-game.love + if-no-files-found: error + retention-days: 1 + + xbox-uwp-build: + name: Xbox UWP build + needs: [xbox-uwp-changes, xbox-uwp-selftest] + if: | + always() + && needs.xbox-uwp-changes.outputs.changed == 'true' + && needs.xbox-uwp-selftest.result == 'success' + runs-on: windows-2022 + steps: + - uses: actions/checkout@v7 + - name: Download shared payload + uses: actions/download-artifact@v8 + with: + name: gen1recomp-xbox-uwp-payload + path: .bazinga/work + - name: Build Xbox UWP package + shell: bash + run: | + bash scripts/build_xbox_uwp.sh \ + --release \ + --version 0.0.0 \ + --game-love .bazinga/work/ci-game.love + - name: Upload Xbox UWP package + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-xbox-uwp + path: | + dist/xbox-uwp/gen1recomp-0.0.0-xbox-uwp.zip + dist/xbox-uwp/gen1recomp-0.0.0-xbox-uwp.zip.sha256 + if-no-files-found: error + retention-days: 7 + + linux-arm64-changes: + name: detect Linux arm64 changes + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.paths.outputs.changed }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - id: paths + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.sha }} + run: | + if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_linux_arm64\.sh$|scripts/linux-arm64/|scripts/pack_love\.sh$|docs/linux-arm64-build\.md$|\.github/workflows/(ci|release)\.yml$)'; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + linux-arm64-selftest: + name: Linux arm64 offline selftest + needs: linux-arm64-changes + if: needs.linux-arm64-changes.outputs.changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + # Deliberately on x86_64: everything this gate checks (pins, the + # host-arch guard, the dependency exclude list, the AppRun fusion + # contract) is answerable without a container or an aarch64 machine, + # so the slow native job below only ever starts on a sane tree. + - name: Linux arm64 offline selftest + run: bash scripts/linux-arm64/selftest_build_linux_arm64.sh + + linux-arm64-build: + name: Linux arm64 AppImage build + needs: [linux-arm64-changes, linux-arm64-selftest] + if: | + always() + && needs.linux-arm64-changes.outputs.changed == 'true' + && needs.linux-arm64-selftest.result == 'success' + # No fork restriction, unlike switch-build: this needs no secrets and no + # self-hosted hardware, just GitHub's free arm64 runner for public repos, + # so contributors get the same coverage on their own PRs. + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v7 + - name: Build the aarch64 AppImage + run: | + set -euo pipefail + scripts/build_linux_arm64.sh --version 0.0.0 + - name: Verify the AppImage is self-contained and bullseye-compatible + run: | + set -euo pipefail + image="dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage" + + # --appimage-extract needs no FUSE, so this works on a runner + # without /dev/fuse and still exercises the real payload. + "$image" --appimage-extract >/dev/null + for required in AppRun bin/love game.love lib/liblove-11.5.so; do + [ -e "squashfs-root/$required" ] \ + || { echo "::error::AppImage is missing $required"; exit 1; } + done + + # Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is + # applied; an unresolved soname here is a user-visible launch crash. + # + # This runs on a HEADLESS runner on purpose, and that is the point. + # The first version of this build bundled Debian's SDL2, which + # hard-links libpulse/libasound/libX11/libwayland, so it only ever + # started on a full desktop -- a bare runner is what exposed it. + missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \ + ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \ + | grep 'not found' || true)" + [ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; } + + # Nothing may hard-link a driver, session or audio-stack library: + # those must be reached through dlopen so the AppImage runs on a box + # with only ALSA, only Wayland, or only KMSDRM. + linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do + objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}' + done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)" + [ -z "$linked" ] \ + || { echo "::error::these must be dlopened, not linked:"; echo "$linked"; exit 1; } + + # The whole point of compiling on bullseye. If a future change moves + # the builder to a newer base, the glibc floor silently rises and + # every user on an older distro gets "GLIBC_2.xx not found" -- catch + # it here instead of in a release. + floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \ + | grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)" + echo "highest required glibc symbol version: $floor" + [ -n "$floor" ] \ + || { echo "::error::found no versioned glibc symbols -- objdump read nothing"; exit 1; } + highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)" + [ "$highest" = "GLIBC_2.31" ] \ + || { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; } + - name: Upload the AppImage + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-linux-arm64 + path: | + dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage + dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage.sha256 + if-no-files-found: error + retention-days: 7 + headless: name: headless suites (no ROM) runs-on: ubuntu-latest @@ -193,6 +383,9 @@ jobs: - name: install luajit run: sudo apt-get update && sudo apt-get install -y luajit + - name: install Pillow + run: python3 -m pip install --upgrade pillow + - name: interpreter version run: luajit -v diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1671a324..c63c2576 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ name: Release # Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS -# IPA, a Nintendo Switch SD-ready zip (experimental), the Anbernic RG34XXSP -# (Stock OS 64-bit MOD / PortMaster) and Linux ARM SBC PortMaster +# IPA, a Nintendo Switch SD-ready zip (experimental), Xbox UWP, the Anbernic +# RG34XXSP (Stock OS 64-bit MOD / PortMaster) and Linux ARM SBC PortMaster # handheld ports on the self-hosted Mac runner, and publishes them as a # GitHub Release. # @@ -44,22 +44,17 @@ concurrency: cancel-in-progress: false jobs: - release: - runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }} - + version: + name: determine release version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.ver.outputs.version }} + tag: ${{ steps.ver.outputs.tag }} steps: - # The self-hosted runner lives under the machine owner's home - # directory; mask it first so absolute paths in every later step's - # output show up as *** in the public workflow logs. - - name: Mask runner paths - run: echo "::add-mask::$HOME" - - - name: Checkout - uses: actions/checkout@v7 + - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - - name: Determine version id: ver env: @@ -67,7 +62,6 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - semver_re='^[0-9]+\.[0-9]+\.[0-9]+$' # 1) Explicit override from a manual run. @@ -97,7 +91,6 @@ jobs: | grep -E "$semver_re" \ | sort -t. -k1,1n -k2,2n -k3,3n \ | tail -1 || true)" - if [ -z "$latest" ]; then version="0.1.0" echo "No existing release tag; starting at $version" @@ -125,10 +118,157 @@ jobs: echo "::error::Release $tag already exists. Pick a different version." exit 1 fi - echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" + love-payload: + name: build release game.love + needs: version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Build shared payload + run: | + scripts/pack_love.sh \ + --output dist/payload/game.love \ + --listing dist/payload/love-listing.txt \ + --version "${{ needs.version.outputs.version }}" + - name: Upload shared payload + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-release-love + path: dist/payload/game.love + if-no-files-found: error + retention-days: 1 + + linux-arm64: + name: build Linux arm64 AppImage + needs: [version, love-payload] + # GitHub's free arm64 runner for public repos. It has to be arm64: the + # AppImage compiles LÖVE natively inside a Debian bullseye arm64 + # container, and the qemu-emulated alternative takes hours. + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v7 + - name: Download shared payload + uses: actions/download-artifact@v8 + with: + name: gen1recomp-release-love + path: .bazinga/work + - name: Build Linux arm64 AppImage + run: | + set -euo pipefail + scripts/build_linux_arm64.sh \ + --version "${{ needs.version.outputs.version }}" \ + --game-love .bazinga/work/game.love + - name: Upload Linux arm64 release + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-linux-arm64-release + path: | + dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage + dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage.sha256 + if-no-files-found: error + retention-days: 1 + + xbox-uwp: + name: build Xbox UWP release + needs: [version, love-payload] + runs-on: windows-2022 + steps: + - uses: actions/checkout@v7 + - name: Download shared payload + uses: actions/download-artifact@v8 + with: + name: gen1recomp-release-love + path: .bazinga/work + - name: Prepare signing certificate + shell: pwsh + env: + CERTIFICATE_BASE64: ${{ secrets.XBOX_UWP_SIGNING_CERTIFICATE }} + CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }} + CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }} + run: | + if ($env:CANONICAL_REPOSITORY -eq 'true' -and + [string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) { + throw 'XBOX_UWP_SIGNING_CERTIFICATE is not configured.' + } + if ([string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) { + "UWP_PUBLISHER=CN=Gen1Recomp" | Out-File $env:GITHUB_ENV -Append + exit 0 + } + $pfx = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.pfx' + [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERTIFICATE_BASE64)) + $flags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet + $cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new( + $pfx, $env:CERTIFICATE_PASSWORD, $flags) + $cer = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.cer' + [IO.File]::WriteAllBytes( + $cer, + $cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)) + Import-Certificate -FilePath $cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople | Out-Null + "UWP_PFX=$pfx" | Out-File $env:GITHUB_ENV -Append + "UWP_CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File $env:GITHUB_ENV -Append + "UWP_PUBLISHER=$($cert.Subject)" | Out-File $env:GITHUB_ENV -Append + - name: Build Xbox UWP package + shell: bash + run: | + bash scripts/build_xbox_uwp.sh \ + --release \ + --version "${{ needs.version.outputs.version }}" \ + --publisher "$UWP_PUBLISHER" \ + --game-love .bazinga/work/game.love + - name: Sign and stage Xbox UWP release + shell: pwsh + env: + CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }} + run: | + if (-not $env:UWP_PFX) { + exit 0 + } + scripts/xbox-uwp/stage_release.ps1 ` + -Version '${{ needs.version.outputs.version }}' ` + -Configuration Release ` + -BuildInfo .bazinga/work/xbox-uwp-build-info.json ` + -CertificatePath $env:UWP_PFX ` + -CertificatePassword $env:CERTIFICATE_PASSWORD + - name: Upload Xbox UWP release + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-xbox-uwp-release + path: | + dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip + dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip.sha256 + if-no-files-found: error + retention-days: 1 + - name: Remove signing certificate + if: always() + shell: pwsh + run: | + if ($env:UWP_CERT_THUMBPRINT) { + Remove-Item "Cert:\LocalMachine\TrustedPeople\$env:UWP_CERT_THUMBPRINT" -ErrorAction SilentlyContinue + } + if ($env:UWP_PFX) { + Remove-Item $env:UWP_PFX -Force -ErrorAction SilentlyContinue + } + + release: + needs: [version, xbox-uwp, linux-arm64] + runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }} + + steps: + # The self-hosted runner lives under the machine owner's home + # directory; mask it first so absolute paths in every later step's + # output show up as *** in the public workflow logs. + - name: Mask runner paths + run: echo "::add-mask::$HOME" + + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + - name: Import signing certificate into a temporary keychain if: github.repository == 'bryanthaboi/gen1recomp' run: | @@ -173,12 +313,12 @@ jobs: # notarize separately below so it uses secret credentials, not a # login-keychain profile. "all" also builds the Linux AppImage, # which needs no signing/notarization. - scripts/build.sh all --version "${{ steps.ver.outputs.version }}" --no-notarize + scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize - name: Build Android run: | set -euo pipefail - scripts/build_android.sh --version "${{ steps.ver.outputs.version }}" + scripts/build_android.sh --version "${{ needs.version.outputs.version }}" - name: Install xcbeautify run: | @@ -192,10 +332,10 @@ jobs: set -euo pipefail if [ "$CANONICAL_REPOSITORY" = true ]; then scripts/build_ios.sh --fetch --device --release \ - --version "${{ steps.ver.outputs.version }}" + --version "${{ needs.version.outputs.version }}" else scripts/build_ios.sh --fetch --release \ - --version "${{ steps.ver.outputs.version }}" + --version "${{ needs.version.outputs.version }}" fi - name: Build Switch @@ -207,14 +347,14 @@ jobs: # Needs native switch-tools (nacptool/elf2nro) and/or Docker on the # Mac self-hosted runner; see docs/switch-build.md. scripts/build_switch.sh --fetch --fused \ - --version "${{ steps.ver.outputs.version }}" + --version "${{ needs.version.outputs.version }}" - name: Build Anbernic RG34XXSP port run: | set -euo pipefail # Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5 # runtime from PortMaster-GUI, so it needs no signing/notarization. - ./build-rg34xxsp.sh --version "${{ steps.ver.outputs.version }}" + ./build-rg34xxsp.sh --version "${{ needs.version.outputs.version }}" - name: Build Linux ARM SBC PortMaster port env: @@ -222,13 +362,13 @@ jobs: # script defaults to the latest published release for standalone # builds, while this explicit local override keeps CI source-aligned. GEN1RECOMP_SOURCE_DIR: ${{ github.workspace }} - GEN1RECOMP_RELEASE_TAG: v${{ steps.ver.outputs.version }} + GEN1RECOMP_RELEASE_TAG: v${{ needs.version.outputs.version }} run: | set -euo pipefail # Same aarch64 PortMaster-style pack for Linux ARM SBC PortMaster. The build # keeps its own cache because the two scripts use different staging # layouts and runtime package paths. - ./build-linux-arm-sbc.sh --version "${{ steps.ver.outputs.version }}" + ./build-linux-arm-sbc.sh --version "${{ needs.version.outputs.version }}" - name: Notarize & staple macOS app if: github.repository == 'bryanthaboi/gen1recomp' @@ -265,18 +405,41 @@ jobs: ditto -c -k --sequesterRsrc --keepParent "$app" "$zip" echo "Notarized + stapled ✓" + - name: Download Xbox UWP release + if: github.repository == 'bryanthaboi/gen1recomp' + uses: actions/download-artifact@v8 + with: + name: gen1recomp-xbox-uwp-release + path: dist/xbox-uwp + + - name: Download Linux arm64 release + if: github.repository == 'bryanthaboi/gen1recomp' + uses: actions/download-artifact@v8 + with: + name: gen1recomp-linux-arm64-release + path: dist/linux-arm64 + - name: Stage release assets if: github.repository == 'bryanthaboi/gen1recomp' id: assets run: | set -euo pipefail - v="${{ steps.ver.outputs.version }}" + v="${{ needs.version.outputs.version }}" outdir="dist/release" rm -rf "$outdir" mkdir -p "$outdir" cp "dist/mac/gen1recomp-macos.zip" "$outdir/gen1recomp-${v}-macos.zip" cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip" cp "dist/linux/gen1recomp-linux.zip" "$outdir/gen1recomp-${v}-linux.zip" + + # arm64 desktop Linux (Raspberry Pi, Armbian, arm64 VMs). Built on + # its own runner because LÖVE publishes no aarch64 binary and the + # AppImage has to be compiled natively; ships as a runnable + # AppImage rather than a zip so `chmod +x && ./it` just works. + arm64_appimage="dist/linux-arm64/gen1recomp-${v}-linux-arm64.AppImage" + [ -f "$arm64_appimage" ] || { echo "::error::$arm64_appimage not found (expected from the linux-arm64 job)"; exit 1; } + cp "$arm64_appimage" "$outdir/gen1recomp-${v}-linux-arm64.AppImage" + chmod +x "$outdir/gen1recomp-${v}-linux-arm64.AppImage" apk="$(find dist/android/debug -name '*.apk' | head -1)" [ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/debug"; exit 1; } cp "$apk" "$outdir/gen1recomp-${v}-android.apk" @@ -291,6 +454,10 @@ jobs: # Local fused .nro stays under dist/switch/ for PR CI / debug; release # publishes the SD-ready zip only. + uwp="dist/xbox-uwp/gen1recomp-${v}-xbox-uwp.zip" + [ -f "$uwp" ] || { echo "::error::$uwp not found (expected from the Xbox UWP job)"; exit 1; } + cp "$uwp" "$outdir/gen1recomp-${v}-xbox-uwp.zip" + # Anbernic handheld port (suffix names the CFW it targets, so a # future RG35XX/other-CFW pack can ship alongside it). rg34="dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip" @@ -322,8 +489,8 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - v="${{ steps.ver.outputs.version }}" - tag="${{ steps.ver.outputs.tag }}" + v="${{ needs.version.outputs.version }}" + tag="${{ needs.version.outputs.tag }}" # Issues this release closes. Three sources, deduped by number: # 1. GitHub's own "closing issues" links on every PR whose @@ -411,9 +578,11 @@ jobs: "dist/release/gen1recomp-${v}-macos.zip" "dist/release/gen1recomp-${v}-windows.zip" "dist/release/gen1recomp-${v}-linux.zip" + "dist/release/gen1recomp-${v}-linux-arm64.AppImage" "dist/release/gen1recomp-${v}-android.apk" "dist/release/gen1recomp-${v}-ios.ipa" "dist/release/gen1recomp-${v}-switch.zip" + "dist/release/gen1recomp-${v}-xbox-uwp.zip" "dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip" "dist/release/gen1recomp-${v}-sbc-portmaster.zip" "dist/release/gen1recomp-${v}.love" @@ -432,7 +601,7 @@ jobs: if: github.repository == 'bryanthaboi/gen1recomp' run: | set -euo pipefail - v="${{ steps.ver.outputs.version }}" + v="${{ needs.version.outputs.version }}" ipa="dist/release/gen1recomp-${v}-ios.ipa" app_repo="mobile/ios/app-repo.json" [ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; } diff --git a/.gitignore b/.gitignore index bc7f4683..d68dbada 100644 --- a/.gitignore +++ b/.gitignore @@ -31,12 +31,29 @@ mobile/ios/love-src/ mobile/ios/cache/ mobile/ios/build/ -# love-nx vendor binaries (fetch per docs/switch-development.md; also covered by .*) +# love-nx vendor binaries (fetch per docs/switch-build.md; also covered by .*) .bazinga/love-nx/ # Final packaged build artifacts (mac/win/web/android/ios/switch) — see scripts/build.sh /dist/ +# Switch OTA launcher build outputs +ports/switch/ota-launcher/build/ +ports/switch/ota-launcher/build-host/ +ports/switch/ota-launcher/*.nro +ports/switch/ota-launcher/*.nacp +ports/switch/ota-launcher/*.elf +ports/switch/ota-launcher/*.map +ports/switch/ota-launcher/romfs/logo.rgba +ports/switch/ota-launcher/romfs/logo.png +ports/switch/ota-launcher/romfs/cacert.pem +ports/switch/ota-launcher/romfs/ota-bootstrap.nro +ports/switch/ota-bootstrap/build/ +ports/switch/ota-bootstrap/*.nro +ports/switch/ota-bootstrap/*.nacp +ports/switch/ota-bootstrap/*.elf +ports/switch/ota-bootstrap/*.map + # Legacy manual convenience-copy location (superseded by /dist/android/) mobile/dist/ @@ -46,3 +63,8 @@ mobile/dist/ # per-machine iOS bundle-id pin (see scripts/build_ios.sh) mobile/ios/bundle_id.local + +# Xbox UWP build output +/ports/uwp/build/ +/ports/uwp/third_party/*/source/ +/ports/uwp/third_party/angle/depot_tools/ diff --git a/README.md b/README.md index c1d4d08e..ba1fff43 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,39 @@ even on a different computer, as long as the same folder comes along. already written to either location is touched automatically, so copy files over yourself if you want to carry existing progress across the switch. +## Launch Options + +By default the app opens the launcher so you can pick a game. Launch options +skip it and start one game directly, which is what you want for a one-click +entry: a desktop shortcut per game, a Steam entry, or a handheld frontend. + +| Option | Effect | +| --- | --- | +| `--game=red` | boot Red, skipping the launcher (`blue` and `yellow` too, or just `r` / `b` / `y`) | +| `--slot=2` | load that save slot; takes a slot number or a slot id | +| `--launcher` | open the launcher anyway, so you can edit a shortcut you already made | + + +## Linux on arm64 (Raspberry Pi) + +Alongside the x86_64 `gen1recomp-*-linux.zip`, every release ships +`gen1recomp-*-linux-arm64.AppImage` for 64-bit ARM desktop Linux — Raspberry +Pi 4/5, Armbian and other SBC distros, and arm64 VMs on Apple Silicon: + +```sh +chmod +x gen1recomp-*-linux-arm64.AppImage +./gen1recomp-*-linux-arm64.AppImage +``` + +LÖVE publishes no aarch64 binary of any kind, so this artifact compiles the +engine — and SDL2, OpenAL and the codecs — from source inside a Debian +bullseye arm64 container. It needs only glibc 2.29+, libstdc++, freetype and +zlib on the host; OpenGL, X11, Wayland, KMSDRM, ALSA and PulseAudio are all +dlopened, so the same image runs on a full desktop, a Wayland-only session or +a KMSDRM handheld with no X server. Build instructions and the reasoning are +in [docs/linux-arm64-build.md](docs/linux-arm64-build.md). + + ## iOS Every release ships `gen1recomp-*-ios.ipa`. Sideload it with AltStore @@ -222,6 +255,40 @@ build and install from source on a Mac instead, see Download from GitHub +## Xbox Dev Mode + +Every release ships `gen1recomp-*-xbox-uwp.zip` for Xbox One and Xbox Series +consoles in Developer Mode. It cannot be installed in retail mode. + +Extract the archive, then use Xbox Device Portal to install the `.msix` and +the x64 package under `Dependencies`. + +### External setup + +1. Put your legally obtained Red, Blue, or Yellow ROMs on an external drive. + Mod ZIPs can go on the same drive. +2. Connect the drive to the Xbox and open Gen1Recomp. +3. Select **Import ROM** or **Import Mod**, then choose the file with the Xbox + file picker. +4. Repeat the ROM import for each version you want to use. + +### Internal setup + +1. Create a folder named `baseroms` on your PC and place your legally obtained + Red, Blue, or Yellow ROMs inside it. +2. ZIP the folder, keeping `baseroms` at the top level of the archive. +3. Launch Gen1Recomp once, then close it. +4. Open Xbox Device Portal and upload the ZIP to + `Gen1Recomp/LocalState/pokemon-love2d/`. +5. Choose **Yes** when Device Portal asks whether to extract the archive. +6. Open Gen1Recomp. The launcher checks baseroms once at startup. When it finds a compatible ROM, that game’s tab shows ROM FOUND and an Import detected ROM button. + +ROMs, generated game data, saves, and mods remain in LocalState and are not +included in the app. + +Source builds and package details are covered in +[the Xbox UWP build notes](ports/uwp/BUILD.md). + ## Handhelds A PortMaster-style port for the **Anbernic RG34XXSP** on Stock OS 64-bit MOD @@ -231,24 +298,18 @@ Install steps, controls, and troubleshooting live in ## Nintendo Switch -Releases ship an SD-ready `gen1recomp-*-switch.zip` (issue -[#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Runtime target -is pinned [love-nx](https://github.com/retronx-team/love-nx) `11.5-nx1`. -Requires a console that can run Switch homebrew. Hardware evidence: **OLED** -(author) and **V1 / Erista** boot (community). +Releases ship an SD-ready `gen1recomp-*-switch.zip`. Runtime target is pinned +[love-nx](https://github.com/retronx-team/love-nx) `11.5-nx1`. Requires a +console that can run Switch homebrew. -- Players: [docs/switch-install.md](docs/switch-install.md) — download the +- Players: [docs/switch-install.md](docs/switch-install.md). Download the zip, extract at the microSD root (install or update), title-override launch, import your own legal ROM, Joy-Con controls and shortcuts. -- Builders: [docs/switch-build.md](docs/switch-build.md) — `--fetch` / - `--loose` / `--fused`, toolchain, Docker fallback, and **CI vs release** - (path-gated ubuntu selftest, canonical fused PR artifact, release hard-fail). - -Limitations, Dusklight-derived method, and how we tested: -[docs/switch-development.md](docs/switch-development.md) and -[docs/switch-hardware-evidence.md](docs/switch-hardware-evidence.md). Community -help — especially HOS / love-nx packaging and broader hardware coverage — is -welcome. +- Builders: [docs/switch-build.md](docs/switch-build.md). `--fetch` / + `--loose` / `--fused`, toolchain, Docker fallback, and CI vs release + (path-gated ubuntu selftest, fused PR artifact on the main repo, release + hard-fail). +- File transfer (MTP / SD / FTP): [docs/switch-transfer.md](docs/switch-transfer.md). ## Modding @@ -297,7 +358,4 @@ This project would not be possible without [pret](https://github.com/pret) > the pret band of decompiling maniacs > and their [pokered](https://github.com/pret/pokered) disassembly. -Nintendo Switch port: [andrewqsantos](https://github.com/andrewqsantos). -Switch hardware testing (V1 boot): [booshankles](https://github.com/booshankles). -

diff --git a/assets/fonts/plainpixel/PlainPixel-Regular.ttf b/assets/fonts/plainpixel/PlainPixel-Regular.ttf new file mode 100644 index 00000000..873fe5f5 Binary files /dev/null and b/assets/fonts/plainpixel/PlainPixel-Regular.ttf differ diff --git a/assets/fonts/plainpixel/README.md b/assets/fonts/plainpixel/README.md new file mode 100644 index 00000000..22509351 --- /dev/null +++ b/assets/fonts/plainpixel/README.md @@ -0,0 +1,13 @@ +# Plain Pixel font + +"Plain Pixel Font" by Douglas Vautour (Burpy Fresh) is licensed under +CC-BY 4.0: https://burpyfresh.itch.io + +Version 0.009 (CJK character additions), unmodified. Characters for most +languages have a 5x11 base but can extend vertically; double-width +characters such as Hiragana and Katakana are 11x11. + +Bundled so a translation mod can opt into TTF text rendering +(`mod.content.font:register("ttf", {})`; see the Translation support +section of docs/new-features.md) instead of drawing hundreds of glyph-page +tiles. The tile font extracted from the player's ROM stays the default. diff --git a/data/scripts/flavor/pewter_city.lua b/data/scripts/flavor/pewter_city.lua index b5047092..eb525b7d 100644 --- a/data/scripts/flavor/pewter_city.lua +++ b/data/scripts/flavor/pewter_city.lua @@ -16,9 +16,13 @@ local function push(game, s, done) game.stack:push(TextBox.new(game, s, done)) end +-- PrintText on a text_end string returns with the box still drawn and +-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters, +-- engine/menus/text_box.asm); no A press clears the question first. Ride +-- TextBox's opts.choice, the same as Commands.ask (#854). local function ask(game, s, cb) - local ChoiceBox = require("src.ui.ChoiceBox") - push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end) + local TextBox = require("src.render.TextBox") + game.stack:push(TextBox.new(game, s, nil, { choice = cb })) end M.PEWTER_CITY = { diff --git a/data/scripts/flavor/viridian_city.lua b/data/scripts/flavor/viridian_city.lua index b9e476da..f78626f8 100644 --- a/data/scripts/flavor/viridian_city.lua +++ b/data/scripts/flavor/viridian_city.lua @@ -23,9 +23,13 @@ local function push(game, s, done) game.stack:push(TextBox.new(game, s, done)) end +-- PrintText on a text_end string returns with the box still drawn and +-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters, +-- engine/menus/text_box.asm); no A press clears the question first. Ride +-- TextBox's opts.choice, the same as Commands.ask (#854). local function ask(game, s, cb) - local ChoiceBox = require("src.ui.ChoiceBox") - push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end) + local TextBox = require("src.render.TextBox") + game.stack:push(TextBox.new(game, s, nil, { choice = cb })) end M.VIRIDIAN_CITY = { diff --git a/data/scripts/gyms.lua b/data/scripts/gyms.lua index c94484e3..ddf126c8 100644 --- a/data/scripts/gyms.lua +++ b/data/scripts/gyms.lua @@ -18,6 +18,26 @@ local M = { VIRIDIAN_GYM = { city = "VIRIDIAN CITY", leader = "GIOVANNI", badge = "EARTHBADGE" }, } +-- The originals' middle branch shared by every leader's text_asm: beaten +-- but EVENT_GOT_TM* unset means the bag was full when the victory script +-- ran GiveItem, so talking to the leader re-runs the ReceiveTM script. +-- Returns true when the retry took over the talk. A save from before +-- #797 already holds the TM without the flag; treat the owned TM as +-- received so those saves fall through to the advice text instead of +-- collecting a second copy. +local function retryTmGive(game, ow, victoryKey, done) + local reward = require("data.scripts.victories")[victoryKey] + if not (reward and reward.gotFlag) then return false end + if game.save.flags[reward.gotFlag] then return false end + local owned = game.save.inventory and game.save.inventory[reward.item] or 0 + if owned > 0 then + game.save.flags[reward.gotFlag] = true + return false + end + ow:offerGymTm(reward, done) + return true +end + -- scripts/PewterGym.asm PewterGymBrockText (text_asm): CheckEvent -- EVENT_BEAT_BROCK branches his dialogue. Before the badge he prints -- _PewterGymBrockPreBattleText and engages the leader battle @@ -25,12 +45,14 @@ local M = { -- badge/TM34 rewards and EVENT_BEAT_BROCK come from -- data/scripts/victories.lua OPP_BROCK#1). After the badge his -- .afterBeat branch prints _PewterGymBrockPostBattleAdviceText ("Go to --- the GYM in CERULEAN..."). The original's middle branch (beat but --- TM34 not yet handed over, CheckEventReuseA EVENT_GOT_TM34) is --- unreachable in the port: the TM is granted with the victory. +-- the GYM in CERULEAN..."). The middle branch (beat but TM34 not yet +-- handed over, CheckEventReuseA EVENT_GOT_TM34 -> call +-- PewterGymScriptReceiveTM34) retries the TM give when the bag was full +-- at the victory (#797). M.PEWTER_GYM.talk = { TEXT_PEWTERGYM_BROCK = function(game, ow, npc, done) if game.save.flags.EVENT_BEAT_BROCK then + if retryTmGive(game, ow, "OPP_BROCK#1", done) then return end local TextBox = require("src.render.TextBox") game.stack:push(TextBox.new(game, game.data.text._PewterGymBrockPostBattleAdviceText @@ -48,16 +70,17 @@ M.PEWTER_GYM.talk = { -- (engageTrainer shows that same pre-battle text via resolveText; the -- badge/TM rewards and the beat flag come from data/scripts/victories.lua) -- -- and once beaten print the post-battle advice text. As with Brock, --- the originals' middle branch (beaten but the TM not yet handed over, --- CheckEventReuseA EVENT_GOT_TM*) is unreachable in the port: the TM is --- granted with the victory. +-- the middle branch (beaten but the TM not yet handed over, +-- CheckEventReuseA EVENT_GOT_TM*) retries the TM give when the bag was +-- full at the victory. -- afterAdvice, when given, takes over `done`: it is handed (game, ow, npc, -- done) and must call done() itself once whatever it's doing (e.g. a fade -- around a HideObject) finishes, rather than having it invoked -- automatically. Only Giovanni's farewell uses this. -local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice) +local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice, victoryKey) return function(game, ow, npc, done) if game.save.flags[beatFlag] then + if victoryKey and retryTmGive(game, ow, victoryKey, done) then return end local TextBox = require("src.render.TextBox") local finish = done if afterAdvice then @@ -79,42 +102,42 @@ end M.CERULEAN_GYM.talk = { TEXT_CERULEANGYM_MISTY = leaderTalk("EVENT_BEAT_MISTY", "_CeruleanGymMistyTM11ExplanationText", - "TM11 teaches\nBUBBLEBEAM!"), + "TM11 teaches\nBUBBLEBEAM!", nil, "OPP_MISTY#1"), } -- scripts/VermilionGym.asm VermilionGymLTSurgeText .got_tm24_already M.VERMILION_GYM.talk = { TEXT_VERMILIONGYM_LT_SURGE = leaderTalk("EVENT_BEAT_LT_SURGE", "_VermilionGymLTSurgePostBattleAdviceText", - "A little word of\nadvice, kid!"), + "A little word of\nadvice, kid!", nil, "OPP_LT_SURGE#1"), } -- scripts/CeladonGym.asm CeladonGymErikaText .afterBeat M.CELADON_GYM.talk = { TEXT_CELADONGYM_ERIKA = leaderTalk("EVENT_BEAT_ERIKA", "_CeladonGymErikaPostBattleAdviceText", - "You are cataloging\nPOKéMON? I must\nsay I'm impressed."), + "You are cataloging\nPOKéMON? I must\nsay I'm impressed.", nil, "OPP_ERIKA#1"), } -- scripts/FuchsiaGym.asm FuchsiaGymKogaText .afterBeat M.FUCHSIA_GYM.talk = { TEXT_FUCHSIAGYM_KOGA = leaderTalk("EVENT_BEAT_KOGA", "_FuchsiaGymKogaPostBattleAdviceText", - "When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!"), + "When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!", nil, "OPP_KOGA#1"), } -- scripts/SaffronGym.asm SaffronGymSabrinaText .afterBeat M.SAFFRON_GYM.talk = { TEXT_SAFFRONGYM_SABRINA = leaderTalk("EVENT_BEAT_SABRINA", "_SaffronGymSabrinaPostBattleAdviceText", - "Everyone has\npsychic power!\nPeople just don't\nrealize it!"), + "Everyone has\npsychic power!\nPeople just don't\nrealize it!", nil, "OPP_SABRINA#1"), } -- scripts/CinnabarGym.asm CinnabarGymBlaineText .afterBeat M.CINNABAR_GYM.talk = { TEXT_CINNABARGYM_BLAINE = leaderTalk("EVENT_BEAT_BLAINE", "_CinnabarGymBlainePostBattleAdviceText", - "FIRE BLAST is the\nultimate fire\ntechnique!"), + "FIRE BLAST is the\nultimate fire\ntechnique!", nil, "OPP_BLAINE#1"), } -- scripts/ViridianGym.asm ViridianGymGiovanniText .afterBeat: after the @@ -142,7 +165,7 @@ M.VIRIDIAN_GYM.talk = { "VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI") end end, done)) - end), + end, "OPP_GIOVANNI#3"), } return M diff --git a/data/scripts/oaks_lab_yellow.lua b/data/scripts/oaks_lab_yellow.lua index 8f4ac856..c56f6091 100644 --- a/data/scripts/oaks_lab_yellow.lua +++ b/data/scripts/oaks_lab_yellow.lua @@ -45,6 +45,8 @@ return { { "stop_music" }, { "play_music", "Music_MeetRival" }, { "show_text", "_OaksLabRivalGrampsText" }, + -- callfar OaksLabPikachuMovementScript, before ShowObject (#1021) + { "pikachu_make_way" }, { "show_object", "OAKS_LAB", "OAKSLAB_RIVAL" }, { "place_npc", RIVAL, 4, 7, "up" }, { "move_npc_to", RIVAL, 4, 3 }, @@ -192,12 +194,13 @@ return { end rows[#rows + 1] = { "face_player_dir", "up" } rows[#rows + 1] = { "face_object", OAK1, "down" } - -- OaksLabPlayerReceivedMonText: no nickname prompt -- the starter - -- Pikachu keeps its species name + -- OaksLabPlayerReceivedMonText clears wMonDataLocation, so AskName runs (#1013) rows[#rows + 1] = { "show_text", "_OaksLabOakGivesText" } rows[#rows + 1] = { "play_sound", "Get_Key_Item" } rows[#rows + 1] = { "show_text", "_OaksLabReceivedText", { RAM = "PIKACHU" } } - rows[#rows + 1] = { "give_pokemon", "PIKACHU", 5, true } + rows[#rows + 1] = { "give_pokemon", "PIKACHU", 5 } + -- DisablePikachuOverworldSpriteDrawing keeps it in the ball (#1009) + rows[#rows + 1] = { "set_field", "pikachuInBall", true } rows[#rows + 1] = { "set_flag", "EVENT_GOT_STARTER" } rows[#rows + 1] = { "set_flag", "EVENT_CHOSE_PIKACHU" } ow.runner:run(rows, { npc = npc, onDone = done }) @@ -296,9 +299,9 @@ return { table.insert(rows, { "move_npc_to", RIVAL, 4, 11 }) table.insert(rows, { "hide_object", "OAKS_LAB", "OAKSLAB_RIVAL" }) table.insert(rows, { "play_music", "Music_OaksLab" }) - -- OaksLabPikachuEscapesPokeballScript: Pikachu hates its ball. - -- The overworld follower itself is still an open port - -- (docs/yellow-version.md runtime backlog); the story beat plays. + -- OaksLabPikachuEscapesPokeballScript: the follower reaches the map (#1009) + table.insert(rows, { "face_player_dir", "up" }) + table.insert(rows, { "set_field", "pikachuInBall", false }) table.insert(rows, { "play_cry", "PIKACHU" }) table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText1" }) table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText2" }) diff --git a/data/scripts/story.lua b/data/scripts/story.lua index 8bcabcd0..61eb95df 100644 --- a/data/scripts/story.lua +++ b/data/scripts/story.lua @@ -800,9 +800,13 @@ M.SILPH_CO_11F = { -- line) would touch, and the whole Silph ending -- the flag, the Master -- Ball, the Saffron streets clearing -- silently never happened. -- - -- engageTrainer shows TEXT_SILPHCO11F_GIOVANNI as the battle text and, - -- via victories.lua OPP_GIOVANNI#2, sets the event on a win; a loss - -- sets nothing, so the trigger re-arms exactly as vanilla does. + -- SilphCo11FDefaultScript orders it DisplayTextID TEXT_SILPHCO11F_GIOVANNI + -- FIRST, then MoveSprite .GiovanniMovement: he speaks from behind the desk + -- and only then walks the three tiles down. Moving him before the box made + -- him cross the room in silence and deliver the speech point-blank (#869), + -- so the box comes first here and engageTrainer skips its own battle text. + -- victories.lua OPP_GIOVANNI#2 sets the event on a win; a loss sets + -- nothing, so the trigger re-arms exactly as vanilla does. onStep = function(game, ow, x, y) if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then return false end if not ((x == 6 and y == 13) or (x == 7 and y == 12)) then return false end @@ -811,21 +815,28 @@ M.SILPH_CO_11F = { if npc.def and npc.def.name == "SILPHCO11F_GIOVANNI" then gio = npc break end end if not gio or ow:trainerDefeated(gio) then return false end - ow:scriptMove(gio, "down", 3, function() - gio:facePlayer(ow.player) - ow:engageTrainer(gio, function() - -- SilphCo11FGiovanniAfterBattleScript: the "Blast it all!" speech, - -- then SilphCo11FTeamRocketLeavesScript behind a fade so every Silph - -- rocket leaves off-screen (the street rockets are handled by - -- M.SAFFRON_CITY.onEnter in story4.lua). Queued, not run here: the - -- battle's own callbacks are still unwinding, so queueScript starts - -- it on the first idle overworld frame -- after the end-battle - -- "Arrgh!!" box victories.lua OPP_GIOVANNI#2 pushes (#722). - if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then - ow:queueScript(silphAftermathRows()) - end - end) - end) + local TextBox = require("src.render.TextBox") + game.stack:push(TextBox.new(game, + game.data.text._SilphCo11FGiovanniText + or "Ah {PLAYER}!\nSo we meet again!", + function() + ow:scriptMove(gio, "down", 3, function() + gio:facePlayer(ow.player) + ow:engageTrainer(gio, function() + -- SilphCo11FGiovanniAfterBattleScript: the "Blast it all!" + -- speech, then SilphCo11FTeamRocketLeavesScript behind a fade so + -- every Silph rocket leaves off-screen (the street rockets are + -- handled by M.SAFFRON_CITY.onEnter in story4.lua). Queued, not + -- run here: the battle's own callbacks are still unwinding, so + -- queueScript starts it on the first idle overworld frame -- + -- after the end-battle "Arrgh!!" box victories.lua OPP_GIOVANNI#2 + -- pushes (#722). + if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then + ow:queueScript(silphAftermathRows()) + end + end, nil, true) + end) + end)) return true end, onEnter = function(game, ow) @@ -1031,38 +1042,43 @@ local championsRoomRivalScript = { { "show_text", "_ChampionsRoomRivalAfterBattleText" }, -- 10 -- ChampionsRoomOakArrivesScript: Music_Cities1AlternateTempo -- (Cities1, kept into HALL_OF_FAME like BIT_NO_MAP_MUSIC after - -- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in - { "play_music", "Music_Cities1", { keep = true } }, -- 11 - { "show_text", "_ChampionsRoomOakText" }, -- 12 - { "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 13 - { "move_npc", 2, "up", 5 }, -- 14 OakEntranceAfterVictoryMovement + -- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in. + -- audio/alternate_tempo.asm Music_Cities1AlternateTempo is not a plain + -- PlayMusic: it fades the current song out (wAudioFadeOutControl = 10), + -- waits 100 frames for the fade, then restarts Cities1 with channel 1 + -- pointed at Music_Cities1_Ch1_AlternateTempo -- `tempo 232` where the + -- normal Music_Cities1_Ch1 opens `tempo 144`, i.e. the slower, heavier + -- reading of the town theme this scene is known for (#847). + { "fade_music", 10 }, -- 11 + { "wait", 100 }, -- 12 + { "play_music", "Music_Cities1", { keep = true, tempo = 232 } }, -- 13 + { "show_text", "_ChampionsRoomOakText" }, -- 14 + { "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 15 + { "move_npc", 2, "up", 5 }, -- 16 OakEntranceAfterVictoryMovement -- OakCongratulatesPlayerScript: rival faces left, Oak faces down - { "face_object", 1, "left" }, -- 15 - { "face_object", 2, "down" }, -- 16 - { "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 17 + { "face_object", 1, "left" }, -- 17 + { "face_object", 2, "down" }, -- 18 + { "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 19 -- OakDisappointedWithRivalScript: Oak turns to the rival (right) - { "face_object", 2, "right" }, -- 18 - { "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 19 + { "face_object", 2, "right" }, -- 20 + { "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 21 -- OakComeWithMeScript: Oak faces down again, then exits up - { "face_object", 2, "down" }, -- 20 - { "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 21 - { "move_npc", 2, "up", 2 }, -- 22 OakExitChampionsRoomMovement - { "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 23 - -- ChampionsRoomPlayerFollowsOakScript / WalkToHallOfFame_RLEMovement - -- (PAD_UP 4, PAD_LEFT 1): the player walks out after Oak instead of the - -- screen just fading on the spot (#704). The entrance walk leaves the - -- player at (4,3) and both north-wall warps sit on row 0, so the original - -- only ever spends three of those simulated steps -- CheckWarpsNoCollision - -- takes the HALL_OF_FAME warp the moment the walk lands on (4,0) and the - -- trailing UP/LEFT are dropped. Scripted steps ignore collision here just - -- as they do in the original (CollisionCheckOnLand skips its checks while - -- wSimulatedJoypadStatesIndex is non-zero), so stepping through the - -- rival's cell at (4,2) is the ported behavior, not a clip. - { "move_player", "up", 3 }, -- 24 + { "face_object", 2, "down" }, -- 22 + { "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 23 + { "move_npc", 2, "up", 2 }, -- 24 OakExitChampionsRoomMovement + { "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 25 + -- ChampionsRoomPlayerFollowsOakScript / WalkToHallOfFame_RLEMovement. + -- The player walks out after Oak instead of the screen just fading on the + -- spot (#704). Route one tile right before walking north so the player + -- reaches the north-wall HALL_OF_FAME warp without sharing the rival's + -- (4,2) cell. The original simulated movement bypasses entity collision, + -- but this scene should not visibly walk through the defeated rival. + { "move_player", "right", 1 }, -- 26 + { "move_player", "up", 3 }, -- 27 -- hand the induction off to the HALL_OF_FAME room (consumed by its -- onEnter), then warp up into it (destWarp 1 lands at (4,7) facing up) - { "set_field", "pendingHallOfFame", true }, -- 25 - { "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 26 + { "set_field", "pendingHallOfFame", true }, -- 28 + { "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 29 } M.CHAMPIONS_ROOM = { diff --git a/data/scripts/story2.lua b/data/scripts/story2.lua index f38b0af6..b7e40f3d 100644 --- a/data/scripts/story2.lua +++ b/data/scripts/story2.lua @@ -400,15 +400,24 @@ M.POKEMON_FAN_CLUB = { TEXT_POKEMONFANCLUB_CHAIRMAN = { { "face_player" }, -- 1 { "check_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 2 - { "jump_if_true", 9 }, -- 3 - { "show_text", "_PokemonFanClubChairmanIntroText" }, -- 4 - { "show_text", "_PokemonFanClubChairmanStoryText" }, -- 5 + { "jump_if_true", "nothing_left" }, -- 3 + -- YesNoChoice (scripts/PokemonFanClub.asm): NO forfeits the voucher (#1050) + { "ask", "_PokemonFanClubChairmanIntroText" }, -- 4 + { "jump_if_false", "no_story" }, -- 5 + { "show_text", "_PokemonFanClubChairmanStoryText" }, -- 6 -- give-then-print like scripts/PokemonFanClub.asm (GiveItem -- fills wStringBuffer; the received text reads it) - { "give_item", "BIKE_VOUCHER", 1, false }, -- 6 - { "show_text", "_PokemonFanClubReceivedBikeVoucherText" }, -- 7 - { "set_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 8 - { "show_text", "_PokemonFanClubExplainBikeVoucherText" }, -- 9 + { "give_item", "BIKE_VOUCHER", 1, false }, -- 7 + { "show_text", "_PokemonFanClubReceivedBikeVoucherText" }, -- 8 + { "set_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 9 + { "show_text", "_PokemonFanClubExplainBikeVoucherText" }, -- 10 + { "jump", "end" }, -- 11 + { "label", "no_story" }, -- 12 + { "show_text", "_PokemonFanClubNoStoryText" }, -- 13 + { "jump", "end" }, -- 14 + -- .nothingleft: the gift is done, he only reminisces now + { "label", "nothing_left" }, -- 15 + { "show_text", "_PokemonFanClubChairFinalText" }, -- 16 }, }, } diff --git a/data/scripts/story3.lua b/data/scripts/story3.lua index 56567a50..4f0c507e 100644 --- a/data/scripts/story3.lua +++ b/data/scripts/story3.lua @@ -151,9 +151,27 @@ M.POKEMON_TOWER_6F = { -- trick, and the speedrun route this bot follows depends on it. if result == "win" or battle.pokeDollEscape then game.save.flags.EVENT_BEAT_GHOST_MAROWAK = true - game.stack:push(TextBox.new(game, - t._PokemonTower6FSoulWasCalmedText - or "The mother's soul\nwas calmed.\012It departed to\nthe afterlife!")) + -- PokemonTower6FMarowakDepartedText (scripts/PokemonTower6F.asm) + -- is two texts, not one: the CUBONE's-mother line first, then + -- PlayCry RESTLESS_SOUL (EQU MAROWAK, constants/pokemon_constants + -- .asm:209) + WaitForSoundToFinish + DelayFrames 30 before the + -- calmed line; the port dropped the first text and the cry + -- (#867). play_cry arms the next show_text, so the cry rides + -- the calmed box's open with the button prompt kept, and the + -- wait row stands in for the asm's 30-frame gap. + local rows = { + { "show_text", t._PokemonTower6FGhostWasCubonesMotherText + or "The GHOST was the\nrestless soul of\vCUBONE's mother!" }, + { "play_cry", "MAROWAK", true }, + { "wait", 30 }, + { "show_text", t._PokemonTower6FSoulWasCalmedText + or "The mother's soul\nwas calmed.\012It departed to\nthe afterlife!" }, + } + if ow.runner then + ow.runner:run(rows) + elseif ow.queueScript then + ow:queueScript(rows) + end elseif result ~= "lose" then -- .did_not_defeat: one simulated step right, off the trigger, -- so fleeing does not leave you standing on a cell that @@ -517,6 +535,14 @@ M.GAME_CORNER = { done() return end + -- GameCornerRocketText hands the battle its own loss line through + -- SaveEndBattleTextPointers (.BattleEndText -> + -- _GameCornerRocketBattleEndText, "Dang!"), and PrintEndBattleText + -- prints it ON the battle screen between TrainerDefeatedText and + -- MoneyForWinningText (engine/battle/core.asm TrainerBattleVictory). + -- He is a text_asm trainer with no def_trainers header, so there is no + -- header.won for engageTrainer to find and the line has to be handed + -- over here or it never shows at all (#862). ow:engageTrainer(npc, function() if not ow:trainerDefeated(npc) then done() @@ -527,19 +553,44 @@ M.GAME_CORNER = { game.data.text._GameCornerRocketAfterBattleText or "Our hideout might\nbe discovered! I\nbetter tell BOSS!", function() - -- #198: GameCornerRocketExitScript (scripts/GameCorner.asm) - -- ApplyMovementData walks the grunt one tile UP into the poster - -- (the hideout's secret entrance at 9,4) before HideObject, so - -- he leaves the floor rather than popping out of existence on - -- (9,5). scriptMove locks player input (#scriptMoves>0) and - -- ignores collision, so we despawn + unfreeze (done) only once - -- the step lands. - ow:scriptMove(npc, "up", 1, function() - hideRocket() - done() - end) + -- #198/#862: GameCornerRocketBattleScript (scripts/GameCorner.asm) + -- picks the exit walk from where the player is standing, because + -- the grunt on (9,5) has to get past him: wYCoord == 6 (talked to + -- from the south) or wXCoord == 8 (from the west) leaves the row + -- clear and takes GameCornerMovement_Rocket_WalkDirect, five steps + -- RIGHT; otherwise the player is east of him on (10,5) and + -- GameCornerMovement_Rocket_WalkAroundPlayer steps DOWN, right, UP + -- and right again to go AROUND him. pokeyellow's copy of the + -- around-path takes one extra RIGHT on the lower row before coming + -- back up (it also has to clear Pikachu); both versions end on + -- (15,5). He never steps UP: (9,4) is the poster wall, which is + -- where the old single UP step sent him. + local px = ow.player and ow.player.cellX + local py = ow.player and ow.player.cellY + local path + if py == 6 or px == 8 then + path = { { "right", 5 } } + elseif require("src.core.GameVersion").isYellow() then + path = { { "down", 1 }, { "right", 3 }, { "up", 1 }, { "right", 3 } } + else + path = { { "down", 1 }, { "right", 2 }, { "up", 1 }, { "right", 4 } } + end + -- GameCornerRocketExitScript only HideObjects him once + -- BIT_SCRIPTED_NPC_MOVEMENT clears, i.e. after the last step. + -- scriptMove locks player input (#scriptMoves>0) and ignores + -- collision, so the despawn + unfreeze (done) ride the final step. + local function step(i) + if i > #path then + hideRocket() + done() + return + end + ow:scriptMove(npc, path[i][1], path[i][2], + function() step(i + 1) end) + end + step(1) end)) - end) + end, game.data.text._GameCornerRocketBattleEndText or "Dang!") end, -- GameCornerClerk1Text (scripts/GameCorner.asm): the offer, a -- YesNoChoice, then ¥1000 for 50 coins. Yellow drops the "1" from the diff --git a/data/scripts/story4.lua b/data/scripts/story4.lua index 874a2b9c..b4766406 100644 --- a/data/scripts/story4.lua +++ b/data/scripts/story4.lua @@ -13,16 +13,30 @@ local function push(game, s, done) game.stack:push(TextBox.new(game, s, done)) end +-- The question stays on screen under the YES/NO menu. The dojo prize +-- balls are the clearest case: FightingDojoHitmonleePokeBallText +-- (scripts/FightingDojo.asm) is `call PrintText` on a text_end string -- +-- no prompt, so no WaitForTextScrollButtonPress -- immediately followed +-- by `call YesNoChoice`, and InitYesNoTextBoxParameters +-- (engine/menus/text_box.asm) puts the menu above the dialogue box +-- rather than replacing it. Ride TextBox's opts.choice, the same as +-- Commands.ask, instead of popping the box with an A press and leaving a +-- bare ChoiceBox over the overworld (#854). local function ask(game, s, cb) - local ChoiceBox = require("src.ui.ChoiceBox") - push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end) + local TextBox = require("src.render.TextBox") + game.stack:push(TextBox.new(game, s, nil, { choice = cb })) end --- fill the extracted text placeholders ({NUM:...}, {RAM:...}, {PLAYER}) +-- fill text placeholders; key on the hram/wram symbol first, since one +-- string can carry two different NUM slots (#1006) local function fill(s, subs) s = s:gsub("{PLAYER}", subs.player or "") - s = s:gsub("{NUM:[^}]*}", function() return tostring(subs.num or "") end) - s = s:gsub("{RAM:[^}]*}", function() return subs.ram or "" end) + s = s:gsub("{NUM:([%w_]*)[^}]*}", function(name) + return tostring(subs[name] or subs.num or "") + end) + s = s:gsub("{RAM:([%w_]*)[^}]*}", function(name) + return subs[name] or subs.ram or "" + end) return s end @@ -73,9 +87,12 @@ local function oaksAide(threshold, itemId, repeatText) { ram = itemName, player = game.save.player.name }), done) end) else + -- .notEnoughOwnedMons prints owned then requirement, two counts push(game, fill(t._OaksAideUhOhText or "You have only\ncaught {NUM:}!", - { num = owned, ram = itemName }), done) + { num = owned, ram = itemName, + hOaksAideNumMonsOwned = owned, + hOaksAideRequirement = threshold }), done) end end) end @@ -155,19 +172,26 @@ local function dojoBall(species, ownBall, otherBall, askKey) push(game, "You'll have to\nbeat the master\nfirst!", done) return end - ask(game, t[askKey] or ("You want\n" .. species .. "?"), function(yes) - if not yes then done() return end - flags["EVENT_GOT_" .. species] = true - flags.EVENT_DEFEATED_FIGHTING_DOJO = true - local Commands = require("src.script.Commands") - local ctx = { save = game.save, game = game, overworld = ow } - Commands.give_pokemon(ctx, species, 30) - -- Hide ONLY the chosen ball; the other stays (FightingDojo.asm hides - -- just the picked object's index) and routes to the greedy line above - -- when talked to (#197). - Commands.hide_object(ctx, "FIGHTING_DOJO", ownBall) - push(game, ("%s got\n%s!"):format(game.save.player.name, species), done) - end) + -- Examining a ball shows that species' POKéDEX entry first + -- (DisplayPokedex in FightingDojo.asm, which also marks it seen), + -- then the yes/no take-it prompt (#853). + local Commands = require("src.script.Commands") + local ctx = { save = game.save, game = game, overworld = ow } + Commands.mark_seen(ctx, species) + local DexEntryMenu = require("src.ui.DexEntryMenu") + game.stack:push(DexEntryMenu.new(game, species, function() + ask(game, t[askKey] or ("You want\n" .. species .. "?"), function(yes) + if not yes then done() return end + flags["EVENT_GOT_" .. species] = true + flags.EVENT_DEFEATED_FIGHTING_DOJO = true + Commands.give_pokemon(ctx, species, 30) + -- Hide ONLY the chosen ball; the other stays (FightingDojo.asm hides + -- just the picked object's index) and routes to the greedy line above + -- when talked to (#197). + Commands.hide_object(ctx, "FIGHTING_DOJO", ownBall) + push(game, ("%s got\n%s!"):format(game.save.player.name, species), done) + end) + end)) end end @@ -229,27 +253,32 @@ M.FIGHTING_DOJO = { M.SILPH_CO_7F = { talk = { - TEXT_SILPHCO7F_SILPH_WORKER_M1 = function(game, ow, npc, done) - local t = text(game) - if game.save.flags.EVENT_GOT_LAPRAS then - push(game, t._SilphCo7FSilphWorkerM1LaprasDescriptionText - or "How is LAPRAS\ndoing?", done) - return - end - push(game, t._SilphCo7FSilphWorkerM1ThankYouText - or "Thank you for\nsaving us!\fI want you to\nhave this LAPRAS!", - function() - game.save.flags.EVENT_GOT_LAPRAS = true - local Commands = require("src.script.Commands") - Commands.give_pokemon({ save = game.save, game = game, overworld = ow }, - "LAPRAS", 15) - push(game, ("%s got\nLAPRAS!"):format(game.save.player.name), - function() - push(game, t._SilphCo7FSilphWorkerM1LaprasDescriptionText - or "It's a good\nswimmer!", done) - end) - end) - end, + -- command rows, not a Lua handler: give_pokemon needs a runner to AskName (#1049) + TEXT_SILPHCO7F_SILPH_WORKER_M1 = { + { "face_player" }, + { "check_flag", "EVENT_GOT_LAPRAS" }, + { "jump_if_true", "has_lapras" }, + { "show_text", "_SilphCo7FSilphWorkerM1HaveThisPokemonText" }, + { "give_pokemon", "LAPRAS", 15 }, + { "jump_if_false", "box_full" }, + -- flag ahead of the jingle, like the Celadon EEVEE (#426) + { "set_flag", "EVENT_GOT_LAPRAS" }, + { "play_sound", "Get_Item1" }, + { "show_text", "_GotMonText", { RAM = "LAPRAS" } }, + { "show_text", "_SilphCo7FSilphWorkerM1LaprasDescriptionText" }, + { "jump", "end" }, + { "label", "box_full" }, + { "show_text", "_BoxIsFullText" }, + { "jump", "end" }, + -- SilphCo7F.asm .saved_silph gates the thanks on Giovanni + { "label", "has_lapras" }, + { "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" }, + { "jump_if_true", "saved" }, + { "show_text", "_SilphCo7FSilphWorkerM1IsOurPresidentOkText" }, + { "jump", "end" }, + { "label", "saved" }, + { "show_text", "_SilphCo7FSilphWorkerM1SavedText" }, + }, }, } diff --git a/data/scripts/story5.lua b/data/scripts/story5.lua index 89324f90..b1a7ffc8 100644 --- a/data/scripts/story5.lua +++ b/data/scripts/story5.lua @@ -49,7 +49,7 @@ local function gift(opts) end end) end - if opts.pre then say(opts.pre, "", give) else give() end + if opts.pre then say(opts.pre, opts.preFallback or "", give) else give() end end end @@ -118,11 +118,21 @@ M.CINNABAR_LAB_METRONOME_ROOM = { }, } --- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher; no pre text) +-- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher). The fisher's +-- YouCanHaveThisText prints before GiveItem, so this gift needs a pre +-- text (#775). Like the SilphCo2F worker (#393) that label carries no +-- leading underscore, and on Red it sits outside the extractor's symbol +-- set, so the literal from text/ViridianCity.asm rides along as the +-- fallback; Yellow resolves the ROM string instead. M.VIRIDIAN_CITY = { talk = { TEXT_VIRIDIANCITY_FISHER = gift({ flag = "EVENT_GOT_TM42", item = "TM_DREAM_EATER", + pre = "ViridianCityFisherYouCanHaveThisText", + preFallback = "Yawn!\nI must have dozed\voff in the sun." + .. "\fI had this dream\nabout a DROWZEE\veating my dream." + .. "\vWhat's this?\vWhere did this TM\vcome from?" + .. "\fThis is spooky!\nHere, you can\vhave this TM.", received = "_ViridianCityFisherReceivedTM42Text", explain = "_ViridianCityFisherTM42ExplanationText", noRoom = "_ViridianCityFisherTM42NoRoomText", diff --git a/data/scripts/story6.lua b/data/scripts/story6.lua index e28c1230..21de982e 100644 --- a/data/scripts/story6.lua +++ b/data/scripts/story6.lua @@ -12,9 +12,13 @@ local function push(game, s, done) game.stack:push(TextBox.new(game, s, done)) end +-- PrintText on a text_end string returns with the box still drawn and +-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters, +-- engine/menus/text_box.asm); no A press clears the question first. Ride +-- TextBox's opts.choice, the same as Commands.ask (#854). local function ask(game, s, cb) - local ChoiceBox = require("src.ui.ChoiceBox") - push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end) + local TextBox = require("src.render.TextBox") + game.stack:push(TextBox.new(game, s, nil, { choice = cb })) end -- ------------------------------------------------------------------- diff --git a/data/scripts/victories.lua b/data/scripts/victories.lua index b1aeaa9f..dc71e796 100644 --- a/data/scripts/victories.lua +++ b/data/scripts/victories.lua @@ -18,6 +18,16 @@ -- script). Leaders are not def_trainers entries, so engageTrainer has -- no header.won -- checkVictoryRewards shows this chain instead of a -- synthetic "received badge/TM" stub. +-- +-- Gym entries split the TM hand-over out of `dialogue`, mirroring the +-- originals' GiveItem check (`call GiveItem` / `jr nc, .BagFull`): +-- `tmPre` is the ReceiveTM script's lead-in (badge info / "Wait! Take +-- this!"), shown at the victory and again when a beaten leader retries +-- the hand-over; `tmDialogue` shows only when the TM actually goes in +-- the bag; `noRoom` is the "make room" line shown instead when the bag +-- is full; `gotFlag` (pokered's EVENT_GOT_TM*) is set only on a +-- successful give, which is what makes the leader's talk script retry +-- later (gyms.lua). local function range(prefix, first, last) local t = {} @@ -33,6 +43,8 @@ return { -- escort NPC and the first Route 22 rival stay gone after the badge. ["OPP_BROCK#1"] = { badge = "BOULDERBADGE", flag = "EVENT_BEAT_BROCK", item = "TM_BIDE", + gotFlag = "EVENT_GOT_TM34", + noRoom = "_PewterGymTM34NoRoomText", deactivate = { "EVENT_BEAT_PEWTER_GYM_TRAINER_0" }, hide = { { "PEWTER_CITY", "PEWTERCITY_YOUNGSTER" }, @@ -41,69 +53,99 @@ return { dialogue = { "_PewterGymBrockReceivedBoulderBadgeText", "_PewterGymBrockBoulderBadgeInfoText", - "_PewterGymBrockWaitTakeThisText", + }, + tmPre = { "_PewterGymBrockWaitTakeThisText" }, + tmDialogue = { "_PewterGymReceivedTM34Text", "_TM34ExplanationText", } }, ["OPP_MISTY#1"] = { badge = "CASCADEBADGE", flag = "EVENT_BEAT_MISTY", item = "TM_BUBBLEBEAM", + gotFlag = "EVENT_GOT_TM11", + noRoom = "_CeruleanGymMistyTM11NoRoomText", deactivate = range("EVENT_BEAT_CERULEAN_GYM_TRAINER_", 0, 1), dialogue = { "_CeruleanGymMistyReceivedCascadeBadgeText", - "_CeruleanGymMistyCascadeBadgeInfoText", + }, + tmPre = { "_CeruleanGymMistyCascadeBadgeInfoText" }, + tmDialogue = { "_CeruleanGymMistyReceivedTM11Text", } }, ["OPP_LT_SURGE#1"] = { badge = "THUNDERBADGE", flag = "EVENT_BEAT_LT_SURGE", item = "TM_THUNDERBOLT", + gotFlag = "EVENT_GOT_TM24", + noRoom = "_VermilionGymLTSurgeTM24NoRoomText", deactivate = range("EVENT_BEAT_VERMILION_GYM_TRAINER_", 0, 2), dialogue = { "_VermilionGymLTSurgeReceivedThunderBadgeText", - "_VermilionGymLTSurgeThunderBadgeInfoText", + }, + tmPre = { "_VermilionGymLTSurgeThunderBadgeInfoText" }, + tmDialogue = { "_VermilionGymLTSurgeReceivedTM24Text", "_TM24ExplanationText", } }, ["OPP_ERIKA#1"] = { badge = "RAINBOWBADGE", flag = "EVENT_BEAT_ERIKA", item = "TM_MEGA_DRAIN", + gotFlag = "EVENT_GOT_TM21", + noRoom = "_CeladonGymTM21NoRoomText", deactivate = range("EVENT_BEAT_CELADON_GYM_TRAINER_", 0, 6), dialogue = { "_CeladonGymErikaReceivedRainbowBadgeText", - "_CeladonGymRainbowBadgeInfoText", + }, + tmPre = { "_CeladonGymRainbowBadgeInfoText" }, + tmDialogue = { "_CeladonGymReceivedTM21Text", "_TM21ExplanationText", } }, ["OPP_KOGA#1"] = { badge = "SOULBADGE", flag = "EVENT_BEAT_KOGA", item = "TM_TOXIC", + gotFlag = "EVENT_GOT_TM06", + noRoom = "_FuchsiaGymKogaTM06NoRoomText", deactivate = range("EVENT_BEAT_FUCHSIA_GYM_TRAINER_", 0, 5), dialogue = { "_FuchsiaGymKogaReceivedSoulBadgeText", - "_FuchsiaGymKogaSoulBadgeInfoText", + }, + tmPre = { "_FuchsiaGymKogaSoulBadgeInfoText" }, + tmDialogue = { "_FuchsiaGymKogaReceivedTM06Text", "_FuchsiaGymKogaTM06ExplanationText", } }, ["OPP_SABRINA#1"] = { badge = "MARSHBADGE", flag = "EVENT_BEAT_SABRINA", item = "TM_PSYWAVE", + gotFlag = "EVENT_GOT_TM46", + noRoom = "_SaffronGymSabrinaTM46NoRoomText", deactivate = range("EVENT_BEAT_SAFFRON_GYM_TRAINER_", 0, 6), dialogue = { "_SaffronGymSabrinaReceivedMarshBadgeText", - "_SaffronGymSabrinaMarshBadgeInfoText", + }, + tmPre = { "_SaffronGymSabrinaMarshBadgeInfoText" }, + tmDialogue = { "_SaffronGymSabrinaReceivedTM46Text", "_TM46ExplanationText", } }, ["OPP_BLAINE#1"] = { badge = "VOLCANOBADGE", flag = "EVENT_BEAT_BLAINE", item = "TM_FIRE_BLAST", + gotFlag = "EVENT_GOT_TM38", + noRoom = "_CinnabarGymBlaineTM38NoRoomText", deactivate = range("EVENT_BEAT_CINNABAR_GYM_TRAINER_", 0, 6), dialogue = { "_CinnabarGymBlaineReceivedVolcanoBadgeText", - "_CinnabarGymBlaineVolcanoBadgeInfoText", + }, + tmPre = { "_CinnabarGymBlaineVolcanoBadgeInfoText" }, + tmDialogue = { "_CinnabarGymBlaineReceivedTM38Text", "_CinnabarGymBlaineTM38ExplanationText", } }, ["OPP_GIOVANNI#3"] = { badge = "EARTHBADGE", flag = "EVENT_BEAT_GIOVANNI", item = "TM_FISSURE", + gotFlag = "EVENT_GOT_TM27", + noRoom = "_ViridianGymGiovanniTM27NoRoomText", deactivate = range("EVENT_BEAT_VIRIDIAN_GYM_TRAINER_", 0, 7), dialogue = { "_ViridianGymGiovanniReceivedEarthBadgeText", - "_ViridianGymGiovanniEarthBadgeInfoText", + }, + tmPre = { "_ViridianGymGiovanniEarthBadgeInfoText" }, + tmDialogue = { "_ViridianGymGiovanniReceivedTM27Text", "_ViridianGymGiovanniTM27ExplanationText", } }, diff --git a/data/scripts/yellow_jessie_james.lua b/data/scripts/yellow_jessie_james.lua index c1e4f226..7379012e 100644 --- a/data/scripts/yellow_jessie_james.lua +++ b/data/scripts/yellow_jessie_james.lua @@ -62,10 +62,15 @@ M.MT_MOON_B2F = { { "walk_npc", 6, { "left", "left", "left", "left", "left" } }, { "face_object", 6, "left" }, { "show_text", "_MtMoonJessieJamesText2" }, + -- MtMoonB2FScript12 arms _MtMoonJessieJamesText3 with + -- SaveEndBattleTextPointers before it sets wCurOpponent, so + -- TrainerBattleVictory prints it on the battle screen as "ROCKET: A + -- brat beat us?" between TrainerDefeatedText and MoneyForWinningText. + -- Its one-word first line only reads right behind that tag (#866). + { "save_end_battle_text", "_MtMoonJessieJamesText3" }, { "start_battle", "trainer", "OPP_ROCKET", 42 }, { "check_battle_result", "win" }, { "jump_if_false", "end" }, - { "show_text", "_MtMoonJessieJamesText3" }, { "show_text", "_MtMoonJessieJamesText4" }, { "stop_music" }, { "play_music", "Music_MeetJessieJames" }, @@ -85,7 +90,8 @@ M.MT_MOON_B2F = { -- motto plays from off-screen FIRST, then the duo pops in at (25,10) / -- (24,10) and whichever of them shares the player's column ($18=24 or -- $19=25, EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT) walks the three --- tiles down to loom over the player while the other steps one. A loss +-- tiles down to loom over the player while the other walks four and ends +-- up beside him. A loss -- re-hides them (RocketHideoutB4FResetScripts via EVENT_6A0), so the -- trigger re-arms clean. -- ------------------------------------------------------------------- @@ -106,7 +112,7 @@ M.ROCKET_HIDEOUT_B4F = { if f.EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES then return false end -- ON_LEFT: player under James's column (25); movement data pairs -- RocketHideoutB4FJessieJamesMovementData_45605/45606 swap so the - -- column-mate walks 3, the other 1. + -- column-mate walks 3, the other 4. local onLeft = (x == 25) ow.runner:run({ { "stop_music" }, @@ -116,16 +122,30 @@ M.ROCKET_HIDEOUT_B4F = { { "emote", "player", "shock", 30 }, { "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JAMES" }, { "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JESSIE" }, - -- James (object 2) then Jessie (object 3), Script4..Script9 order - { "walk_npc", 2, onLeft and { "down", "down", "down" } or { "down" } }, + -- James (object 2) then Jessie (object 3), Script4..Script9 order. + -- RocketHideoutB4FJessieJamesMovementData_45605 is a lone $4 that FALLS + -- THROUGH into _45606 ($4 $4 $4 $ff), so MoveSprite_ (home/pathfinding.asm) + -- reads _45605 as FOUR steps and _45606 as three; $4 is DOWN in Yellow's + -- Func_5288 lookup (engine/overworld/movement.asm), which walks with no + -- collision test. From (25,10)/(24,10) against a player on y=14 the + -- column-mate stops three down, right above him, and the other walks the + -- full four to stand alongside -- which is what the facings below assume. + -- Reading _45605 as a single step stranded whoever was off-column three + -- tiles away, so James never reached the player (#865). + { "walk_npc", 2, onLeft and { "down", "down", "down" } + or { "down", "down", "down", "down" } }, { "face_object", 2, onLeft and "down" or "left" }, - { "walk_npc", 3, onLeft and { "down" } or { "down", "down", "down" } }, + { "walk_npc", 3, onLeft and { "down", "down", "down", "down" } + or { "down", "down", "down" } }, { "face_object", 3, onLeft and "right" or "down" }, { "show_text", "_RocketHideoutJessieJamesText2" }, + -- RocketHideoutB4FScript10 saves _RocketHideoutJessieJamesText3 as the + -- end-battle text, so it prints as "ROCKET: Such a dreadful twerp!" on + -- the battle screen ahead of MoneyForWinningText (#866). + { "save_end_battle_text", "_RocketHideoutJessieJamesText3" }, { "start_battle", "trainer", "OPP_ROCKET", 43 }, { "check_battle_result", "win" }, { "jump_if_false", "lost" }, - { "show_text", "_RocketHideoutJessieJamesText3" }, { "show_text", "_RocketHideoutJessieJamesText4" }, { "stop_music" }, { "play_music", "Music_MeetJessieJames" }, @@ -175,16 +195,27 @@ M.POKEMON_TOWER_7F = { { "show_text", "_PokemonTowerJessieJamesText1" }, { "face_player_dir", "up" }, { "emote", "player", "shock", 30 }, - -- Jessie (object 1) then James (object 2), Script1..Script6 order - { "walk_npc", 1, onLeft and { "down" } or { "down", "down", "down" } }, + -- Jessie (object 1) then James (object 2), Script1..Script6 order. + -- Same fall-through blob as the hideout: PokemonTower7FMovementData_60d7a + -- is a lone $4 running into _60d7b ($4 $4 $4 $FF), so _60d7a is FOUR + -- steps and _60d7b is three. From (10,8)/(11,8) against a player on + -- y=12 the column-mate halts one tile above him and the other closes the + -- full four to his side; the single-step reading is why James only + -- "moved a bit" here (#865). + { "walk_npc", 1, onLeft and { "down", "down", "down", "down" } + or { "down", "down", "down" } }, { "face_object", 1, onLeft and "right" or "down" }, - { "walk_npc", 2, onLeft and { "down", "down", "down" } or { "down" } }, + { "walk_npc", 2, onLeft and { "down", "down", "down" } + or { "down", "down", "down", "down" } }, { "face_object", 2, onLeft and "down" or "left" }, { "show_text", "_PokemonTowerJessieJamesText2" }, + -- PokemonTower7FScript7 saves _PokemonTowerJessieJamesText3 as the + -- end-battle text: "ROCKET: You will regret this!" on the battle screen, + -- before the prize money (#866). + { "save_end_battle_text", "_PokemonTowerJessieJamesText3" }, { "start_battle", "trainer", "OPP_ROCKET", 44 }, { "check_battle_result", "win" }, { "jump_if_false", "end" }, - { "show_text", "_PokemonTowerJessieJamesText3" }, { "show_text", "_PokemonTowerJessieJamesText4" }, { "stop_music" }, { "play_music", "Music_MeetJessieJames" }, @@ -254,10 +285,12 @@ M.SILPH_CO_11F = { { "walk_npc", 6, jessieDirs }, { "face_object", 6, jessieFace }, { "show_text", "_SilphCoJessieJamesText2" }, + -- SilphCo11FScript11 saves _SilphCoJessieJamesText3 (SilphCo11FText_624c2) + -- as the end-battle text: "ROCKET: Like always..." before the money (#866). + { "save_end_battle_text", "_SilphCoJessieJamesText3" }, { "start_battle", "trainer", "OPP_ROCKET", 45 }, { "check_battle_result", "win" }, { "jump_if_false", "end" }, - { "show_text", "_SilphCoJessieJamesText3" }, { "show_text", "_SilphCoJessieJamesText4" }, { "stop_music" }, { "play_music", "Music_MeetJessieJames" }, diff --git a/docs/architecture.md b/docs/architecture.md index a1d58df6..09bbc60d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -38,7 +38,7 @@ the same core data and graphics into the source tree for verification. | | `src/core/SaveData.lua` | Lua-serialized save in the LÖVE save dir | | render | `src/render/Renderer.lua` | 160x144 canvas, integer nearest scaling | | | `src/render/TileRenderer.lua` | one SpriteBatch per map (8x8 quads) + border-block ring | -| | `src/render/SpriteRenderer.lua` | 6-frame walker sheets, flipped right facing | +| | `src/render/SpriteRenderer.lua` | variable-size anchored sprite sheets, 6-frame walkers and flipped right facing | | | `src/render/Font.lua` | glyph rendering via charmap (greedy longest match) | | | `src/render/TextBox.lua` | dialogue box: typewriter, `\n` line, `\v` scroll, `\f` page | | | `src/render/Camera.lua`, `Transition.lua` | follow camera, warp fades | diff --git a/docs/behavior-porting-notes.md b/docs/behavior-porting-notes.md index 8693bcef..d26affce 100644 --- a/docs/behavior-porting-notes.md +++ b/docs/behavior-porting-notes.md @@ -234,8 +234,12 @@ What was ported from pokered's engine code and where it came from. pre-battle text and engages the leader battle (badge/TM via data/scripts/victories.lua); post-badge talk prints the leader's post-battle advice text (Misty's is her TM11 explanation). The - originals' middle branch (beaten but TM not handed over) is - unreachable since the TM is granted with the victory. Giovanni's + originals' middle branch (beaten but TM not handed over, + CheckEventReuseA EVENT_GOT_TM*) is ported too: the victory's GiveItem + goes through the bag's capacity check, a full bag shows the leader's + "make room" text instead of the received lines and leaves + EVENT_GOT_TM* unset, and talking to the leader re-runs the ReceiveTM + script until the TM goes in (#797). Giovanni's farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a fade-to-black/fade-in Transition matching ViridianGym.asm's GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted diff --git a/docs/launcher.md b/docs/launcher.md index 065d40d0..c9a43616 100644 --- a/docs/launcher.md +++ b/docs/launcher.md @@ -195,11 +195,13 @@ through `src/import/SaveFileIO.lua`, which sits on top of (checked against `listSlots`). `SaveFileIO.exportActiveSlot` loads the active slot, encodes it back with `SaveConvert.exportSav` (a slot never keeps `rawImport`, so this is a zero-filled template export, which is valid), and - writes `exports//gen1recomp--.sav` in the save - directory (`exports/` and `exports//` are created as needed). On - desktop it returns the absolute path (`love.filesystem.getSaveDirectory()`), - which the notice line shows with an "Open folder" affordance - (`love.system.openURL("file://" .. dir)`). + writes `exports//gen1recomp--.sav` under the same + root `persistFs` writes slots to: the portable game folder when `portable.txt` + marks the install, otherwise the save directory (`exports/` and + `exports//` are created as needed; #752). On desktop it returns the + absolute path (`SaveData.portableBaseDir()` when portable, else + `love.filesystem.getSaveDirectory()`), which the notice line shows with an + "Open folder" affordance (`love.system.openURL("file://" .. dir)`). On Android the bytes are also staged as `pending_export.sav` and `love.system.createFile(suggestedName)` opens `ACTION_CREATE_DOCUMENT` so the player can save to Downloads / Drive / etc.; on return `export_done.flag` diff --git a/docs/linux-arm64-build.md b/docs/linux-arm64-build.md new file mode 100644 index 00000000..b02e0ced --- /dev/null +++ b/docs/linux-arm64-build.md @@ -0,0 +1,202 @@ +# Linux arm64 (aarch64) AppImage + +Releases ship `gen1recomp--linux-arm64.AppImage` alongside the +existing x86_64 `gen1recomp--linux.zip`. It targets 64-bit ARM +desktop Linux: Raspberry Pi 4/5 running Raspberry Pi OS, Armbian and other +SBC distros, arm64 VMs on Apple Silicon, Ampere/Graviton desktops, and the +aarch64 handhelds that run a full distro. + +> The Anbernic RG34XXSP has its own PortMaster-style pack +> (`gen1recomp-*-rg34xxsp-stockos64-mod.zip`, see +> [anbernic-rg34xxsp.md](anbernic-rg34xxsp.md)). That one bundles PortMaster's +> LÖVE runtime and expects the device's own SDL; this AppImage is the generic +> desktop-Linux artifact and shares nothing with it but the `game.love`. + +## For players + +```sh +chmod +x gen1recomp-*-linux-arm64.AppImage +./gen1recomp-*-linux-arm64.AppImage +``` + +Then use **Import ROM** in the launcher to point it at your own legal Red / +Blue / Yellow cartridge dump, exactly as on every other platform. + +If your system has no FUSE (`dlopen(): error loading libfuse.so.2`), either +install it (`sudo apt install libfuse2`) or run without it: + +```sh +./gen1recomp-*-linux-arm64.AppImage --appimage-extract-and-run +``` + +### What the host has to provide + +Very little, and this is enforced by an assertion in the build rather than by +good intentions. The only libraries the AppImage requires at startup are: + +``` +glibc 2.29+ libstdc++ libfreetype6 zlib +``` + +Everything else — OpenGL/Mesa, X11, Wayland, KMSDRM, ALSA, PulseAudio — is +**dlopened**, so it is used when present and skipped when absent. That means +one image runs on a full desktop, on a Wayland-only session, on a +KMSDRM-only handheld with no X server, and on a box with ALSA but no +PulseAudio, without a different build for each. + +That property does not come for free from Debian's packages, and getting it +is most of what the build below is doing; see +[Why five libraries are built from source](#why-five-libraries-are-built-from-source). + +## For builders + +```sh +scripts/build_linux_arm64.sh --version 0.1.0 +``` + +Output: + +``` +dist/linux-arm64/gen1recomp--linux-arm64.AppImage +dist/linux-arm64/gen1recomp--linux-arm64.AppImage.sha256 +``` + +Useful flags: `--game-love PATH` reuses an already-packed payload (CI does +this so every platform ships identical bytes), `--rebuild-image` forces the +builder container to rebuild, `--clean-cache` throws away the pinned +downloads and the compiled LÖVE prefix. + +### Requirements + +An **aarch64 host** with **docker or podman**. A Raspberry Pi 5 is the +reference machine (a cold build takes about 10 minutes on one — six libraries +plus the engine; rebuilds reuse the cached prefix and take seconds). Apple Silicon with Docker +Desktop and GitHub's `ubuntu-24.04-arm` runner both work too. + +The script refuses to run on x86_64 rather than falling back to qemu-user +emulation: that path takes hours and has produced miscompiled LuaJIT. + +### Why this is not just another `scripts/build.sh` target + +`scripts/build.sh linux` downloads LÖVE's official `love-11.5-x86_64.AppImage`, +unpacks its squashfs, drops `game.love` in, and glues it back together. That +trick is not available here — **LÖVE publishes no aarch64 binary at all.** The +11.5 release has win32, win64, macOS, Android, iOS and one x86_64 AppImage, +and that is the entire list. + +So this build compiles LÖVE 11.5 from the official `linux-src` tarball and +assembles the AppImage from scratch. Every pinned input — the LÖVE source, the +five libraries built alongside it, and the AppImage type-2 runtime — is +SHA-256 verified on the host before the container ever sees it, and the +container itself runs with no network access. + +### Why the build happens in a Debian bullseye container + +glibc is backward compatible but not forward compatible: a binary linked +against glibc 2.41 will not start on a system with 2.31, and there is no way +to fix that after the fact. Compiling on the oldest base we support is +therefore the only thing that makes one artifact work everywhere. + +Bullseye (glibc 2.31) is that base. The resulting binaries actually come out +needing only **glibc 2.29** and **GLIBCXX_3.4.21**, so the AppImage covers +everything from Ubuntu 20.04 and Raspberry Pi OS bullseye through current +trixie. + +This is a statement about the *compile environment*, not about where the +artifact runs — building on your own newer distro would silently raise that +floor and strand every user on an older one, with no symptom until they +download it. CI enforces the floor: `linux-arm64-build` fails if the highest +required glibc symbol version climbs above 2.31. + +### Why five libraries are built from source + +SDL2, OpenAL, libtheora, libogg/libvorbis and libmpg123 are compiled rather +than installed from bullseye. In every case the reason is *correctness*, not +a newer version number — Debian builds these for a system where every +dependency is installed and co-versioned, which is the opposite of an +AppImage's situation. Each one broke the build in a different way, and all +three failure modes are now assertions that fail the build instead of +shipping. + +**1. Hard-linked backends (SDL2, OpenAL).** Debian's `libSDL2` lists +`libpulse`, `libasound`, `libX11` and `libwayland-client` as `DT_NEEDED` — +resolved by the loader at startup, not dlopened. An AppImage bundling it +refuses to start unless the host has *all four*. It appeared to work in +testing only because a desktop Pi has all four; a headless CI runner is what +exposed it. Debian's OpenAL does the same via `libsndio`, which itself +hard-links `libasound`. Built from source with `--enable-*-shared` and +`ALSOFT_DLOPEN`, both dlopen their backends instead. + +**2. A stray link (libtheora).** Debian's `libtheoradec.so.1` is linked +against `libcairo.so.2` — a packaging artifact, since a video decoder has no +business drawing vector graphics — and cairo drags in X11, xcb, fontconfig +and freetype. `--disable-examples` produces a `libtheoradec` needing only +`libogg`. + +**3. SONAME collision with the host (ogg, vorbis, mpg123).** The subtle one. +OpenAL dlopens ALSA, ALSA's config loads its PulseAudio hook plugin, and that +plugin pulls the *host's* `libsndfile` into our process. `libsndfile` links +`libogg`, `libvorbis` and `libmpg123` — the same three we bundle. The loader +resolves a SONAME exactly once per process, so the host's `libsndfile` binds +to *our* copies: + +``` +openal -> libasound -> libasound_module_conf_pulse -> libsndfile (host, new) + `-> mpg123_info2 -> libmpg123 (ours, bullseye 1.26) +``` + +`mpg123_info2` arrived in mpg123 1.32, so the plugin failed to relocate, ALSA +config collapsed, and the game ran with **no audio device at all**. Not +bundling these instead would make `libogg`/`libvorbis`/`libmpg123` mandatory +host packages; building them current means our copies *satisfy* the host's +`libsndfile` rather than starving it. + +The same collision is why the font stack — freetype, fontconfig, libpng, +brotli, zlib — is left to the host entirely. Bundling a bullseye freetype +2.10.4 meant a host `libcairo` could not find `FT_Get_Transform` (added in +2.11) and the game died at startup. Leaving the whole stack to the host keeps +it self-consistent, while `liblove` — compiled against 2.10.4 — only ever +asks for symbols every supported host already has. + +The general rule this all reduces to: **never bundle a library the host's own +stack may also load, unless yours is at least as new as theirs.** + +### CI + +Three jobs, path-gated on `scripts/build_linux_arm64.sh`, +`scripts/linux-arm64/`, `scripts/pack_love.sh` and this document: + +- **`linux-arm64-selftest`** (`ubuntu-latest`, x86_64) — offline gate. Checks + the pins are real digests on a dated tag rather than the moving + `continuous` one, that the Dockerfile still builds on bullseye, that the + exclude list still classifies known sonames correctly, that AppRun still + launches `game.love` with `--fused`, and that the host-arch guard actually + fires. Needs no container and no arm64 machine. +- **`linux-arm64-build`** (`ubuntu-24.04-arm`) — the real build, then extracts + the artifact and asserts the layout, that every bundled object resolves + under AppRun's `LD_LIBRARY_PATH`, and that the glibc floor is still ≤ 2.31. + Uploads the AppImage for 7 days. +- **release** — `linux-arm64` runs on `ubuntu-24.04-arm`, reuses the shared + `game.love` from the `love-payload` job, and the AppImage is staged and + published like every other release asset. + +Unlike the Switch job, none of this needs secrets or self-hosted hardware, so +it runs on fork PRs too. + +### Updating the pins + +Both pins live in `scripts/linux-arm64/common.sh`: + +- `LOVE_VERSION` / `LOVE_SRC_SHA256` — bumping any version invalidates the + cached prefix automatically (its name is keyed by every source version at + once, so a partial rebuild cannot mix vintages). Check that bullseye still + has `-dev` packages new enough for the new release; `build_appimage.sh` + asserts every optional module actually linked, because LÖVE's `configure` + exits 0 and silently drops a module when one is missing. +- `SDL2_*`, `OPENAL_*`, `THEORA_*`, `OGG_*`, `VORBIS_*`, `MPG123_*` — the + source-built libraries. Bumping these is usually safe and occasionally + necessary: `libmpg123` in particular must stay at least as new as what a + target host's `libsndfile` expects, which is asserted for `mpg123_info2`. +- `APPIMAGE_RUNTIME_TAG` / `APPIMAGE_RUNTIME_SHA256` — always a dated tag + from [AppImage/type2-runtime](https://github.com/AppImage/type2-runtime/releases). + The selftest fails the build if this ever points at `continuous`. diff --git a/docs/modding.md b/docs/modding.md index 5aa0bec0..b13fb048 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -35,6 +35,16 @@ An edited vanilla map becomes a `mod.content.maps:patch` carrying only the fields that moved; a new map becomes a `:register`. See `docs/new-features.md` and the extension's own README. +## Read-only map overviews + +`mod.world:mapOverview()` returns collision `rows` at map-cell resolution, +optional visual `tileRows` at 2x resolution, and optional `tileDetailRows` at +4x resolution. Visual rows contain Game Boy shades from `"0"` (lightest) to +`"3"` (darkest); their matching width and height fields describe the grid. +`markers` contains active `{ kind, x, y }` points in map-cell coordinates for +`warp`, visible `item`, and untaken `hidden` locations. All fields are +read-only snapshots; mods choose which layers to render. + ## Rendering pipelines Most registries hand the engine *content*. `render_pipelines` hands it @@ -100,6 +110,44 @@ Three rules worth knowing: Returning `nil` from `drawWorld` is a normal answer meaning "not this frame"; the engine draws the vanilla world instead. +## Variable-size overworld sprites + +The `sprites` registry keeps the vanilla 16x16 grounded walker as its default, +but a mod can describe any frame rectangle and anchor for player characters, +NPCs, followers, mounts, vehicles, bosses, or other field actors: + +```lua +mod.content.sprites:register("SPRITE_COMPANION", { + image = "mods/example/companion.png", -- one frame per row + frames = 6, + walker = true, + frameWidth = 32, + frameHeight = 32, + anchorX = 16, -- frame-relative bottom-center anchor + anchorY = 32, +}) +``` + +`frameWidth` and `frameHeight` are sheet pixels. `anchorX` and `anchorY` are +measured from each frame's top-left; when omitted they default to the frame's +horizontal center and bottom edge, so a larger sprite grows upward while its +feet stay on the same world cell. Omitting all four fields is exactly the +vanilla 16x16 placement. The normal player/NPC/follower draw paths consume +these values automatically, including horizontal flips and the fishing pose. + +Custom render pipelines can use the same geometry without reproducing the +pose rules: + +```lua +local geometry = sprite:getPoseGeometry(facing, walkPhase, stepFlip) +-- geometry.quad, .x/.y/.width/.height, .anchorX/.anchorY, .mirror +local originX, originY = sprite:getScreenOrigin(px, py, camX, camY) +``` + +`getFrameGeometry(frame)` is the corresponding accessor for a specific +zero-based sheet frame. Both accessors return fresh tables and share the +renderer’s frame selection and mirror conventions. + ## Battle sprite scaling The enemy's front pic draws at 1x and the player's back pic at 2x, the way @@ -140,6 +188,90 @@ default** (1x front, 2x back). ball-to-pic grow multiplies your scale through each stage, so a rescaled mon still grows into place from the ball, grounded the whole way. +## Durable tool storage and runtime checkpoints + +`mod.save` remains the right place for state that should travel with the next +normal Pokémon SAVE. Tools that need independently written, larger data-only +records can use `mod.storage`; the engine scopes every logical key by game +version, opaque playthrough identity, and mod id, and routes it through the same +standard or portable persistence backend as saves: + +```lua +local context, code, message = mod.storage:context(game) +local ok, code, message = mod.storage:write(game, "history/quick/q0001", { + format = 1, createdAt = os.time(), payload = { money = 3000 }, +}) +local value, code, message = mod.storage:read(game, "history/quick/q0001") +local keys, code, message = mod.storage:list(game, "history/quick") +local deleted, code, message = mod.storage:delete(game, "history/quick/q0001") +``` + +`context` returns `{ engineVersion, gameVersion, playthroughId }`. The engine +version is compatibility metadata; physical launcher-slot and path identity stays +private. + +Values must be tables containing serializable data only. Keys are conservative +slash-separated segments (letters, digits, `_`, `-`); paths and filesystem +handles are never exposed. Writes are staged and decode-verified, reads recover +from a valid staged/backup generation, and methods return structured errors for +normal data or I/O failures. The playthrough identity is allocated lazily on the +first storage/checkpoint call, so an unused API changes no save bytes. + +`mod.checkpoints` captures and reconstructs engine-owned semantic runtime state: + +```lua +local capability = mod.checkpoints:inspect(game) +if capability.canCapture then + local checkpoint, code, message = mod.checkpoints:capture(game) + -- Store the detached data-only checkpoint through mod.storage. +end + +local ok, code, message = mod.checkpoints:restore(game, checkpoint) +``` + +Checkpoint format 1 supports settled overworld control and proven battle +player-decision safe points. Battle checkpoints are limited to ordinary +single-player wild/trainer origins with no suspended script; link, Safari, +ghost, demo, scripted, animation, message, queue, and forced-action phases fail +closed. New checkpoints preserve gameplay RNG, while legacy overworld records +without RNG remain loadable. Capture excludes global options and runtime +objects. Restore validates format, game/playthrough identity, content, +coordinates, battle relationships, continuation, and RNG before mutation; +preserves current options; suppresses normal map-entry/save-load/intro side +effects; verifies a recapture; and rolls back runtime plus RNG in memory if +reconstruction fails. Callers that need crash recovery should durably capture +their own recovery checkpoint before restore. + +Checkpoint ownership follows the persistence model rather than mod identity: + +- canonical `game.save` progress, including every mod's `save.modData` / + `mod.save` bucket and data-only fields added to saved Pokémon, rewinds; +- global and per-mod options remain at their current values; +- independently written `mod.storage` records do not rewind; and +- mod-owned runtime objects, references, and caches are never serialized. + +Successful restore emits `checkpoint.restored` only after reconstruction and +differential recapture have committed. Mods that cache rewound progress or hold +references to reconstructed runtime objects can re-read their own public state +and rebuild at that point: + +```lua +mod.events:on("checkpoint.restored", function(ev) + -- ev.kind is "overworld" or "battle"; ev.game is fully reconstructed. + cachedQuestStage = mod.save:get("quest_stage", 0) + rebuildRuntimeFor(ev.game, ev.kind) +end) +``` + +The event is not emitted for validation failure, failed reconstruction, or a +successful rollback. Its payload contains no checkpoint data or other mod's +private state. A mod that deliberately stores progress-coupled truth in +`mod.storage` must version and reconcile that relationship itself; the engine +cannot distinguish it safely from independent history, configuration, or cache +data. + +See RFC 0003, RFC 0004, and RFC 0005 for exact contracts and error codes. + ## Developer console Boot with developer mode on to unlock the in-game console and hot-reload @@ -174,6 +306,32 @@ It runs immediately before queued button edges are promoted, so input added by the wrapper is visible during that same fixed step. The callback receives `(next, game, dt)` and must call `next(game, dt)`. +`input.pointer` delivers uncaptured gameplay pointer events -- touches and +real mouse input alike. The callback receives `(next, game, ev)` where `ev` +is `{ phase, source, id, x, y, dx, dy, pressure, button }`: `phase` is +`"pressed"`, `"moved"`, `"released"` or `"cancelled"`; `source` is `"touch"` +or `"mouse"`; `id` is the LÖVE touch id or `"mouse"`; and the coordinates +are LOVE window units, the same space `render.hud`'s viewport and the touch +overlay lay out in. The on-screen touch controls keep first refusal: a +pointer that begins on a virtual control belongs to the pad for its whole +lifecycle and never reaches the hook, while one that begins outside stays +visible even if it later crosses a control. A real mouse reaches the hook +without `POKEPORT_TOUCH` (synthesized `istouch` mouse twins are dropped, so +a mobile touch fires once), and focus or visibility loss and input recovery +deliver a `"cancelled"` for every pointer the hook saw pressed but not yet +released. Return `true` without calling `next` to consume the event. + +`mod.input` presses GB buttons source-safely. `mod.input:tap(game, btn)` +queues exactly one `wasPressed` edge for the next fixed step and holds +nothing; `local token = mod.input:press(game, btn)` holds the button until +`mod.input:release(token)`. Buttons are `up`, `down`, `left`, `right`, `a`, +`b`, `start` and `select`. Every press is its own input source inside the +engine's multi-source bookkeeping, so releasing a token never clears a hold +the keyboard, a controller, the touch overlay or another mod still owns; +`release` is idempotent and refuses tokens taken by another mod. +Outstanding tokens are released automatically on entry-chunk rollback, hot +reload and input recovery. + `ui.title_menu.items` receives `(next, game, items)` and follows the same decorate-after-`next` convention as `ui.start_menu.items`. It is the safe place for a tool to offer a fresh-session action before gameplay begins. @@ -195,10 +353,76 @@ the finished `worldCanvas` and `uiCanvas` with their SGB `zones` / `worldZones`, `worldActive`, the frame metrics (`ww`, `wh`, `pw`, `ph`, `ox`, `oy`, `vpw`, `vph`, `scale`, `Sx`, `Sy`, `dpiX`, `dpiY`), `renderer:blitCanvas(...)` for a palette-correct blit of either canvas into an arbitrary screen rect, and the -`secondScreen` bridge (`available()` / `push(imageData, w, h)` / `setEnabled`) -for driving a second physical display. This is what lets a mod lay the two -passes out as two stacked Game Boy screens, or push one onto a second screen, -without the engine knowing the layout. +`secondScreen` bridge (`available()` / `push(imageData, w, h)` / `pollTouch()` / +`setEnabled`) for driving a second physical display. `pollTouch()` returns the +oldest queued event as `"action,x,y"` in submitted-frame coordinates, or `nil`. +This is what lets a mod lay the two passes out as two stacked Game Boy screens, +or push one onto a second screen, without the engine knowing the layout. + +`screen.render_visible` receives `(next, state)` while the main screen is being +composed. Return `false` to omit that state from drawing, opacity selection and +palette-zone ownership. The state remains on the stack and keeps its normal +update and input ownership, so a mod can mirror a native menu on another +display without reimplementing it. The default is `true`. Treat the wrapper as +a pure predicate: the renderer may ask it more than once per frame. + +Scrollable list states expose `state.kind` for use with this hook. Generic +lists fall back to their title; PC lists use stable, localization-independent +identifiers: `pc_box_withdraw`, `pc_box_deposit`, `pc_box_release`, +`pc_box_change`, `pc_item_withdraw`, `pc_item_deposit`, and `pc_item_toss`. + +`battle.bottom_ui_visible` and `battle.status_hud_visible` independently +control the battle text/menu layer and the HP/status panels. Both receive +`(next, state)` and default to `true`, so vanilla rendering is unchanged. +Pushed text boxes also pass through `battle.bottom_ui_visible`; a wrapper that +only owns battle presentation should return `false` only for its active battle +or text-box state. + +`core.logic_speed` receives `(next, game)` once per `Game:logicSpeed()` call +(once per frame). Vanilla behavior resolves the per-category GAME SPEED +option (`GameSpeed.CATEGORIES`: overworld/battle/menu) for whichever +category `Game.speedCategoryInStack` says is active right now. A mod may +call `next(game)` and return its result to pass that resolution through, or +return a different number outright to override it for that frame (a bot mod +forcing 1X for one route segment, say, regardless of the category or saved +option). The result is clamped to the nearest valid `GameSpeed.LEVELS` entry +regardless of what a subscriber returns, so a bad value (0, negative, `nil`) +cannot destabilize the fixed-step accumulator. This hook runs *after* link +play's 1X lock and the `--speed`/equivalent run-argument override, both of +which stay unconditional and are never visible to a subscriber. Developer mode also arms the mod loader's dev tripwire, which flags mods that reach outside their permission set. + +## Process-lifecycle hooks + +These exist so a platform-specific launcher integration (a native shell +that embeds this engine and wraps its window in platform UI) can live +entirely in a mod instead of hand-patching `main.lua`, which every other +engine change also touches. + +`core.update` receives `(next, game, dt)` once per frame from +`love.update`. Vanilla behavior is `game:update(dt)`, unconditionally. A +mod may skip calling `next(game, dt)` to pause the simulation for that +frame (e.g. while a native settings sheet is on top), and may run +additional per-frame polling before or after that call regardless of +whether it calls `next` -- useful for one-shot flags that must be observed +every frame even while paused. + +`core.quit_to_launcher` receives `(next)` once from `love.quit()`. `next()` +returns the engine's own decision for whether closing the window should +return to the Lua launcher instead of exiting; a mod may return `false` +outright, without ever calling `next`, to veto that and let the process +really quit -- for a platform host that owns its own "return to launcher" +UI and would otherwise get looped straight back into the game it just +quit. + +A manifest may also declare `force_enable_env`, an environment variable +name that re-enables the mod regardless of a saved disable in +`options.mods` when that variable is set to `"1"`. This is for a mod that +cannot function disabled on the one build where its env var is set (a +platform-bridge mod bundled only with that build's launcher, for example). + +Neither hook needs a `Runtime.wantsHook` guard before calling it: `Hooks:call` +already falls straight through to the vanilla function when no mod has +wrapped the name, at negligible cost. diff --git a/docs/new-features.md b/docs/new-features.md index ea28584b..93c6d23e 100644 --- a/docs/new-features.md +++ b/docs/new-features.md @@ -1,531 +1,26 @@ -# New features (deliberate additions beyond the original) - -Intentional enhancements this port adds on top of faithful Pokémon Red -behavior. They have no Game Boy equivalent and are kept by design. -Genuine divergences from the original (things still missing, wrong, or -approximated) live in docs/known-differences.md; faithfully-ported -behavior is in docs/behavior-porting-notes.md. - -## Survey zoom - -The mouse wheel (or `-`/`=`), the Options **ZOOM** row, or hotkey `4` -zooms the overworld between 1 pixel per world pixel (full survey) and 2× -the window fit scale (close-up), in crisp integer steps. This has no Game -Boy equivalent: - -- Connected maps render their full bodies, and their NPCs appear as - visual-only "ghosts", they wander but have no sight lines, triggers, - dialogue, or collision until the map is actually entered. -- Menus, text boxes, and battles draw at normal scale on top of the - zoomed world. Zoom input is ignored while a script, menu, or battle is - active; the zoom offset is persisted as `save.options.zoom` (default - `0` = FIT) and survives New Game via `options.lua`. -- Hotkey `4` ticks through every integer zoom level (survey → FIT → - close-up → wrap). The Options row shows `FIT` / `OUTn` / `INn`. -- Beyond the border ring the void fill repeats indefinitely (see VOID - FILL below); interiors keep their own border block. Each visible map - area is colorized with its own SGB palette (the original recolored the - whole screen per map). -- Neighbor maps load two connection hops out so corner-adjacent maps - don't pop in and out, and ghost NPCs share instances with the real ones - so their wander positions persist across seamless connection crossings - (a warp or fresh map entry still respawns everything at its script - position, like the original's per-entry sprite init). - -## VOID FILL - -The Options **VOID FILL** row picks what paints the infinite beyond-edge -space on OVERWORLD-tileset maps during survey zoom: - -- **TREES** (default): solid tree wall block `$0F`. -- **WATER**: animated water tile `$14` (same hshift cycle as on-map water). -- **BLACK**: solid black. - -Other tilesets are unchanged (house/cave borders stay as authored). -Persisted as `save.options.voidFill`. - -## Tilt mode - -The `3` key (and the Options menu TILT row) cycles a visual-only perspective -tilt of the overworld through **OFF → 15° → 35° → 50° → OFF** for an HD-2D / -diorama look. Like survey zoom this is purely presentational and has no -Game Boy equivalent: - -- The entire map tilts as one rigid ground plane, paths, grass, water, - floors, and every background-tile structure (buildings, trees, fences, - signs; in Gen 1 these are baked into the tile layer, not sprites), so - rows above the player recede and rows below come toward the viewer. Only - things that actually *stand* on the ground draw as upright billboards, - unscaled and pixel-identical to flat mode: the player, NPCs, item balls, - and the standing FX attached to them (emote bubbles, the fishing rod, - the FLY bird). The Poké Center heal-machine overlay stays on the ground - plane with the machine tiles (it is OAM glued to a BG graphic, not a - standing sprite). An earlier revision tried - billboarding buildings/trees/signs too (cutting them out of the ground - per hand-curated per-tileset tables); that chased an endless tail of - special cases, dense tree canopy, fences fused into grass, building - facades with their own baked-in fake perspective, because Gen 1's art - was never drawn with a clean seam between ground and standing scenery. It - wasn't merged; tilting everything but the characters as one plane is the - simpler, shipped tradeoff (buildings recede/foreshorten with the ground - like a photo of a diorama, rather than standing fully upright next to - a full-height character). -- Cycling tweens the angle between levels over ~0.25s rather than snapping; - with tilt fully off the world pass drops back onto the flat blit path, so - flat rendering stays pixel-identical to tilt-off and off costs nothing. -- Tilt input is gated exactly like survey zoom, honored only while - free-roaming, ignored while a script, menu, or battle is active, and it - composes with survey zoom (the zoom scale feeds the projection). The tilt - level is persisted in `save.options.tilt` (default OFF). -- It applies everywhere the overworld draws, interiors and caves included. - Menus, text boxes, and battles render flat on top, unaffected, and the - infinite beyond-the-border-ring fill stays flat by design. -- Collision, movement, sight lines, triggers, encounters, and scripts are - untouched; nothing about the tilt reaches gameplay. - -## Colors mode - -The `2` key (and the Options menu COLORS row) cycles the display mode -through **OG RED → SGB → ADVANCED → OG → OG INV → SGB INV → CLASSIC → OG RED** -(on Blue the first slot labels **OG BLUE**; on Yellow, **OG YELLOW**). -The first three are the real colorizations; the rest are DMG-shade novelties: - -- **OG RED** / **OG BLUE**: the Game Boy Color boot-ROM look for that cart -- - one global BG palette + one OBJ palette, every map, no per-map variation - (Red/Blue ship no CGB code, so on a GBC the boot ROM colors them globally). - The player/NPCs keep the boot-ROM OBJ color over the terrain via the OBP - bake + post-zone redraw (`PaletteFX.GBC_BG` / `GBC_OBJ`, or Blue's blue/pink - pair). -- **OG YELLOW** (Yellow playthrough, same `ogred` save id): Pokemon Yellow's - authentic GBC look from `CGBBasePalettes` (`data/palettes_yellow.lua`, - sourced from pret/pokeyellow). Per-map / per-species colors, not a single - boot-ROM ramp -- Yellow was CGB-enhanced. -- **SGB** (default): the per-map Super Game Boy region palettes - (`data/sgb/sgb_palettes.asm`). Sprites tint with the region palette, as on - real SGB. (This is the mode formerly mislabeled "GBC".) -- **ADVANCED**: pokered-gbc SuperPalettes -- real per-tile GBC coloring plus - per-species mon colors (`data/palettes_gbc.lua`). (Formerly labeled - "RED++"; it is the richest colorization rather than anything Red-specific.) -- **OG**: force the four DMG grays (colorization off). -- **OG INV**: inverted DMG grays. -- **SGB INV**: each SGB zone palette with shade order reversed. -- **CLASSIC**: original Game Boy pea-soup greens - (`#9BBC0F` / `#8BAC0F` / `#306230` / `#0F380F`). - -The shade-remap transform is applied centrally in `PaletteFX.sendColors`, so -it covers overworld, menus, battles, and tilt upright billboards. OG RED's -global BG palette is supplied by `OverworldState:overworldBgColors` (per-map -override in the overworld pass). Persisted as `save.options.colors`; the -`gbc` / `gbc_inv` / `redpp` save ids are kept for back-compat under the new -labels. - -## GBC FX - -The `5` key (and the Options menu GBC FX row) cycles a "played on real -unlit-GBC hardware" post-process through **OFF → 1 → 2 → 3 → 4**. The -levels are a cumulative ladder: - -- **1**: reflective-screen backing transparency. -- **2**: + LCD pixel grid. -- **3**: + pixel drop shadows. -- **4**: + sunlight glare and rainbow shimmer with a drifting light. - -It runs as a final present pass after world + UI composite in -`Renderer:endFrame`, inspired by the Pixel Transparency RetroArch shader -([github.com/mattakins/Pixel_Transparency](https://github.com/mattakins/Pixel_Transparency)). -Default OFF; persisted as `save.options.gbcfx`. - -Mobile GPUs often compile the pass but present a black frame, so Android and -iOS hide the row entirely, pin the level to OFF, and rewrite a level already -persisted in `options.lua` (issue #136). `POKEPORT_GBCFX` overrides that -decision either way, same tri-state as `POKEPORT_TOUCH`: `=0` refuses the -effect, `=1` forces it available. The Anbernic handheld pack exports `0` from -its launcher because the device reports `"Linux"` while its GPU is in the -phone class (see [Anbernic RG34XXSP](anbernic-rg34xxsp.md)). - -## Performance tier (low-end devices) - -The Options **PERFORMANCE** row scales the port's optional presentation -extras down for weaker hardware. The extras it governs are the three -heaviest things the port adds on top of the original -- the whole-screen 3D -**TILT** (transforms the entire map as a ground plane), the **GBC FX** -post-process shader (a fullscreen pass), and survey **ZOOM** (zooming out -renders the connected neighbor maps, a lot of extra overdraw) -- plus a hard -FPS ceiling. None of this touches game logic, which is fixed-step off `dt` -(`src/core/FixedStep.lua`), so every tier plays identically; they differ -only in how much eye-candy the renderer is allowed to do. - -| Tier | TILT | GBC FX | Survey ZOOM | Extra FPS ceiling | -| ------------ | ---- | ------ | ----------- | ----------------- | -| **HIGH** | on | on | on | none | -| **BALANCED** | off | off | on | none | -| **LOW** | off | off | off | 60 | -| **AUTO** | picks a default from the device (below) ||| - -- **AUTO** (the default) reads the device once at boot: ARM Linux handhelds - (e.g. the RG34XXSP) resolve to **LOW**, phones/tablets and very-low-core - desktops to **BALANCED**, and everything else -- a normal desktop, and - every existing `options.lua` that predates this option -- to **HIGH**, - so the common case is unchanged. See `src/core/Performance.detect`. -- AUTO only chooses the *default*; all four tiers are selectable, so a - wrong guess is one row away from being overridden. -- The clamps are applied **live** against your stored options and never - rewrite them (`Game:applyOptions`), so a lower tier hides your TILT / GBC - FX / ZOOM without forgetting them -- raising the tier restores exactly - what you had. (This is why the TILT / GBC FX / ZOOM rows still show your - saved choice on a clamped tier: it's your preference, waiting for a tier - that can afford it.) -- Persisted as `save.options.performance` (`auto` | `high` | `balanced` | - `low`); unit-tested in `tests/engine/performance_tiers.lua`. - -## Peer-to-peer link play (lua-enet) - -Trades and link battles connect two copies of the game directly over -lua-enet (ENet ships inside LÖVE, nothing to install, no server to run) -on a reliable-ordered channel, replacing the original standalone Python -room-code relay (`tools/relay_server.py`, deleted). HOST A GAME shows the -host's LAN address (UDP 7777; `POKEPORT_LINK_PORT` overrides); JOIN A -GAME enters it. Closing performs a graceful ENet disconnect so the final -confirm/bye always lands; a vanished peer exits with "The link was -broken." Internet play needs a forwarded UDP port or a VPN (deliberate -tradeoff vs. the relay). Headless tests drive the protocol over an -in-memory loopback (`Net.loopbackPair`); under LÖVE the same test file -also exercises real UDP pairing. - -Red, Blue, and Yellow copies link with each other, as the real cable -does. The compatibility fingerprint hashes only data a link mode can -actually read, so Yellow's Dragonair/Dragonite catch-rate retunes (the -only R/B/Y link-surface difference) no longer read as different games -(issue #511). Moving the fingerprint is a link parity change: builds -from before this fix will refuse to pair with builds after it. - -## Fair play in link and online matches - -A link session is decided by the battle and nothing else, so for its -duration: - -- **Game speed is pinned to normal.** The GAME SPEED option and - `POKEPORT_SPEED` are ignored from the moment LINK PLAY opens until it - closes, and apply again after. Fast-forward otherwise runs one peer's - queue faster than the peer it is locked to and drains a tournament shot - clock faster than the opponent racing it. -- **Online play runs vanilla, except for your language.** Picking ONLINE - MATCH or TOURNAMENT with mods enabled offers to switch the gameplay ones - off and relaunch (mods merge at boot, so a restart is the only way). The - restart is confirmed, not silent. They stay listed as disabled, ready to - switch back on. A mod that declares itself a translation and provably - writes nothing but text stays on: the two games hash the same link - surface, so a Spanish install and an English one can battle and trade, - each reading the game in its own language and naming the other player's - party out of its own text. -- **Only a meaningful split ends a match.** The per-turn state signature - both peers exchange is split three ways: `actives` and `bench` carry - species, HP, status, stat stages, PP and the rest of the party, and a - divergence there ends the match as a draw. `volatile` carries per-turn - flags both sides recompute anyway - a divergence there is logged and - reported to mods, and play continues. - -The relay logs which component diverged on which turn, so a desync report -names something specific. - -## Custom boot text - -The boot sequence replaces the Nintendo / GAME FREAK identifiers with -"bois club" / "bryanthaboi", a deliberate branding customization. The -rest of the boot beats (copyright splash, "presents" shooting-star, the -Nidorino-vs-Gengar attract scene) mirror the original. - - -## Custom Options - -Options persist in a standalone `options.lua` (separate from the game -progress `save.lua`), so audio/display/battle preferences survive New Game -and aren't wiped when a save slot is cleared. Changing a row in the Options -menu or cycling hotkeys `2`/`3`/`4`/`5` writes immediately; an in-game save also -flushes the live options. Old saves that still embed an `options` table are -migrated once into `options.lua` on load. - -- Music / SFX volume -- PIKACHU VOL (0-7, Yellow only): trims Pikachu's PCM voice clips under the - SFX level, so the follower's constant chatter, the title-screen cry and - every in-battle "Pika!" can be pulled down (or muted at 0) without - quieting the rest of the sound effects. The row is hidden on Red/Blue, - which have no voice clips. -- Music Filter -- OG GLITCHES on / off (Gen 1 quirks vs. modern-clean battle rules) -- BATTLE LAYOUT (OG / WIDE); see "Widescreen battle layout" below -- COLORS (OG RED / SGB / ADVANCED / OG / OG INV / SGB INV / CLASSIC), also - hotkey `2` (OG RED = GBC boot-ROM look; ADVANCED uses pokered-gbc - SuperPalettes + per-species mon colors) -- TILT (OFF / 15 / 35 / 50), also hotkey `3` while free-roaming -- ZOOM (FIT / OUTn / INn), also hotkey `4` while free-roaming; wheel and - `-`/`=` step one level and save -- VOID FILL (TREES / WATER / BLACK) for OVERWORLD beyond-edge space -- GBC FX (OFF / 1 / 2 / 3 / 4), also hotkey `5` -- MAX FPS (30 / 40 / 50 / 60 / 75 / 90 / 100 / 120 / 144 / 160, default 60), - a hard render frame-rate cap (`save.options.fpsCap`). - -## Battle transition cascade + white battle letterbox - -Into-battle wipes still run the original eight styles inside the classic -160×144 letterbox. On wide/tall windows (survey zoom), matching black 8×8 -blocks cascade outward from that square into the surrounding world so the -void outside the OG wipe fills in lockstep. Once the battle state is up, -letterbox voids around the battle canvas fill **white** instead of black -so the whole window reads as one continuous battle screen. - -## Widescreen battle layout - -Options **BATTLE LAYOUT** picks the battle screen's composition: **OG** -(the default: the original 160×144 arrangement, unchanged) or **WIDE**, -which gives battles a 304×144 native-pixel surface and a Gen 3-style -arrangement on it: - -- the foe's status box upper left, the foe's picture upper right; -- the player's picture lower left, the player's status box lower right, - with a longer HP bar and the numeric HP under it; -- a full-width message window; -- a split "What will X do?" prompt / 2×2 command window; -- a 2×2 move menu, navigated with all four directions, with a PP and type - panel attached to its right. - -Only the composition changes. Pictures, palettes, HP-bar colors, font -pages, window borders, sounds, animations, timing and every battle rule -stay the engine's, so a COLORS mode or an asset mod still owns the look. -Each side's picture keeps its original pixels and placement math and is -composited into its own region of the wider battlefield -- nothing is -scaled or squeezed -- and animations, which are authored in the original -160-pixel space, shift as one rigid group onto whichever side they play -on. The whole screen is drawn at the window's integer fit scale for the -wider surface, so a 304-pixel screen is drawn a step smaller than a -160-pixel one in the same window. - -The wide surface is live only while the battle itself is the screen on -top: a party menu, the bag or a nickname prompt is a 160×144 screen and -brings the classic surface back with it. - -## On-screen touch controls (mobile) - -On Android/iOS the game draws a translucent d-pad (bottom-left), A/B -buttons (bottom-right, Game Boy diagonal), and +/- START/SELECT (bottom -center) over the frame, using Xelu's CC0 controller prompts -(`assets/touch/`). Real buttons, not gestures: press lands the frame the -finger does, sliding on the d-pad changes direction without lifting, and -multi-touch chords (e.g. hold a direction + tap B) work. The overlay only -appears while no controller is being used: the first gamepad button or -stick push hides it, the next screen touch brings it back, and unplugging -the last controller restores it immediately. Layout re-derives from the -window size on rotation. Desktop testing: `POKEPORT_TOUCH=1 love .` forces -the overlay on and lets the mouse act as a finger (`=0` forces it off). - -The launcher's **Touch Controls** button opens a drag editor: move each -button freely, resize the whole pad with **-/+** (60% to 160%), **Disable** -to hide the overlay permanently (for controllers / emulation handhelds -- -distinct from the temporary gamepad auto-hide), **Reset** for defaults, -**Done** to save into `options.lua` as normalized window fractions so a -different screen keeps the relative placement. - -Portrait and landscape are edited and saved separately (#633): the editor -follows whichever orientation is on screen, and **Reset** only clears that -one, so a layout that works held upright does not have to double as the -one used sideways. An `options.lua` from before this split keeps its single -layout in both orientations until one of them is edited. In-game, Options → -**TOUCH PAD** toggles the same on/off flag without leaving a play session. - -## Screen orientation lock (Android) - -Options → **ORIENTATION** (also in the launcher's gear menu) locks the -screen to **PORTRAIT**, **LANDSCAPE** (either landscape, following the -device), or **REVERSE LANDSCAPE**, or leaves it on **AUTO** (#592). AUTO -allows every orientation but defers to the system: with auto-rotate turned -off in Android's quick settings, the game stays put instead of following -the sensor (#716). Changes apply immediately -- the screen rotates as the -row is stepped -- and persist in `options.lua`. Android only: iOS follows -the app's fixed orientation list, and desktop windows rotate nothing. - -## Translation support - -Every string the player can read is now reachable from a mod, so a -translation is an ordinary content mod rather than a fork. - -Two things had to change. Text layout stopped counting bytes: the dialogue -box measures a line in glyphs (charmap sequences), so a 3-byte character -costs one column, a cut never lands inside a character, and a page with a -non-default `advance` re-measures instead of overflowing. That also fixed -25 vanilla English lines that were wrapping early because `é` in POKéMON -and POKéDEX costs two bytes ("I study POKéMON as" is 19 bytes and 18 -glyphs, and the box was breaking it). - -Second, the text the engine writes itself - battle messages, item results, -menu labels, the link-play screens - moved behind `src/core/Strings.lua` -and the new `strings` registry. Extracted script text was already -overridable through `text`; this covers the other half. Entries are keyed -by the English source, so a translation that has not reached a string yet -keeps rendering in English and a half-finished translation stays playable. - -Authors generate the whole thing: - -```sh -python3 tools/modkit.py translation francais --language "Francais" -``` - -That scaffolds a mod with every translatable string as an empty catalog, -plus a glyph-page and charmap stub, a naming-grid stub, and a -`francais-worksheet/` directory holding the English to translate from -(deliberately outside the mod: extracted text is ROM content and must not -be packed). `--refresh` re-harvests after an engine update, keeping -existing translations and parking orphaned keys rather than dropping them. - -See the wiki's Translations guide. - -## Save editor (bundled, reachable from the launcher) - -The save editor ships inside every build instead of being a developer-only -script, and the launcher's SAVE SLOT card grows an **Edit** label next to -Delete on every slot that actually holds a save. Edit suspends the -launcher, opens that slot's file in the editor, and **Close** hands the -process back to the launcher with the slot list re-read (a rename, a badge -or a dex change shows up on the row immediately). Unsaved edits arm a -confirm first, so leaving cannot lose work. `love . --editor` still opens -it standalone, where Close quits instead; `--save ` points it at any -file, and a save can be dragged onto the window. - -The editor now wears the launcher's visual language - the same navy radial -field, 16px translucent cards, tri-colour version rail and green/yellow/red -semantics - so the two windows read as one app. Six tabs: - -- **Party**: the roster with sprites, HP bars and level chips on the left, - and the mon inspector permanently docked on the right instead of floating - over the list. Species, level, DVs and moves all round-trip through the - Gen 1 formulas, so the inspector can never show illegal stats. -- **Boxes**: the 12 PC boxes as a 5x4 grid with a fill meter per box and a - party dock, so deposit and withdraw live in one place. Empty slots are - clickable and create a mon there. -- **Items**: money, a searchable item picker (replacing the arrows that - cycled one id at a time through ~250 items), the configurable bag (20 slots - by default), PC storage - with no slot cap, and the eight badges as toggle chips. The picker, the bag - and PC storage all scroll under the mouse wheel, so the whole catalog is - reachable one-handed without typing a query. -- **Events**: flags, defeated trainers, taken items and per-map object - toggles, with a real filter field and a two-column paged grid. -- **Map**: any map rendered with the game's own renderer, warps followable, - and the player / lastHeal / lastOutdoor spawn points settable by clicking - a cell. Setting lastOutdoor on a map the game would not accept as an - outdoor source is refused with the reason. -- **Dex**: seen / owned completion meters and a four-column grid; owning - implies seen and un-seeing clears owned, exactly as the game requires. - -Two rules run through all of it. Every mutation goes through one funnel -that sets the dirty flag and writes the status line together, so nothing -changes silently and no branch can quietly no-op - "Party is full", "Bag is -full", "click a cell first" all say so. And every destructive verb (Remove, -Release, Clear all, Wipe dex) arms on the first click and commits on the -second, relabelling itself to `Confirm?` in between. - -A validation pill in the tab rail mirrors what the running game would -quarantine on load; clicking it jumps to the tab holding the first problem. - -## Tiled map editing (mod authoring) - -`tools/tiled_export.py` turns the imported ROM cache into a Tiled workspace, -so maps can be edited in a real map editor and exported back out as a mod. -It has its own document: docs/tiled-map-editing.md. - -## Pokédex diploma (both versions) - -The Celadon Mansion 3F game designer shows the dex-completion diploma -once 150 species are owned. On Yellow, the graphic artist next to him -then offers to print it, saving the certificate as a PNG under `prints/` -in the save directory, and Bill's PC gains Yellow's PRINT BOX item which -exports the current box list the same way. - -## Pokédex printing (Yellow) - -Yellow's Game Boy Printer PRNT option in the Pokédex side menu is stood in -for by an image export: choosing PRNT renders the mon's entry page (sprite, -kind, number, height/weight, dex text) to a PNG at 4x scale under -`prints/` in the save directory, then reports the filename in a dialog. -No printer hardware or link cable emulation involved; the file is the -printout. - -## Find Mods (community mod indexes) - -A FIND MODS tab sits beside MODS in the launcher and browses a published -mod index: a metadata-only feed listing mods that live in their authors' -own repositories. No index ships with the launcher and none is ever added -automatically, so the tab opens on an "Add an index" prompt until you name -one; paste an index URL or its `owner/repo` and it is remembered in -`options.lua`. More than one index can be added, and the listings merge. - -A feed author can publish per-mod release stats by adding three optional -fields to an entry -- `downloads` (total across every release), and -`first_release` / `last_release` (ISO days) -- which the listing shows in -the same gold line the MODS tab uses. When a feed does not carry them, -the row fetches the mod's own GitHub releases instead -- the same cached -`ModUpdate` fetch the MODS tab uses, one entry per frame -- so the stats -appear for any mod with a `github` field regardless of feed maintenance. -The fields are additive: feeds that carry them stay readable by every -build that predates them, and feeds that do not render exactly as before. - -## Soft reset (all versions) - -Holding A, B, START and SELECT together restarts the game the way flicking -a Game Boy's power switch did, dropping straight back to the title screen. -It works from anywhere, including mid-battle, which the QUIT entry on the -start menu cannot do: the original combo is how stationary and gift -Pokemon get their stats rerolled without sitting through a full relaunch. -Unsaved progress is discarded, exactly as on hardware. - -As on the original, the four buttons have to stay held for 16 straight -polls (better than a quarter of a second) and any direction in the mix -cancels it, so it is hard to hit by accident -- including on the on-screen -touch controls, where it would take four fingers held on four separate -controls. - -## Controls rebinding (CONTROLS screen) - -OPTIONS -> CONTROLS lists every Game Boy button with its current keyboard -key and controller button side by side (Z/A). Press A on a row, then press -and release the key or pad button you want; the rebind commits on the -release. If that input already belongs to another row, the two rows swap, -so no button is ever stranded without an input and no input ever serves -two buttons. Holding a second key or pad button while the first is still -down backs out of the capture without touching a keyboard; Escape still -cancels too. SELECT clears one row back to its default, and START resets -every binding after a confirmation. - -Controllers a system has no mapping for (common on Linux handhelds and -off-brand pads) report bare button numbers rather than names. Those are -rebindable on the same screen and show up as JOY1, JOY2 and so on in the -controller column. Recognized controllers are read only through their -named buttons, so a rebind on those is never shadowed by the factory -layout underneath it. - -## Mod profiles (#593) - -The mod manager's PROFILES tab holds named setups. A profile remembers which -mods are on, every mod's own options, and which save slot each game version -plays, so swapping profiles swaps the whole playthrough and not just the mod -list. The setup that existed before profiles shipped becomes PROFILE 1 the -first time the manager opens. - -EXPORT.. writes the selected profile to `profiles/.g1rmodlist` in the -save directory; drop a `.g1rmodlist` someone shared into that folder and -IMPORT.. adds it. Imported profiles never overwrite an existing one (a name -clash gets a number). Mods the shared profile names but that are not installed -are reported when the profile is applied; installing them is still a manual -trip through the mods list or Find Mods. - -## Windows: no console windows on launcher actions - -Checking for updates, browsing a mod index, adding a mod repo, installing a -mod and picking a ROM all run a host tool (curl, PowerShell) in a child -process. On Windows those children used to each open their own console -window, so a session could end up buried under half a dozen of them. The -game now claims one console for itself at boot and hides it; the children -inherit that invisible console and nothing pops up. Nothing else changes: -file pickers are ordinary desktop dialogs and still appear normally, and a -run started from a terminal (`lovec.exe`, what `scripts\run.ps1` prefers) -keeps its terminal and its printed output. Set `POKEPORT_CONSOLE=1` to opt -out. +# New Features + +Features intentionally added beyond the original Pokémon Red, Blue, and Yellow games: + +* **Survey zoom** with connected-map rendering and configurable void fill +* **Perspective tilt mode** for an HD-2D-style overworld +* **Multiple color modes**, including original, SGB, advanced GBC, monochrome, and classic green +* **Optional GBC screen effects**, including pixel grids, shadows, glare, and transparency +* **Performance presets** and configurable FPS limits +* **Peer-to-peer link play** for trades and battles between Red, Blue, and Yellow +* **Persistent custom options** stored separately from game saves +* **Optional widescreen battle layout** +* **Mobile touch controls** with editable layouts, vibration, and orientation settings +* **Translation and custom font support** +* **Built-in save editor** for parties, boxes, items, events, maps, and Pokédex data +* **Tiled map editing tools** for mod authors +* **Pokédex diploma and printer image exports** +* **Community mod browser** +* **Soft reset button combination** +* **Keyboard and controller rebinding** +* **Mod profiles** with separate mod settings and save slots +* **Improved launcher and save editor UI**, including background downloads and update checks +* **Direct-launch options** for shortcuts, Steam entries, and handheld frontends +* **Custom boot branding** + +Actual approximations, and missing original behavior are documented separately in `docs/known-differences.md`. diff --git a/docs/required-to-function.md b/docs/required-to-function.md index b6b6ea2a..bc681949 100644 --- a/docs/required-to-function.md +++ b/docs/required-to-function.md @@ -1,12 +1,11 @@ # What This Port Requires The packaged desktop app requires one user-supplied input on first boot: a -canonical 1 MiB US Pokemon Red ROM. +canonical 1 MiB US Pokemon Red, Blue, or Yellow ROM. -The importer verifies SHA-1 -`ea9bcae617fdf159b045185467ae58b2e4a48b9a`. Other revisions, Virtual -Console releases, and Pokemon Blue are rejected rather than decoded with -incorrect addresses. +The importer verifies the SHA-1 for the game (see `src/core/GameVersion.lua` +for specific hashes). Other revisions and Virtual Console releases are rejected +rather than decoded with incorrect addresses. After verification, the app generates its private cache in the LÖVE save directory. It does not keep a copy of the ROM. Later boots use the cache. @@ -15,9 +14,11 @@ Python and Pillow are not required by the packaged app. ## Bundled Metadata Assembly removes high-level names and some relationships that the Lua port -needs. `tools/rom_manifest.json` therefore contains: +needs. The version-specific files `tools/rom_manifest.json`, +`tools/rom_manifest_blue.json`, and `tools/rom_manifest_yellow.json` therefore +contain: -- the 3,268 ROM symbol addresses actually read by the extractor +- the ROM symbol addresses actually read by the extractor - symbolic IDs and ordering for maps, species, moves, items, and trainers - source-erased dimensions, image names, and map object integration names - hand-ported field/script integration tables diff --git a/docs/rfcs/0002-screen-render-visible.md b/docs/rfcs/0002-screen-render-visible.md new file mode 100644 index 00000000..da546bde --- /dev/null +++ b/docs/rfcs/0002-screen-render-visible.md @@ -0,0 +1,54 @@ +# RFC 0002 — Let mods hide an active screen state from the main render + +## Status + +Proposed. Engine: `StateStack.lua`, `Game.lua`. Tests: +`screen_render_visible.lua`. + +## Motivation + +A mod can render a native menu on a companion display through +`render.compose`, but it cannot remove that menu from the main display without +also popping it. Popping transfers update and input ownership and forces the +mod to reimplement native menu behavior. + +## The decision it extends + +No prior D-number. Extends the render-hook plan in `docs/modding.md` and the +state-stack rendering contract in `docs/architecture.md`. + +## The exact API delta + +Backward-compatible, additive-only. + +### `screen.render_visible` + +New hook called with `(state) -> boolean` through the public wrapper signature +`(next, state)`. Its vanilla result is `true`. + +Returning `false` excludes the state from the main draw, from opaque-base +selection and from palette-zone ownership. It does not remove the state or +change update, input, push or pop behavior. The call sites are +`StateStack:visibleBase`, `StateStack:draw` and the equivalent draw and palette +walks in `Game:draw`. + +The hook is guarded by `Runtime.wantsHook`, so the no-subscriber path allocates +nothing. It is a pure render predicate and may be evaluated more than once per +frame. + +## Migration note for existing mods + +**Nothing.** With no subscriber every state remains visible, and the existing +state-stack, event and hook behavior is unchanged. + +## Parity tests + +- **No-mod:** the topmost opaque state still owns drawing and palette zones, + and `Runtime.wantsHook("screen.render_visible")` stays false. +- **Mod-API:** a fixture mod registers through `mod.hooks:wrap`, hides one + opaque state and proves the state beneath draws and owns the palette while + the hidden state remains topmost and continues updating. + +## Deprecation etiquette + +Nothing deprecated. This is one additive hook with a `true` vanilla default. diff --git a/docs/rfcs/0003-multiplayer-session-layer.md b/docs/rfcs/0003-multiplayer-session-layer.md new file mode 100644 index 00000000..66af086a --- /dev/null +++ b/docs/rfcs/0003-multiplayer-session-layer.md @@ -0,0 +1,114 @@ +# RFC 0003 — Add a reusable multiplayer session layer + +## Status + +Proposed. Engine: `Session.lua`, `Net.lua`, `LinkState.lua`, +`Tournament.lua`. Tests: `link_session.lua`. + +## Motivation + +Link play and tournaments currently own transport lifecycle details and +temporarily remove and reinsert packets in `Net.inbox` when a handshake or +battle starts. That makes packet ownership fragile and gives a future +shared-world mode no stable host/guest-aware boundary to reuse. + +The engine needs one small layer that preserves today's wire protocol while +owning received-packet order and terminal cleanup. Pokémon, battle, tournament, +save, and overworld rules remain outside that layer. + +## The decision it extends + +Extends the existing split between `Net` (backend setup, framing, and relay +controls), `Handshake`/`Protocol` (mode payloads), and the states that +interpret those payloads. It does not replace any of those components. + +## The exact API delta + +Backward-compatible and internal-only. + +### `Session.new(transport, options)` + +Wraps one successfully configured Net-compatible transport. `options.role` +is exactly `"host"` or `"guest"`; `options.kind` is a non-empty +local label such as `"link"` or `"tournament"`. Role and kind are +immutable session metadata selected locally and are never inferred from peer +packets. + +The facade forwards the narrow fields current consumers need: +`paired`, `code`, `address`, `target`, +`error`, and `closed`. +It forwards valid outbound tables unchanged through `send(message)`. + +### Receive and lifecycle methods + +- `update()` pumps the transport, validates decoded inbound values, and + appends accepted messages to a private FIFO. +- `pollOne()` removes the oldest queued message. +- `poll()` removes every queued message in order. +- `take(type)` removes the first queued message with that type without + disturbing any other message. +- `hasPending()` reports whether the FIFO is non-empty. +- `getRole()`, `getKind()`, `getStatus()`, and + `getFailure()` expose local metadata and lifecycle. +- `close()` closes the underlying transport once and is safe to repeat. + +Statuses are `connecting`, `paired`, `draining`, `closed`, +and `failed`. A transport close or failure becomes `draining` while +accepted packets remain queued. The terminal `closed`/`error` +compatibility projection appears only after that FIFO drains, so a last packet +travelling with a disconnect remains observable. + +An inbound value is structurally valid only when it is a table with a string +`type`. Invalid decoded values end the session with a protocol failure. +Unknown but structurally valid types remain queued for the owning mode; the +session does not contain a packet allowlist. + +## Authority direction + +A later `WorldSession` may compose this facade. In that mode the host +will own the world snapshot, map state, NPC state, event results, and shared +progression. A guest will bring a trainer identity plus their Pokémon party, +inventory, and other explicitly selected profile snapshot. + +Guest profile data and commands will be untrusted input. The host must validate +them and must authorize every world mutation before rebroadcasting the result. +The concrete snapshot schema, command vocabulary, conflict rules, and +persistence policy require a separate RFC and are not introduced here. + +## Compatibility and security + +No packet envelope, message name, payload shape, framing rule, relay protocol, +save schema, or engine protocol version changes. Existing valid outbound +messages encode exactly as before, and existing link and tournament screens +keep their current player-facing behavior. + +The layer does not authenticate players or encrypt traffic. Existing LAN and +relay access assumptions remain unchanged; knowledge of a join address or code +still grants the same access it grants today. Authentication, reconnect +identity, rate limits, and abuse controls remain future protocol decisions. + +## Migration note for players, mods, and peers + +**Nothing.** `LinkState` and `Tournament` adopt the facade +internally. Existing peers receive the same messages, mods gain no new API, and +players do not migrate saves or settings. + +## Parity tests + +- **ROM-free facade:** constructor validation, immutable role/kind, unchanged + send shape, FIFO ordering, typed retrieval, unknown typed packets, draining, + terminal failure latching, protected transport calls, and decoded-value + rejection. +- **Existing modes:** source guards prohibit direct inbox mutation; headless + module loads and the complete engine tier cover both migrated states. +- **ROM-backed link play:** run the existing link driver when generated ROM + data is available; the normal quick suite remains the required baseline. + +## Deprecation etiquette and non-goals + +Nothing deprecated. This RFC adds an internal facade and removes no transport +method. + +It does not add shared-world packets, co-op screens, a remote actor, save +transfer, server persistence, matchmaking, reconnect, or a protocol-version +bump. Those changes require the world-specific layer and its own review. diff --git a/docs/rfcs/0003-playthrough-storage.md b/docs/rfcs/0003-playthrough-storage.md new file mode 100644 index 00000000..96c574e2 --- /dev/null +++ b/docs/rfcs/0003-playthrough-storage.md @@ -0,0 +1,124 @@ +# RFC 0003 — Playthrough-scoped mod storage + +## Status + +Proposed. Engine: `SaveData.lua`, `SaveSerializer.lua`, `Storage.lua`, +`Loader.lua`. Tests: `playthrough_identity.lua`, `storage.lua`, the existing +save-slot and mod-save suites. + +## Motivation + +`mod.save` intentionally lives inside the normal progress record. That is the +right home for quest state, but not for independent tool data such as replay +captures, checkpoint histories, or recovery records: writing it would require a +normal Pokémon SAVE, and storing copies of progress beneath `save.modData` would +recursively embed the save that contains them. + +Mods also cannot safely infer which launcher slot or portable filesystem backs +the active playthrough. Direct filesystem access would expose private paths and +make isolation dependent on engine implementation details. + +## The decision it extends + +Extends the per-mod persistence contract documented in `docs/modding.md` and the +wiki's Save Model. `mod.save` and `mod.options` keep their existing behavior. + +## The exact API delta + +Backward-compatible, additive-only. `Loader:_api` binds a new `mod.storage` +facade to the calling mod id. Mods receive logical keys and decoded values, never +filesystem handles or physical paths. + +### Lazy opaque playthrough identity + +`SaveData.ensurePlaythroughId(save[, fs]) -> id | nil` allocates an opaque +32-hex-character identity without consuming gameplay RNG. It is called only when +`mod.storage` or `mod.checkpoints` first needs a scope; New Game, ordinary SAVE, +and ordinary load remain byte-compatible when no caller uses either API. + +The id is stored in `save.meta.playthroughId` after allocation. Until the next +ordinary SAVE writes it into progress, a mapping in `options.lua` keeps legacy +saves stable by game version and active launcher slot (or the legacy flat-save +scope). A newly created playthrough never adopts the previous playthrough's +mapping for that slot. + +`SaveData.persistenceFs([fs])` is engine-only routing used by the storage +implementation. It follows the same standard/portable backend as progress and +honors injected test filesystems; it is not exposed on the mod object. + +### `mod.storage:context(game)` + +Returns: + +```lua +{ engineVersion = "0.9.0", gameVersion = "red", playthroughId = "..." } +``` + +or `nil, code, message`. `engineVersion` is warning-grade compatibility metadata; +the context intentionally omits launcher slot ids and paths. + +### `mod.storage:write(game, key, value)` + +Accepts a data-only table and returns `true`, or +`false, code, message`. Keys are nonempty slash-separated segments containing +letters, digits, underscore, or dash. Empty segments, leading/trailing slash, +`.`/`..`, and other characters are rejected. + +The engine encodes deterministically, stages and decodes a `.tmp` witness, +preserves the previous valid generation, writes and decodes the main record, +then rolls the verified bytes to `.bak`. A failed stage or replacement leaves a +verified prior generation readable. + +### `mod.storage:read(game, key)` + +Returns a freshly decoded table, or `nil, code, message`. It tries main, staged, +then backup data. A valid staged/backup value is returned and promoted +best-effort; corrupt bytes are never executed. + +### `mod.storage:list(game[, prefix])` + +Returns sorted logical keys beneath a valid prefix, an exact key when the prefix +names one, or `nil, code, message`. Physical witness filenames are hidden. + +### `mod.storage:delete(game, key)` + +Deletes only that key's main, backup, and staged witnesses. Returns `true`, or +`false, code, message`. + +### Scope and errors + +Physical records are scoped as: + +`persistence root / mod_storage / game version / playthrough id / mod id` + +Stable error codes are `not_in_playthrough`, `storage_unavailable`, +`invalid_key`, `encode_failed`, `write_failed`, `verify_failed`, and +`not_found`. Ordinary data and I/O failures are return values, not callback- +terminating errors. + +The restricted serializer's recursive writer runs outside LuaJIT traces. A +1,000-process GC stress regression found compiled recursion could intermittently +drop a newly inserted nested identity entry and produce undecodable bytes; save +encoding is infrequent and I/O-bound, so interpreter execution is the safe +boundary. + +## Migration note for existing mods + +**Nothing.** No API is removed, no manifest field changes, and no storage path or +playthrough id is created unless a mod invokes `mod.storage` or +`mod.checkpoints`. Existing save bytes remain unchanged on the no-caller path. + +## Parity tests + +- **No-mod:** New Game plus ordinary save/load creates no identity or storage + file; the existing save-slot and mod-save suites remain green. +- **Engine identity:** lazy allocation, save/load preservation, stable legacy + mapping, fresh-playthrough replacement, and version/slot isolation. +- **Public Mod API:** two real API-2 entry chunks prove data-only roundtrip, + deterministic listing, key rejection, mod/game/playthrough isolation, + corrupt-main recovery, failure retention, exact delete, and no-mod no-write. + +## Deprecation etiquette + +Nothing deprecated. The additions are one bound public facade and engine-private +persistence/identity helpers. diff --git a/docs/rfcs/0004-runtime-checkpoints.md b/docs/rfcs/0004-runtime-checkpoints.md new file mode 100644 index 00000000..1df98749 --- /dev/null +++ b/docs/rfcs/0004-runtime-checkpoints.md @@ -0,0 +1,138 @@ +# RFC 0004 — Stable runtime checkpoints for mods + +## Status + +Proposed. Engine: `Checkpoint.lua`, `Game.lua`, `OverworldController.lua`, +`Loader.lua`. Tests: `checkpoints.lua`, existing world and engine suites. + +## Motivation + +Mods can observe world events and request semantic actions, but no supported API +can capture canonical progress at a proven-safe runtime boundary or reconstruct +the overworld without replaying map-entry scripts. Reaching into the state stack, +controller, ScriptRunner, or save restore internals would bind distributable mods +to private objects and can duplicate story side effects. + +The engine is the only component that can authoritatively decide whether the +runtime is settled and rebuild its controller objects. A generic checkpoint seam +lets tools store data-only records while keeping those responsibilities private. + +## The decision it extends + +Extends the public world/tool surfaces in `docs/modding.md`. It does not change +`mod.world`, normal CONTINUE, vanilla SAVE, or save lifecycle hooks/events. + +## The exact API delta + +Backward-compatible, additive-only. `Loader:_api` binds `mod.checkpoints`; mods +never receive `Game`, StateStack, controller, coroutine, renderer, or filesystem +internals inside a checkpoint. + +### `mod.checkpoints:inspect(game)` + +Returns a capability record. Stable overworld control returns: + +```lua +{ canCapture = true, canRestore = true, kind = "overworld" } +``` + +A refusal returns the same booleans as `false` plus `kind`, `reason`, and a +player-readable `message`. Format-1 supports only an overworld whose controller +is topmost, player movement has settled on a tile, and no transition, foreground +or parallel ScriptRunner, queued script, scripted move, engagement, emote, +teleport, field animation, or similar partial controller mutation is active. + +Refusal reasons are `not_in_playthrough`, `not_overworld`, `screen_busy`, +`transition_busy`, `script_busy`, `animation_busy`, and `movement_busy`. +Identity allocation is lazy and happens only after an active topmost overworld +has been established. + +### `mod.checkpoints:capture(game)` + +Returns a detached data-only format-1 checkpoint, or +`nil, code, message`: + +```lua +{ + format = 1, + kind = "overworld", + identity = { + engineVersion = "...", gameVersion = "red", playthroughId = "...", + }, + save = { -- canonical dynamic progress, excluding global options }, + runtime = { overworld = { + map = "PALLET_TOWN", x = 5, y = 6, + facing = "down", surfing = false, + } }, +} +``` + +`engineVersion` is metadata for caller compatibility warnings; the engine does +not reject patch/minor mismatches on restore. Capture deep-copies through the restricted serializer before and after +`OverworldController:captureSave` synchronizes live map, tile, facing, and surf +state. It excludes `save.options`, functions, userdata, threads, metatables as +behavior, controller instances, and static content registries. Failure code +`capture_failed` covers non-data progress and synchronization errors. + +### `mod.checkpoints:restore(game, checkpoint)` + +Returns `true`, or `false, code, message`. Before mutation it requires the current +runtime to be capturable and validates a detached copy of the complete record: +format, kind, internal identity consistency, current game/playthrough identity, +map availability, integral in-bounds tile, facing, surfing, and synchronized save +position. + +Validation codes are `invalid_checkpoint`, `invalid_content`, `unsupported_format`, +`unsupported_runtime_kind`, `wrong_game`, `wrong_playthrough`, `invalid_map`, and +`invalid_position`, in addition to the capability refusal reasons. + +The canonical save validator runs against the detached record. Unlike ordinary +CONTINUE, a checkpoint never accepts a quarantine, remap, reclaim, clamp, or +repair: any such content change returns `invalid_content` before live mutation. + +The engine captures an in-memory rollback checkpoint, preserves current global +options, then reconstructs semantic overworld state through +`Game:restoreCheckpointSave`. Checkpoint entry suppresses normal map exit/entry +events, `onEnter` scripts, forced-movement/current checks, and last-map rewrites; +it does not emit normal `save.loading`/`save.loaded` lifecycle events. After +reconstruction, the engine recaptures and byte-compares normalized data. A failed +apply rolls back and returns `restore_failed`; failure of that rollback returns +`rollback_failed`. Only after a successful comparison does the engine emit +`checkpoint.restored` with `{ game = game, kind = "overworld" }`. Validation +failure, failed apply, and successful rollback emit nothing. + +Durable recovery remains a caller responsibility: in-memory rollback handles a +runtime exception, not process termination. + +## Runtime boundary and future kinds + +This RFC's original Level A contract intentionally rejects battles, menus, +transitions, animations, and suspended/queued scripts. RFC 0005 subsequently +adds a separately inventoried `battle` kind with deterministic RNG and +differential reconstruction tests; it does not broaden script or arbitrary-frame +support implied here. + +## Migration note for existing mods + +**Nothing required.** No existing hook, save, controller, or world action changes +when `mod.checkpoints` is unused. The reconstruction path is called only by a +successful public restore after validation. Mods whose runtime caches derive from +rewound `game.save` or `mod.save` state may optionally subscribe to +`checkpoint.restored` and rebuild from their own public state. + +## Parity tests + +- **No-mod:** the complete ROM-free engine suite and existing world behavior stay + green; ordinary New Game/save/load allocates no checkpoint identity. +- **Public Mod API:** a real API-2 entry chunk proves stable inspection and every + unsafe refusal, detached data-only capture, exact map/tile/facing/surf sync, + `A -> mutate B -> restore A -> recapture A2` equality across representative + progress, settings preservation, compatibility rejection without mutation, + map-side-effect suppression, injected reconstruction rollback, mod-owned + metadata and `mod.save` rewind, independent `mod.storage`/options preservation, + and success-only runtime-cache reconciliation through `checkpoint.restored`. + +## Deprecation etiquette + +Nothing deprecated. This adds one public facade and a checkpoint-only semantic +reconstruction route. diff --git a/docs/rfcs/0005-battle-runtime-checkpoints.md b/docs/rfcs/0005-battle-runtime-checkpoints.md new file mode 100644 index 00000000..f94d2ba4 --- /dev/null +++ b/docs/rfcs/0005-battle-runtime-checkpoints.md @@ -0,0 +1,140 @@ +# RFC 0005 — Persistent battle safe-point checkpoints + +## Status + +Proposed. Extends RFC 0004. Engine: `BattleCheckpoint.lua`, `Checkpoint.lua`, +`Game.lua`, `BattleState.lua`, and `OverworldController.lua`. Tests: +`battle_checkpoint_*.lua`, `checkpoints.lua`, and the existing no-mod suites. + +## Motivation + +RFC 0004 lets a tool capture and reconstruct settled overworld progress without +private engine access. A battle is a different runtime: its queue can hold Lua +functions and UI factories, its controller contains renderer objects and live +references, completion is currently an `onFinish` closure, and scripted battles +resume a suspended `ScriptRunner` coroutine. Copying the controller would create +a record that is neither data-only nor process-independent. + +The engine can instead expose a narrow semantic safe point. This gives all mods +the strongest persistent battle checkpoint the current architecture can prove, +without claiming mid-animation or suspended-script support. + +## API delta + +No new facade is added. The existing additive `mod.checkpoints` API gains a +second format-1 runtime kind. + +### Capability + +`mod.checkpoints:inspect(game)` returns this only when an ordinary single-player +wild or trainer battle is settled at the player command menu: + +```lua +{ canCapture = true, canRestore = true, kind = "battle" } +``` + +The action/message queue, waits, UI, animations, HP/status presentation, and +faint processing must be settled. The player must actually control the menu. +The underlying overworld must have no running/queued script or scripted move, +and the battle must carry an engine-owned semantic continuation descriptor. + +Additional refusal codes are `battle_phase_busy`, `battle_origin_unsupported`, +`battle_variant_unsupported`, and `link_battle_unsupported`. Link, Safari, +ghost, old-man/demo, fishing, static-object, script-suspended, and mod-created +closure continuations remain rejected. + +### Capture + +A battle checkpoint remains detached and data-only: + +```lua +{ + format = 1, + kind = "battle", + identity = { engineVersion = "...", gameVersion = "red", + playthroughId = "..." }, + save = { -- canonical dynamic progress, excluding global options }, + runtime = { + overworld = { map = "ROUTE_1", x = 7, y = 8, + facing = "left", surfing = false }, + battle = { -- normalized semantic model and continuation }, + }, + rng = { love = "..." }, +} +``` + +The model carries player/enemy roster indices, dynamic enemy Pokémon, turn and +escape state, HP/PP/status/stages/volatiles, participants, level-up tracking, +trainer AI state, battle ruleset identity, side/field extension data, and +normalized pointer relationships such as multi-turn move slots and Mimic +restoration entries. Definitions, sprites, canvases, queues, callbacks, and +controller objects are reconstructed or excluded. + +Callback-bearing battle extension tokens fail with `battle_extension_unsafe`; +invalid live reference relationships fail with `battle_state_invalid`. Nothing +is silently stripped. + +New overworld checkpoints also carry the LÖVE gameplay RNG state. Legacy +format-1 overworld checkpoints without `rng` remain loadable and leave the +current stream untouched. + +### Restore + +Battle restore validates the detached save, map, content references, ruleset, +roster indices, move references, continuation identity, and RNG before live +mutation. The engine then: + +1. reconstructs the saved overworld return point without entry side effects; +2. creates a fresh `BattleState` from current content registries; +3. applies the normalized battle model and rebuilds object-reference relations; +4. binds an engine-owned wild/trainer completion continuation; +5. installs the battle directly at the settled menu without replaying its intro; +6. restores the RNG after reconstruction has finished; and +7. recaptures and compares the complete checkpoint; and +8. emits `checkpoint.restored` with `{ game = game, kind = "battle" }` after the + comparison succeeds. + +The pre-operation checkpoint is the transaction rollback. A failed post-install +RNG restore is covered: both battle runtime and RNG are reconstructed back to +their original values. Validation failure, failed reconstruction, and successful +rollback emit no checkpoint lifecycle event. + +## Continuation decision + +Ordinary random wild battles resume through `OverworldState:afterBattle`. +Ordinary trainer battles use a descriptor containing map id, stable NPC id, +trainer class/party, and optional header event; a win reapplies the same defeated +flag, event, reward, and `afterBattle` path. Reconstructed overworld input and +NPC freeze state are normalized instead of reviving the old closure. + +`Commands.start_battle` is deliberately unsupported: its completion closure +mutates script context and resumes a coroutine whose program counter and Lua +stack cannot be serialized. Existing script rejection remains the correct safe +contract until a separate semantic ScriptRunner checkpoint RFC exists. + +## Migration note + +**Existing mods require no changes.** The facade and format number are unchanged; +the new kind, RNG field, and success-only lifecycle event are additive. +Overworld-only callers may continue to filter `capability.kind`. Mods with derived +runtime caches may rebuild them from restored public state when the event fires. +No-mod behavior is unchanged when checkpoints are unused. + +## Verification + +- settled/unsafe boundary and every variant refusal; +- data-only wild and trainer capture, including callback-bearing extension + rejection; +- process-independent controller and continuation reconstruction; +- exact differential recapture for wild and trainer states; +- HP, PP, status/stages/volatiles, AI layer, participants, enemy roster, + multi-turn move references, and Mimic restore pointers; +- exact damage, critical, accuracy, random AI, escape, next encounter, and next + raw RNG result after reload; +- corrupt content/continuation rejection before mutation; +- injected post-install failure with full runtime and RNG rollback; +- mod-added Pokémon metadata and `mod.save` rewind while independent + `mod.storage` and options remain current; +- exactly one post-verification `checkpoint.restored` event and none on failure; +- legacy overworld checkpoint compatibility; +- complete ROM-free engine and public mod-API suites. diff --git a/docs/rfcs/0006-platform-lifecycle-hooks.md b/docs/rfcs/0006-platform-lifecycle-hooks.md new file mode 100644 index 00000000..9763fed1 --- /dev/null +++ b/docs/rfcs/0006-platform-lifecycle-hooks.md @@ -0,0 +1,107 @@ +# RFC 0006 — Generic process-lifecycle hooks for platform launcher integrations + +## Status + +Proposed. Engine: `PlatformHooks.lua` (new), `main.lua`, `Manifest.lua`, +`Loader.lua`. Tests: `tests/modkit/cases/platform_lifecycle_hooks.lua`, +`tests/mod_loader_tests.lua`, `tests/mod_manifest_tests.lua`. + +## Motivation + +A platform-specific launcher wrapper -- a native shell that embeds this +engine and owns its own UI around the game window (a mobile app shell, +say, presenting its own settings/import/save screens and only handing +control to the LÖVE window once play starts) needs three things no +current hook covers: + +1. Pause the simulation while its own UI is on top of the game window. +2. Live-reload options it wrote from outside any Lua UI. +3. Veto `main.lua`'s "closing the window returns to the Lua launcher" + behavior when the platform shell owns that job itself -- without this, + a shell that re-fronts its own launcher UI on quit gets looped straight + back into `HostShell.restart()`'s in-process reboot instead. + +Implementing this by hand-patching `main.lua`'s `love.update`/`love.quit` +directly ties every such integration to editing the one file every other +engine change also touches, guaranteeing merge conflicts for any second +platform integration (or any unrelated engine PR landing around the same +time). No existing hook covers "should the per-frame simulation step run" +or "should closing the window return to the Lua launcher." + +## The decision it extends + +No prior D-number. Extends the hook-contract section of `docs/modding.md` +alongside `input.step`, `render.hud`, `screen.render_visible`, etc. + +## The exact API delta + +Backward-compatible, additive-only. + +### `core.update` + +New hook, `(game, dt) -> nil` through the public wrapper signature +`(next, game, dt)`, called once per frame from `love.update` via +`src/core/PlatformHooks.lua`'s `PlatformHooks.update(game, dt)`. Vanilla +behavior (used when no mod claims the hook) is `game:update(dt)`, +unconditionally -- identical to `love.update`'s behavior before this hook +existed. A subscriber may skip calling `next(game, dt)` to pause the +simulation for that frame, or do additional per-frame work before/after +calling it regardless of whether it calls `next`. + +### `core.quit_to_launcher` + +New hook, `() -> boolean` through the public wrapper signature `(next)`, +called once from `love.quit()` via +`PlatformHooks.quitToLauncher(vanilla)`. `vanilla` is the pre-existing +non-platform-specific decision (`Game and not Importer and not +quitToLauncher and not scripted and not launchedIntoGame`). A subscriber +may return `false` outright to veto returning to the Lua launcher (without +ever calling `next`, so the vanilla condition is never evaluated), or call +`next()` and return its result to pass the vanilla decision through +unchanged. + +Neither hook is guarded by `Runtime.wantsHook` -- both fire unconditionally +every call, matching the existing `input.step` precedent +(`src/core/Game.lua`), since `Hooks:call` already fast-paths to a bare +`vanilla(...)` call when no mod has wrapped the name. + +### `Manifest.force_enable_env` + +New optional manifest field, a bare env-var name. `Loader:load` re-enables +a mod carrying this field whenever that variable is set to `"1"`, +regardless of a saved disable in `options.mods`. This exists for exactly +the mod class this RFC is for: a platform-bridge mod that ships only with +one build and cannot function disabled there, but must still behave like +every other mod (a manifest opt-in, not an engine special case) on every +build that doesn't set its variable. + +## Migration note for existing mods + +**Nothing.** With no subscriber, `love.update` still calls `Game:update(dt)` +unconditionally every frame and `love.quit()`'s restart-to-launcher +decision is exactly the pre-existing condition -- bit-identical to today's +behavior on every platform where no mod wraps either hook. A manifest with +no `force_enable_env` field behaves exactly as before. + +## Parity tests + +- **No-mod:** `core.update`'s vanilla runs exactly once per call with the + hook chain empty; `core.quit_to_launcher`'s vanilla return value passes + through unchanged. Both hooks are picked up automatically by the + catalog-driven no-mod gate (`tests/engine/gate_hooks.lua`, which scans + for `Runtime.call("...")` call sites), so neither needs a dedicated + no-mod test file. +- **Mod-API:** `tests/modkit/cases/platform_lifecycle_hooks.lua` proves, + through a fixture mod loaded via the public loader (not the engine's + internals), that a subscriber can skip the vanilla update call (pause), + run extra per-frame polling regardless of pause state, and veto the + quit-to-launcher decision without the vanilla condition ever running. +- `tests/mod_loader_tests.lua` and `tests/mod_manifest_tests.lua` cover + `force_enable_env`: a matching env var re-enables a mod saved as + disabled, and an unset one leaves the saved disable alone. + +## Deprecation etiquette + +Nothing deprecated. These are two additive hooks and one additive manifest +field; `main.lua`'s only footprint is one `require` and two call sites +into `src/core/PlatformHooks.lua`. diff --git a/docs/rfcs/0007-per-category-game-speed.md b/docs/rfcs/0007-per-category-game-speed.md new file mode 100644 index 00000000..eb34b3a4 --- /dev/null +++ b/docs/rfcs/0007-per-category-game-speed.md @@ -0,0 +1,212 @@ +# RFC 0007 — Per-category GAME SPEED and the `core.logic_speed` hook + +## Status + +Proposed. Engine: `GameSpeed.lua`, `Game.lua`, `BattleState.lua`, +`OptionsMenu.lua`, `SaveData.lua`, `LauncherSettings.lua`. Tests: +`tests/engine/game_speed_categories_test.lua`, +`tests/engine/gate_hooks.lua` (structural, automatic), `tests/run_tests.lua` +(OptionsMenu row walk), `tests/mod_ui_tests.lua` (row id/order). + +## Motivation + +`GameSpeed` (`src/core/GameSpeed.lua`) is a single fast-forward multiplier +applied uniformly to the whole logic clock in `Game:logicSpeed()` / +`Game:update()` -- overworld walking, menu navigation and battle turns all +scale together. A player who wants 4X battles (grinding, a long gym fight) +but 1X overworld (so a scripted cutscene or NPC dialogue doesn't blur past) +has no way to get both; the one GAME SPEED row is a single ladder that +applies everywhere at once. + +This needs to be an engine change, not a mod: there is no per-frame seam a +mod can use to swap the multiplier mid-step, and no public event granular +enough to say "which category is active" (`screen.pushed`/`screen.popped` +and `battle.started`/`battle.ended` are the closest and are not enough -- +see Decisions below). The engine's own speed resolution has to become +category-aware. + +A category-aware speed resolution is also the general seam a +platform-launcher integration or automation tool needs to read or override +the effective multiplier for a given frame without caring which category +produced it -- this RFC's `core.logic_speed` hook is written for that case +alongside the player-facing Options rows. + +## The decision it extends + +No prior D-number. Extends `GameSpeed.lua`'s multiplier ladder (unchanged) +with per-category resolution. + +## The exact API delta + +Backward-compatible except for one save-data field rename, which ships with +an automatic migration (see below) -- nothing in the public mod API (hooks, +events, registries, `mod.*`) is renamed or removed. + +### `save.options`: `speed` -> `speedOverworld` / `speedBattle` / `speedMenu` + +`GameSpeed.CATEGORIES = { "overworld", "battle", "menu" }` is the new list +of categories, and `GameSpeed.optionKey(category)` maps a category to its +`save.options` field name (`"overworld"` -> `"speedOverworld"`, etc.). +`GameSpeed.LEVELS`, `.DEFAULT`, `.levelLabel`, `.clamp` and `.cycle` are +unchanged -- the ladder and its behavior are exactly what they were, just +applied three times instead of once. + +`SaveData.defaultOptions()` drops `speed = 1` and adds `speedOverworld = 1`, +`speedBattle = 1`, `speedMenu = 1`. `SaveData.mergeOptions()` migrates: a +loaded options table that still has `speed` and none of the three new +fields seeds all three from it, so an existing player's fast-forward +preference carries over instead of two of the three categories silently +resetting to 1X. `speed` is dropped on the way out (not carried forward), +so a re-save never re-triggers the migration. + +### `Game.speedCategoryInStack(stack)` + +New static helper, `(stack) -> "battle" | "overworld" | "menu"`. Walks the +whole state stack top-down -- the same idiom `Game.wideBattleInStack` and +`Game.fillScaleInStack` already use -- looking for `state.isBattle` (new +marker, `BattleState.isBattle = true`, covering every battle: wild, +trainer, link, safari, the old-man demo) or `state.isOverworld` (existing +marker, `OverworldController`'s `OverworldState.isOverworld = true`). The +first match wins; a state with neither marker (a menu, a text box, a +naming screen, a cutscene) is transparent to the walk and falls through to +whatever is under it. Nothing in the stack matching either falls back to +`"menu"`. + +### `Game:logicSpeed()` / `Game:_resolveLogicSpeed()` + +`Game:_resolveLogicSpeed()` is new: it resolves `Game.speedCategoryInStack` +against the live stack, maps the category to its `save.options` key via +`GameSpeed.optionKey`, and returns `GameSpeed.clamp` of that option (or +`GameSpeed.DEFAULT`). This is the exact category-resolution logic the new +hook wraps. + +`Game:logicSpeed()` keeps its existing early returns -- link play forces +`1`, a run-argument speed override wins over the saved option -- unchanged, +and in the same order, before ever calling the hook. Only once neither +applies does it call the `core.logic_speed` hook. + +### `Game:_cycleSpeed(dir)` + +The keyboard hotkey and the gamepad shoulders/triggers that used to cycle +the single `speed` option now cycle whichever category +`Game.speedCategoryInStack` says is active: pressing the hotkey during a +battle speeds up just the battle, on the overworld just the walk, in a menu +just the menu. This is the natural per-category answer for a control that +used to have one option to reach and now has three -- see Decisions below +for why this reading was chosen over, say, always cycling `overworld`. + +### `core.logic_speed` + +New hook, `(game) -> number` through the public wrapper signature +`(next, game)`, called once per `Game:logicSpeed()` (i.e. once per frame). +Vanilla behavior (used when no mod claims the hook) is +`Game:_resolveLogicSpeed()` -- exactly the category resolution above, +nothing else. A subscriber may call `next(game)` and return its result to +pass the vanilla multiplier through, or return a different number outright +to override it for that frame (e.g. a bot mod forcing `1` during one route +segment regardless of what category or option is active). + +This intentionally sits *after* the link and speed-override checks in +`Game:logicSpeed()`, not around them: link play staying locked to 1X "no +matter what either player set this to" is exactly the invariant that would +break if a mod's hook could override it, and the run-argument override +exists so a bot/screenshot run's speed does not depend on a mod any more +than on the player's saved option. Both stay unconditional early returns a +mod never sees. + +Not guarded by `Runtime.wantsHook`: `Hooks:call` already fast-paths to a +bare `vanilla(...)` call when no mod has wrapped the name, and this hook +fires every frame regardless. + +## Decisions on the issue's open questions + +**1. Overlays on top of another category's state (a party menu, a choice +box, a naming screen opened mid-battle or mid-overworld).** Resolved by +making the category a property of stack *position*, not of the overlay's +own type: an overlay with no `isBattle`/`isOverworld` marker is transparent +to `Game.speedCategoryInStack`'s walk and inherits whatever is under it. A +party swap opened mid-battle reads as `"battle"`; a bag opened while +walking reads as `"overworld"`. This was chosen over giving every UI state +its own fixed category (which would make a fast-forwarded battle visibly +stutter back to 1X every time its party menu opens) because it matches +what the player is actually doing moment to moment, and it reuses a +pattern the codebase already leans on for exactly this "menus opened over +X should behave like X" class of problem (`Game.fillScaleInStack`, +`Game.wideBattleInStack`). + +**2. Cutscenes/scripts.** No fourth category. A scripted sequence runs +through the owning state's own machinery -- the overworld's script runner +or a battle's message queue -- rather than pushing a state of its own, so +it is already covered by decision 1: it inherits whatever category the +state driving it resolves to. A cutscene state that genuinely has nothing +under it (a pre-game intro) falls to `"menu"`, the default for anything +that is not battle or overworld gameplay -- consistent with those being +pre-game presentation, not something a player is likely to want scaled +differently from menu navigation. + +**3. Category granularity (splitting "menu" further).** Deferred. Start +with the three named here; `GameSpeed.CATEGORIES` and `GameSpeed.optionKey` +are written so adding a fourth later (a Pokédex/Bag category, say) is one +entry plus one new `save.options` field, not a resolution-logic rewrite. +No current request motivates it. + +**4. The GAME SPEED hotkey/shoulder buttons, once "the" speed is three +things.** `Game:_cycleSpeed` now cycles whichever category is currently +active (`Game.speedCategoryInStack`), rather than, say, always cycling +`overworld` or requiring a modifier key to pick a category. A single +physical control that means "speed up whatever I'm looking at right now" +is the reading that needs no new UI and matches what a player pressing it +mid-battle almost certainly wants. + +## Migration note for existing mods + +**Nothing**, for the mod API surface: `content.X:register/override/get`, +`events:on`, `hooks:wrap`, `mod.log`, `mod:read`, manifest v1 fields are +untouched, and `GameSpeed.LEVELS`/`.DEFAULT`/`.levelLabel`/`.clamp`/`.cycle` +keep their exact signatures and behavior. + +**One save-data field**, for anything that read `save.options.speed` +directly (not a formal registry/hook surface, but worth naming): it is +superseded by `speedOverworld`/`speedBattle`/`speedMenu`, migrated +automatically on load (see above) so a save from before this RFC keeps its +player's chosen speed. A mod reading `save.options.speed` after this change +sees `nil` (the key is dropped on migration, not kept as a stale alias) and +should read the per-category fields, or hook `core.logic_speed` to observe +the resolved multiplier directly regardless of which category produced it. + +## Parity tests + +- **No-mod:** `core.logic_speed` needs no dedicated no-mod test file -- + `tests/engine/gate_hooks.lua` walks the live hook catalog (which scans + `src` for `Runtime.call("...")` call sites), so the new + `Runtime.call("core.logic_speed", ...)` site is picked up and gated + automatically: vanilla runs exactly once with an empty hook chain, an + unsubscribed-but-live bus passes values and multiple returns through + unchanged, and `Runtime.wantsHook` reads `false`. +- **Mod-API:** `tests/engine/game_speed_categories_test.lua` exercises the + hook through the public API (`Hooks.new()` + `bus:wrap("core.logic_speed", + ...)` + `Runtime.call`, the same idiom other hooks' tests use) -- a + subscriber can read the vanilla category resolution via `next(game)` and + can override it outright -- plus direct coverage of + `Game.speedCategoryInStack` (battle-on-top, overworld-on-top, an overlay + inheriting each, an empty/unmatched stack falling to `"menu"`) and + `Game:logicSpeed()`'s precedence (link forces 1X over all three + categories and over a hook override; the run-argument override wins over + the category resolution). +- `tests/run_tests.lua`'s OptionsMenu walk exercises the three new rows + (OVERWORLD SPEED / BATTLE SPEED / MENU SPEED) cycling and wrapping + independently, in place of the old single GAME SPEED row. +- `tests/mod_ui_tests.lua`'s row-id/order check and hardcoded row-index + activations (MODS, CONTROLS) are updated for the two extra rows. +- A link-play driver should set all three per-category speeds high before + asserting `game:logicSpeed()` reads `1` during a real link session, + proving the lock wins over every category at once, not just whichever + one happens to be active. + +## Deprecation etiquette + +Nothing deprecated in the mod-facing hook/event/registry catalog -- this +adds one hook, additive. The `save.options.speed` field is superseded with +an automatic migration rather than a deprecation notice, since it was never +a registered mod-API surface (no schema entry, no registry) -- the same +treatment any other `save.options` field would get if it needed reshaping. diff --git a/docs/switch-build.md b/docs/switch-build.md index 74b33567..61f8a574 100644 --- a/docs/switch-build.md +++ b/docs/switch-build.md @@ -1,38 +1,66 @@ -# Build the Nintendo Switch NRO — contributor guide +# Build Gen1Recomp for Nintendo Switch Want to play a release build instead? Download the SD-ready zip and extract it -at your microSD root — see [switch-install.md](switch-install.md). +at your microSD root. See [switch-install.md](switch-install.md). This guide is for contributors who build Gen1Recomp for Switch from source. -Hardware evidence, MTP operator loops, and deeper notes live in -[switch-development.md](switch-development.md). -> Releases ship `gen1recomp-*-switch.zip` (SD tree under `switch/gen1recomp/`; -> issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Hardware -> evidence: **OLED** (author) and **V1 boot** (community). See -> [switch-development.md](switch-development.md) for known limitations. +> Releases ship `gen1recomp-*-switch.zip` (SD tree under `switch/gen1recomp/`). +> Runtime target is pinned [love-nx](https://github.com/retronx-team/love-nx) +> `11.5-nx1`. Player install and limitations: [switch-install.md](switch-install.md). --- ## Prerequisites by OS All packaging entrypoints are **bash**. On Windows, use Git Bash, MSYS2, or -WSL — not cmd.exe or PowerShell (AD-008). +WSL, not cmd.exe or PowerShell (AD-008). ### macOS / Linux 1. Install [devkitPro pacman](https://devkitpro.org/wiki/devkitPro_pacman). -2. Install Switch tools: +2. Install Switch tools (**required for `--fused`**): ```sh sudo dkp-pacman -S switch-dev ``` -3. Ensure `nacptool` and `elf2nro` are on `PATH` (or under - `$DEVKITPRO/tools/bin` — the fused script prepends that when set). +3. OTA launcher toolchain, **native or Docker** (either is fine): -**Optional:** Install [Docker](https://docs.docker.com/get-docker/) so fused -builds can fall back to the pinned image when native tools are missing. + ```sh + bash scripts/switch/install_devkitpro_deps.sh # native + # or install Docker (same pin as fused builds) + ``` + +4. Ensure `DEVKITPRO` is exported (typical macOS: `/opt/devkitpro`) and + `nacptool` / `elf2nro` are on `PATH` (or under `$DEVKITPRO/tools/bin`). + +Fused game builds can also use Docker when native `nacptool`/`elf2nro` are absent. + +### Native OTA launcher (included in `--fused`) + +In-console OTA uses a **separate DEVKITPRO NRO** (not LÖVE). The LÖVE +self-updater (`Check.lua`) is disabled on NX. Source: +`ports/switch/ota-launcher/`. Host protocol tests (no toolchain): + +```sh +make -C ports/switch/ota-launcher host-test +# or +scripts/switch/build_ota_launcher.sh # host-test first; NRO needs DEVKITPRO/Docker +``` + +`--fused` always builds the fused game, native OTA launcher, and dual-NRO SD +zip. The same `*-switch.zip` is the OTA download asset. **DEVKITPRO is +required.** OTA launcher: native packages **or** Docker. Both are supported. + +Release-like build from repo root: + +```sh +scripts/build_switch.sh --fetch --fused --version X.Y.Z +``` + +See `ports/switch/ota-launcher/README.md` and +`scripts/switch/ota_launcher.manifest`. ### Windows (Git Bash / MSYS2 / WSL) @@ -42,16 +70,15 @@ builds can fall back to the pinned image when native tools are missing. - **WSL** (Ubuntu/etc.) with the Linux pacman flow above, or - **Git Bash** for `--fetch` / `--loose`; for `--fused` prefer MSYS2 or WSL if Docker bind-mounts from Git Bash paths misbehave. -2. Install `switch-dev` (or rely on Docker fallback — see below). +2. Install `switch-dev` (or rely on Docker fallback; see below). 3. Do **not** expect `scripts/build_switch.sh` to run under cmd/PowerShell. ### What you must install yourself | You install | Script does **not** install | | ----------- | --------------------------- | -| bash, git, zip tooling the repo already expects | — | -| `dkp-pacman` + `switch-dev` (native fused) | `dkp-pacman -S …` | -| Docker (optional fused fallback) | Docker Engine | +| bash, git, zip tooling the repo already expects | (none) | +| `dkp-pacman` + `switch-dev` + OTA packages **or** Docker | `dkp-pacman -S …` | | A legal `.gb` ROM (to play) | Any ROM or game data | --- @@ -64,12 +91,12 @@ builds can fall back to the pinned image when native tools are missing. | ---- | ------------ | | `--fetch` | Downloads pinned **love.nro** + **love.elf** into `.bazinga/love-nx/11.5-nx1/` and verifies SHA-256 against `scripts/switch/love-nx-11.5-nx1.sha256`. | | `--loose` | Packs `game.love`, copies pinned `love.nro` → `dist/switch/loose/` as `gen1recomp.nro` + `game.love` side by side. Needs the pin. | -| `--fused` | Builds `dist/switch/gen1recomp--switch.nro` (game in romfs) via `nacptool` + `elf2nro`, then packs `dist/switch/gen1recomp--switch.zip` (SD-ready tree). Needs the pin + toolchain (native or Docker). GitHub Releases publish the **zip only**. | +| `--fused` | Builds fused game NRO, OTA launcher NRO, and dual-NRO SD zip. **Requires DEVKITPRO** + `switch-dev`. OTA launcher: native packages or Docker. GitHub Releases publish the **zip only**. | Rules: - `--fetch` alone is fine; combine as `--fetch --loose` or `--fetch --fused`. -- `--loose` and `--fused` are **XOR** — pick one packaging path per run. +- `--loose` and `--fused` are **XOR**. Pick one packaging path per run. - `--version X.Y.Z` sets the NACP / filename version (defaults to short git SHA). ### What `--fetch` downloads @@ -109,13 +136,15 @@ scripts/build_switch.sh --fetch # Loose pair for iteration (fetch + assemble) scripts/build_switch.sh --fetch --loose -# Single fused NRO + SD-ready zip for a release-like artifact +# Fused game + OTA launcher + dual-NRO SD zip for a release-like artifact scripts/build_switch.sh --fetch --fused --version 0.2.0 ``` Outputs land under `dist/switch/` (and `dist/switch/loose/` for loose mode). -The fused path also writes `gen1recomp--switch.nro.sha256` and -`gen1recomp--switch.zip` (+ `.sha256` sidecar for the zip). +The fused path also writes `gen1recomp--switch.nro` (game), +`gen1recomp--launcher.nro`, `gen1recomp--game.nro`, +`gen1recomp--switch.nro.sha256`, and `gen1recomp--switch.zip` +(+ `.sha256` sidecar for the zip). Offline packaging smoke (no network, no nacptool required): @@ -143,7 +172,7 @@ the NX runtime modules `src/core/NxAssetOverlay.lua`, `src/core/Platform.lua`, `tests/engine/switch_diagnostics_test.lua`, `tests/engine/platform_nx_*`, or the Switch-related workflow YAML), CI runs: -1. **Offline selftest** on `ubuntu-latest` (forks **and** the canonical repo): +1. **Offline selftest** on `ubuntu-latest` (forks **and** the main repo): `scripts/switch/selftest_build_switch.sh`, `scripts/switch/verify_payload.sh --self-test`, `luajit tests/switch_ci_workflows_test.lua`, @@ -151,14 +180,10 @@ or the Switch-related workflow YAML), CI runs: headlessly (`luajit tests/engine/assets_version_fallback_test.lua`, `luajit tests/engine/nx_generated_guard_test.lua`, `luajit tests/engine/nx_yellow_boot_test.lua`). -2. **Fused NRO build** only on the **canonical** repository +2. **Fused NRO build** only on the **main** repository (`bryanthaboi/gen1recomp`), on the self-hosted Mac runner (`scripts/build_switch.sh --fetch --fused`), and only when the workflow - head is that repo (same-repo push/PR). **Fork repository** CI never runs - fused. **Fork → canonical PRs** also skip Switch fused (offline selftest - still runs) so untrusted head code is not executed on the self-hosted Mac; - iOS device build eligibility is unchanged. Fused also waits for a successful - offline selftest before starting on the Mac runner. + head is that repo (same-repo push/PR). Fork CI never runs fused. Fork PRs into the main repo also skip Switch fused (offline selftest still runs) so untrusted head code is not executed on the self-hosted Mac; iOS device build eligibility is unchanged. Fused also waits for a successful offline selftest before starting on the Mac runner. 3. On successful PR fused builds, a follow-up workflow posts a PR comment linking the Actions artifact named `gen1recomp-switch-nro` (comment tag `switch-build-result`; see @@ -169,7 +194,7 @@ Unrelated PRs do not burn the self-hosted Mac on Switch packaging. ### Release hard-fail (`.github/workflows/release.yml`) GitHub Releases always build Switch on the same self-hosted Mac runner as the -other platforms — this is a **hard gate** (no `continue-on-error`): +other platforms. This is a **hard gate** (no `continue-on-error`): ```sh scripts/build_switch.sh --fetch --fused --version "" @@ -181,9 +206,26 @@ A Switch packaging failure fails the entire release job. The release asset is ### Runner provisioning -The self-hosted Mac runner must have **native switch-tools** (`nacptool` / -`elf2nro`) **and/or Docker** available. CI and release do not silently run -`dkp-pacman -S`; keep the runner image/host provisioned per this guide. +The self-hosted Mac runner **must** have **DEVKITPRO** installed and exported. +`--fused` preflight fails early with setup steps if it is missing. + +**One-time setup on the runner** (if not already present): + +```sh +# devkitPro pacman installer from https://devkitpro.org/wiki/devkitPro_pacman +sudo dkp-pacman -S switch-dev +export DEVKITPRO=/opt/devkitpro +export PATH="$DEVKITPRO/tools/bin:$PATH" + +# OTA launcher: pick one +bash scripts/switch/install_devkitpro_deps.sh # native +# or ensure Docker is installed (same pin as fused builds) +``` + +CI and release still run `scripts/build_switch.sh --fetch --fused`. Preflight +requires DEVKITPRO and either native OTA packages or Docker. Without all of +that, the job fails with the setup steps above. Scripts never auto-run +`dkp-pacman -S` during CI. --- @@ -194,11 +236,9 @@ These scripts and this guide do **not**: - Push files to the console (no automated MTP / FTP / SD scripting) - Bundle or download any Pokémon ROM - Install `dkp-pacman` / `switch-dev` for you -- Provide `nxlink` / netloader deploy (deferred — see [switch-transfer.md](switch-transfer.md)) -- Validate **Applet Mode** — use title override (hold **R**) for full memory +- Provide `nxlink` / netloader deploy (deferred; see [switch-transfer.md](switch-transfer.md)) +- Validate **Applet Mode**. Use title override (hold **R**) for full memory Player install steps: [switch-install.md](switch-install.md). Manual transfer (MTP / SD / FTP, macOS / Linux / Windows): -[switch-transfer.md](switch-transfer.md). -Hardware depth and evidence: [switch-development.md](switch-development.md), -[switch-hardware-evidence.md](switch-hardware-evidence.md). +[switch-transfer.md](switch-transfer.md). diff --git a/docs/switch-development.md b/docs/switch-development.md deleted file mode 100644 index 8e00e072..00000000 --- a/docs/switch-development.md +++ /dev/null @@ -1,528 +0,0 @@ -# Nintendo Switch development (love-nx) - -> Fused NRO support for issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531). -> Releases ship `gen1recomp-*-switch.zip` (SD-ready tree). Console copy is -> extract/merge at microSD root; title override required. See -> [Known limitations](#known-limitations-read-before-reviewing). - -**Canonical install / build / transfer docs** (start here unless you need hardware depth): - -- Players → [switch-install.md](switch-install.md) -- Builders → [switch-build.md](switch-build.md) (`scripts/build_switch.sh --fetch` downloads the pinned love-nx pair) -- Transfer (MTP / SD / FTP on macOS, Linux, Windows) → [switch-transfer.md](switch-transfer.md) - -This document covers what landed, known limitations, how hardware was tested, -vendor layout, build/deploy, and the contributor transfer loop (detail lives in -the transfer runbook). - -## Acknowledgments - -- **Port / love-nx packaging:** [andrewqsantos](https://github.com/andrewqsantos) -- **Community hardware testing** (Switch V1 / Erista boot): [booshankles](https://github.com/booshankles) -- **Method guidance:** [Dusklight Switch port](https://github.com/HayatoG/dusklight/tree/main/platforms/switch) / love-nx -- **Upstream project:** [bryanthaboi](https://github.com/bryanthaboi) / Gen1Recomp - -## Status - -| Area | State | -| ---- | ----- | -| Feature | **Available** — playable fused NRO path (issue #531) | -| Runtime | Pinned love-nx **`11.5-nx1`** | -| Product artifact | Releases: SD-ready `gen1recomp-*-switch.zip`; local/PR: fused `.nro`; loose `nro`+`game.love` for iteration | -| Hardware | **OLED** validated (author, title override); **V1 / Erista** boot confirmed (community). Lite, docked soak, and Pro Controller matrices welcome | -| Deploy / install | Releases publish SD-ready zip; **extract/merge at microSD root** (MTP / SD / FTP — [switch-transfer.md](switch-transfer.md)); no `nxlink` path yet | -| Contributor transfer | Documented for **macOS, Linux, and Windows**; OpenMTP on Mac is one example, not the only contract | -| Network features on NX | Self-update / remote mod download **disabled** (`networkValidated == false`) | -| Community help | Welcome — especially HOS / love-nx packaging and broader hardware coverage | - -### What landed - -- Detect `NX` via `src/core/Platform.lua` without reusing Android flags -- Writable ROM inbox under `getSaveDirectory()/imports/` + per-tab “Scan again” (SHA-1 match for the open game) -- Joy-Con / gamepad mapping shared by launcher and gameplay (Nintendo A/B UX on NX) -- Launcher L/R tab switch; gameplay L/R game-speed cycle; Select+face display chords -- Focus loss / joystick reconnect recovery; opt-in `switch-debug.txt` diagnostics -- Loose assemble + fused NRO build scripts (`scripts/build_switch.sh`, `scripts/switch/*`) -- Payload gates so ROM / generated cache / saves never enter `game.love` -- Community mod zip inbox at `imports/mods/` (rescan installs; FIND MODS stays network-gated) -- Raw `.sav` inbox at `imports/saves/{red,blue,yellow}/` (**Import save** rescan) + export pull path `exports/{red,blue,yellow}/` (MTP hint; no openURL) -- Hardware evidence for Phase 0 probe, ROM import, naming A/B, save/suspend, fused NRO — see `docs/switch-hardware-evidence.md` -- Path-gated CI selftest + canonical fused PR artifact; release Switch hard-fail -- Save editor pad/touch input (virtual cursor, A click, B close) — see `tools/save-editor/README.md` -- Dynamic display size on NX only: handheld **1280×720**, docked/TV **1920×1080** (`src/core/NxDisplay.lua` + resizable conf so love-nx SDL can follow dock/undock at runtime) - -### Known gaps / welcome contributions - -- Docked vs handheld soak (≥30 min) and Lite coverage — resolution switch is implemented; long soak still welcome -- Switch Lite and fuller Pro Controller / third-party pad matrices -- Applet Mode remains unsupported by design (title override required) -- `nxlink` / netloader contrib fast-loop (deferred — see [switch-transfer.md](switch-transfer.md)) - -Transfer runbooks for Linux/Windows (and SD/FTP alternatives) are in -[switch-transfer.md](switch-transfer.md). Community mod zip install OLED smoke -is **pass** — see NXMOD-12 in [switch-hardware-evidence.md](switch-hardware-evidence.md). - -## Design references (Dusklight) - -This work borrowed method — not the native stack — from the [Dusklight Switch port](https://github.com/HayatoG/dusklight/tree/main/platforms/switch), especially [`LESSONS_AND_REUSE.md`](https://github.com/HayatoG/dusklight/blob/main/platforms/switch/LESSONS_AND_REUSE.md): - -| Dusklight lesson | How Gen1Recomp applied it | -| ---------------- | ------------------------- | -| Emulators hide Tegra failures | Gate milestones on **real OLED hardware**, not Ryujinx/Yuzu alone | -| Prove the lower layer first | `tools/switch-probe` before full launcher | -| Know which binary ran | Embedded `build-info.json` (commit / love-nx tag) | -| Cap continuous logs | Opt-in diagnostics, ≤1 Hz flush; Lua error log rotation | -| Crash symbolization needs the exact ELF | Keep pinned `love.elf` with the NRO under test | -| Full memory matters | Title override; Applet Mode is not the validation path | -| Do not treat SD FS like desktop POSIX | Lua stays on `love.filesystem`; inbox + MTP for user files | -| Isolate platform code | Capability module instead of Android flag overload | -| NVK / WSI / `audren` stacks | **Not** copied — love-nx already supplies video/audio/input/FS | - -The packaging goal matches Dusklight’s **single self-contained `.nro`**; contributor transfer stays multi-host (not Mac-only). - -## Known limitations (read before reviewing) - -1. **Transfer is manual and multi-method.** Runtime only needs files under the LÖVE save directory / NRO install folder. Use MTP, direct SD, or FTP per [switch-transfer.md](switch-transfer.md). macOS + OpenMTP is a documented example for OLED evidence — not “Switch requires a Mac.” -2. **Deploy is manual.** There is no automated push to the console and no `nxlink` path yet. Operators build locally, transfer files, then title-override launch. -3. **Hardware coverage.** Author P0/P1 pass rows were recorded on one Switch OLED; Switch V1 boot was confirmed independently. Treat Lite, docked soak, and other hosts as unknown until someone re-runs the checklist. -4. **No ROM/save/mod zip bytes in git.** Legal dumps and third-party mods stay on the console (or local untracked folders). -5. **AppleDouble sidecars** (`._*`) from some MTP clients can break zip/ROM/`.sav` scans — the launcher skips hidden `.*` names (including `._*.sav`); still prefer clean copies. - -## How we tested - -| Layer | What | Where | -| ----- | ---- | ----- | -| Unit / headless | Platform NX flags, RomImporter inbox, dual-path input, mod zip inbox, save `.sav` inbox, display chords, payload/self-tests | `tests/*`, `scripts/test.sh` | -| Switch CI / packaging | Path-gated offline selftest (`selftest_build_switch.sh`, `verify_payload.sh --self-test`, `switch_ci_workflows_test.lua`); canonical fused PR artifact | `.github/workflows/ci.yml`, [switch-build.md](switch-build.md) § CI and release | -| Probe on hardware | `getOS()==NX`, 1280×720, save path, Joy-Con events | `tools/switch-probe` → OLED | -| Integration on hardware | MTP inbox ROM import, Play Red/Blue, naming A/B, quit/reopen save, suspend×10, reboot, fused NRO alone + NRO-only update | `docs/switch-hardware-evidence.md` | -| Community hardware | Switch V1 / Erista boot with prebuilt NRO | [booshankles](https://github.com/booshankles) — see evidence log | -| Known gaps | Docked soak, ≥30 min long-play, Lite, automated/`nxlink` deploy | Matrix deferred / absent rows | - -Operator evidence must stay in `docs/switch-hardware-evidence.md`. **Do not invent passes** for hardware not run. - -## love-nx 11.5-nx1 (pinned) - -**Tag:** [11.5-nx1](https://github.com/retronx-team/love-nx/releases/tag/11.5-nx1) - -**Local layout (not committed):** - -```text -.bazinga/love-nx/11.5-nx1/ -├── love.nro # homebrew launcher binary (loose mode: copied to gen1recomp.nro) -└── love.elf # required for fused NRO builds (devkitPro nacptool/elf2nro) -``` - -**Manifest:** `scripts/switch/love-nx-11.5-nx1.sha256` lists expected artifact names and SHA-256 checksums. Checksums are filled when binaries are fetched (`TBD_*` placeholders until then). - -### Fetch instructions - -Preferred (automated checksum verify): - -```bash -scripts/build_switch.sh --fetch -``` - -That downloads pinned `love.nro` + `love.elf` into `.bazinga/love-nx/11.5-nx1/` -and checks them against `scripts/switch/love-nx-11.5-nx1.sha256`. See -[switch-build.md](switch-build.md) for the full mode glossary. - -Manual fallback: - -1. Open the [11.5-nx1 release](https://github.com/retronx-team/love-nx/releases/tag/11.5-nx1) and download `love.nro` and `love.elf`. -2. Create the directory: `mkdir -p .bazinga/love-nx/11.5-nx1` -3. Move both files into that directory. -4. Confirm checksums match the manifest: - - ```bash - shasum -a 256 .bazinga/love-nx/11.5-nx1/love.nro \ - .bazinga/love-nx/11.5-nx1/love.elf - ``` - -**Never commit** love-nx binaries, ROM dumps, or generated cache into git. The repo `.gitignore` excludes `.bazinga/` (vendor cache) and `/dist/` (build output). - -## Loose-mode dist layout - -Development builds place `gen1recomp.nro` and `game.love` side by side: - -```text -dist/switch/loose/ -├── gen1recomp.nro -└── game.love -``` - -Assemble with: - -```bash -scripts/build_switch.sh --loose -``` - -(See `scripts/switch/assemble_loose.sh` for the underlying copy + checksum step.) - -## Transfer & deploy (current contributor loop) - -Detail for **macOS / Linux / Windows** and **MTP / SD / FTP** lives in -[switch-transfer.md](switch-transfer.md). Summary: - -| Layer | Intent | -| ----- | ------ | -| **Runtime / players** | Extract the release zip at microSD root (`switch/gen1recomp/`) and land ROMs/mods under the save-dir inboxes. The game does not hard-depend on OpenMTP or macOS. | -| **Contributor loop** | Manual copy via MTP (DBI responder), direct SD (Hekate UMS / reader), or FTP. Fully manual — no CI deploy, no `nxlink` yet. | - -The Mac + OpenMTP steps that remain below are the **OLED evidence reproduction** path; prefer the transfer runbook for day-to-day contrib on other hosts. - -**Still avoided for routine evidence** (keeps SD handling honest): - -- Treating `nxlink` / netloader as the release deploy story (deferred) -- DBI `MicroSD install` / `NAND install` / NSP-style virtual folders for the `.love`/`.nro` pair - -If MTP fails: check cable, USB port, DBI state, and that only one MTP client holds the device — then retry or switch to SD/FTP. Do not silently rewrite evidence using an untested path and claim parity with recorded SHA-256 round-trips. - -### Manual deploy checklist (today) - -1. Build on the contributor host (`scripts/build_switch.sh --loose` or fused). -2. Close Gen1Recomp on the Switch; open DBI → `Run MTP responder`. -3. Copy artifacts with your MTP client into `1: SD Card/switch/gen1recomp/` (and ROMs/mods into the save-dir inboxes when needed). -4. Wait for the transfer queue; refresh; optionally round-trip SHA-256 on first artifacts of a type. -5. Exit MTP; launch via **title override** (hold **R** on a title → hbmenu, not Applet Mode). - -## OpenMTP + DBI transfer (loose build, Mac evidence example) - -Full multi-OS / multi-method steps: [switch-transfer.md](switch-transfer.md). -The numbered Mac loop below reproduces the OLED evidence path. - -### On the Switch - -1. Close Gen1Recomp if it is running. -2. Open **DBI** from hbmenu. -3. Select **`Run MTP responder`** (DBI documents `X` on the main screen). -4. Keep DBI on that screen for the entire transfer. -5. Connect the Switch to the Mac with a USB-C data cable. - -### On the Mac - -1. Close any other MTP clients. -2. Open **OpenMTP** and select the DBI device. -3. In the remote pane, open **`1: SD Card`**. -4. Navigate to **`switch/`** and create **`gen1recomp/`** if needed. -5. Enter **`1: SD Card/switch/gen1recomp/`**. -6. Drag from the local pane: - - ```text - dist/switch/loose/gen1recomp.nro - dist/switch/loose/game.love - ``` - -7. Wait for the OpenMTP queue to finish completely. -8. Refresh the remote listing and confirm file sizes match the local files. -9. On the Switch, exit MTP responder normally in DBI before launching the app. - -Expected layout on SD: - -```text -1: SD Card/ -└── switch/ - └── gen1recomp/ - ├── gen1recomp.nro - └── game.love -``` - -## Round-trip SHA-256 verification - -For the **first deploy** of each artifact type (loose pair, later fused NRO), verify MTP integrity: - -1. **Before send** — record local hashes: - - ```bash - shasum -a 256 dist/switch/loose/gen1recomp.nro \ - dist/switch/loose/game.love - ``` - -2. **After send** — in OpenMTP, copy the same files from `1: SD Card/switch/gen1recomp/` back to an empty local folder, e.g. `dist/switch/mtp-roundtrip/`. - -3. **Compare** round-trip hashes: - - ```bash - shasum -a 256 dist/switch/mtp-roundtrip/gen1recomp.nro \ - dist/switch/mtp-roundtrip/game.love - ``` - -4. Local pre-send and round-trip hashes **must match**. Record results in the test report template below. - -Repeat whenever a cable glitch or interrupted transfer is suspected. - -## Title override launch (full memory) - -Applet Mode is **not** the primary validation path. Use **title override** so hbmenu runs with full memory: - -1. Confirm the OpenMTP transfer queue finished. -2. Exit MTP responder in DBI; disconnect USB if desired. -3. Hold **`R`** while launching any legitimately installed title. -4. Keep holding until **hbmenu** appears. -5. Confirm hbmenu does **not** show **Applet Mode**. -6. Launch **`gen1recomp`** (or the probe NRO during Phase 0). - -Album / applet launches are only useful to document applet-specific limitations; P0/P1 gates use title override. - -## Phase 0 hardware checklist - -Complete **in order** on OLED hardware. Operator fills evidence fields — leave blank until tested. - -| Step | Action | Pass | Evidence / notes | -| ---- | ------ | ---- | ---------------- | -| P0-0a | Fetch love-nx 11.5-nx1; record manifest SHA-256 | yes | See `scripts/switch/love-nx-11.5-nx1.sha256` | -| P0-0b | Build `switch-probe.love` per `tools/switch-probe/README.md` | yes | | -| P0-0c | Assemble loose probe (`game.love` = probe) to `dist/switch/loose/` | yes | | -| P0-0d | MTP deploy to `1: SD Card/switch/gen1recomp/`; round-trip SHA-256 | yes | nro `8290ac15…5918f5`; love `9f198637…fa2e34f` | -| P0-0e | Title override → probe boots; `getOS()` shows `NX` | yes | `getOS()`=`NX`, `love._os`=`NX` | -| P0-0f | Probe lists 1280×720 (or documented dims), save path, gamepad/touch log | yes | save `sdmc:/switch/gen1recomp/switch-probe`; Joy-Con Y→#3 X→#4 | -| P0-1a | Replace `game.love` with unpatched Gen1Recomp build | yes | feat/switch-nx inbox build | -| P0-1b | MTP replace `game.love` only; round-trip SHA-256 | yes | | -| P0-1c | Title override → launcher reaches import screen | yes | | -| P0-1d | Joy-Con: can navigate launcher (no touch-only) | yes | Full report: `docs/switch-hardware-evidence.md` | - -**Operator:** Andrew **Date:** 2026-08-01 **Console:** Switch OLED only -**Deploy:** manual Mac + OpenMTP + DBI MTP (not automated) -**love-nx tag:** 11.5-nx1 **gen1recomp commit:** `df7cea4` - -## Phase 0 test report template - -Copy this block into your hardware notes or PR evidence. **Do not commit ROM files or ROM hashes of private dumps.** - -```markdown -## Switch Phase 0 — hardware report - -- Operator: -- Date: -- Console model: -- Atmosphère / HOS version: -- gen1recomp commit: -- love-nx tag: 11.5-nx1 -- love.nro SHA-256 (local): -- game.love SHA-256 (local, pre-send): -- MTP round-trip SHA-256 (gen1recomp.nro): -- MTP round-trip SHA-256 (game.love): -- Title override used: yes / no -- Applet Mode observed: yes / no (should be no for P0) -- Probe getOS(): -- Probe dimensions: -- Probe save directory shown: -- Gamepad events logged: yes / no -- Touch events logged: yes / no -- Unpatched launcher boot: pass / fail -- Joy-Con launcher navigation: pass / fail / not tested -- Notes: -``` - -## Fast dev loop (loose mode) - -While iterating on Lua/assets: - -1. Edit on Mac; run `scripts/test.sh --quick`. -2. Rebuild `.bazinga/work/game.love` (`scripts/build.sh mac --no-notarize` or project pack step). -3. Close Gen1Recomp on Switch. -4. DBI → `Run MTP responder`. -5. OpenMTP → `1: SD Card/switch/gen1recomp/`. -6. Replace **only** `game.love`; wait for queue + refresh listing. -7. Exit MTP responder; launch via title override. -8. Keep `gen1recomp.nro` unchanged until the love-nx pin changes. - -```bash -scripts/test.sh --quick -scripts/build.sh mac --no-notarize -scripts/build_switch.sh --loose -shasum -a 256 .bazinga/work/game.love -``` - -## Controller input mapping (NX) - -Measured on Switch OLED (`feat/switch-nx`, love-nx `11.5-nx1`, 1280×720). Both `joystickpressed` and `gamepadpressed` fire for Joy-Con; prefer the gamepad path when `joystick:isGamepad()` is true. - -| Path | Control | Mapping | -| ---- | ------- | ------- | -| `gamepadpressed` | D-pad / left stick | move | -| `gamepadpressed` | SDL `a` / `b` on **NX** | swapped via `NX_GAMEPAD_BINDINGS`: physical **A** (east) = GB A confirm, physical **B** (south) = GB B cancel | -| `gamepadpressed` | SDL `a` / `b` on desktop | identity (SDL south = GB A) | -| `gamepadpressed` | `start` / `back` | Start / Select (+ / −) | -| `gamepadpressed` | Right / left shoulder (no Select) | Cycle game speed up / down (same as PC hotkey `1` / speed-down path) | -| `joystickpressed` (raw) | only if **not** `isGamepad()` | face/menu fallback | -| `joystickpressed` (raw) | `#1` / `#2` on NX | Nintendo B / A → GB B / A | -| `joystickpressed` (raw) | `#9` / `#10` | Select / Start (− / +) | - -**Nintendo UX on Switch:** physical A confirms, physical B cancels (explicit NX remap of SDL face labels). - -**Launcher extras** (`RomImporter`): physical **A** clicks at the virtual cursor; **L** / **R** switch tabs; **Start** / **Select** start Play when a ROM is ready (else open Choose ROM). D-pad / left stick move the virtual cursor. - -**Dual-path rule:** love-nx emits both `gamepadpressed` and `joystickpressed` for Joy-Con. When `joystick:isGamepad()` is true, Input and RomImporter **ignore raw** face/menu so NamingScreen does not see A+B in one frame. `NamingScreen` also prefers A over B if both edges still fire. - -Implementation: `src/core/GamepadMap.lua` (`NX_RAW_*`, `ignoreRawForJoystick`, `displayChordDigit`), `src/core/Game.lua` (shoulder speed), `src/import/RomImporter.lua` (launcher tabs). Launcher and gameplay share the same converter. - -## ROM inbox (NX) - -Legal dumps land in a shared MTP inbox; **Scan again** is tab-scoped: - -| Item | Value | -| ---- | ----- | -| Save-relative path | `imports/` (also accepts loose `.gb`/`.gbc` at the save-dir root) | -| MTP destination | `1: SD Card//imports/` (see launcher notice for the live `getSaveDirectory()` path) | -| Candidates | `*.gb` / `*.gbc` (hidden `.*` AppleDouble names skipped) | -| Rescan | Game tab → **Scan again** — imports only the dump whose SHA-1 matches that tab (`GameVersion.forSha1`). Other known dumps stay for their own tabs | -| Already ready | Same SHA already imported → “No new ROM found.” | - -Players may drop Red, Blue, and Yellow into the same folder. Opening Yellow and pressing **Scan again** must not start a Red import. - -## Mod zip inbox (NX) - -Community mods install from a **separate** MTP inbox (not mixed into the ROM `imports/` scan): - -| Item | Value | -| ---- | ----- | -| Save-relative path | `imports/mods/` | -| MTP destination | `1: SD Card//imports/mods/` (see launcher notice for the live `getSaveDirectory()` path) | -| Candidates | `*.zip` only | -| Rescan | MODS tab → **Scan again** (installs each zip via `LauncherMods.installZip`; source zips are retained on success and failure) | -| FIND MODS | Remains network-gated / hidden on NX (`networkValidated == false`) | - -Do **not** commit third-party mod zip bytes into git. Drop the zip over MTP, rescan, enable in MODS, then Play. - -**MTP tip (esp. macOS clients):** OpenMTP/Finder often creates AppleDouble sidecars named `._Something.zip` / `._cart.gb` / `._foo.sav`. Those are not real archives, ROMs, or saves — the launcher ignores hidden `.*` names under `imports/`, `imports/mods/`, and `imports/saves//`. If install still fails with “could not be opened” / “not a zip file”, delete any `._*` under the inbox and confirm the real zip starts with the `PK` magic (re-copy the release asset if unsure). This is a host-side annoyance of the current manual MTP loop, not something players should need forever. - -Drop any community release `.zip` into `imports/mods/`, rescan, enable. -Player-facing install steps: [switch-install.md](switch-install.md#community-mods). -Mods own their OPTIONS / rebinds — do not duplicate third-party control tables here. - -## Save `.sav` inbox (NX) - -Raw Gen1 battery images use a **separate** MTP inbox (not mixed into ROM `imports/` or mod `imports/mods/`): - -| Item | Value | -| ---- | ----- | -| Save-relative path | `imports/saves/red/`, `imports/saves/blue/`, `imports/saves/yellow/` | -| MTP destination | `1: SD Card//imports/saves//` (see launcher notice for the live `getSaveDirectory()` path) | -| Candidates | non-hidden `*.sav` only in **that game’s** folder | -| Rescan | SAVE FILES → **Import save** on the matching game tab (scans only that folder) | -| After success | Retire to `*.sav.imported` + append content hash to `imports/saves//.imported-sha1` | -| Exports | **Export save** writes under `exports//gen1recomp--.sav`; NX shows an MTP path notice (no `openURL`) | - -Do **not** commit `.sav` bytes into git. Drop the file into the matching game folder over MTP, press **Import save** on that tab, then play. Pull exports from `exports//`. - -**MTP tip:** the same AppleDouble `._*.sav` rule applies — see the mod inbox tip above. - -## Joy-Con display chords (Select + face) - -PC digit hotkeys for COLORS / TILT / GBC FX / pipelines have Joy-Con equivalents. Hold **Select** (`back` / −) and press a face/shoulder button; the engine runs the same path as `Game:keypressed` for that digit (including `writeOptions` / Pipelines parity). - -| Chord (Nintendo UX) | Engine key | Stock engine effect | -| ------------------- | ---------- | ------------------- | -| Select + **A** | `2` | COLORS cycle | -| Select + **B** | `3` | TILT / perspective | -| Select + **Y** | `5` | GBC FX | -| Select + **X** | `6` | Mod pipeline hotkey (if registered) | -| Select + **L** (left shoulder) | `7` | Mod pipeline hotkey (if registered) | - -Keys `2` / `3` / `4` / `5` are claimed by the engine before mod pipeline hotkeys run, so a community mod cannot rebind those digits through `Pipelines.hotkey`. Mods that need their own controls should use OPTIONS rows or unclaimed hotkeys. - -Without Select held, face buttons keep normal GB A/B gameplay mapping (no accidental color/tilt cycles). The **Options** menu remains available for the same settings — chords are optional shortcuts, not the only path. - -On NX, A/B chords resolve through the Nintendo UX face remap so physical **A** → key `2` and physical **B** → key `3` match this table. - -**OPTIONS → PERFORMANCE** clamps the port’s own extras (TILT / GBC FX / survey ZOOM) and can cap FPS — useful on weaker handheld budgets. Details: [new-features.md — Performance tier](new-features.md#performance-tier-low-end-devices). - -Community mod zip install smoke (MODS inbox + Play): NXMOD-12 in [switch-hardware-evidence.md](switch-hardware-evidence.md). - -**Opt-in diagnostics:** create an empty `switch-debug.txt` in the save directory; events flush to `switch.log` at ≤1 Hz with build identity (no ROM/save bytes). - -**NX asset probe (always on Play):** every Switch Play writes `nx-asset-probe.log` in the save directory (`pokemon-love2d/`). It lists whether `assets/generated/…` vs `yellow|blue/assets/generated/…` exist, what `Assets.resolve` returns, and whether `newImage` / `newImageData` open — for Yellow/Blue blank-sprite triage. No ROM bytes. - -**Blue/Yellow cache overlay (NX):** fused love-nx cannot reliably mount `yellow|blue/assets/generated` onto the un-prefixed path, so `src/core/NxAssetOverlay.lua` wraps EVERY read-side love API that accepts a filesystem path (`filesystem.read/load/lines/newFileData/getInfo`, `graphics.newImage/newFont`, `image.newImageData`, `audio.newSource`, `sound.newSoundData`, `font.newFontData`) once at boot — only when `Platform.isNX()`. Covering the whole read surface (not just the loaders the boot needs today) keeps future states and mods inside the fallback automatically; write-side functions stay stock. Core code must NOT call love loaders on literal `assets/generated` paths (enforced by `tests/engine/nx_generated_guard_test.lua`); the chip-audio worker is a separate Lua state and gets the prefix explicitly via `audio.programPrefix` from `ChipAudio.slimAudio`. - -**Hardware re-test:** T16 **pass** @ `2699c9a` (naming A=confirm / B=cancel). T19 **pass** (quit/reopen, suspend×10, reboot) — operator 2026-08-01. - -**Suspend/resume audio:** after resume, chip music is stopped to avoid duplicate streams; confirm on hardware during P0-09/10 (T19). - -## Lua error log (save directory) - -On any uncaught Lua error, Gen1Recomp appends a redacted trace to `lua-error.log` in the LÖVE save directory (`love.filesystem.getSaveDirectory()`). The on-screen error overlay includes a hint pointing at that file. Logs rotate to `lua-error.log.1` when the active file exceeds 32 KiB. ROM/save bytes and non-printable data are stripped — never commit or share logs that might contain private paths without reviewing them first. - -## Native crash triage (love-nx / Atmosphère) - -love-nx native faults land under the console’s `crash_reports/` folder on SD (reachable via the same manual MTP workflow used for game deploys). - -1. **Collect** — DBI → `Run MTP responder`; copy `sdmc:/crash_reports/*.bin` (or the dated subfolder) to the contributor host. Prefer keeping the microSD in-console for routine pulls. -2. **Redact** — delete any attached screenshots or notes that mention ROM filenames, save paths, or private hashes before sharing logs publicly. -3. **Symbolize** — use the **pinned** `love.elf` from `.bazinga/love-nx/11.5-nx1/` that matches `build-info.json` / `scripts/switch/love-nx-11.5-nx1.sha256`. Never use a “latest” download. - - ```bash - # Example: aarch64-none-elf-addr2line from devkitPro - aarch64-none-elf-addr2line -e .bazinga/love-nx/11.5-nx1/love.elf -f -C 0xADDRESS_FROM_CRASH_REPORT - ``` - -4. **Correlate** — compare `gitCommit` / `loveNxTag` from embedded `build-info.json` with the operator’s hardware notes. - -If `addr2line` cannot resolve an address, archive the crash `.bin` with the exact `love.elf` SHA-256 used for the build — addresses are only meaningful against that ELF. - -## P0 / P1 hardware matrix (ADR §9) - -Operator evidence lives in `docs/switch-hardware-evidence.md`. **Do not invent passes** for rows that require hardware not yet run. - -| ID | Requirement | Status | Evidence | -| -- | ----------- | ------ | -------- | -| P0-0a–f | love-nx pin, probe, MTP, title override | **pass** | Phase 0 checklist above; T4 | -| P0-1a–d | Unpatched launcher boot + Joy-Con nav | **pass** | T4 / `docs/switch-hardware-evidence.md` | -| P0-02 | MTP inbox import path shown | **pass** | T12 | -| P0-03 | Rescan imports ROM | **pass** | T12 | -| P0-04 | Canonical hash routes version | **pass** | T12 | -| P0-05 | Source dump retained in inbox | **pass** | T12 | -| P0-06 | Play reaches game after import | **pass** | T12 | -| P0-07 | Joy-Con launcher navigation | **pass** | T16 @ `2699c9a` | -| P0-08 | Joy-Con gameplay (incl. naming A/B) | **pass** | T16 @ `2699c9a` | -| P0-09 | Save survives quit + reopen | **pass** | T19 | -| P0-10 | ≥10 suspend cycles, no stuck input/dup audio | **pass** | T19 (operator 2026-08-01) | -| P0-12 | Fused NRO boots without adjacent `game.love` | **pass** | T24 — `docs/switch-hardware-evidence.md` | -| P0-14 | Fused NRO MTP round-trip SHA-256 | **pass** | T24 — first artifact `b019e2e8…` @ `6fb5602` (redeploy after Blue fix) | -| P0-15 | Replace NRO only; saves persist | **pass** | T24 — operator NRO-only update keeps saves | -| P1-01 | Docked vs handheld spot-check | **deferred** | Code: `NxDisplay` 720p↔1080p; OLED dock soak not recorded yet | -| P1-02 | Applet Mode documented unsupported | **pass** | Title override required; Album path not validated | -| P1-03 | Long-play soak (≥30 min) | **deferred** | No soak session recorded | -| P1-04 | Reboot persistence | **pass** | T19 | -| P1-05 | Audio resume after suspend | **pass** | T19 (no dup audio reported) | -| — | Switch V1 / Erista boot | **pass** (boot) | Community — [booshankles](https://github.com/booshankles); see evidence log | -| — | Switch Lite / docked soak | **untested** / **deferred** | Welcome contributions | -| — | Automated / `nxlink` deploy | **absent** | Manual MTP / SD / FTP only (AD-009) | -| — | Multi-OS transfer runbooks | **pass** | [switch-transfer.md](switch-transfer.md) | -| — | Community mod zip OLED smoke (NXMOD-12) | **pass** | `docs/switch-hardware-evidence.md` | - -## Review guidance - -Maintainers may review as one PR or split later. Suggested slices (optional): - -Each slice should declare: **no ROM/save bytes committed**, **love-nx pin with manifest checksums**, **hardware-tested rows listed with linked evidence**, **Applet Mode unsupported**, **network/updater disabled on NX**, **deploy still manual** (MTP / SD / FTP; no nxlink yet), **OpenMTP is one example not the sole contract**. - -### Slice 1 — Platform + import (`platform/import`) - -- `src/core/Platform.lua`, `conf.lua` NX branch -- `src/import/RomImporter.lua` (NX flags, inbox, scan, shell/updater gates) -- Tests: `tests/engine/platform_nx_*`, `tests/engine/rom_importer_nx_*` (ROM-free T2) -- Docs: inbox/MTP import sections only - -### Slice 2 — Input + lifecycle (`input/lifecycle`) - -- `src/core/GamepadMap.lua`, `Input.lua`, `main.lua` focus/joystick hooks -- `src/debug/SwitchDiagnostics.lua` (opt-in probe + error log) -- Tests: input/diagnostics suites -- Docs: controller mapping, suspend/audio notes - -### Slice 3 — Build + docs (`build/docs`) - -- `scripts/pack_love.sh`, `scripts/build_switch.sh`, `scripts/switch/*` -- `assets/switch/icon.jpg`, `docs/switch-development.md`, hardware evidence templates -- Gates: `pack_love.sh --dry-run`, `verify_payload.sh --self-test`, fused build script (devkitPro host) - -**Pre-merge checklist:** - -- [ ] Manifest `scripts/switch/love-nx-11.5-nx1.sha256` filled; binaries not in git -- [ ] `verify_payload.sh` rejects generated cache / ROM / `.sav` / `.bak` -- [ ] P0 matrix rows marked pass only with linked hardware evidence -- [x] Fused NRO P0-12/14/15 pass with T24 evidence (`docs/switch-hardware-evidence.md`) -- [ ] Updater / remote mod download hidden on NX (`networkValidated == false`) - diff --git a/docs/switch-hardware-evidence.md b/docs/switch-hardware-evidence.md deleted file mode 100644 index 12e93c04..00000000 --- a/docs/switch-hardware-evidence.md +++ /dev/null @@ -1,187 +0,0 @@ -# Switch hardware evidence (Phase 0 + import + input) - -> **Hardware evidence log.** Author passes below were recorded on **one -> Nintendo Switch OLED** with a **manual** Mac → DBI MTP deploy loop. A -> separate community row records Switch V1 / Erista boot. These rows do -> **not** claim Lite, docked soak, or automated install. See -> `docs/switch-development.md` for status and limitations. - -**love-nx:** `11.5-nx1` -**Author console:** Switch OLED -**Deploy method (author):** manual OpenMTP + DBI `Run MTP responder` (no CI / no nxlink) -**Operator (author rows):** Andrew ([andrewqsantos](https://github.com/andrewqsantos)) -**Date (author rows):** 2026-08-01 - -Do **not** commit ROM dumps or private dump hashes. Do **not** mark a row **pass** without hardware notes for that row. - ---- - -## Community — Switch V1 / Erista boot — pass (boot) - -| Field | Value | -| ----- | ----- | -| Console | Nintendo Switch V1 (Erista) | -| Check | Prebuilt fused NRO boots under title override | -| Tester | [booshankles](https://github.com/booshankles) | -| Notes | Community confirmation only — not a full P0/P1 matrix re-run on V1 | - ---- - -## Phase 0 — probe (T4) — pass - -| Field | Value | -| ----- | ----- | -| Commit (import era) | `df7cea4` | -| `getOS()` / `love._os` | `NX` | -| Dimensions | 1280×720 | -| Save (probe) | `sdmc:/switch/gen1recomp/switch-probe` | -| Joy-Con | `joystickpressed` + `gamepadpressed` (Y→`#3`, X→`#4`) | - -| Artifact | SHA-256 | -| -------- | ------- | -| `gen1recomp.nro` | `8290ac153d4c630e48c9b26ef9123f5204ed8ee0cef3042511707b5b645918f5` | - ---- - -## T12 — Red import + Play — pass - -Inbox MTP → “Scan again” → Play; Joy-Con launcher/gameplay (not touch-only). - ---- - -# T16 — Joy-Con launcher + gameplay — pass (naming re-verify) - -### Round 1 @ `7504753` — partial - -| Check | Result | -| ----- | ------ | -| Launcher / overworld (Joy-Con only) | **pass** | -| Naming player/rival | **fail** (dual-path a+b; see below) | -| Touch required | **no** | -| `game.love` SHA-256 | `bd3a35461bf453c1f0465a5a289421aef3b5c72d3bf1f8d76e86231256829e0e` | - -### Naming failure (root cause) — fixed in `efd81d8` + `2699c9a` - -- love-nx fires **`gamepadpressed` + `joystickpressed` on the same physical press**. -- `NamingScreen` tested `wasPressed("b")` before `"a"` → if both true in one frame, always deletes. -- Dual-path fix: ignore raw when `isGamepad()` (`efd81d8`). -- SDL-only UX then had physical B confirm / A erase; NX face remap (`2699c9a`) restores Nintendo A=confirm / B=cancel. - -### Round 2 @ `2699c9a` — pass (Nintendo UX) - -| Field | Value | -| ----- | ----- | -| Commit tested | `2699c9a` | -| `game.love` SHA-256 | `a208b21e1f30b00e2e8c6fa6efe14f0e06d1db0ae1e50b810b16d9fb852926bc` | -| Touch required | **no** | - -| Check | Result | -| ----- | ------ | -| Naming — player | **pass** — physical **A** confirms letter, **B** cancels/erases | -| Naming — rival | **pass** (same) | -| Launcher / overworld (prior round) | **pass** (unchanged mapping for d-pad/stick) | - -T16 hardware gate: **closed**. - ---- - -## T19 — save / suspend — pass - -| Check | Result | -| ----- | ------ | -| Save in-game → full quit → title-override reopen → load save | **pass** (@ `7504753` / retained) | -| Suspend/resume ×10 (launcher / gameplay / mixed) | **pass** (operator 2026-08-01) | -| Full console reboot persistence | **pass** (operator 2026-08-01) | - -T19 hardware gate: **closed**. No stuck input, duplicate audio, or crash reported. - ---- - -## T24 — fused NRO alone + NRO-only update — **pass** - -| Field | Value | -| ----- | ----- | -| First fused attempt | `6fb5602` (Blue Play failed — mount) | -| Fix commits | `b1ad7c7` (logs/generated overlay), `ac6dfe7` (Blue/Yellow mount) | -| Deploy | isolated folder, no adjacent `game.love` | -| Boot fused | **pass** | -| ROM import | **pass** | -| Play **Red** | **pass** | -| Play **Blue** (after `ac6dfe7`) | **pass** (operator 2026-08-01) | -| NRO-only replace | **pass** — saves retained; app still boots/plays | -| Touch required | no | - -T24 hardware gate: **closed**. - ---- - -## SWBLD — `build_switch.sh --fetch --fused` + install path — **pass** - -Operator smoke for the switch-build-pipeline packaging CLI (closes matrix-deferred happy paths from validation). - -| Field | Value | -| ----- | ----- | -| Command | `scripts/build_switch.sh --fetch --fused --version 0.0.0-test` | -| Host | macOS + native switch-tools (or Docker fallback if used) | -| Commit / build-info | `9147a64` (`gitCommit` in build-info) | -| love-nx | `11.5-nx1` (manifest checksums match) | -| Artifact | `dist/switch/gen1recomp-0.0.0-test-switch.nro` | -| NRO SHA-256 | `210efb884a8d27443dc1c64ed8f071b0f862d8d0c9b140ad8185093c4e4027db` | -| Install doc | `docs/switch-install.md` — at the time of this row: copy NRO under `sdmc:/switch/gen1recomp/` (releases now ship an SD-ready zip; same folder) | -| Console | Switch OLED | -| Operator | Andrew | -| Date | 2026-08-01 | - -| Check | Result | -| ----- | ------ | -| `--fetch` + `--fused` produce NRO + `.sha256` | **pass** | -| Copy NRO to SD folder per install doc | **pass** (operator) | -| Title-override launch / play | treated as prior T24 path; this row records **packaging + deploy to folder** success | - -SWBLD packaging smoke: **closed** for Mac fused build + file-to-SD install step. - ---- - -## NXMOD-12 — Community mod zip OLED smoke — **pass** - -Closed from existing OLED photo evidence on issue -[#531](https://github.com/bryanthaboi/gen1recomp/issues/531) (operator comment -with launcher MODS + overworld shots). Photos live on the orphan branch -[`switch-oled-photos`](https://github.com/andrewqsantos/gen1recomp/tree/switch-oled-photos) -of the operator fork — **not** committed to this repo. Do **not** commit -third-party mod `.zip` bytes. Community mods own their OPTIONS / rebinds; -this entry only proves the MODS inbox + Play path on OLED. - -| Field | Value | -| ----- | ----- | -| Status | **pass** | -| gen1recomp commit | evidence era on `feat/switch-nx` (see #531); packaging pin love-nx `11.5-nx1` | -| love-nx tag | `11.5-nx1` | -| Console | Switch OLED | -| Mod | community release `.zip` (not vendored; not named here) | -| Zip committed to git? | **no** | -| Photo evidence | [#531 comment](https://github.com/bryanthaboi/gen1recomp/issues/531) — MODS tab + overworld | -| MODS tab photo | https://raw.githubusercontent.com/andrewqsantos/gen1recomp/switch-oled-photos/IMG_1766.jpg | -| Overworld photo | https://raw.githubusercontent.com/andrewqsantos/gen1recomp/switch-oled-photos/IMG_1771.jpg | -| Operator | Andrew | -| Date | 2026-08-01 | - -### Checklist - -| Step | Pass / fail / pending | Notes | -| ---- | --------------------- | ----- | -| MTP zip into save `imports/mods/` | **pass** | Photo evidence + prior inbox path | -| MODS → Scan again → mod listed | **pass** | IMG_1766 — community mod installed | -| Enable mod + Play Red boots without crash | **pass** | Overworld / Pallet / Oak lab photos on #531 | -| Overworld Select+A → visible colors change | **pass** | Stock COLORS chord path exercised | -| Overworld Select+B → visible tilt/perspective change | **pass** | Stock TILT chord path exercised (IMG_1771) | - -### Evidence notes - -```text -Operator: Andrew -Date: 2026-08-01 -Commit tested: feat/switch-nx era documented on issue #531 -Pass / fail summary: PASS — MODS zip install + Play on Switch OLED -Photo branch: andrewqsantos/gen1recomp@switch-oled-photos -``` diff --git a/docs/switch-install.md b/docs/switch-install.md index e173dfc2..2a54aa06 100644 --- a/docs/switch-install.md +++ b/docs/switch-install.md @@ -2,15 +2,11 @@ Every GitHub Release that includes Switch support ships an SD-ready zip: `gen1recomp-*-switch.zip`. Extract it at the root of your microSD (install -**or** update — same steps), launch with **title override**, then import your +or update, same steps), launch with **title override**, then import your own legal `.gb` ROM. > You need a console that can run Switch homebrew (custom firmware / hbmenu). -> This project does not help you set that up. Tracks issue -> [#531](https://github.com/bryanthaboi/gen1recomp/issues/531). -> Hardware: **OLED** validated by the porter; **V1 / Erista** boot confirmed -> by the community. Lite and other setups welcome more reports. -> See [switch-development.md](switch-development.md) for limitations. +> This project does not help you set that up. Prefer building from source? See [switch-build.md](switch-build.md). @@ -29,12 +25,18 @@ help from [booshankles](https://github.com/booshankles). Extract the zip at the **root** of the microSD so you get: ```text -sdmc:/switch/gen1recomp/gen1recomp.nro +sdmc:/switch/gen1recomp/gen1recomp.nro # native OTA launcher (hbmenu entry) +sdmc:/switch/gen1recomp/gen1recomp-game.nro # fused LÖVE game +sdmc:/switch/gen1recomp/version.txt sdmc:/switch/gen1recomp/pokemon-love2d/imports/ sdmc:/switch/gen1recomp/pokemon-love2d/imports/mods/ sdmc:/switch/gen1recomp/pokemon-love2d/imports/saves/... ``` +Older single-NRO zips only had `gen1recomp.nro` (the fused game). Current +releases use the dual-NRO layout above. Open `gen1recomp` in hbmenu (the +launcher). + Merge folders if your OS asks. Any method works: **MTP** (DBI → Run MTP responder + a client), **direct SD** (Hekate UMS or a card reader), or **FTP**. Exit MTP / unmount / stop FTP cleanly before launching. Step-by-step for @@ -42,17 +44,44 @@ macOS, Linux, and Windows: [switch-transfer.md](switch-transfer.md). ### Updating -Use the **same** extract/merge. It replaces `gen1recomp.nro` (and the small -help `README.txt` / `INSTALL.txt` files). Saves, imported ROMs, mods, and -options live under `pokemon-love2d/` — **do not delete that folder** when -updating, or you will lose progress. +#### Native OTA launcher (in-console) + +Switch OTA runs in a separate **native launcher NRO** (libnx + curl), not the +LÖVE self-updater (`src/update/Check.lua`). hbmenu opens `gen1recomp.nro`. + +When a newer release exists, the launcher downloads the same install zip +(`gen1recomp-*-switch.zip`), checks SHA-256 against `sha256sums.txt`, replaces +both `gen1recomp-game.nro` and `gen1recomp.nro` (keeps NACP version in sync +for hbmenu and Sphaira), then loads the game with `envSetNextLoad`. + +If you are up to date or offline, it skips straight to the game with no +prompt. If an update is available, you get a short prompt styled like the +in-game launcher: black background, RGB rail, logo, A/B buttons. Saves under +`pokemon-love2d/` are not touched. See `src/update/SwitchOta.lua` for the +wire format. + +The LÖVE self-updater stays **disabled** on NX (`networkValidated == false`). + +**Sphaira forwarder (HOME shortcut):** Sphaira copies name/version/icon into +the installed forwarder at creation time. After an OTA (or zip) update, the +`.nro` on the microSD already has the new version, but the HOME shortcut +keeps the old badge until you **reinstall the forwarder once** in Sphaira +(Install Forwarder again on `gen1recomp.nro`). Browsing the NRO in Sphaira / +hbmenu always shows the live file version. + +#### Manual zip (fallback) + +Use the **same** extract/merge of `gen1recomp-*-switch.zip`. It replaces the +NROs (and the small help `README.txt` / `INSTALL.txt` files). Saves, +imported ROMs, mods, and options live under `pokemon-love2d/`. **Do not +delete that folder** when updating, or you will lose progress. ## 3. Launch with title override **Applet Mode is not supported** for this game (not enough memory). 1. On the Switch HOME menu, highlight any installed title. -2. Hold **R** and launch that title — this opens hbmenu with full memory +2. Hold **R** and launch that title. This opens hbmenu with full memory (title override). 3. From hbmenu, open `gen1recomp`. @@ -66,14 +95,14 @@ This project ships **no** game data. On first launch: (`.gbc`) dump into `switch/gen1recomp/pokemon-love2d/imports/` (the launcher also shows the live save-dir path). All three can sit in the same folder. -2. Use **Scan again** on that game’s tab (Red / Blue / Yellow). Rescan - matches by ROM SHA-1 for the open tab only — a Red dump never imports +2. Use **Scan again** on that game's tab (Red / Blue / Yellow). Rescan + matches by ROM SHA-1 for the open tab only. A Red dump never imports from the Yellow tab (and vice versa). ## 5. Import / Export a raw `.sav` Continue a cart or PC battery save (or pull a slot off-console) via MTP / -SD / FTP — same transfer methods as ROMs. Paths are **per game**: +SD / FTP, same transfer methods as ROMs. Paths are **per game**: | Game | Import inbox | Export folder | | ---- | ------------ | ------------- | @@ -81,19 +110,19 @@ SD / FTP — same transfer methods as ROMs. Paths are **per game**: | Blue | `imports/saves/blue/` | `exports/blue/` | | Yellow | `imports/saves/yellow/` | `exports/yellow/` | -(Under the save dir `pokemon-love2d/` — the zip already creates these folders.) +(Under the save dir `pokemon-love2d/`. The zip already creates these folders.) -1. Copy a Gen1 `.sav` (32 KB) into that game’s inbox under the save dir +1. Copy a Gen1 `.sav` (32 KB) into that game's inbox under the save dir ([switch-transfer.md](switch-transfer.md)). -2. With the game’s ROM already imported, open **that game’s tab** → +2. With the game's ROM already imported, open **that game's tab** → **SAVE FILES** → **Import save**. Only that folder is scanned. 3. A successful import retires the file to `*.sav.imported` and records its content hash so pressing **Import save** again does not clone slots. Failed imports leave the original `.sav` in place. 4. To pull a slot off the console, use **Export save**, then copy the file - from that game’s **`exports//`** folder via MTP / SD / FTP. + from that game's **`exports//`** folder via MTP / SD / FTP. -Do not put `.sav` files into git. Prefer clean copies — some MTP clients +Do not put `.sav` files into git. Prefer clean copies. Some MTP clients create `._*.sav` AppleDouble sidecars that are not real saves. ## Controls @@ -130,12 +159,12 @@ create `._*.sav` AppleDouble sidecars that are not real saves. Mods install from a zip inbox (same transfer methods as ROMs): 1. Copy a release `.zip` into the save-dir **`imports/mods/`** path the - launcher shows (MTP / SD / FTP — [switch-transfer.md](switch-transfer.md)). + launcher shows (MTP / SD / FTP. See [switch-transfer.md](switch-transfer.md)). 2. In the launcher, open **MODS** → **Scan again** → enable the mod → **Play**. Remote **FIND MODS** / GitHub download stays **off** on Switch. Do not put -mod zips into git. Community mods ship their own OPTIONS / rebinds — this port +mod zips into git. Community mods ship their own OPTIONS / rebinds. This port does not document third-party control tables. ### Joy-Con shortcuts (Select + face) @@ -153,12 +182,23 @@ hotkeys (`2`/`3`/`5` are claimed before any mod pipeline hotkey runs). | Select + **L** | `7` | Mod pipeline hotkey (if a mod registers `7`) | If the handheld stutters with extras on, try **OPTIONS → PERFORMANCE** → -`LOW` or `BALANCED`. Full chord notes for contributors: -[switch-development.md](switch-development.md#joy-con-display-chords-select--face). +`LOW` or `BALANCED`. + +## Limitations + +- You need homebrew (custom firmware, hbmenu). This project does not set that + up. +- Launch with title override (hold **R** on a title). Applet Mode (Album) is + not supported. The game needs full memory. +- ROMs, mods, and saves are copied manually via MTP, direct SD, or FTP. There + is no automated deploy. +- Updates use the native OTA launcher only. The LÖVE self-updater and remote + **FIND MODS** stay off on Switch. +- Tested on Switch OLED. Switch V1 / Erista boot confirmed by the community. + Other models may work but are less tested. ## Prefer building it yourself? Building the fused NRO (and SD-ready zip) from source is covered in [switch-build.md](switch-build.md). Copying artifacts and inbox files (MTP / SD / FTP on macOS, Linux, Windows): [switch-transfer.md](switch-transfer.md). -Status, limitations, and how we tested: [switch-development.md](switch-development.md). diff --git a/docs/switch-transfer.md b/docs/switch-transfer.md index f7cd153b..1e53de2f 100644 --- a/docs/switch-transfer.md +++ b/docs/switch-transfer.md @@ -1,17 +1,16 @@ # Switch file transfer (MTP / SD / FTP) -Canonical ways to put Gen1Recomp artifacts and inbox files onto a Nintendo -Switch. **Any method is valid** if the bytes land in the destinations below. +Ways to put Gen1Recomp artifacts and inbox files onto a Nintendo Switch. +**Any method is valid** if the bytes land in the destinations below. -This is the home runbook for contributors on **macOS, Linux, and Windows**. +This is the runbook for contributors on **macOS, Linux, and Windows**. Player install (what to download, title override) stays in [switch-install.md](switch-install.md). Packaging stays in -[switch-build.md](switch-build.md). Hardware evidence lives in -[switch-hardware-evidence.md](switch-hardware-evidence.md). +[switch-build.md](switch-build.md). > **Not supported yet:** `nxlink` / hbmenu netloader automation. Useful later -> for a fast contrib rebuild loop; deferred on purpose (AD-009). Do not treat -> netloader as the release or ROM/mod install path. +> for a fast contrib rebuild loop; deferred (AD-009). Do not treat netloader as +> the release or ROM/mod install path. --- @@ -23,7 +22,7 @@ Player install (what to download, title override) stays in | Loose iteration pair | `sdmc:/switch/gen1recomp/gen1recomp.nro` **and** `game.love` beside it | | ROM inbox | LÖVE save dir → `imports/` (launcher shows the live `getSaveDirectory()` path; under MTP often `1: SD Card//imports/`) | | Mod zip inbox | Same save dir → `imports/mods/` then MODS → **Scan again** | -| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow/` then that game’s SAVE FILES → **Import save** | +| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow/` then that game's SAVE FILES → **Import save** | | Save exports | Same save dir → `exports/red\|blue\|yellow/` (pull after **Export save**; MTP / SD / FTP) | | Opt-in diagnostics | Empty `switch-debug.txt` in the save dir → `switch.log` | | Lua error log | `lua-error.log` in the save dir | @@ -34,7 +33,7 @@ files, or third-party mod zips to git. --- -## Canonical methods +## Transfer methods ### 1. MTP (DBI responder + host client) @@ -47,8 +46,8 @@ before launching. #### macOS (example: OpenMTP) -[OpenMTP](https://github.com/ganeshrvel/openmtp) is the loop used for OLED -hardware evidence — **one contributor example**, not a Mac-only product rule. +[OpenMTP](https://github.com/ganeshrvel/openmtp) is a documented example for +macOS, not a Mac-only requirement. 1. Quit other MTP clients. 2. Open OpenMTP → select the DBI device → **`1: SD Card`**. @@ -60,25 +59,25 @@ hardware evidence — **one contributor example**, not a Mac-only product rule. 5. Wait for the queue; refresh; exit MTP responder; title-override launch. macOS clients often create AppleDouble sidecars (`._Something.zip`, -`._cart.gb`, `._foo.sav`). Those are not real archives or saves — the +`._cart.gb`, `._foo.sav`). Those are not real archives or saves. The launcher skips hidden `.*` names. Delete `._*` junk if a zip/ROM/`.sav` fails to open. #### Linux 1. Install desktop MTP support if needed (e.g. `gvfs-mtp` on GNOME/GTK - desktops, or your distro’s KDE MTP stack). + desktops, or your distro's KDE MTP stack). 2. With DBI MTP active, open **Files** / **Dolphin** / **Thunar** and select the Switch / DBI device → **`1: SD Card`**. 3. Extract the release zip at SD root (merge), or copy into `switch/gen1recomp/` and the save-dir inboxes as above. 4. Use **only one** MTP accessor at a time. If `mtp-tools` / `mtpfs` reports - “device is busy”, close the file manager’s MTP mount (or the CLI mount) + "device is busy", close the file manager's MTP mount (or the CLI mount) and retry with a single client. 5. Eject/unmount cleanly; exit MTP on the Switch; title-override launch. If MTP is unavailable or flaky on Linux, use **direct SD** (Hekate UMS or a -card reader) or **FTP** instead — same destinations in the table above. +card reader) or **FTP** instead. Same destinations in the table above. #### Windows @@ -93,7 +92,7 @@ card reader) or **FTP** instead — same destinations in the table above. 5. Safely disconnect; exit MTP on the Switch; title-override launch. If MTP is unavailable or flaky on Windows, use **direct SD** (Hekate UMS or a -card reader) or **FTP** instead — same destinations in the table above. +card reader) or **FTP** instead. Same destinations in the table above. ### 2. Direct SD (Hekate UMS or card reader) @@ -109,9 +108,9 @@ Do not yank the card or unplug UMS mid-write. ### 3. FTP (any SD-exposing Switch FTP) -Any homebrew FTP server that can write the microSD is fine — for example -**DBI’s own FTP**, **sys-ftpd-light**, or **Sphaira** (names are illustrations -only; pick what your CFW setup already uses). +Any homebrew FTP server that can write the microSD is fine. For example +**DBI's own FTP**, **sys-ftpd-light**, or **Sphaira**. Names are illustrations +only; pick what your CFW setup already uses. 1. Start the FTP server on the Switch; note IP/port/credentials from that app. 2. From the host, connect with any FTP client and upload to the same @@ -154,7 +153,7 @@ Copy the file back from the SD and compare hashes. Round-trip must match. | Symptom | What to try | | ------- | ----------- | | Device busy / no MTP volume | One client only; different cable/port; Windows MTP USB Device driver; alternate method (SD or FTP) | -| Zip/ROM/`.sav` “could not be opened” | Delete `._*` sidecars (including `._*.sav`); confirm real zip starts with `PK` | +| Zip/ROM/`.sav` "could not be opened" | Delete `._*` sidecars (including `._*.sav`); confirm real zip starts with `PK` | | Half-copied NRO / crash on boot | Re-copy; verify SHA-256; exit transfer mode before launch | | App opens in Applet Mode | Use title override (hold **R**), not Album | @@ -164,5 +163,3 @@ Copy the file back from the SD and compare hashes. Round-trip must match. - Players: [switch-install.md](switch-install.md) - Builders: [switch-build.md](switch-build.md) -- Status / hardware matrix: [switch-development.md](switch-development.md) -- Evidence log: [switch-hardware-evidence.md](switch-hardware-evidence.md) diff --git a/docs/updater.md b/docs/updater.md index b0de76c8..c27615a3 100644 --- a/docs/updater.md +++ b/docs/updater.md @@ -124,3 +124,8 @@ bundled game, in that case. `love.filesystem.isFused()` is false, and a working tree's `engine` is the `"0.0.0-dev"` placeholder that always reports up to date, so a source checkout is always "the game" itself; updating it means pulling the repo. +- **Nintendo Switch does not use this LÖVE self-updater.** On NX, + `Platform.networkValidated()` is `false`, so `Boot.run` / `Check` never + download `.love` payloads. In-console OTA uses the **native OTA launcher** + (DEVKITPRO), documented in [switch-install.md](switch-install.md). Wire + format: `src/update/SwitchOta.lua`. Manual zip install remains the fallback. diff --git a/libs/flexlove/FlexLove.lua b/libs/flexlove/FlexLove.lua deleted file mode 100644 index 2f72f720..00000000 --- a/libs/flexlove/FlexLove.lua +++ /dev/null @@ -1,1953 +0,0 @@ -local packageName = ... or "FlexLove" -local modulePath = packageName:match("(.-)[^%.]+$") -- Get the module path prefix (e.g., "libs." or "") --- If modulePath is empty (e.g., require("FlexLove")), use the package name -if modulePath == "" then - modulePath = packageName .. "." -end - -local function req(name) - return require(modulePath .. "modules." .. name) -end - ----@type ErrorHandler -local ErrorHandler = req("ErrorHandler") -local ModuleLoader = req("ModuleLoader") -ModuleLoader.init({ ErrorHandler = ErrorHandler }) - -local function safeReq(name, isOptional) - local module = ModuleLoader.safeRequire(modulePath .. "modules." .. name, isOptional) - if isOptional and module and module._isStub then - return nil - end - return module -end - --- Required core modules -local utils = req("utils") -local Calc = req("Calc") -local Units = req("Units") -local Context = req("Context") ----@type StateManager -local StateManager = req("StateManager") -local RoundedRect = req("RoundedRect") -local Grid = req("Grid") -local InputEvent = req("InputEvent") -local TextEditor = req("TextEditor") ----@type LayoutEngine -local LayoutEngine = req("LayoutEngine") -local Renderer = req("Renderer") ----@type EventHandler -local EventHandler = req("EventHandler") -local ScrollManager = req("ScrollManager") ----@type ZIndex -local ZIndex = req("ZIndex") ----@type Element -local Element = req("Element") ----@type Color -local Color = req("Color") - --- Lua 5.2+ compatibility for unpack (bare global `unpack` is nil under --- Lua 5.4, which the stock test runner uses). mirrors the shim in --- modules/Blur.lua:2 and modules/Element.lua:162. -local unpack = table.unpack or unpack - ----@type Select -local Select = req("Select") - --- Behavior: mouse/touch event handling, pressed-state, hit-testing (task 02). --- Auto-attaches to interactive elements via shouldAttach(props). -local Clickable = req("behaviors.Clickable") - --- Behavior: Renderer ownership + theme-state rendering (task 07). Owns the --- single Renderer:draw call and creates the per-element Renderer. Attaches to --- every renderable element (see behaviors/Themed.lua for the always-attach --- rationale). Must precede Clickable in the registry so its core Renderer:draw --- runs before Clickable's pressed-state overlay (onDraw layering). -local Themed = req("behaviors.Themed") - --- Behavior: image loading + image rendering config (task 07). Enriches the --- shared element._renderer with image config, runs the deferred image-load --- pipeline, and persists _loadedImage across immediate-mode frames. Attaches to --- elements with imagePath/image. -local Imageable = req("behaviors.Imageable") - --- Behavior: animation update, interpolation, chaining, transition wiring --- (task 06). Auto-attaches to elements that pre-declare `transitions`, and --- late-attaches on demand via Animated.ensureAttached when an animation is --- created post-construction (animateTo/fadeIn/direct assignment/transition fire). -local Animated = req("behaviors.Animated") - --- Behavior: Select state-machine lifecycle (task 05). Owns select subsystem --- init, managed-frame layout sync each frame, and select save/restore. Auto- --- attaches to elements with selectParent or selectOption props. -local Selectable = req("behaviors.Selectable") - --- Behavior: TextEditor subsystem ownership — text editing, cursor management, --- text selection, text-related input handling, and text-editor save/restore --- (task 04). Auto-attaches to editable elements and text-bearing elements via --- shouldAttach(props); onAttach allocates the TextEditor (editable only). --- Element retains 1-line forwarders routed through this module for the 27 --- text-editor delegate methods, eliminating the `if self._textEditor` guards. -local TextEditable = req("behaviors.TextEditable") - --- Behavior: ScrollManager lifecycle (task 03, landed via the task 08 capstone). --- Owns ScrollManager creation + immediate-mode scrollbar interaction-state --- restore (formerly Element:_initScrollManager). Auto-attaches to elements that --- declare overflow / overflowX / overflowY. Placed late in the registry: its --- onAttach creates the ScrollManager, which no other behavior's onAttach --- depends on. The ScrollManager update / scrollbar draw / state save-restore --- stay inline in Element:update / Element:draw / Element:saveState as --- unconditional 1-line delegates (task 09 folds them into hooks). -local Scrollable = req("behaviors.Scrollable") - --- Behavior: generic public-property persistence across the immediate-mode --- recreation cycle (task 12). Owns the `_props` snapshot (event-driven mutations --- to `text` / `display` / `opacity` / ... that must survive per-frame Element --- recreation). Auto-attaches to every element; placed LAST in the registry so --- its restoreState overrides subsystem-hydrated state, preserving the legacy --- restore ordering (behaviors first, `_props` tail). With this behavior in --- place, Element:saveState / Element:restoreState collapse to a pure --- behavior-dispatch loop and Element owns zero property-extraction logic. -local Persistable = req("behaviors.Persistable") - --- Optional modules (can be excluded in minimal builds) -local Blur = safeReq("Blur", true) ----@type Performance -local Performance = safeReq("Performance", true) ----@type KeyboardNavigation -local KeyboardNavigation = safeReq("KeyboardNavigation", true) ----@type FocusIndicator -local FocusIndicator = safeReq("FocusIndicator", true) -local ImageRenderer = safeReq("ImageRenderer", true) -local ImageScaler = safeReq("ImageScaler", true) -local NinePatch = safeReq("NinePatch", true) -local ImageCache = safeReq("ImageCache", true) -local GestureRecognizer = safeReq("GestureRecognizer", true) ----@type PropertySchema -local PropertySchema = req("PropertySchema") ----@type Animation -local Animation = safeReq("Animation", true) ----@type Theme -local Theme = safeReq("Theme", true) - --- Handle Animation.Transform safely -local Transform = Animation and Animation.Transform or nil - -local enums = utils.enums - -local flexlove = Context -flexlove._VERSION = "0.15.0" -flexlove._DESCRIPTION = "UI Library for LÖVE Framework based on flexbox" -flexlove._URL = "https://github.com/mikefreno/FlexLove" -flexlove._LICENSE = [[ - MIT License - - Copyright (c) 2025 Mike Freno - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -]] - --- GC (Garbage Collection) configuration ----@type GCConfig -flexlove._gcConfig = { - strategy = "auto", -- "auto", "periodic", "manual", "disabled" - memoryThreshold = 100, -- MB before forcing GC - interval = 60, -- Frames between GC steps (for periodic mode) - stepSize = 200, -- Work units per GC step (higher = more aggressive) -} ----@type GCState -flexlove._gcState = { - framesSinceLastGC = 0, - lastMemory = 0, - gcCount = 0, -} - --- Deferred callback queue for operations that cannot run while Canvas is active ----@type function[] -flexlove._deferredCallbacks = {} - --- Track accumulated delta time for immediate mode updates -flexlove._accumulatedDt = 0 - --- Touch ownership tracking: maps touch ID (string) to the element that owns it ----@type table -flexlove._touchOwners = {} - --- Touch-drag scroll tracking: survives immediate-mode element recreation. --- Maps touch ID -> { id, lastX, lastY }. Scroll position is persisted into --- StateManager on every move (same contract as wheelmoved). ----@type table -flexlove._touchScroll = {} - ----@type table -flexlove._mouseButtonStates = {} - --- Shared GestureRecognizer instance for touch routing (initialized in init()) ----@type GestureRecognizer|nil -flexlove._gestureRecognizer = nil - ---- Check if FlexLove initialization is complete and ready to create elements ---- Use this before creating elements to avoid automatic queueing ----@return boolean ready True if FlexLove is initialized and ready to use -function flexlove.isReady() - return flexlove._initState == "ready" -end - ---- Set up FlexLove for your application's specific needs - configure responsive scaling, theming, rendering mode, and debugging tools ---- Use this to establish a consistent UI foundation that adapts to different screen sizes and provides performance insights ---- After initialization, any queued element creation calls will be automatically processed ----@param config FlexLoveConfig? -function flexlove.init(config) - flexlove._initState = "initializing" - config = config or {} - - flexlove._ErrorHandler = ErrorHandler.init({ - includeStackTrace = config.includeStackTrace, - logLevel = config.reportingLogLevel, - logTarget = config.errorLogTarget, - logFile = config.errorLogFile, - maxLogSize = config.errorLogMaxSize, - maxLogFiles = config.maxErrorLogFiles, - enableRotation = config.errorLogRotateEnabled, - }) - - -- Initialize Performance if available - if Performance then - flexlove._Performance = Performance.init({ - enabled = config.performanceMonitoring or true, - hudEnabled = false, -- Start with HUD disabled - hudToggleKey = config.performanceHudKey or "f3", - hudPosition = config.performanceHudPosition or { x = 10, y = 10 }, - warningThresholdMs = config.performanceWarningThreshold or 13.0, - criticalThresholdMs = config.performanceCriticalThreshold or 16.67, - logToConsole = config.performanceLogToConsole or false, - logWarnings = config.performanceWarnings or false, - warningsEnabled = config.performanceWarnings or false, - memoryProfiling = config.memoryProfiling or config.immediateMode and true or false, - }, { ErrorHandler = flexlove._ErrorHandler }) - - if config.immediateMode then - flexlove._Performance:registerTableForMonitoring( - "StateManager.stateStore", - StateManager._getInternalState().stateStore - ) - flexlove._Performance:registerTableForMonitoring( - "StateManager.stateMetadata", - StateManager._getInternalState().stateMetadata - ) - end - else - flexlove._Performance = Performance - end - - -- Initialize optional modules if available - if ModuleLoader.isModuleLoaded(modulePath .. "modules.ImageRenderer") then - ImageRenderer.init({ ErrorHandler = flexlove._ErrorHandler, utils = utils }) - end - - if ModuleLoader.isModuleLoaded(modulePath .. "modules.ImageScaler") then - ImageScaler.init({ ErrorHandler = flexlove._ErrorHandler }) - end - - if ModuleLoader.isModuleLoaded(modulePath .. "modules.NinePatch") then - NinePatch.init({ ErrorHandler = flexlove._ErrorHandler }) - end - - -- Initialize Blur module with immediate mode optimization config - if ModuleLoader.isModuleLoaded(modulePath .. "modules.Blur") then - local blurOptimizations = config.immediateModeBlurOptimizations - if blurOptimizations == nil then - blurOptimizations = true -- Default to enabled - end - Blur.init({ - ErrorHandler = flexlove._ErrorHandler, - immediateModeOptimizations = blurOptimizations and config.immediateMode or false, - }) - end - - -- Initialize required modules - StateManager.init({ ErrorHandler = flexlove._ErrorHandler }) - Calc.init({ ErrorHandler = flexlove._ErrorHandler }) - Units.init({ Context = Context, ErrorHandler = flexlove._ErrorHandler, Calc = Calc }) - Color.init({ ErrorHandler = flexlove._ErrorHandler }) - utils.init({ ErrorHandler = flexlove._ErrorHandler }) - - -- Initialize optional ImageCache module - if ModuleLoader.isModuleLoaded(modulePath .. "modules.ImageCache") then - ImageCache.init({ ErrorHandler = flexlove._ErrorHandler }) - end - - -- Initialize optional Animation module - if ModuleLoader.isModuleLoaded(modulePath .. "modules.Animation") then - Animation.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color }) - end - - -- Initialize optional Theme module - if ModuleLoader.isModuleLoaded(modulePath .. "modules.Theme") then - Theme.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color, utils = utils }) - end - - LayoutEngine.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance, utils = utils }) - EventHandler.init({ - ErrorHandler = flexlove._ErrorHandler, - Performance = flexlove._Performance, - InputEvent = InputEvent, - utils = utils, - Context = Context, - }) - - -- Initialize shared GestureRecognizer for touch routing - if GestureRecognizer then - flexlove._gestureRecognizer = GestureRecognizer.new({}, { InputEvent = InputEvent, utils = utils }) - end - - -- Initialize KeyboardNavigation and FocusIndicator if enabled - local keyboardConfig = config.keyboardNavigation - if - KeyboardNavigation - and (keyboardConfig == true or (type(keyboardConfig) == "table" and keyboardConfig.enabled ~= false)) - then - KeyboardNavigation.init({ - Context = Context, - Element = Element, - ErrorHandler = flexlove._ErrorHandler, - utils = utils, - InputEvent = InputEvent, - }) - - if FocusIndicator then - FocusIndicator.init({ Context = Context, Color = Color }) - KeyboardNavigation.FocusIndicator = FocusIndicator - -- Also set FocusIndicator reference in EventHandler for clearing on mouse click - EventHandler._FocusIndicator = FocusIndicator - -- Note: FocusIndicator is only updated from keyboard navigation (_focusElement) - -- Mouse clicks and activation clear the indicator - end - - -- Apply configuration if provided - flexlove._applyKeyboardNavConfig(keyboardConfig) - end - - flexlove._defaultDependencies = { - Context = Context, - Theme = Theme, - Color = Color, - Calc = Calc, - Units = Units, - Blur = Blur, - ImageRenderer = ImageRenderer, - ImageScaler = ImageScaler, - NinePatch = NinePatch, - RoundedRect = RoundedRect, - ImageCache = ImageCache, - utils = utils, - Grid = Grid, - InputEvent = InputEvent, - GestureRecognizer = GestureRecognizer, - StateManager = StateManager, - TextEditor = TextEditor, - LayoutEngine = LayoutEngine, - Renderer = Renderer, - EventHandler = EventHandler, - ScrollManager = ScrollManager, - ErrorHandler = flexlove._ErrorHandler, - Performance = flexlove._Performance, - Transform = Transform, - Animation = Animation, - ZIndex = ZIndex, - Select = Select, - PropertySchema = PropertySchema, - -- Behavior registry (behavior-mode-unification task 09). Two ordering - -- invariants: - -- * Update: Animated (geometry) → Scrollable (scroll interaction) → - -- Clickable (hit-testing) — animated geometry must be current for - -- hit-testing, and scrollbar press state must be set before Clickable's - -- EventHandler processes mouse events. - -- * Draw: Themed (core Renderer:draw) runs before Clickable (pressed-state - -- overlay); Scrollable (drawLayer="overlay") is dispatched AFTER children - -- for scrollbar-on-top. Imageable/Animated/Selectable/TextEditable onDraw - -- are no-ops, so their position is unconstrained for layering. - -- 7 entries. (task 09 reordered Animated+Scrollable ahead of Clickable.) - clickableBehaviors = { Themed, Clickable, Imageable }, - -- Persistable is the registry tail (task 12): its restoreState applies the - -- `_props` override AFTER every subsystem behavior has hydrated, preserving - -- the legacy restore ordering (behaviors first, `_props` last). - behaviors = { Themed, Animated, Scrollable, Clickable, Imageable, Selectable, TextEditable, Persistable }, - TextEditable = TextEditable, - } - - -- Initialize Element module with dependencies - Element.init(flexlove._defaultDependencies) - - if config.baseScale then - flexlove.baseScale = { - width = config.baseScale.width or 1920, - height = config.baseScale.height or 1080, - } - - local currentWidth, currentHeight = Units.getViewport() - flexlove.scaleFactors.x = currentWidth / flexlove.baseScale.width - flexlove.scaleFactors.y = currentHeight / flexlove.baseScale.height - end - - if config.theme and ModuleLoader.isModuleLoaded(modulePath .. "modules.Theme") then - local success, err = pcall(function() - if type(config.theme) == "string" then - Theme.load(config.theme) - Theme.setActive(config.theme) - flexlove.defaultTheme = config.theme - elseif type(config.theme) == "table" then - local theme = Theme.new(config.theme) - Theme.setActive(theme) - flexlove.defaultTheme = theme.name - end - end) - - if not success then - flexlove._ErrorHandler:warn("FlexLove", "THM_005", { - error = tostring(err), - }) - end - end - - local immediateMode = config.immediateMode or false - flexlove.setMode(immediateMode and "immediate" or "retained") - - flexlove._autoFrameManagement = config.autoFrameManagement or false - - -- Configure GC strategy - if config.gcStrategy then - flexlove._gcConfig.strategy = config.gcStrategy - end - if config.gcMemoryThreshold then - flexlove._gcConfig.memoryThreshold = config.gcMemoryThreshold - end - if config.gcInterval then - flexlove._gcConfig.interval = config.gcInterval - end - if config.gcStepSize then - flexlove._gcConfig.stepSize = config.gcStepSize - end - - if config.stateRetentionFrames or config.maxStateEntries then - StateManager.configure({ - stateRetentionFrames = config.stateRetentionFrames, - maxStateEntries = config.maxStateEntries, - }) - end - flexlove.initialized = true - flexlove._initState = "ready" - - -- Configure debug draw overlay - flexlove._debugDraw = config.debugDraw or false - flexlove._debugDrawKey = config.debugDrawKey or nil - - -- Process all queued element creations - local queue = flexlove._initQueue - flexlove._initQueue = {} -- Clear queue before processing to prevent re-entry issues - - for _, item in ipairs(queue) do - local element = Element.new(item.props) - if item.callback and type(item.callback) == "function" then - local success, err = pcall(item.callback, element) - if not success then - flexlove._ErrorHandler:warn( - "FlexLove", - string.format("Failed to execute queued element callback: %s", tostring(err)) - ) - end - end - end -end - ---- Enable keyboard navigation after initialization (for deferred or conditional setup) ---- Useful when you need to conditionally enable keyboard navigation based on runtime conditions ---- Automatically initializes KeyboardNavigation and FocusIndicator modules if not already initialized ----@param config KeyboardNavigationConfig? Optional configuration table ---- @usage ---- -- Enable with defaults ---- FlexLove.enableKeyboardNavigation() ---- ---- -- Enable with custom configuration ---- FlexLove.enableKeyboardNavigation({ ---- directionalNavigation = true, ---- wrapAround = false, ---- focusIndicator = { ---- enabled = true, ---- color = {1, 0.8, 0, 0.8}, ---- lineWidth = 3, ---- }, ---- }) ---- Enable debug mode for keyboard navigation ---- Use this to troubleshoot keyboard navigation issues ----@param enabled boolean -function flexlove.setKeyboardNavigationDebug(enabled) - if KeyboardNavigation and KeyboardNavigation.config then - KeyboardNavigation.config.debugMode = enabled - print(string.format("[FlexLove] Keyboard navigation debug mode: %s", tostring(enabled))) - end -end - ---- Apply keyboard navigation configuration (internal helper) ----@param config table -function flexlove._applyKeyboardNavConfig(config) - if type(config) ~= "table" then - return - end - - if config.enabled ~= nil then - KeyboardNavigation.config.enabled = config.enabled - end - if config.directionalNavigation ~= nil then - KeyboardNavigation.config.directionalNavigation = config.directionalNavigation - end - if config.wrapAround ~= nil then - KeyboardNavigation.config.wrapAround = config.wrapAround - end - if config.dropFocusOnSelection ~= nil then - KeyboardNavigation.config.dropFocusOnSelection = config.dropFocusOnSelection - end - - if config.focusIndicator and FocusIndicator then - local fiConfig = config.focusIndicator - if fiConfig.enabled ~= nil then - FocusIndicator.config.enabled = fiConfig.enabled - end - if fiConfig.draw ~= nil then - FocusIndicator.config.draw = fiConfig.draw - end - if fiConfig.color then - FocusIndicator.setColor( - fiConfig.color[1] or 0.2, - fiConfig.color[2] or 0.6, - fiConfig.color[3] or 1.0, - fiConfig.color[4] or 0.8 - ) - end - if fiConfig.lineWidth ~= nil then - FocusIndicator.config.lineWidth = fiConfig.lineWidth - end - if fiConfig.pulseEnabled ~= nil then - FocusIndicator.config.pulseEnabled = fiConfig.pulseEnabled - end - end -end - ---- Enable keyboard navigation after initialization (for deferred or conditional setup) ---- Useful when you need to conditionally enable keyboard navigation based on runtime conditions ---- Automatically initializes KeyboardNavigation and FocusIndicator modules if not already initialized ----@usage ---- -- Enable with defaults ---- FlexLove.enableKeyboardNavigation() ---- ---- -- Enable with custom configuration ---- FlexLove.enableKeyboardNavigation({ ---- directionalNavigation = true, ---- wrapAround = false, ---- dropFocusOnSelection = false, ---- focusIndicator = { ---- enabled = true, ---- color = {1, 0.8, 0, 0.8}, ---- lineWidth = 3, ---- draw = function(element, bounds, style) end, ---- }, ---- }) ----@param config KeyboardNavigationConfig -function flexlove.enableKeyboardNavigation(config) - if not KeyboardNavigation then - return - end - config = config or {} - - -- Check if already initialized - if KeyboardNavigation.config and KeyboardNavigation._deps then - -- Already initialized, just apply config if provided - flexlove._applyKeyboardNavConfig(config) - return - end - - -- Initialize KeyboardNavigation - KeyboardNavigation.init({ - Context = Context, - Element = Element, - ErrorHandler = flexlove._ErrorHandler, - utils = utils, - InputEvent = InputEvent, - }) - - -- Initialize FocusIndicator if available - if FocusIndicator then - FocusIndicator.init({ Context = Context, Color = Color }) - KeyboardNavigation.FocusIndicator = FocusIndicator - -- Also set FocusIndicator reference in EventHandler for clearing on mouse click - EventHandler._FocusIndicator = FocusIndicator - -- Note: FocusIndicator is only updated from keyboard navigation (_focusElement) - -- Mouse clicks and activation clear the indicator - end - - flexlove._applyKeyboardNavConfig(config) -end - ---- Safely schedule operations that modify LÖVE's rendering state (like window mode changes) to execute after all canvas operations complete ---- Prevents crashes from attempting canvas-incompatible operations during rendering ----@param callback function The callback to execute -function flexlove.deferCallback(callback) - if type(callback) ~= "function" then - flexlove._ErrorHandler:warn("FlexLove", "CORE_001") - return - end - table.insert(flexlove._deferredCallbacks, callback) -end - ---- Execute deferred operations at the safest point in the render cycle - after all canvas operations are complete ---- Call this at the end of love.draw() to enable window resizing and other state-modifying operations without crashes ---- @usage ---- function love.draw() ---- love.graphics.setCanvas(myCanvas) ---- FlexLove.draw() ---- love.graphics.setCanvas() -- Release ALL canvases ---- FlexLove.executeDeferredCallbacks() -- Now safe to execute ---- end -function flexlove.executeDeferredCallbacks() - if #flexlove._deferredCallbacks == 0 then - return - end - - -- Copy callbacks and clear queue before execution - -- This prevents infinite loops if callbacks defer more callbacks - local callbacks = flexlove._deferredCallbacks - flexlove._deferredCallbacks = {} - - for _, callback in ipairs(callbacks) do - local success, err = xpcall(callback, debug.traceback) - if not success then - flexlove._ErrorHandler:warn("FlexLove", "CORE_002", { - error = tostring(err), - }) - end - end -end - ---- Recalculate all UI layouts when the window size changes - ensures your interface adapts seamlessly to new dimensions ---- Hook this to love.resize() to maintain proper scaling and positioning across window size changes -function flexlove.resize() - local newWidth, newHeight = love.window.getMode() - - if flexlove.baseScale then - flexlove.scaleFactors.x = newWidth / flexlove.baseScale.width - flexlove.scaleFactors.y = newHeight / flexlove.baseScale.height - end - - if ModuleLoader.isModuleLoaded(modulePath .. "modules.Blur") then - Blur.clearCache() - end - - -- Release old canvases explicitly - if flexlove._gameCanvas then - flexlove._gameCanvas:release() - end - if flexlove._backdropCanvas then - flexlove._backdropCanvas:release() - end - - flexlove._gameCanvas = nil - flexlove._backdropCanvas = nil - flexlove._canvasDimensions = { width = 0, height = 0 } - - for _, win in ipairs(flexlove.topElements) do - win:resize(newWidth, newHeight) - end -end - ---- Switch between immediate mode (React-like, recreates UI each frame) and retained mode (persistent elements) to match your architectural needs ---- Use immediate for simpler state management and declarative UIs, retained for performance-critical applications with complex state ----@param mode "immediate"|"retained" -function flexlove.setMode(mode) - if mode == "immediate" then - flexlove._immediateMode = true - flexlove._immediateModeState = StateManager - flexlove._frameStarted = false - flexlove._autoBeganFrame = false - -- Notify StateManager of mode change - StateManager.setImmediateMode(true) - elseif mode == "retained" then - flexlove._immediateMode = false - flexlove._immediateModeState = nil - flexlove._frameStarted = false - flexlove._autoBeganFrame = false - flexlove._currentFrameElements = {} - flexlove._frameNumber = 0 - -- Notify StateManager of mode change - StateManager.setImmediateMode(false) - else - error("[FlexLove] Invalid mode: " .. tostring(mode) .. ". Expected 'immediate' or 'retained'") - end -end - ---- Check which rendering mode is active to conditionally handle state management logic ---- Useful for libraries and reusable components that need to adapt to different rendering strategies ----@return "immediate"|"retained" -function flexlove.getMode() - return flexlove._immediateMode and "immediate" or "retained" -end - ---- Manually start a new frame in immediate mode for precise control over the UI lifecycle ---- Only needed when you want explicit frame boundaries; otherwise FlexLove auto-manages frames -function flexlove.beginFrame() - if not flexlove._immediateMode then - return - end - - -- Reset accumulated delta time for new frame - flexlove._accumulatedDt = 0 - - -- Start performance frame timing - if flexlove._Performance then - flexlove._Performance:startFrame() - end - - -- Cleanup elements from PREVIOUS frame (after they've been drawn) - -- This breaks circular references and allows GC to collect memory - if flexlove._currentFrameElements then - local function cleanupChildren(elem) - for _, child in ipairs(elem.children) do - cleanupChildren(child) - end - elem:_cleanup() - end - - for _, element in ipairs(flexlove._currentFrameElements) do - if not element.parent then - cleanupChildren(element) - end - end - end - - flexlove._frameNumber = flexlove._frameNumber + 1 - StateManager.incrementFrame() - flexlove._currentFrameElements = {} - flexlove._frameStarted = true - flexlove.topElements = {} - - Context.clearFrameElements() -end - ---- Finalize the frame in immediate mode, triggering layout calculations and state persistence ---- Only needed when manually controlling frames with beginFrame(); otherwise handled automatically -function flexlove.endFrame() - if not flexlove._immediateMode then - return - end - - Context.sortElementsByZIndex() - - -- Layout all top-level elements now that all children have been added - for _, element in ipairs(flexlove._currentFrameElements) do - if not element.parent then - element:layoutChildren() - end - end - - flexlove._handleSelectPointerDismissal() - - -- Update all top-level elements created this frame - for _, element in ipairs(flexlove._currentFrameElements) do - if not element.parent then - element:update(flexlove._accumulatedDt) - end - end - - -- Save state for all elements created this frame - for _, element in ipairs(flexlove._currentFrameElements) do - if element.id and element.id ~= "" then - local stateUpdate = element:saveState() - local stateChanged = StateManager.updateStateIfChanged(element.id, stateUpdate) - if stateChanged and (element.backdropBlur or element.contentBlur) and Blur then - Blur.clearElementCache(element.id) - end - end - end - - StateManager.cleanup() - StateManager.forceCleanupIfNeeded() - -- Flush dirty state from this frame (no-op in retained mode) - StateManager.flushFrame() - flexlove._frameStarted = false - - -- End performance frame timing - if flexlove._Performance then - flexlove._Performance:endFrame() - flexlove._Performance:resetFrameCounters() - end -end - ----@type love.Canvas? -flexlove._gameCanvas = nil ----@type love.Canvas? -flexlove._backdropCanvas = nil ----@type {width: number, height: number} -flexlove._canvasDimensions = { width = 0, height = 0 } - ---- Recursively draw debug boundaries for an element and all its children ---- Draws regardless of visibility/opacity to reveal hidden or transparent elements ----@param element Element -local function drawDebugElement(element) - local color = element._debugColor - if color then - local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom) - - -- Fill with 0.5 opacity - love.graphics.setColor(color[1], color[2], color[3], 0.5) - love.graphics.rectangle("fill", element.x, element.y, bw, bh) - - -- Border with full opacity, 1px line - love.graphics.setColor(color[1], color[2], color[3], 1) - love.graphics.setLineWidth(1) - love.graphics.rectangle("line", element.x, element.y, bw, bh) - end - - for _, child in ipairs(element.children) do - drawDebugElement(child) - end -end - ---- Render the debug draw overlay for all elements in the tree ---- Traverses every element regardless of visibility or opacity -function flexlove._renderDebugOverlay() - -- Save current graphics state - local prevR, prevG, prevB, prevA = love.graphics.getColor() - local prevLineWidth = love.graphics.getLineWidth() - - -- Clear any active scissor so debug draws are always visible - love.graphics.setScissor() - - for _, win in ipairs(flexlove.topElements) do - drawDebugElement(win) - end - - -- Restore graphics state - love.graphics.setColor(prevR, prevG, prevB, prevA) - love.graphics.setLineWidth(prevLineWidth) -end - ---- Render all UI elements with optional backdrop blur support for glassmorphic effects ---- Place your game scene in gameDrawFunc to enable backdrop blur on UI elements; use postDrawFunc for overlays ----@param gameDrawFunc function|nil pass component draws that should be affected by a backdrop blur ----@param postDrawFunc function|nil pass component draws that should NOT be affected by a backdrop blur -function flexlove.draw(gameDrawFunc, postDrawFunc) - if flexlove._immediateMode and flexlove._autoBeganFrame then - flexlove.endFrame() - flexlove._autoBeganFrame = false - end - - local outerCanvas = love.graphics.getCanvas() - local gameCanvas = nil - - if type(gameDrawFunc) == "function" then - local width, height = love.graphics.getDimensions() - - if - not flexlove._gameCanvas - or flexlove._canvasDimensions.width ~= width - or flexlove._canvasDimensions.height ~= height - then - -- Release old canvases before creating new ones - if flexlove._gameCanvas then - flexlove._gameCanvas:release() - end - if flexlove._backdropCanvas then - flexlove._backdropCanvas:release() - end - - flexlove._gameCanvas = love.graphics.newCanvas(width, height) - flexlove._backdropCanvas = love.graphics.newCanvas(width, height) - flexlove._canvasDimensions.width = width - flexlove._canvasDimensions.height = height - end - - gameCanvas = flexlove._gameCanvas - - love.graphics.setCanvas(gameCanvas) - love.graphics.clear() - gameDrawFunc() - love.graphics.setCanvas(outerCanvas) - - love.graphics.setColor(1, 1, 1, 1) - love.graphics.draw(gameCanvas, 0, 0) - end - - table.sort(flexlove.topElements, function(a, b) - return a.z < b.z - end) - - local function hasBackdropBlur(element) - if element.backdropBlur and element.backdropBlur.radius > 0 then - return true - end - for _, child in ipairs(element.children) do - if hasBackdropBlur(child) then - return true - end - end - return false - end - - local needsBackdropCanvas = false - for _, win in ipairs(flexlove.topElements) do - if hasBackdropBlur(win) then - needsBackdropCanvas = true - break - end - end - - if needsBackdropCanvas and gameCanvas then - local backdropCanvas = flexlove._backdropCanvas - local prevColor = { love.graphics.getColor() } - - love.graphics.setCanvas(backdropCanvas) - love.graphics.clear() - love.graphics.setColor(1, 1, 1, 1) - love.graphics.draw(gameCanvas, 0, 0) - - love.graphics.setCanvas(outerCanvas) - love.graphics.setColor(unpack(prevColor)) - - for _, win in ipairs(flexlove.topElements) do - -- Check if this element tree has backdrop blur - local needsBackdrop = hasBackdropBlur(win) - - -- Draw element with backdrop blur applied if needed - if needsBackdrop then - win:draw(backdropCanvas) - else - win:draw(nil) - end - - -- IMPORTANT: Update backdrop canvas for EVERY element (respecting z-index order) - -- This ensures that lower z-index elements are visible in the backdrop blur - -- of higher z-index elements - love.graphics.setCanvas(backdropCanvas) - love.graphics.setColor(1, 1, 1, 1) - win:draw(nil) - love.graphics.setCanvas(outerCanvas) - end - else - for _, win in ipairs(flexlove.topElements) do - win:draw(nil) - end - end - - if type(postDrawFunc) == "function" then - postDrawFunc() - end - - -- Render performance HUD if enabled - if flexlove._Performance then - flexlove._Performance:renderHUD() - end - - -- Render focus indicator if keyboard navigation is enabled - if KeyboardNavigation and KeyboardNavigation.config and KeyboardNavigation.config.enabled and FocusIndicator then - FocusIndicator:draw() - end - - -- Render debug draw overlay if enabled - if flexlove._debugDraw then - flexlove._renderDebugOverlay() - end - - love.graphics.setCanvas(outerCanvas) - - -- NOTE: Deferred callbacks are NOT executed here because the calling code - -- (e.g., main.lua) might still have a canvas active. Callbacks must be - -- executed by calling FlexLove.executeDeferredCallbacks() at the very end - -- of love.draw() after ALL canvases have been released. -end - ---- Check if element is an ancestor of target ----@param element Element The potential ancestor element ----@param target Element The target element to check ----@return boolean isAncestor True if element is an ancestor of target -local function isAncestor(element, target) - local current = target.parent - while current do - if current == element then - return true - end - current = current.parent - end - return false -end - ----@param element Element ----@param results Element[] -local function collectOpenSelects(element, results) - if element._selectState and element._selectState.open then - table.insert(results, element) - end - - for _, child in ipairs(element.children) do - collectOpenSelects(child, results) - end -end - -function flexlove._handleSelectPointerDismissal() - local isLeftDown = love.mouse.isDown(1) - local wasLeftDown = flexlove._mouseButtonStates[1] or false - - if isLeftDown and not wasLeftDown then - local mx, my = love.mouse.getPosition() - local target = flexlove.getElementAtPosition(mx, my) - local openSelects = {} - - for _, element in ipairs(flexlove.topElements) do - collectOpenSelects(element, openSelects) - end - - for _, selectParent in ipairs(openSelects) do - local containsTarget = target and (target == selectParent or isAncestor(selectParent, target)) - if not containsTarget then - selectParent:closeSelect() - end - end - end - - flexlove._mouseButtonStates[1] = isLeftDown -end - ---- Determine which UI element the user is interacting with at a specific screen position ---- Essential for custom input handling, tooltips, or debugging click targets in complex layouts ----@param x number ----@param y number ----@return Element? -function flexlove.getElementAtPosition(x, y) - local candidates = {} - local blockingElements = {} - - local function collectHits(element, scrollOffsetX, scrollOffsetY) - scrollOffsetX = scrollOffsetX or 0 - scrollOffsetY = scrollOffsetY or 0 - - -- pointHitsElement is the single canonical bounds + display:none guard. - if Context.pointHitsElement(element, x, y, scrollOffsetX, scrollOffsetY) then - -- Skip invisible/transparent elements and their entire subtree - if element.visibility == "hidden" or element.opacity <= 0 then - return - end - - -- Collect interactive elements (those with onEvent handlers) - if - (element.onEvent or element.editable or element._selectState or element.selectOption) and not element.disabled - then - table.insert(candidates, element) - end - - -- Collect all visible elements for input blocking - -- Elements with opacity > 0 block input to elements below them - if element.opacity > 0 then - table.insert(blockingElements, element) - end - - -- Check if this element has scrollable overflow - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - local hasScrollableOverflow = ( - overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" - or overflowX == "hidden" - or overflowY == "hidden" - ) - - -- Accumulate scroll offset for children if this element has overflow clipping - local childScrollOffsetX = scrollOffsetX - local childScrollOffsetY = scrollOffsetY - if hasScrollableOverflow then - childScrollOffsetX = childScrollOffsetX + (element._scrollX or 0) - childScrollOffsetY = childScrollOffsetY + (element._scrollY or 0) - end - - for _, child in ipairs(element.children) do - collectHits(child, childScrollOffsetX, childScrollOffsetY) - end - end - end - - for _, element in ipairs(flexlove.topElements) do - collectHits(element) - end - - -- Sort both lists by composite z-index (highest first). Uses the same - -- rootZ * ROOT_WEIGHT + depth * DEPTH_WEIGHT + ownZ key as - -- Context.sortElementsByZIndex / findInteractiveAtPosition so the hit-test - -- topmost matches the visual draw order across overlapping windows. - -- Skip the precompute + sort when there is 0 or 1 element (the common case - -- for getElementAtPosition which is called on every love.mousemoved event). - if #candidates > 1 then - local candidateZ = {} - for i = 1, #candidates do - candidateZ[candidates[i]] = Context.getEffectiveZIndex(candidates[i]) - end - table.sort(candidates, function(a, b) - return candidateZ[a] > candidateZ[b] - end) - end - - if #blockingElements > 1 then - local blockerZ = {} - for i = 1, #blockingElements do - blockerZ[blockingElements[i]] = Context.getEffectiveZIndex(blockingElements[i]) - end - table.sort(blockingElements, function(a, b) - return blockerZ[a] > blockerZ[b] - end) - end - - -- If we have interactive elements, return the topmost one - -- But only if there's no blocking element with higher z-index (that isn't an ancestor) - if #candidates > 0 then - local topCandidate = candidates[1] - - -- Check if any blocking element would prevent this interaction - if #blockingElements > 0 then - local topBlocker = blockingElements[1] - -- If the top blocker has higher z-index than the top candidate, - -- and the blocker is NOT an ancestor of the candidate, - -- return the blocker (even though it has no onEvent, it blocks input) - if topBlocker.z > topCandidate.z and not isAncestor(topBlocker, topCandidate) then - return topBlocker - end - end - - return topCandidate - end - - -- No interactive elements, but return topmost blocking element if any - -- This prevents clicks from passing through non-interactive overlays - return blockingElements[1] -end - ---- Update all UI animations, interactions, and state changes each frame ---- Hook this to love.update() to enable hover effects, animations, text cursors, and scrolling ----@param dt number -function flexlove.update(dt) - -- Update Performance module with actual delta time for accurate FPS - if flexlove._Performance then - flexlove._Performance:updateDeltaTime(dt) - end - - -- Update keyboard navigation (animations, etc.) - if KeyboardNavigation then - KeyboardNavigation:update(dt) - end - - -- Garbage collection management - flexlove._manageGC() - - -- Invalidate the per-frame findInteractiveAtPosition cache so Clickable's - -- per-element occlusion lookup (one per interactive element per frame) is - -- recomputed fresh for this frame's tree. Within the frame every - -- Clickable.onUpdate then shares one cached result instead of re-walking - -- + re-sorting the tree per element (unified-event-routing task 05 fix). - flexlove.clearInteractiveCache() - - -- Select-pointer dismissal: if the left mouse button was just pressed, - -- check whether any open Select dropdowns should be closed (click-outside). - -- This calls getElementAtPosition ONLY on the click frame, not every frame. - flexlove._handleSelectPointerDismissal() - - -- In immediate mode, accumulate dt and skip updating here - elements will be updated in endFrame after layout - if flexlove._immediateMode then - flexlove._accumulatedDt = flexlove._accumulatedDt + dt - else - for _, win in ipairs(flexlove.topElements) do - win:update(dt) - end - end - - -- Note: State saving happens in endFrame() after element:update() is called - -- This ensures all state changes (including cursor blink) are captured once per frame -end - ---- Internal GC management function (called from update) -function flexlove._manageGC() - local strategy = flexlove._gcConfig.strategy - - if strategy == "disabled" then - return - end - - local currentMemory = collectgarbage("count") / 1024 -- Convert to MB - flexlove._gcState.lastMemory = currentMemory - flexlove._gcState.framesSinceLastGC = flexlove._gcState.framesSinceLastGC + 1 - - -- Check memory threshold (applies to all strategies except disabled) - if currentMemory > flexlove._gcConfig.memoryThreshold then - -- Force full GC when exceeding threshold - collectgarbage("collect") - flexlove._gcState.gcCount = flexlove._gcState.gcCount + 1 - flexlove._gcState.framesSinceLastGC = 0 - return - end - - -- Strategy-specific GC - if strategy == "periodic" then - -- Run incremental GC step every N frames - if flexlove._gcState.framesSinceLastGC >= flexlove._gcConfig.interval then - collectgarbage("step", flexlove._gcConfig.stepSize) - flexlove._gcState.gcCount = flexlove._gcState.gcCount + 1 - flexlove._gcState.framesSinceLastGC = 0 - end - elseif strategy == "auto" then - -- Let Lua's automatic GC handle it, but help with incremental steps - -- Run a small step every frame to keep memory under control - if flexlove._gcState.framesSinceLastGC >= 5 then - collectgarbage("step", 50) -- Small steps to avoid frame drops - flexlove._gcState.framesSinceLastGC = 0 - end - end - -- "manual" strategy: no automatic GC, user must call flexlove.collectGarbage() -end - ---- Manually trigger garbage collection to prevent frame drops during critical gameplay moments ---- Use this to control when memory cleanup happens rather than letting it occur unpredictably ----@param mode? string "collect" for full GC, "step" for incremental (default: "collect") ----@param stepSize? number Work units for step mode (default: 200) -function flexlove.collectGarbage(mode, stepSize) - mode = mode or "collect" - stepSize = stepSize or 200 - - if mode == "collect" then - collectgarbage("collect") - flexlove._gcState.gcCount = flexlove._gcState.gcCount + 1 - flexlove._gcState.framesSinceLastGC = 0 - elseif mode == "step" then - collectgarbage("step", stepSize) - elseif mode == "count" then - return collectgarbage("count") / 1024 -- Return memory in MB - end -end - ---- Choose how FlexLove manages memory cleanup to balance performance and memory usage for your app's needs ---- Use "manual" for tight control in performance-critical sections, "auto" for hands-off operation ----@param strategy string "auto", "periodic", "manual", or "disabled" -function flexlove.setGCStrategy(strategy) - if strategy == "auto" or strategy == "periodic" or strategy == "manual" or strategy == "disabled" then - flexlove._gcConfig.strategy = strategy - else - flexlove._ErrorHandler:warn("FlexLove", "CORE_003", { - strategy = tostring(strategy), - }) - end -end - ---- Monitor memory management behavior to diagnose performance issues and tune GC settings ---- Use this to identify memory leaks or optimize garbage collection timing ----@return GCStats stats GC statistics -function flexlove.getGCStats() - return { - gcCount = flexlove._gcState.gcCount, - framesSinceLastGC = flexlove._gcState.framesSinceLastGC, - currentMemoryMB = flexlove._gcState.lastMemory, - strategy = flexlove._gcConfig.strategy, - threshold = flexlove._gcConfig.memoryThreshold, - } -end - ---- Forward text input to focused editable elements like text fields and text areas ---- Hook this to love.textinput() to enable text entry in your UI ----@param text string -function flexlove.textinput(text) - local focusedElement = Context.getFocused() - if focusedElement and not focusedElement.disabled then - focusedElement:textinput(text) - end -end - ---- Handle keyboard input for text editing, navigation, and performance overlay toggling ---- Hook this to love.keypressed() to enable text selection, cursor movement, and the performance HUD ----@param key string ----@param scancode string ----@param isrepeat boolean -function flexlove.keypressed(key, scancode, isrepeat) - if flexlove._Performance then - flexlove._Performance:keypressed(key) - end - if flexlove._debugDrawKey and key == flexlove._debugDrawKey then - flexlove._debugDraw = not flexlove._debugDraw - end - - -- Handle keyboard navigation (if module is available and enabled) - if KeyboardNavigation and KeyboardNavigation.config and KeyboardNavigation.config.enabled then - -- Debug logging for keyboard navigation entry point - if KeyboardNavigation.config.debugMode then - print(string.format("[FlexLove.keypressed] Keyboard nav enabled, handling key: %s", key)) - end - - -- Check if we're in text input mode (editable element focused) - local focusedElement = Context.getFocused() - local isTextInputMode = focusedElement and (focusedElement.editable or focusedElement._textEditor) - - -- Only handle navigation if not in text input mode, or if in text input mode without modifiers - local shouldHandleNav = not isTextInputMode - or ( - isTextInputMode - and not ( - love.keyboard.isDown("lctrl") - or love.keyboard.isDown("rctrl") - or love.keyboard.isDown("lalt") - or love.keyboard.isDown("ralt") - ) - ) - - if shouldHandleNav then - local handled = KeyboardNavigation:handleKeyPress(key, scancode, isrepeat) - if KeyboardNavigation.config.debugMode and not handled then - print(string.format("[FlexLove.keypressed] Key %s was NOT handled by keyboard navigation", key)) - end - if handled then - return -- Navigation handled the key, don't forward to element - end - end - end - - -- Forward to focused element for text input - local focusedElement = Context.getFocused() - if focusedElement and not focusedElement.disabled then - focusedElement:keypressed(key, scancode, isrepeat) - end -end - ---- Enable mouse wheel scrolling in scrollable containers and lists ---- Hook this to love.wheelmoved() to allow users to scroll through content naturally ----@param dx number ----@param dy number -function flexlove.wheelmoved(dx, dy) - local mx, my = love.mouse.getPosition() - local element = Context.findScrollableAtPosition(mx, my) - - if element then - element:_handleWheelScroll(dx, dy) - - -- In immediate mode, persist scroll manager state for next frame - if flexlove._immediateMode and element._stateId and element._scrollManager then - local scrollManagerState = element._scrollManager:getState() - StateManager.updateState(element._stateId, { - scrollManager = scrollManagerState, - }) - end - end -end - ---- Find the touch-interactive element at a given position using z-index ordering ---- Similar to getElementAtPosition but checks for touch-enabled elements ----@param x number Touch X position ----@param y number Touch Y position ----@return Element|nil element The topmost touch-enabled element at position -function flexlove._getTouchElementAtPosition(x, y) - local candidates = {} - - local function collectTouchHits(element, scrollOffsetX, scrollOffsetY) - scrollOffsetX = scrollOffsetX or 0 - scrollOffsetY = scrollOffsetY or 0 - - -- pointHitsElement is the single canonical bounds + display:none guard. - if Context.pointHitsElement(element, x, y, scrollOffsetX, scrollOffsetY) then - -- Check if element is touch-enabled and interactive - if - element.touchEnabled - and not element.disabled - and (element.onEvent or element.onTouchEvent or element.onGesture) - then - table.insert(candidates, element) - end - - -- Check if this element has scrollable overflow (for touch scrolling) - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - local hasScrollableOverflow = ( - overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" - or overflowX == "hidden" - or overflowY == "hidden" - ) - - -- Accumulate scroll offset for children - local childScrollOffsetX = scrollOffsetX - local childScrollOffsetY = scrollOffsetY - if hasScrollableOverflow then - childScrollOffsetX = childScrollOffsetX + (element._scrollX or 0) - childScrollOffsetY = childScrollOffsetY + (element._scrollY or 0) - end - - for _, child in ipairs(element.children) do - collectTouchHits(child, childScrollOffsetX, childScrollOffsetY) - end - end - end - - for _, element in ipairs(flexlove.topElements) do - collectTouchHits(element) - end - - -- Sort by z-index (highest first) — topmost element wins - table.sort(candidates, function(a, b) - return a.z > b.z - end) - - return candidates[1] -end - -local function elementIsTouchScrollable(element) - if not element or not element._scrollManager then - return false - end - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - return overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" -end - --- Walk parents from a hit target so a finger on a button/row still scrolls --- the containing list. Falls back to the wheel path's scrollable lookup when --- the press lands on empty space inside a scroller. -local function findTouchScrollTarget(x, y, startElement) - local el = startElement - while el do - if elementIsTouchScrollable(el) then - return el - end - el = el.parent - end - return Context.findScrollableAtPosition(x, y) -end - -local function findElementByStateId(stateId) - if not stateId or stateId == "" then - return nil - end - local function walk(element) - if element.id == stateId or element._stateId == stateId then - return element - end - for _, child in ipairs(element.children or {}) do - local found = walk(child) - if found then - return found - end - end - return nil - end - for _, element in ipairs(flexlove.topElements or {}) do - local found = walk(element) - if found then - return found - end - end - for _, element in ipairs(flexlove._currentFrameElements or {}) do - local found = walk(element) - if found then - return found - end - end - return nil -end - -local function persistTouchScroll(element) - if flexlove._immediateMode and element and element._stateId and element._scrollManager then - StateManager.updateState(element._stateId, { - scrollManager = element._scrollManager:getState(), - }) - end -end - -local function resolveTouchScrollElement(track) - if not track then - return nil - end - local element = findElementByStateId(track.id) - if elementIsTouchScrollable(element) then - return element - end - return nil -end - ---- Handle touch press events from LÖVE's touch input system ---- Routes touch to the topmost element at the touch position and assigns touch ownership ---- Hook this to love.touchpressed() to enable touch interaction ----@param id lightuserdata Touch identifier from LÖVE ----@param x number Touch X position in screen coordinates ----@param y number Touch Y position in screen coordinates ----@param dx number X distance moved (usually 0 on press) ----@param dy number Y distance moved (usually 0 on press) ----@param pressure number Touch pressure (0-1, if supported by device) -function flexlove.touchpressed(id, x, y, dx, dy, pressure) - local touchId = tostring(id) - pressure = pressure or 1.0 - - -- Apply base scaling if configured - local touchX, touchY = x, y - if flexlove.baseScale then - touchX = x / flexlove.scaleFactors.x - touchY = y / flexlove.scaleFactors.y - end - - -- Find the topmost touch-enabled element at this position - local element = flexlove._getTouchElementAtPosition(touchX, touchY) - - if element then - -- Assign touch ownership: this element receives all subsequent events for this touch - flexlove._touchOwners[touchId] = element - - -- Create and route touch event - local touchEvent = InputEvent.fromTouch(id, touchX, touchY, "began", pressure) - element:handleTouchEvent(touchEvent) - - -- Feed to shared gesture recognizer - if flexlove._gestureRecognizer then - local gestures = flexlove._gestureRecognizer:processTouchEvent(touchEvent) - if gestures then - for _, gesture in ipairs(gestures) do - element:handleGesture(gesture) - end - end - end - end - - -- Scroll target is the nearest scrollable ancestor (or the scroller under - -- empty space). Tracked by stable id so immediate-mode recreation can resume. - local scrollEl = findTouchScrollTarget(touchX, touchY, element) - if scrollEl and scrollEl._scrollManager then - local scrollId = scrollEl._stateId or scrollEl.id - if scrollId and scrollId ~= "" then - flexlove._touchScroll[touchId] = { - id = scrollId, - lastX = touchX, - lastY = touchY, - } - end - scrollEl._scrollManager:handleTouchPress(touchX, touchY) - persistTouchScroll(scrollEl) - end -end - ---- Handle touch move events from LÖVE's touch input system ---- Routes touch to the element that owns this touch ID (from the original press), regardless of current position ---- Hook this to love.touchmoved() to enable touch drag and gesture tracking ----@param id lightuserdata Touch identifier from LÖVE ----@param x number Touch X position in screen coordinates ----@param y number Touch Y position in screen coordinates ----@param dx number X distance moved since last event ----@param dy number Y distance moved since last event ----@param pressure number Touch pressure (0-1, if supported by device) -function flexlove.touchmoved(id, x, y, dx, dy, pressure) - local touchId = tostring(id) - pressure = pressure or 1.0 - - -- Apply base scaling if configured - local touchX, touchY = x, y - if flexlove.baseScale then - touchX = x / flexlove.scaleFactors.x - touchY = y / flexlove.scaleFactors.y - end - - -- Route to owning element (touch ownership persists from press to release) - local element = flexlove._touchOwners[touchId] - if element then - -- Create and route touch event - local touchEvent = InputEvent.fromTouch(id, touchX, touchY, "moved", pressure) - element:handleTouchEvent(touchEvent) - - -- Feed to shared gesture recognizer - if flexlove._gestureRecognizer then - local gestures = flexlove._gestureRecognizer:processTouchEvent(touchEvent) - if gestures then - for _, gesture in ipairs(gestures) do - element:handleGesture(gesture) - end - end - end - end - - local track = flexlove._touchScroll[touchId] - local scrollEl = resolveTouchScrollElement(track) - if track and scrollEl then - local sm = scrollEl._scrollManager - -- Immediate mode recreates managers each frame; re-arm drag from the - -- last persisted touch point so a move after beginFrame still scrolls. - if not sm._touchScrolling then - sm:handleTouchPress(track.lastX, track.lastY) - end - sm:handleTouchMove(touchX, touchY) - persistTouchScroll(scrollEl) - track.lastX = touchX - track.lastY = touchY - end -end - ---- Handle touch release events from LÖVE's touch input system ---- Routes touch to the owning element and cleans up touch ownership tracking ---- Hook this to love.touchreleased() to properly end touch interactions ----@param id lightuserdata Touch identifier from LÖVE ----@param x number Touch X position in screen coordinates ----@param y number Touch Y position in screen coordinates ----@param dx number X distance moved since last event ----@param dy number Y distance moved since last event ----@param pressure number Touch pressure (0-1, if supported by device) -function flexlove.touchreleased(id, x, y, dx, dy, pressure) - local touchId = tostring(id) - pressure = pressure or 1.0 - - -- Apply base scaling if configured - local touchX, touchY = x, y - if flexlove.baseScale then - touchX = x / flexlove.scaleFactors.x - touchY = y / flexlove.scaleFactors.y - end - - -- Route to owning element - local element = flexlove._touchOwners[touchId] - if element then - -- Create and route touch event - local touchEvent = InputEvent.fromTouch(id, touchX, touchY, "ended", pressure) - element:handleTouchEvent(touchEvent) - - -- Feed to shared gesture recognizer - if flexlove._gestureRecognizer then - local gestures = flexlove._gestureRecognizer:processTouchEvent(touchEvent) - if gestures then - for _, gesture in ipairs(gestures) do - element:handleGesture(gesture) - end - end - end - end - - local track = flexlove._touchScroll[touchId] - local scrollEl = resolveTouchScrollElement(track) - if track and scrollEl then - local sm = scrollEl._scrollManager - if not sm._touchScrolling then - sm:handleTouchPress(track.lastX, track.lastY) - end - sm:handleTouchMove(touchX, touchY) - sm:handleTouchRelease() - persistTouchScroll(scrollEl) - end - - -- Clean up touch ownership (touch is complete) - flexlove._touchOwners[touchId] = nil - flexlove._touchScroll[touchId] = nil -end - ---- Get the number of currently active touches being tracked ----@return number count Number of active touch points -function flexlove.getActiveTouchCount() - local count = 0 - for _ in pairs(flexlove._touchOwners) do - count = count + 1 - end - return count -end - ---- Get the element that currently owns a specific touch ----@param touchId string|lightuserdata Touch identifier ----@return Element|nil element The element owning this touch, or nil -function flexlove.getTouchOwner(touchId) - return flexlove._touchOwners[tostring(touchId)] -end - ---- Retrieve an element by its ID from the UI tree ---- Works in both immediate and retained modes; searches all known elements including top-level and nested children ----@param id string The element ID to search for ----@return Element|nil element The found element, or nil if not found -function flexlove.getById(id) - if not id or id == "" then - return nil - end - - local function findElementById(element, targetId) - if element.id == targetId then - return element - end - - for _, child in ipairs(element.children) do - local result = findElementById(child, targetId) - if result then - return result - end - end - - return nil - end - - for _, win in ipairs(flexlove.topElements) do - local result = findElementById(win, id) - if result then - return result - end - end - - if flexlove._currentFrameElements then - for _, element in ipairs(flexlove._currentFrameElements) do - local result = findElementById(element, id) - if result then - return result - end - end - end - - if Context._zIndexOrderedElements then - for _, element in ipairs(Context._zIndexOrderedElements) do - local result = findElementById(element, id) - if result then - return result - end - end - end - - return nil -end - ---- Clean up all UI elements and reset FlexLove to initial state when changing scenes or shutting down ---- Use this to prevent memory leaks when transitioning between game states or menus -function flexlove.destroy() - for _, win in ipairs(flexlove.topElements) do - win:destroy() - end - flexlove.topElements = {} - flexlove.baseScale = nil - flexlove.scaleFactors = { x = 1.0, y = 1.0 } - flexlove._cachedViewport = { width = 0, height = 0 } - - -- Release canvases explicitly before destroying - if flexlove._gameCanvas then - flexlove._gameCanvas:release() - end - if flexlove._backdropCanvas then - flexlove._backdropCanvas:release() - end - - flexlove._gameCanvas = nil - flexlove._backdropCanvas = nil - flexlove._canvasDimensions = { width = 0, height = 0 } - Context.clearFocus() - StateManager:reset() - - -- Clean up touch state - flexlove._touchOwners = {} - flexlove._touchScroll = {} - flexlove._mouseButtonStates = {} - if flexlove._gestureRecognizer then - flexlove._gestureRecognizer:reset() - end -end - ---- Create a new UI element with flexbox layout, styling, and interaction capabilities ---- This is your primary API for building interfaces - buttons, panels, text, images, and containers ---- If called before FlexLove.init(), the element creation will be automatically queued and executed after initialization ----@param props ElementProps ----@param callback? function Optional callback function(element) that will be called with the created element (useful when queued) ----@return Element -- Returns element if initialized, nil if queued for later creation -function flexlove.new(props, callback) - props = props or {} - - if not flexlove.initialized then - -- Queue element creation for after initialization - table.insert(flexlove._initQueue, { - props = props, - callback = callback, - }) - - if flexlove._initState == "uninitialized" then - if flexlove._ErrorHandler then - flexlove._ErrorHandler:warn( - "FlexLove", - "[FlexLove] Element creation queued - FlexLove.init() has not been called yet. Element will be created automatically after init() is called." - ) - end - end - return nil - end - - -- Use global mode to determine behavior - if not flexlove._immediateMode then - return Element.new(props) - end - - -- Immediate mode - proceed with immediate-mode logic - -- Auto-begin frame if not manually started (convenience feature) - if not flexlove._frameStarted then - flexlove.beginFrame() - flexlove._autoBeganFrame = true - end - - -- Immediate mode: generate ID if not provided - if not props.id then - props.id = StateManager.generateID(props, props.parent) - end - - -- Get or create state for this element - local state = StateManager.getState(props.id, {}) - - -- Mark state as used this frame - StateManager.markStateUsed(props.id) - - -- Inject scroll state into props BEFORE creating element - -- This ensures scroll position is set before layoutChildren/detectOverflow is called - -- ScrollManager state uses _scrollX/_scrollY with underscore prefix - if state.scrollManager then - props._scrollX = state.scrollManager._scrollX or 0 - props._scrollY = state.scrollManager._scrollY or 0 - else - -- Fallback to old state structure for backward compatibility - props._scrollX = state._scrollX or 0 - props._scrollY = state._scrollY or 0 - end - - local element = Element.new(props) - - -- Restore all state from StateManager (delegates to sub-modules) - element:restoreState(state) - - -- Bind element to StateManager for interactive states - element._stateId = props.id - - -- Set initial theme state based on StateManager state - -- This will be updated in Element:update() but we need an initial value - if element.themeComponent then - local eventState = state.eventHandler or {} - if element.disabled or eventState.disabled then - element._themeState = "disabled" - elseif element.active or eventState.active then - element._themeState = "active" - elseif eventState._pressed and next(eventState._pressed) then - element._themeState = "pressed" - elseif eventState._hovered then - element._themeState = "hover" - else - element._themeState = "normal" - end - end - - table.insert(flexlove._currentFrameElements, element) - - return element -end - ---- Check how many UI element states are being tracked in immediate mode to detect memory leaks ---- Use this during development to ensure states are properly cleaned up ----@return number -function flexlove.getStateCount() - if not flexlove._immediateMode then - return 0 - end - return StateManager.getStateCount() -end - ---- Remove stored state for a specific element when you know it won't be rendered again ---- Use this to immediately free memory for elements you've removed from your UI ----@param id string -function flexlove.clearState(id) - if not flexlove._immediateMode then - return - end - StateManager.clearState(id) -end - ---- Wipe all element state when transitioning between completely different UI screens ---- Use this for scene transitions to start with a clean slate and prevent state pollution -function flexlove.clearAllStates() - if not flexlove._immediateMode then - return - end - StateManager.clearAllStates() -end - ---- Inspect state management metrics to diagnose performance issues and optimize immediate mode usage ---- Use this to understand state lifecycle and identify unexpected state accumulation ----@return { stateCount: number, frameNumber: number, oldestState: number|nil, newestState: number|nil } -function flexlove.getStateStats() - if not flexlove._immediateMode then - return { stateCount = 0, frameNumber = 0 } - end - return StateManager.getStats() -end - ---- Create a calc() expression for dynamic CSS-like calculations ---- Use this to create responsive layouts that adapt to viewport and parent dimensions ---- @usage ---- local button = FlexLove.new({ ---- x = FlexLove.calc("50% - 10vw"), ---- y = FlexLove.calc("50% - 5vh"), ---- width = "20vw", ---- height = "10vh", ---- }) ----@param expr string The calc expression (e.g., "50% - 10vw", "100px + 20%") ----@return CalcObject calcObject A calc expression object that will be evaluated during layout -function flexlove.calc(expr) - return Calc.new(expr) -end - ---- Get the currently focused element ---- Returns the element that is currently receiving keyboard input (e.g., text input, text area) ----@return Element|nil The focused element, or nil if no element has focus -function flexlove.getFocusedElement() - return Context.getFocused() -end - ---- Set focus to a specific element ---- Automatically blurs the previously focused element if different ---- Use this to programmatically focus text inputs or other interactive elements ----@param element Element|nil The element to focus (nil to clear focus) -function flexlove.setFocusedElement(element) - Context.setFocused(element) -end - ---- Clear focus from any element ---- Removes keyboard focus from the currently focused element -function flexlove.clearFocus() - Context.setFocused(nil) -end - ---- Enable or disable the debug draw overlay that renders element boundaries with random colors ---- Each element gets a unique color: full opacity border and 0.5 opacity fill to identify collisions and overlaps ----@param enabled boolean True to enable debug draw overlay, false to disable -function flexlove.setDebugDraw(enabled) - flexlove._debugDraw = enabled -end - ---- Check if the debug draw overlay is currently active ----@return boolean enabled True if debug draw overlay is enabled -function flexlove.getDebugDraw() - return flexlove._debugDraw -end - -flexlove.Animation = Animation -flexlove.Color = Color -flexlove.Theme = Theme -flexlove.enums = enums - -return flexlove diff --git a/libs/flexlove/modules/Animation.lua b/libs/flexlove/modules/Animation.lua deleted file mode 100644 index 66a3d5d1..00000000 --- a/libs/flexlove/modules/Animation.lua +++ /dev/null @@ -1,1579 +0,0 @@ -local Easing = {} - ----@type EasingFunction -function Easing.linear(t) - return t -end - ----@type EasingFunction -function Easing.easeInQuad(t) - return t * t -end - ----@type EasingFunction -function Easing.easeOutQuad(t) - return t * (2 - t) -end - ----@type EasingFunction -function Easing.easeInOutQuad(t) - return t < 0.5 and 2 * t * t or -1 + (4 - 2 * t) * t -end - ----@type EasingFunction -function Easing.easeInCubic(t) - return t * t * t -end - ----@type EasingFunction -function Easing.easeOutCubic(t) - local t1 = t - 1 - return t1 * t1 * t1 + 1 -end - ----@type EasingFunction -function Easing.easeInOutCubic(t) - return t < 0.5 and 4 * t * t * t or (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 -end - ----@type EasingFunction -function Easing.easeInQuart(t) - return t * t * t * t -end - ----@type EasingFunction -function Easing.easeOutQuart(t) - local t1 = t - 1 - return 1 - t1 * t1 * t1 * t1 -end - ----@type EasingFunction -function Easing.easeInOutQuart(t) - if t < 0.5 then - return 8 * t * t * t * t - else - local t1 = t - 1 - return 1 - 8 * t1 * t1 * t1 * t1 - end -end - ----@type EasingFunction -function Easing.easeInQuint(t) - return t * t * t * t * t -end - ----@type EasingFunction -function Easing.easeOutQuint(t) - local t1 = t - 1 - return 1 + t1 * t1 * t1 * t1 * t1 -end - ----@type EasingFunction -function Easing.easeInOutQuint(t) - if t < 0.5 then - return 16 * t * t * t * t * t - else - local t1 = t - 1 - return 1 + 16 * t1 * t1 * t1 * t1 * t1 - end -end - ----@type EasingFunction -function Easing.easeInExpo(t) - return t == 0 and 0 or math.pow(2, 10 * (t - 1)) -end - ----@type EasingFunction -function Easing.easeOutExpo(t) - return t == 1 and 1 or 1 - math.pow(2, -10 * t) -end - ----@type EasingFunction -function Easing.easeInOutExpo(t) - if t == 0 then - return 0 - end - if t == 1 then - return 1 - end - - if t < 0.5 then - return 0.5 * math.pow(2, 20 * t - 10) - else - return 1 - 0.5 * math.pow(2, -20 * t + 10) - end -end - ----@type EasingFunction -function Easing.easeInSine(t) - return 1 - math.cos(t * math.pi / 2) -end - ----@type EasingFunction -function Easing.easeOutSine(t) - return math.sin(t * math.pi / 2) -end - ----@type EasingFunction -function Easing.easeInOutSine(t) - return -(math.cos(math.pi * t) - 1) / 2 -end - ----@type EasingFunction -function Easing.easeInCirc(t) - return 1 - math.sqrt(1 - t * t) -end - ----@type EasingFunction -function Easing.easeOutCirc(t) - local t1 = t - 1 - return math.sqrt(1 - t1 * t1) -end - ----@type EasingFunction -function Easing.easeInOutCirc(t) - if t < 0.5 then - return (1 - math.sqrt(1 - 4 * t * t)) / 2 - else - local t1 = -2 * t + 2 - return (math.sqrt(1 - t1 * t1) + 1) / 2 - end -end - ----@type EasingFunction -function Easing.easeInBack(t) - local c1 = 1.70158 - local c3 = c1 + 1 - return c3 * t * t * t - c1 * t * t -end - ----@type EasingFunction -function Easing.easeOutBack(t) - local c1 = 1.70158 - local c3 = c1 + 1 - local t1 = t - 1 - return 1 + c3 * t1 * t1 * t1 + c1 * t1 * t1 -end - ----@type EasingFunction -function Easing.easeInOutBack(t) - local c1 = 1.70158 - local c2 = c1 * 1.525 - - if t < 0.5 then - return (2 * t * 2 * t * ((c2 + 1) * 2 * t - c2)) / 2 - else - local t1 = 2 * t - 2 - return (t1 * t1 * ((c2 + 1) * t1 + c2) + 2) / 2 - end -end - ----@type EasingFunction -function Easing.easeInElastic(t) - if t == 0 then - return 0 - end - if t == 1 then - return 1 - end - - local c4 = (2 * math.pi) / 3 - return -math.pow(2, 10 * t - 10) * math.sin((t * 10 - 10.75) * c4) -end - ----@type EasingFunction -function Easing.easeOutElastic(t) - if t == 0 then - return 0 - end - if t == 1 then - return 1 - end - - local c4 = (2 * math.pi) / 3 - return math.pow(2, -10 * t) * math.sin((t * 10 - 0.75) * c4) + 1 -end - ----@type EasingFunction -function Easing.easeInOutElastic(t) - if t == 0 then - return 0 - end - if t == 1 then - return 1 - end - - local c5 = (2 * math.pi) / 4.5 - - if t < 0.5 then - return -(math.pow(2, 20 * t - 10) * math.sin((20 * t - 11.125) * c5)) / 2 - else - return (math.pow(2, -20 * t + 10) * math.sin((20 * t - 11.125) * c5)) / 2 + 1 - end -end - ----@type EasingFunction -function Easing.easeOutBounce(t) - local n1 = 7.5625 - local d1 = 2.75 - - if t < 1 / d1 then - return n1 * t * t - elseif t < 2 / d1 then - local t1 = t - 1.5 / d1 - return n1 * t1 * t1 + 0.75 - elseif t < 2.5 / d1 then - local t1 = t - 2.25 / d1 - return n1 * t1 * t1 + 0.9375 - else - local t1 = t - 2.625 / d1 - return n1 * t1 * t1 + 0.984375 - end -end - ----@type EasingFunction -function Easing.easeInBounce(t) - return 1 - Easing.easeOutBounce(1 - t) -end - ----@type EasingFunction -function Easing.easeInOutBounce(t) - if t < 0.5 then - return (1 - Easing.easeOutBounce(1 - 2 * t)) / 2 - else - return (1 + Easing.easeOutBounce(2 * t - 1)) / 2 - end -end - ---- Create a custom back easing function with configurable overshoot ----@param overshoot number? Overshoot amount (default: 1.70158) ----@return EasingFunction -function Easing.back(overshoot) - overshoot = overshoot or 1.70158 - local c3 = overshoot + 1 - - return function(t) - return c3 * t * t * t - overshoot * t * t - end -end - ---- Create a custom elastic easing function ----@param amplitude number? Amplitude (default: 1) ----@param period number? Period (default: 0.3) ----@return EasingFunction -function Easing.elastic(amplitude, period) - amplitude = amplitude or 1 - period = period or 0.3 - - return function(t) - if t == 0 then - return 0 - end - if t == 1 then - return 1 - end - - local s = period / 4 - local a = amplitude - - if a < 1 then - a = 1 - s = period / 4 - else - s = period / (2 * math.pi) * math.asin(1 / a) - end - - return a * math.pow(2, -10 * t) * math.sin((t - s) * (2 * math.pi) / period) + 1 - end -end - --- ============================================================================ --- TRANSFORM --- ============================================================================ - -local Transform = {} -Transform.__index = Transform - ---- Create a new transform instance ----@param props Transform? ----@return Transform transform -function Transform.new(props) - props = props or {} - - local self = setmetatable({}, Transform) - - self.rotate = props.rotate or 0 - self.scaleX = props.scaleX or 1 - self.scaleY = props.scaleY or 1 - self.translateX = props.translateX or 0 - self.translateY = props.translateY or 0 - self.skewX = props.skewX or 0 - self.skewY = props.skewY or 0 - self.originX = props.originX or 0.5 - self.originY = props.originY or 0.5 - - return self -end - ---- Apply transform to LÖVE graphics context ----@param transform Transform Transform instance ----@param x number Element x position ----@param y number Element y position ----@param width number Element width ----@param height number Element height -function Transform.apply(transform, x, y, width, height) - if not transform then - return - end - - local ox = x + width * transform.originX - local oy = y + height * transform.originY - - love.graphics.push() - love.graphics.translate(ox, oy) - - if transform.rotate ~= 0 then - love.graphics.rotate(transform.rotate) - end - - if transform.scaleX ~= 1 or transform.scaleY ~= 1 then - love.graphics.scale(transform.scaleX, transform.scaleY) - end - - if transform.skewX ~= 0 or transform.skewY ~= 0 then - love.graphics.shear(transform.skewX, transform.skewY) - end - - love.graphics.translate(-ox, -oy) - love.graphics.translate(transform.translateX, transform.translateY) -end - ---- Remove transform from LÖVE graphics context -function Transform.unapply() - love.graphics.pop() -end - ---- Interpolate between two transforms ----@param from Transform Starting transform ----@param to Transform Ending transform ----@param t number Interpolation factor (0-1) ----@return Transform interpolated -function Transform.lerp(from, to, t) - if type(from) ~= "table" then - from = Transform.new() - end - if type(to) ~= "table" then - to = Transform.new() - end - if type(t) ~= "number" or t ~= t then - t = 0 - elseif t == math.huge then - t = 1 - elseif t == -math.huge then - t = 0 - else - t = math.max(0, math.min(1, t)) - end - - return Transform.new({ - rotate = (from.rotate or 0) * (1 - t) + (to.rotate or 0) * t, - scaleX = (from.scaleX or 1) * (1 - t) + (to.scaleX or 1) * t, - scaleY = (from.scaleY or 1) * (1 - t) + (to.scaleY or 1) * t, - translateX = (from.translateX or 0) * (1 - t) + (to.translateX or 0) * t, - translateY = (from.translateY or 0) * (1 - t) + (to.translateY or 0) * t, - skewX = (from.skewX or 0) * (1 - t) + (to.skewX or 0) * t, - skewY = (from.skewY or 0) * (1 - t) + (to.skewY or 0) * t, - originX = (from.originX or 0.5) * (1 - t) + (to.originX or 0.5) * t, - originY = (from.originY or 0.5) * (1 - t) + (to.originY or 0.5) * t, - }) -end - ---- Check if transform is identity (no transformation) ----@param transform Transform ----@return boolean isIdentity -function Transform.isIdentity(transform) - if not transform then - return true - end - - return transform.rotate == 0 - and transform.scaleX == 1 - and transform.scaleY == 1 - and transform.translateX == 0 - and transform.translateY == 0 - and transform.skewX == 0 - and transform.skewY == 0 -end - ---- Clone a transform ----@param transform Transform ----@return Transform clone -function Transform.clone(transform) - if not transform then - return Transform.new() - end - - return Transform.new({ - rotate = transform.rotate, - scaleX = transform.scaleX, - scaleY = transform.scaleY, - translateX = transform.translateX, - translateY = transform.translateY, - skewX = transform.skewX, - skewY = transform.skewY, - originX = transform.originX, - originY = transform.originY, - }) -end - --- ============================================================================ --- INTERPOLATION HELPERS --- ============================================================================ - ---- Helper function to interpolate numeric values ----@param startValue number Starting value ----@param finalValue number Final value ----@param easedT number Eased time (0-1) ----@return number interpolated Interpolated value -local function lerpNumber(startValue, finalValue, easedT) - return startValue * (1 - easedT) + finalValue * easedT -end - ---- Helper function to interpolate Color values ----@param startColor any Starting color (Color instance or parseable color) ----@param finalColor any Final color (Color instance or parseable color) ----@param easedT number Eased time (0-1) ----@param ColorModule table Color module reference ----@return any interpolated Interpolated Color instance -local function lerpColor(startColor, finalColor, easedT, ColorModule) - if not ColorModule or not ColorModule.parse or not ColorModule.lerp then - return startColor - end - - local colorA = ColorModule.parse(startColor) - local colorB = ColorModule.parse(finalColor) - - return ColorModule.lerp(colorA, colorB, easedT) -end - ---- Helper function to interpolate table values (padding, margin, cornerRadius) ----@param startTable table Starting table ----@param finalTable table Final table ----@param easedT number Eased time (0-1) ----@return table interpolated Interpolated table -local function lerpTable(startTable, finalTable, easedT) - local result = {} - - local keys = {} - for k in pairs(startTable) do - keys[k] = true - end - for k in pairs(finalTable) do - keys[k] = true - end - - for key in pairs(keys) do - local startVal = startTable[key] - local finalVal = finalTable[key] - - if type(startVal) == "number" and type(finalVal) == "number" then - result[key] = lerpNumber(startVal, finalVal, easedT) - elseif startVal ~= nil then - result[key] = startVal - else - result[key] = finalVal - end - end - - return result -end - ----@class Animation -local Animation = { - _Transform = Transform, -} -Animation.__index = Animation - ---- Build smooth, timed transitions between visual states ----@param props AnimationProps Animation properties ----@return Animation animation The new animation instance -function Animation.new(props) - if type(props) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_001") - end - props = { duration = 1, start = {}, final = {} } - end - - if type(props.duration) ~= "number" or props.duration <= 0 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_002") - end - props.duration = 1 - end - - if type(props.start) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_001") - end - props.start = {} - end - - if type(props.final) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_001") - end - props.final = {} - end - - local self = setmetatable({}, Animation) - self.duration = props.duration - self.start = props.start - self.final = props.final - self.keyframes = props.keyframes - self.transform = props.transform - self.transition = props.transition - self.elapsed = 0 - - self.onStart = props.onStart - self.onUpdate = props.onUpdate - self.onComplete = props.onComplete - self.onCancel = props.onCancel - self._hasStarted = false - - self._paused = false - self._reversed = false - self._speed = 1.0 - self._state = "pending" - - local easingName = props.easing or "linear" - if type(easingName) == "string" then - self.easing = Easing[easingName] or Easing.linear - elseif type(easingName) == "function" then - self.easing = easingName - else - self.easing = Easing.linear - end - - self._cachedResult = {} - self._resultDirty = true - - return self -end - ---- Advance the animation timeline ----@param dt number Delta time in seconds ----@param element table? Optional element reference for callbacks ----@return boolean completed True if animation is complete -function Animation:update(dt, element) - if type(dt) ~= "number" or dt < 0 or dt ~= dt or dt == math.huge then - dt = 0 - end - - if self._paused then - return false - end - - if self._delay and self._delayElapsed then - if self._delayElapsed < self._delay then - self._delayElapsed = self._delayElapsed + dt - return false - end - end - - if not self._hasStarted then - self._hasStarted = true - self._state = "playing" - if self.onStart and type(self.onStart) == "function" then - local success, err = pcall(self.onStart, self, element) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onStart", - error = tostring(err), - }) - end - end - end - - dt = dt * self._speed - - if self._reversed then - self.elapsed = self.elapsed - dt - if self.elapsed <= 0 then - self.elapsed = 0 - self._state = "completed" - self._resultDirty = true - if self.onComplete and type(self.onComplete) == "function" then - local success, err = pcall(self.onComplete, self, element) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onComplete", - error = tostring(err), - }) - end - end - return true - end - else - self.elapsed = self.elapsed + dt - if self.elapsed >= self.duration then - self.elapsed = self.duration - self._resultDirty = true - - if self._repeatCount then - self._repeatCurrent = (self._repeatCurrent or 0) + 1 - - if self._repeatCount == 0 or self._repeatCurrent < self._repeatCount then - if self._yoyo then - self._reversed = not self._reversed - if self._reversed then - self.elapsed = self.duration - else - self.elapsed = 0 - end - else - self.elapsed = 0 - end - return false - end - end - - self._state = "completed" - if self.onComplete and type(self.onComplete) == "function" then - local success, err = pcall(self.onComplete, self, element) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onComplete", - error = tostring(err), - }) - end - end - return true - end - end - - self._resultDirty = true - - if self.onUpdate and type(self.onUpdate) == "function" then - local progress = self.elapsed / self.duration - local success, err = pcall(self.onUpdate, self, element, progress) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onUpdate", - error = tostring(err), - }) - end - end - - return false -end - ---- Find the two keyframes surrounding the current progress ----@param progress number Current animation progress (0-1) ----@return Keyframe? prevFrame The keyframe before current progress ----@return Keyframe? nextFrame The keyframe after current progress -function Animation:findKeyframes(progress) - if not self.keyframes or #self.keyframes < 2 then - return nil, nil - end - - local prevFrame = self.keyframes[1] - local nextFrame = self.keyframes[#self.keyframes] - - for i = 1, #self.keyframes - 1 do - if progress >= self.keyframes[i].at and progress <= self.keyframes[i + 1].at then - prevFrame = self.keyframes[i] - nextFrame = self.keyframes[i + 1] - break - end - end - - return prevFrame, nextFrame -end - ---- Interpolate between two keyframes ----@param prevFrame Keyframe Starting keyframe ----@param nextFrame Keyframe Ending keyframe ----@param easedT number Eased time (0-1) for interpolation ----@return table result Interpolated values -function Animation:lerpKeyframes(prevFrame, nextFrame, easedT) - local result = {} - - local keys = {} - for k in pairs(prevFrame.values) do - keys[k] = true - end - for k in pairs(nextFrame.values) do - keys[k] = true - end - - local numericSet = { - width = true, - height = true, - opacity = true, - x = true, - y = true, - gap = true, - imageOpacity = true, - scrollbarWidth = true, - borderWidth = true, - fontSize = true, - lineHeight = true, - } - - local colorSet = { - backgroundColor = true, - borderColor = true, - textColor = true, - scrollbarColor = true, - scrollbarBackgroundColor = true, - imageTint = true, - } - - local tableSet = { - padding = true, - margin = true, - cornerRadius = true, - } - - for key in pairs(keys) do - local startVal = prevFrame.values[key] - local finalVal = nextFrame.values[key] - - if numericSet[key] and type(startVal) == "number" and type(finalVal) == "number" then - result[key] = lerpNumber(startVal, finalVal, easedT) - elseif colorSet[key] and Animation._ColorModule then - if startVal ~= nil and finalVal ~= nil then - result[key] = lerpColor(startVal, finalVal, easedT, Animation._ColorModule) - end - elseif tableSet[key] and type(startVal) == "table" and type(finalVal) == "table" then - result[key] = lerpTable(startVal, finalVal, easedT) - elseif type(startVal) == type(finalVal) then - if type(startVal) == "number" then - result[key] = lerpNumber(startVal, finalVal, easedT) - else - result[key] = finalVal - end - end - end - - return result -end - ---- Calculate the current animated values ----@return table result Interpolated values -function Animation:interpolate() - if not self._resultDirty then - return self._cachedResult - end - - local t = math.min(self.elapsed / self.duration, 1) - - if self.keyframes and type(self.keyframes) == "table" and #self.keyframes >= 2 then - local prevFrame, nextFrame = self:findKeyframes(t) - - if prevFrame and nextFrame then - local localProgress = 0 - if nextFrame.at > prevFrame.at then - localProgress = (t - prevFrame.at) / (nextFrame.at - prevFrame.at) - end - - local easingFn = Easing.linear - if prevFrame.easing then - if type(prevFrame.easing) == "string" then - easingFn = Easing[prevFrame.easing] or Easing.linear - elseif type(prevFrame.easing) == "function" then - easingFn = prevFrame.easing - end - end - - local success, easedT = pcall(easingFn, localProgress) - if not success or type(easedT) ~= "number" or easedT ~= easedT or easedT == math.huge or easedT == -math.huge then - easedT = localProgress - end - - local keyframeResult = self:lerpKeyframes(prevFrame, nextFrame, easedT) - - local result = self._cachedResult - for k in pairs(result) do - result[k] = nil - end - for k, v in pairs(keyframeResult) do - result[k] = v - end - - self._resultDirty = false - return result - end - end - - local success, easedT = pcall(self.easing, t) - if not success or type(easedT) ~= "number" or easedT ~= easedT or easedT == math.huge or easedT == -math.huge then - easedT = t - end - - local result = self._cachedResult - - for k in pairs(result) do - result[k] = nil - end - - local numericProperties = { - "width", - "height", - "opacity", - "x", - "y", - "gap", - "imageOpacity", - "scrollbarWidth", - "borderWidth", - "fontSize", - "lineHeight", - } - - local colorProperties = { - "backgroundColor", - "borderColor", - "textColor", - "scrollbarColor", - "scrollbarBackgroundColor", - "imageTint", - } - - local tableProperties = { - "padding", - "margin", - "cornerRadius", - } - - for _, prop in ipairs(numericProperties) do - local startVal = self.start[prop] - local finalVal = self.final[prop] - - if type(startVal) == "number" and type(finalVal) == "number" then - result[prop] = lerpNumber(startVal, finalVal, easedT) - end - end - - if Animation._ColorModule then - for _, prop in ipairs(colorProperties) do - local startVal = self.start[prop] - local finalVal = self.final[prop] - - if startVal ~= nil and finalVal ~= nil then - result[prop] = lerpColor(startVal, finalVal, easedT, Animation._ColorModule) - end - end - end - - for _, prop in ipairs(tableProperties) do - local startVal = self.start[prop] - local finalVal = self.final[prop] - - if type(startVal) == "table" and type(finalVal) == "table" then - result[prop] = lerpTable(startVal, finalVal, easedT) - end - end - - if Animation._Transform and self.start.transform and self.final.transform then - result.transform = Animation._Transform.lerp(self.start.transform, self.final.transform, easedT) - end - - if self.transform and type(self.transform) == "table" then - for key, value in pairs(self.transform) do - result[key] = value - end - end - - self._resultDirty = false - return result -end - ---- Attach animation to an element ----@param element table The element to apply animation to -function Animation:apply(element) - if not element or type(element) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_003") - end - return - end - element.animation = self -end - ---- Apply interpolated values to an element during update. ---- Called each frame while the animation is active (not yet finished). ----@param element table Element to apply interpolated properties to -function Animation:applyInterpolation(element) - local anim = self:interpolate() - - -- Numeric properties - element.width = anim.width or element.width - element.height = anim.height or element.height - element.opacity = anim.opacity or element.opacity - element.x = anim.x or element.x - element.y = anim.y or element.y - element.gap = anim.gap or element.gap - element.imageOpacity = anim.imageOpacity or element.imageOpacity - element.scrollbarWidth = anim.scrollbarWidth or element.scrollbarWidth - element.borderWidth = anim.borderWidth or element.borderWidth - element.fontSize = anim.fontSize or element.fontSize - element.lineHeight = anim.lineHeight or element.lineHeight - - -- Color properties - if anim.backgroundColor then - element.backgroundColor = anim.backgroundColor - end - if anim.borderColor then - element.borderColor = anim.borderColor - end - if anim.textColor then - element.textColor = anim.textColor - end - if anim.scrollbarColor then - element.scrollbarColor = anim.scrollbarColor - end - if anim.scrollbarBackgroundColor then - element.scrollbarBackgroundColor = anim.scrollbarBackgroundColor - end - if anim.imageTint then - element.imageTint = anim.imageTint - end - - -- Table properties - if anim.padding then - element.padding = anim.padding - end - if anim.margin then - element.margin = anim.margin - end - if anim.cornerRadius then - element.cornerRadius = anim.cornerRadius - end - if anim.transform then - element.transform = anim.transform - end - - -- Backward compatibility: opacity-only animation updates background alpha - if anim.opacity and not anim.backgroundColor then - element.backgroundColor.a = anim.opacity - end -end - ---- Pause animation -function Animation:pause() - if self._state == "playing" or self._state == "pending" then - self._paused = true - self._state = "paused" - end -end - ---- Resume animation -function Animation:resume() - if self._state == "paused" then - self._paused = false - self._state = "playing" - end -end - ---- Check if paused ----@return boolean paused -function Animation:isPaused() - return self._paused -end - ---- Reverse animation direction -function Animation:reverse() - self._reversed = not self._reversed -end - ---- Check if reversed ----@return boolean reversed -function Animation:isReversed() - return self._reversed -end - ---- Set playback speed ----@param speed number Speed multiplier -function Animation:setSpeed(speed) - if type(speed) == "number" and speed > 0 then - self._speed = speed - end -end - ---- Get playback speed ----@return number speed -function Animation:getSpeed() - return self._speed -end - ---- Seek to specific time ----@param time number Time in seconds -function Animation:seek(time) - if type(time) == "number" then - self.elapsed = math.max(0, math.min(time, self.duration)) - self._resultDirty = true - end -end - ---- Get animation state ----@return string state -function Animation:getState() - return self._state -end - ---- Cancel animation ----@param element table? Optional element reference -function Animation:cancel(element) - if self._state ~= "cancelled" and self._state ~= "completed" then - self._state = "cancelled" - if self.onCancel and type(self.onCancel) == "function" then - local success, err = pcall(self.onCancel, self, element) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onCancel", - error = tostring(err), - }) - end - end - end -end - ---- Reset animation -function Animation:reset() - self.elapsed = 0 - self._hasStarted = false - self._paused = false - self._state = "pending" - self._resultDirty = true -end - ---- Get animation progress ----@return number progress -function Animation:getProgress() - return math.min(self.elapsed / self.duration, 1) -end - ---- Chain animations ----@param nextAnimation Animation|function ----@return Animation nextAnimation -function Animation:chain(nextAnimation) - if type(nextAnimation) == "function" then - self._nextFactory = nextAnimation - return self - elseif type(nextAnimation) == "table" then - self._next = nextAnimation - return nextAnimation - else - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_004") - end - return self - end -end - ---- Add delay before animation starts ----@param seconds number Delay duration ----@return Animation self -function Animation:delay(seconds) - if type(seconds) ~= "number" or seconds < 0 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_005") - end - seconds = 0 - end - self._delay = seconds - self._delayElapsed = 0 - return self -end - ---- Set repeat count ----@param count number Repeat count (0 = infinite) ----@return Animation self -function Animation:repeatCount(count) - if type(count) ~= "number" or count < 0 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_006") - end - count = 0 - end - self._repeatCount = count - self._repeatCurrent = 0 - return self -end - ---- Enable yoyo mode ----@param enabled boolean? Enable yoyo (default: true) ----@return Animation self -function Animation:yoyo(enabled) - if enabled == nil then - enabled = true - end - self._yoyo = enabled - return self -end - ---- Create fade animation ----@param duration number Duration in seconds ----@param fromOpacity number Starting opacity ----@param toOpacity number Ending opacity ----@param easing string? Easing function name ----@return Animation animation -function Animation.fade(duration, fromOpacity, toOpacity, easing) - if type(duration) ~= "number" or duration <= 0 then - duration = 1 - end - if type(fromOpacity) ~= "number" then - fromOpacity = 1 - end - if type(toOpacity) ~= "number" then - toOpacity = 0 - end - - return Animation.new({ - duration = duration, - start = { opacity = fromOpacity }, - final = { opacity = toOpacity }, - easing = easing, - }) -end - ---- Create scale animation ----@param duration number Duration in seconds ----@param fromScale {width:number,height:number} Starting scale ----@param toScale {width:number,height:number} Ending scale ----@param easing string? Easing function name ----@return Animation animation -function Animation.scale(duration, fromScale, toScale, easing) - if type(duration) ~= "number" or duration <= 0 then - duration = 1 - end - if type(fromScale) ~= "table" then - fromScale = { width = 1, height = 1 } - end - if type(toScale) ~= "table" then - toScale = { width = 1, height = 1 } - end - - return Animation.new({ - duration = duration, - start = { width = fromScale.width or 0, height = fromScale.height or 0 }, - final = { width = toScale.width or 0, height = toScale.height or 0 }, - easing = easing, - }) -end - ---- Create keyframe animation ----@param props {duration:number, keyframes:Keyframe[], onStart:function?, onUpdate:function?, onComplete:function?, onCancel:function?} ----@return Animation animation -function Animation.keyframes(props) - if type(props) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_007") - end - props = { duration = 1, keyframes = {} } - end - - if type(props.duration) ~= "number" or props.duration <= 0 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_002") - end - props.duration = 1 - end - - if type(props.keyframes) ~= "table" or #props.keyframes < 2 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_008") - end - props.keyframes = { - { at = 0, values = {} }, - { at = 1, values = {} }, - } - end - - local sortedKeyframes = {} - for i, kf in ipairs(props.keyframes) do - if type(kf) == "table" and type(kf.at) == "number" and type(kf.values) == "table" then - table.insert(sortedKeyframes, kf) - end - end - - table.sort(sortedKeyframes, function(a, b) - return a.at < b.at - end) - - if #sortedKeyframes > 0 then - if sortedKeyframes[1].at > 0 then - table.insert(sortedKeyframes, 1, { at = 0, values = sortedKeyframes[1].values }) - end - if sortedKeyframes[#sortedKeyframes].at < 1 then - table.insert(sortedKeyframes, { at = 1, values = sortedKeyframes[#sortedKeyframes].values }) - end - end - - return Animation.new({ - duration = props.duration, - start = {}, - final = {}, - keyframes = sortedKeyframes, - onStart = props.onStart, - onUpdate = props.onUpdate, - onComplete = props.onComplete, - onCancel = props.onCancel, - }) -end - ---- Link an array of animations into a chain (static helper) ---- Each animation's completion triggers the next in sequence ----@param animations Animation[] Array of animations to chain ----@return Animation first The first animation in the chain -function Animation.chainSequence(animations) - if type(animations) ~= "table" or #animations == 0 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("Animation", "ANIM_004") - end - return Animation.new({ duration = 0, start = {}, final = {} }) - end - - for i = 1, #animations - 1 do - animations[i]:chain(animations[i + 1]) - end - - return animations[1] -end - --- ============================================================================ --- ANIMATION GROUP (Utility) --- ============================================================================ - -local AnimationGroup = {} -AnimationGroup.__index = AnimationGroup - ---- Coordinate multiple animations ----@param props AnimationGroupProps ----@return AnimationGroup group -function AnimationGroup.new(props) - if type(props) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("AnimationGroup", "ANIM_009") - end - props = { animations = {} } - end - - if type(props.animations) ~= "table" or #props.animations == 0 then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("AnimationGroup", "ANIM_010") - end - props.animations = {} - end - - local self = setmetatable({}, AnimationGroup) - - self.animations = props.animations - self.mode = props.mode or "parallel" - self.stagger = props.stagger or 0.1 - self.onComplete = props.onComplete - self.onStart = props.onStart - - if self.mode ~= "parallel" and self.mode ~= "sequence" and self.mode ~= "stagger" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("AnimationGroup", "ANIM_011", { - mode = tostring(self.mode), - }) - end - self.mode = "parallel" - end - - self._currentIndex = 1 - self._staggerElapsed = 0 - self._startedAnimations = {} - self._hasStarted = false - self._paused = false - self._state = "ready" - - return self -end - ---- Update all animations in parallel ----@param dt number Delta time ----@param element table? Optional element reference ----@return boolean finished -function AnimationGroup:_updateParallel(dt, element) - local allFinished = true - - for i, anim in ipairs(self.animations) do - local isCompleted = false - if type(anim.getState) == "function" then - isCompleted = anim:getState() == "completed" - elseif anim._state then - isCompleted = anim._state == "completed" - end - - if not isCompleted then - local finished = anim:update(dt, element) - if not finished then - allFinished = false - end - end - end - - return allFinished -end - ---- Update animations in sequence ----@param dt number Delta time ----@param element table? Optional element reference ----@return boolean finished -function AnimationGroup:_updateSequence(dt, element) - if self._currentIndex > #self.animations then - return true - end - - local currentAnim = self.animations[self._currentIndex] - local finished = currentAnim:update(dt, element) - - if finished then - self._currentIndex = self._currentIndex + 1 - if self._currentIndex > #self.animations then - return true - end - end - - return false -end - ---- Update animations with stagger ----@param dt number Delta time ----@param element table? Optional element reference ----@return boolean finished -function AnimationGroup:_updateStagger(dt, element) - self._staggerElapsed = self._staggerElapsed + dt - - for i, anim in ipairs(self.animations) do - local startTime = (i - 1) * self.stagger - - if self._staggerElapsed >= startTime and not self._startedAnimations[i] then - self._startedAnimations[i] = true - end - end - - local allFinished = true - for i, anim in ipairs(self.animations) do - if self._startedAnimations[i] then - local isCompleted = false - if type(anim.getState) == "function" then - isCompleted = anim:getState() == "completed" - elseif anim._state then - isCompleted = anim._state == "completed" - end - - if not isCompleted then - local finished = anim:update(dt, element) - if not finished then - allFinished = false - end - end - else - allFinished = false - end - end - - return allFinished -end - ---- Advance all animations in the group ----@param dt number Delta time ----@param element table? Optional element reference ----@return boolean finished -function AnimationGroup:update(dt, element) - if type(dt) ~= "number" or dt < 0 or dt ~= dt or dt == math.huge then - dt = 0 - end - - if self._paused or self._state == "completed" or self._state == "cancelled" then - return self._state == "completed" - end - - if not self._hasStarted then - self._hasStarted = true - self._state = "playing" - if self.onStart and type(self.onStart) == "function" then - local success, err = pcall(self.onStart, self) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onStart", - error = tostring(err), - }) - end - end - end - - local finished = false - - if self.mode == "parallel" then - finished = self:_updateParallel(dt, element) - elseif self.mode == "sequence" then - finished = self:_updateSequence(dt, element) - elseif self.mode == "stagger" then - finished = self:_updateStagger(dt, element) - end - - if finished then - self._state = "completed" - if self.onComplete and type(self.onComplete) == "function" then - local success, err = pcall(self.onComplete, self) - if not success then - Animation._ErrorHandler:warn("Animation", "EVT_002", { - callback = "onComplete", - error = tostring(err), - }) - end - end - end - - return finished -end - ---- Pause all animations -function AnimationGroup:pause() - self._paused = true - for _, anim in ipairs(self.animations) do - if type(anim.pause) == "function" then - anim:pause() - end - end -end - ---- Resume all animations -function AnimationGroup:resume() - self._paused = false - for _, anim in ipairs(self.animations) do - if type(anim.resume) == "function" then - anim:resume() - end - end -end - ---- Check if paused ----@return boolean paused -function AnimationGroup:isPaused() - return self._paused -end - ---- Reverse all animations -function AnimationGroup:reverse() - for _, anim in ipairs(self.animations) do - if type(anim.reverse) == "function" then - anim:reverse() - end - end -end - ---- Set speed for all animations ----@param speed number Speed multiplier -function AnimationGroup:setSpeed(speed) - for _, anim in ipairs(self.animations) do - if type(anim.setSpeed) == "function" then - anim:setSpeed(speed) - end - end -end - ---- Cancel all animations ----@param element table? Optional element reference -function AnimationGroup:cancel(element) - if self._state ~= "cancelled" and self._state ~= "completed" then - self._state = "cancelled" - for _, anim in ipairs(self.animations) do - if type(anim.cancel) == "function" then - anim:cancel(element) - end - end - end -end - ---- Reset all animations -function AnimationGroup:reset() - self._currentIndex = 1 - self._staggerElapsed = 0 - self._startedAnimations = {} - self._hasStarted = false - self._paused = false - self._state = "ready" - - for _, anim in ipairs(self.animations) do - if type(anim.reset) == "function" then - anim:reset() - end - end -end - ---- Get group state ----@return string state -function AnimationGroup:getState() - return self._state -end - ---- Get group progress ----@return number progress -function AnimationGroup:getProgress() - if #self.animations == 0 then - return 1 - end - - if self.mode == "sequence" then - local completedAnims = self._currentIndex - 1 - local currentProgress = 0 - - if self._currentIndex <= #self.animations then - local currentAnim = self.animations[self._currentIndex] - if type(currentAnim.getProgress) == "function" then - currentProgress = currentAnim:getProgress() - end - end - - return (completedAnims + currentProgress) / #self.animations - else - local totalProgress = 0 - for _, anim in ipairs(self.animations) do - if type(anim.getProgress) == "function" then - totalProgress = totalProgress + anim:getProgress() - else - totalProgress = totalProgress + 1 - end - end - return totalProgress / #self.animations - end -end - ---- Apply group to element ----@param element table The element to apply animations to -function AnimationGroup:apply(element) - if not element or type(element) ~= "table" then - if Animation._ErrorHandler then - Animation._ErrorHandler:warn("AnimationGroup", "ANIM_003") - end - return - end - element.animationGroup = self -end - --- ============================================================================ --- MODULE INITIALIZATION --- ============================================================================ - ---- Initialize Animation module with dependencies ----@param deps table Dependencies: { ErrorHandler = ErrorHandler, Color = Color? } -function Animation.init(deps) - if type(deps) == "table" then - Animation._ErrorHandler = deps.ErrorHandler - Animation._ColorModule = deps.Color - end -end - -Animation.Easing = Easing -Animation.Transform = Transform -Animation.Group = AnimationGroup - -return Animation diff --git a/libs/flexlove/modules/Behavior.lua b/libs/flexlove/modules/Behavior.lua deleted file mode 100644 index c4c11b99..00000000 --- a/libs/flexlove/modules/Behavior.lua +++ /dev/null @@ -1,188 +0,0 @@ --- modules/Behavior.lua --- --- Base module for the pluggable behavior system that drives the Behavior & --- Mode Unification refactor. --- --- A *behavior* is a small, stateless table produced by `Behavior.new(spec)` --- that implements a fixed lifecycle hook set. Concrete behaviors (Clickable, --- Scrollable, TextEditable, Selectable, ...) each live in their own module and --- are attached to an Element. The Element's `update`/`draw`/save-restore paths --- iterate `element.behaviors` and dispatch to the appropriate hooks, replacing --- the swarm of `if self.scrollable` / immediate-mode-branch checks previously --- hard-coded in Element.lua. --- --- Element.new iterates a registry of behavior prototypes and auto-attaches --- whichever return true from `shouldAttach(props)`. Element therefore never --- needs to know what an individual behavior does — only that it conforms to --- this interface. --- --- Design constraints (locked — tasks 02-13 depend on this API): --- * Pure Lua — NO `love` import, NO dependency on utils/Color/Units/ErrorHandler. --- Stays fully stub-testable standalone (see testing/__tests__/behavior_test.lua). --- * Minimal interface — exactly 6 lifecycle hooks + a `shouldAttach` predicate. --- Do NOT add hooks "just in case"; new capabilities become new behaviors, --- not new hooks. Extending HOOK_NAMES is an architectural decision that must --- be mirrored by every concrete behavior. --- * Immutable instances — behavior tables are produced once and treated as --- read-only. Per-element runtime state lives on the element (or a subsystem --- the behavior attaches), NEVER on the behavior instance itself, so a single --- behavior instance can be shared across many elements. --- --- Lifecycle hook contract (each receives the owning element as first argument): --- onAttach(element) — called once when the behavior is attached --- (element fully constructed). Allocate --- subsystems / register listeners here. --- onDetach(element) — called once when the behavior is detached --- (element destroyed / mode switch). Tear --- down anything onAttach created. --- onUpdate(element, dt) — called every frame from Element:update. --- onDraw(element, ctx) — called every frame from Element:draw; `ctx` --- is the draw context (viewport transform, --- scissor state, theme renderer, ...). --- saveState(element) -> state — called during Element save-state; returns --- a serializable snapshot (or nil) so the --- behavior's runtime state survives the --- immediate-mode recreation cycle. --- restoreState(element, state) — called after reconstruction with the --- snapshot previously returned by saveState. --- --- shouldAttach(props) -> boolean — class-level predicate (not a hook): given --- an element's props table, return true if --- this behavior should be auto-attached. --- Defaults to false (opt-in). - ---- A behavior instance: a frozen table of lifecycle hooks + a shouldAttach ---- predicate. All hooks are always present (custom override or no-op default). ----@class Behavior ----@field onAttach fun(element:table) ----@field onDetach fun(element:table) ----@field onUpdate fun(element:table, dt:number) ----@field onDraw fun(element:table, ctx:table) ----@field saveState fun(element:table):any ----@field restoreState fun(element:table, state:any) ----@field shouldAttach fun(props:table):boolean - -local Behavior = {} - --- The fixed, ordered lifecycle hook set. Order is preserved so downstream tasks --- (Element behavior iteration) can rely on a deterministic dispatch sequence. --- HOOK_NAMES is intentionally NOT extended casually — see file header. -Behavior.HOOK_NAMES = { - "onAttach", - "onDetach", - "onUpdate", - "onDraw", - "saveState", - "restoreState", -} - --- Allowlist of spec keys accepted by Behavior.new. Anything else is rejected so --- a typo (e.g. `onUpdat`) surfaces immediately instead of silently no-op'ing. --- Hook keys (HOOK_NAMES + shouldAttach) MUST be functions; metadata keys --- (drawLayer) may hold any value. -local ALLOWED_KEYS = { - onAttach = true, - onDetach = true, - onUpdate = true, - onDraw = true, - saveState = true, - restoreState = true, - shouldAttach = true, - drawLayer = true, -} - --- Spec keys whose values are NOT required to be functions (passive metadata --- consumed by dispatch sites, e.g. Element:draw's pre/post-children split). -local NON_FUNCTION_KEYS = { - drawLayer = true, -} - --- Default no-op hook. Behaviors override only the hooks they need; every other --- hook resolves to this so dispatch sites never have to nil-check. -local function noop() end - --- Default shouldAttach predicate: never auto-attach unless the behavior opts in --- by providing its own predicate. This is the safe default — a behavior with no --- opinion about which elements it applies to stays inert in the auto-attach --- pass (it can still be attached explicitly by name in a later task). -local function defaultShouldAttach() - return false -end - --- Module-level default predicate exposed for callers/tests that want to --- reference the base default directly without constructing an instance. -Behavior.shouldAttach = defaultShouldAttach - ---- Factory: create a frozen behavior instance from a spec table. ---- ---- `spec` is a table whose keys may be any subset of the 6 lifecycle hook names ---- plus `shouldAttach`; each value (when present) must be a function. The ---- returned table contains every lifecycle hook (custom override OR no-op) and ---- a `shouldAttach` predicate (custom OR always-false default), so dispatch ---- sites can call any hook unconditionally without nil-checking. ---- ---- Unknown spec keys and non-function values raise an error immediately so ---- mistakes fail fast at construction rather than as silent no-ops later. ---- ----@param spec table|nil spec table overriding select hooks / shouldAttach ----@return Behavior -function Behavior.new(spec) - spec = spec or {} - - -- Validate spec keys up front so typos surface here, not as silent no-ops. - for key, value in pairs(spec) do - if not ALLOWED_KEYS[key] then - error(string.format("Behavior.new: unknown spec key '%s'", tostring(key)), 2) - end - if not NON_FUNCTION_KEYS[key] and type(value) ~= "function" then - error(string.format("Behavior.new: spec key '%s' must be a function, got %s", tostring(key), type(value)), 2) - end - end - - local instance = {} - - -- Populate every lifecycle hook: custom override when provided, no-op default - -- otherwise. Guarantees `instance.hook` is always callable. - for _, hook in ipairs(Behavior.HOOK_NAMES) do - instance[hook] = spec[hook] or noop - end - - -- shouldAttach defaults to always-false; behaviors opt in by supplying one. - instance.shouldAttach = spec.shouldAttach or defaultShouldAttach - - -- drawLayer: optional metadata field (default nil = "background"/pre-children). - -- Dispatch sites (Element:draw) use it to split rendering into pre-children - -- (background layers) and post-children (overlay layers, e.g. scrollbars). - instance.drawLayer = spec.drawLayer - - -- Freeze: prevent adding new fields. Behavior instances are shared, stateless - -- objects; runtime state belongs on the element, never on the behavior. - -- (Reassigning an existing hook is still possible via direct index write — - -- Lua metatables cannot intercept that — but the freeze communicates intent - -- and catches accidental field additions.) - local mt = { - __newindex = function(_, key) - error(string.format("Behavior: behavior instances are immutable (cannot set '%s')", tostring(key)), 2) - end, - --- Mark the metatable so consumers can detect a Behavior instance. - ---@return string - __tostring = function() - return "Behavior" - end, - __metatable = "Behavior", - } - setmetatable(instance, mt) - - return instance -end - ---- Type guard: returns true if `value` is a Behavior instance produced by ---- `Behavior.new`. Used by Element's attach path to validate registry entries ---- without depending on identity. ----@param value any ----@return boolean -function Behavior.isBehavior(value) - return type(value) == "table" and getmetatable(value) == "Behavior" -end - -return Behavior diff --git a/libs/flexlove/modules/Blur.lua b/libs/flexlove/modules/Blur.lua deleted file mode 100644 index 413592a9..00000000 --- a/libs/flexlove/modules/Blur.lua +++ /dev/null @@ -1,686 +0,0 @@ --- Lua 5.2+ compatibility for unpack -local unpack = table.unpack or unpack - --- Warning cache to prevent duplicate warnings for the same element -local warningCache = {} - -local Cache = { - canvases = {}, - quads = {}, - blurInstances = {}, -- Cache blur instances by quality - blurredCanvases = {}, -- Cache pre-blurred canvases for immediate mode - MAX_CANVAS_SIZE = 20, - MAX_QUAD_SIZE = 20, - MAX_BLURRED_CANVAS_CACHE = 50, -- Maximum cached blurred canvases - RADIUS_THRESHOLD = 0.5, -- Skip blur below this radius - LARGE_BLUR_THRESHOLD = 250 * 250, -- Warn if blur area exceeds this (250x250px) -} - ---- Round canvas size to nearest bucket for better reuse ----@param size number Size to bucket ----@return number bucketSize Bucketed size -local function bucketSize(size) - if size <= 128 then - return math.ceil(size / 32) * 32 - elseif size <= 512 then - return math.ceil(size / 64) * 64 - elseif size <= 1024 then - return math.ceil(size / 128) * 128 - else - return math.ceil(size / 256) * 256 - end -end - ---- Get or create a canvas from cache ----@param width number Canvas width ----@param height number Canvas height ----@return love.Canvas canvas The cached or new canvas -function Cache.getCanvas(width, height) - -- Use bucketed sizes for better cache reuse - local bucketedWidth = bucketSize(width) - local bucketedHeight = bucketSize(height) - local key = string.format("%dx%d", bucketedWidth, bucketedHeight) - - if not Cache.canvases[key] then - Cache.canvases[key] = {} - end - - local cache = Cache.canvases[key] - - for i, entry in ipairs(cache) do - if not entry.inUse then - entry.inUse = true - return entry.canvas - end - end - - local canvas = love.graphics.newCanvas(bucketedWidth, bucketedHeight) - table.insert(cache, { canvas = canvas, inUse = true }) - - if #cache > Cache.MAX_CANVAS_SIZE then - local removed = table.remove(cache, 1) - if removed and removed.canvas then - removed.canvas:release() - end - end - - return canvas -end - ---- Release a canvas back to the cache ----@param canvas love.Canvas Canvas to release -function Cache.releaseCanvas(canvas) - for _, sizeCache in pairs(Cache.canvases) do - for _, entry in ipairs(sizeCache) do - if entry.canvas == canvas then - entry.inUse = false - return - end - end - end -end - ---- Get or create a quad from cache ----@param x number X position ----@param y number Y position ----@param width number Quad width ----@param height number Quad height ----@param sw number Source width ----@param sh number Source height ----@return love.Quad quad The cached or new quad -function Cache.getQuad(x, y, width, height, sw, sh) - local key = string.format("%d,%d,%d,%d,%d,%d", x, y, width, height, sw, sh) - - if not Cache.quads[key] then - Cache.quads[key] = {} - end - - local cache = Cache.quads[key] - - for i, entry in ipairs(cache) do - if not entry.inUse then - entry.inUse = true - return entry.quad - end - end - - local quad = love.graphics.newQuad(x, y, width, height, sw, sh) - table.insert(cache, { quad = quad, inUse = true }) - - if #cache > Cache.MAX_QUAD_SIZE then - table.remove(cache, 1) - end - - return quad -end - ---- Release a quad back to the cache ----@param quad love.Quad Quad to release -function Cache.releaseQuad(quad) - for _, keyCache in pairs(Cache.quads) do - for _, entry in ipairs(keyCache) do - if entry.quad == quad then - entry.inUse = false - return - end - end - end -end - ---- Generate cache key for blurred canvas ----@param elementId string Element ID ----@param x number X position ----@param y number Y position ----@param width number Width ----@param height number Height ----@param radius number Blur radius ----@param quality number Blur quality ----@param isBackdrop boolean Whether this is backdrop blur ----@return string key Cache key -function Cache.generateBlurCacheKey(elementId, x, y, width, height, radius, quality, isBackdrop) - return string.format( - "%s:%d:%d:%d:%d:%.1f:%d:%s", - elementId, - x, - y, - width, - height, - radius, - quality, - tostring(isBackdrop) - ) -end - ---- Get cached blurred canvas ----@param key string Cache key ----@return love.Canvas|nil canvas Cached canvas or nil -function Cache.getBlurredCanvas(key) - local entry = Cache.blurredCanvases[key] - if entry then - entry.lastUsed = os.time() - return entry.canvas - end - return nil -end - ---- Store blurred canvas in cache ----@param key string Cache key ----@param canvas love.Canvas Canvas to cache -function Cache.setBlurredCanvas(key, canvas) - -- Limit cache size - local count = 0 - for _ in pairs(Cache.blurredCanvases) do - count = count + 1 - end - - if count >= Cache.MAX_BLURRED_CANVAS_CACHE then - -- Remove oldest entry - local oldestKey = nil - local oldestTime = math.huge - for k, v in pairs(Cache.blurredCanvases) do - if v.lastUsed < oldestTime then - oldestTime = v.lastUsed - oldestKey = k - end - end - - if oldestKey then - if Cache.blurredCanvases[oldestKey].canvas then - Cache.blurredCanvases[oldestKey].canvas:release() - end - Cache.blurredCanvases[oldestKey] = nil - end - end - - Cache.blurredCanvases[key] = { - canvas = canvas, - lastUsed = os.time(), - } -end - ---- Clear blurred canvas cache for specific element ----@param elementId string Element ID to clear cache for -function Cache.clearBlurredCanvasesForElement(elementId) - for key, entry in pairs(Cache.blurredCanvases) do - if key:match("^" .. elementId .. ":") then - if entry.canvas then - entry.canvas:release() - end - Cache.blurredCanvases[key] = nil - end - end -end - ---- Clear all caches -function Cache.clear() - -- Release all blurred canvases - for _, entry in pairs(Cache.blurredCanvases) do - if entry.canvas then - entry.canvas:release() - end - end - - Cache.canvases = {} - Cache.quads = {} - Cache.blurInstances = {} - Cache.blurredCanvases = {} - warningCache = {} -- Clear warning cache on cache clear -end - --- ============================================================================ --- SHADER BUILDER --- ============================================================================ - -local ShaderBuilder = {} - ---- Build Gaussian blur shader with given parameters ----@param taps number Number of samples (must be odd, >= 3) ----@param offset number Offset value ----@param offsetType string "weighted" or "center" ----@param sigma number Sigma value for Gaussian distribution ----@return love.Shader shader The compiled blur shader -function ShaderBuilder.build(taps, offset, offsetType, sigma) - taps = math.floor(taps) - sigma = sigma >= 1 and sigma or (taps - 1) * offset / 6 - sigma = math.max(sigma, 1) - - local steps = (taps + 1) / 2 - - local gOffsets = {} - local gWeights = {} - for i = 1, steps do - gOffsets[i] = offset * (i - 1) - gWeights[i] = math.exp(-0.5 * (gOffsets[i] - 0) ^ 2 * 1 / sigma ^ 2) - end - - local offsets = {} - local weights = {} - for i = #gWeights, 2, -2 do - local oA, oB = gOffsets[i], gOffsets[i - 1] - local wA, wB = gWeights[i], gWeights[i - 1] - wB = oB == 0 and wB / 2 or wB - local weight = wA + wB - offsets[#offsets + 1] = offsetType == "center" and (oA + oB) / 2 or (oA * wA + oB * wB) / weight - weights[#weights + 1] = weight - end - - local code = { - [[ - extern vec2 direction; - vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) {]], - } - - local norm = 0 - if #gWeights % 2 == 0 then - code[#code + 1] = "vec4 c = vec4( 0.0 );" - else - local weight = gWeights[1] - norm = norm + weight - code[#code + 1] = string.format("vec4 c = %f * texture2D(tex, tc);", weight) - end - - local template = "c += %f * ( texture2D(tex, tc + %f * direction)+ texture2D(tex, tc - %f * direction));\n" - for i = 1, #offsets do - local offset = offsets[i] - local weight = weights[i] - norm = norm + weight * 2 - code[#code + 1] = string.format(template, weight, offset, offset) - end - code[#code + 1] = string.format("return c * vec4(%f) * color; }", 1 / norm) - - local shaderCode = table.concat(code) - return love.graphics.newShader(shaderCode) -end - ---- Get or create a blur instance from cache ----@param quality number Quality level (1-10) ----@return table blurData Cached blur data {shader, taps} -function Cache.getBlurInstance(quality) - if not Cache.blurInstances[quality] then - local taps = 3 + (quality - 1) * 1.5 - taps = math.floor(taps) - if taps % 2 == 0 then - taps = taps + 1 - end - - local shader = ShaderBuilder.build(taps, 1.0, "weighted", -1) - Cache.blurInstances[quality] = { - shader = shader, - taps = taps, - } - end - - return Cache.blurInstances[quality] -end - ----@class BlurProps ----@field quality number? Quality level (1-10, default: 5) - ----@class Blur ----@field shader love.Shader The blur shader ----@field quality number Quality level (1-10) ----@field taps number Number of shader taps ----@field _ErrorHandler table? Reference to ErrorHandler module -local Blur = {} -Blur.__index = Blur - ---- Check if we should warn about large blur area in immediate mode ----@param elementId string|nil Element ID for caching warnings ----@param width number Blur area width ----@param height number Blur area height ----@param blurType string "content" or "backdrop" -local function checkLargeBlurWarning(elementId, width, height, blurType) - -- Skip if no ErrorHandler available - if not Blur._ErrorHandler then - return - end - - -- Skip if not in immediate mode - if not Blur._blurOptimizations then - return - end - - -- Calculate blur area - local area = width * height - - -- Skip if area is below threshold - if area <= Cache.LARGE_BLUR_THRESHOLD then - return - end - - -- Generate warning key (use elementId if available, otherwise use dimensions) - local warningKey = elementId or string.format("%dx%d:%s", width, height, blurType) - - -- Skip if already warned for this element/area - if warningCache[warningKey] then - return - end - - -- Mark as warned - warningCache[warningKey] = true - - -- Issue warning - local message = - string.format("Large %s blur area detected (%dx%d = %d pixels) in immediate mode", blurType, width, height, area) - - local suggestion = - "Consider using retained mode for this component to avoid recreating blur effects every frame. Large blur operations are expensive and can cause performance issues in immediate mode." - - Blur._ErrorHandler:warn("Blur", "PERF_003", { - area = string.format("%.0fx%.0f", width or 0, height or 0), - }) -end - ---- Create a new blur effect instance ----@param props BlurProps? Blur configuration ----@return Blur blur The new blur instance -function Blur.new(props) - props = props or {} - - local quality = props.quality or 5 - quality = math.max(1, math.min(10, quality)) - - -- Get cached blur instance for this quality level - local blurData = Cache.getBlurInstance(quality) - - local self = setmetatable({}, Blur) - self.shader = blurData.shader - self.quality = quality - self.taps = blurData.taps - - return self -end - ---- Apply blur to a region of the screen ----@param radius number Blur radius in pixels ----@param x number X position ----@param y number Y position ----@param width number Width of region ----@param height number Height of region ----@param drawFunc function Function to draw content to be blurred -function Blur:applyToRegion(radius, x, y, width, height, drawFunc) - if type(drawFunc) ~= "function" then - if Blur._ErrorHandler then - Blur._ErrorHandler:warn("Blur", "BLUR_001") - end - return - end - - if radius <= 0 or width <= 0 or height <= 0 then - drawFunc() - return - end - - -- Early exit for very low radius (optimization) - if radius < Cache.RADIUS_THRESHOLD then - drawFunc() - return - end - - -- Check for large blur area in immediate mode - checkLargeBlurWarning(nil, width, height, "content") - - -- Calculate offset multiplier based on radius and quality - -- Higher quality = more samples = smaller steps for same radius - local offsetMultiplier = radius / self.quality - - local canvas1 = Cache.getCanvas(width, height) - local canvas2 = Cache.getCanvas(width, height) - - local prevCanvas = love.graphics.getCanvas() - local prevShader = love.graphics.getShader() - local prevColor = { love.graphics.getColor() } - local prevBlendMode = love.graphics.getBlendMode() - - love.graphics.setCanvas(canvas1) - love.graphics.clear() - love.graphics.push() - love.graphics.origin() - love.graphics.translate(-x, -y) - drawFunc() - love.graphics.pop() - - love.graphics.setShader(self.shader) - love.graphics.setColor(1, 1, 1, 1) - love.graphics.setBlendMode("alpha", "premultiplied") - - -- Single pass with radius-controlled offset - love.graphics.setCanvas(canvas2) - love.graphics.clear() - self.shader:send("direction", { offsetMultiplier / width, 0 }) - love.graphics.draw(canvas1, 0, 0) - - love.graphics.setCanvas(canvas1) - love.graphics.clear() - self.shader:send("direction", { 0, offsetMultiplier / height }) - love.graphics.draw(canvas2, 0, 0) - - love.graphics.setCanvas(prevCanvas) - love.graphics.setShader() - love.graphics.setBlendMode(prevBlendMode) - love.graphics.draw(canvas1, x, y) - - love.graphics.setShader(prevShader) - love.graphics.setColor(unpack(prevColor)) - - Cache.releaseCanvas(canvas1) - Cache.releaseCanvas(canvas2) -end - ---- Apply backdrop blur effect (blur content behind a region) ----@param radius number Blur radius in pixels ----@param x number X position ----@param y number Y position ----@param width number Width of region ----@param height number Height of region ----@param backdropCanvas love.Canvas Canvas containing the backdrop content -function Blur:applyBackdrop(radius, x, y, width, height, backdropCanvas) - if not backdropCanvas then - if Blur._ErrorHandler then - Blur._ErrorHandler:warn("Blur", "BLUR_002") - end - return - end - - if radius <= 0 or width <= 0 or height <= 0 then - return - end - - -- Early exit for very low radius (optimization) - if radius < Cache.RADIUS_THRESHOLD then - return - end - - -- Calculate offset multiplier based on radius and quality - local offsetMultiplier = radius / self.quality - - local canvas1 = Cache.getCanvas(width, height) - local canvas2 = Cache.getCanvas(width, height) - - local prevCanvas = love.graphics.getCanvas() - local prevShader = love.graphics.getShader() - local prevColor = { love.graphics.getColor() } - local prevBlendMode = love.graphics.getBlendMode() - - love.graphics.setCanvas(canvas1) - love.graphics.clear() - love.graphics.setColor(1, 1, 1, 1) - love.graphics.setBlendMode("alpha", "premultiplied") - - local backdropWidth, backdropHeight = backdropCanvas:getDimensions() - local quad = Cache.getQuad(x, y, width, height, backdropWidth, backdropHeight) - love.graphics.draw(backdropCanvas, quad, 0, 0) - - love.graphics.setShader(self.shader) - - -- Single pass with radius-controlled offset - love.graphics.setCanvas(canvas2) - love.graphics.clear() - self.shader:send("direction", { offsetMultiplier / width, 0 }) - love.graphics.draw(canvas1, 0, 0) - - love.graphics.setCanvas(canvas1) - love.graphics.clear() - self.shader:send("direction", { 0, offsetMultiplier / height }) - love.graphics.draw(canvas2, 0, 0) - - love.graphics.setCanvas(prevCanvas) - love.graphics.setShader() - love.graphics.setBlendMode(prevBlendMode) - love.graphics.draw(canvas1, x, y) - - love.graphics.setShader(prevShader) - love.graphics.setColor(unpack(prevColor)) - - Cache.releaseCanvas(canvas1) - Cache.releaseCanvas(canvas2) - Cache.releaseQuad(quad) -end - ---- Get the current quality level ----@return number quality Quality level (1-10) -function Blur:getQuality() - return self.quality -end - ---- Get the number of shader taps ----@return number taps Number of shader taps -function Blur:getTaps() - return self.taps -end - ---- Clear all caches (call on window resize or memory cleanup) -function Blur.clearCache() - Cache.clear() -end - ---- Apply backdrop blur with caching support ----@param radius number Blur radius in pixels ----@param x number X position ----@param y number Y position ----@param width number Width of region ----@param height number Height of region ----@param backdropCanvas love.Canvas Canvas containing the backdrop content ----@param elementId string|nil Element ID for caching (nil disables caching) -function Blur:applyBackdropCached(radius, x, y, width, height, backdropCanvas, elementId) - -- If caching is disabled or no element ID, fall back to regular apply - if not Blur._blurOptimizations or not elementId then - return self:applyBackdrop(radius, x, y, width, height, backdropCanvas) - end - - -- Generate cache key - local cacheKey = Cache.generateBlurCacheKey(elementId, x, y, width, height, radius, self.quality, true) - - -- Check cache - local cachedCanvas = Cache.getBlurredCanvas(cacheKey) - if cachedCanvas then - -- Draw cached blur - local prevCanvas = love.graphics.getCanvas() - local prevShader = love.graphics.getShader() - local prevColor = { love.graphics.getColor() } - local prevBlendMode = love.graphics.getBlendMode() - - love.graphics.setCanvas(prevCanvas) - love.graphics.setShader() - love.graphics.setBlendMode(prevBlendMode) - love.graphics.draw(cachedCanvas, x, y) - - love.graphics.setShader(prevShader) - love.graphics.setColor(unpack(prevColor)) - return - end - - -- Not cached, render and cache - if not backdropCanvas then - if Blur._ErrorHandler then - Blur._ErrorHandler:warn("Blur", "BLUR_002") - end - return - end - - if radius <= 0 or width <= 0 or height <= 0 then - return - end - - -- Early exit for very low radius (optimization) - if radius < Cache.RADIUS_THRESHOLD then - return - end - - -- Check for large blur area in immediate mode - checkLargeBlurWarning(elementId, width, height, "backdrop") - - -- Calculate offset multiplier based on radius and quality - local offsetMultiplier = radius / self.quality - - local canvas1 = Cache.getCanvas(width, height) - local canvas2 = Cache.getCanvas(width, height) - - local prevCanvas = love.graphics.getCanvas() - local prevShader = love.graphics.getShader() - local prevColor = { love.graphics.getColor() } - local prevBlendMode = love.graphics.getBlendMode() - - love.graphics.setCanvas(canvas1) - love.graphics.clear() - love.graphics.setColor(1, 1, 1, 1) - love.graphics.setBlendMode("alpha", "premultiplied") - - local backdropWidth, backdropHeight = backdropCanvas:getDimensions() - local quad = Cache.getQuad(x, y, width, height, backdropWidth, backdropHeight) - love.graphics.draw(backdropCanvas, quad, 0, 0) - - love.graphics.setShader(self.shader) - - -- Single pass with radius-controlled offset - love.graphics.setCanvas(canvas2) - love.graphics.clear() - self.shader:send("direction", { offsetMultiplier / width, 0 }) - love.graphics.draw(canvas1, 0, 0) - - love.graphics.setCanvas(canvas1) - love.graphics.clear() - self.shader:send("direction", { 0, offsetMultiplier / height }) - love.graphics.draw(canvas2, 0, 0) - - -- Cache the result - local cachedResult = love.graphics.newCanvas(width, height) - love.graphics.setCanvas(cachedResult) - love.graphics.clear() - love.graphics.setShader() - love.graphics.setBlendMode("alpha", "premultiplied") - love.graphics.draw(canvas1, 0, 0) - Cache.setBlurredCanvas(cacheKey, cachedResult) - - love.graphics.setCanvas(prevCanvas) - love.graphics.setShader() - love.graphics.setBlendMode(prevBlendMode) - love.graphics.draw(canvas1, x, y) - - love.graphics.setShader(prevShader) - love.graphics.setColor(unpack(prevColor)) - - Cache.releaseCanvas(canvas1) - Cache.releaseCanvas(canvas2) - Cache.releaseQuad(quad) -end - ---- Clear blur cache for specific element ----@param elementId string Element ID -function Blur.clearElementCache(elementId) - Cache.clearBlurredCanvasesForElement(elementId) -end - ---- Initialize Blur module with dependencies ----@param deps table Dependencies: { ErrorHandler = ErrorHandler?, immediateModeOptimizations = boolean? } -function Blur.init(deps) - if type(deps) == "table" then - Blur._ErrorHandler = deps.ErrorHandler - Blur._blurOptimizations = deps.immediateModeOptimizations or false - end -end - -Blur.Cache = Cache -Blur.ShaderBuilder = ShaderBuilder - -return Blur diff --git a/libs/flexlove/modules/Calc.lua b/libs/flexlove/modules/Calc.lua deleted file mode 100644 index b62d6bb6..00000000 --- a/libs/flexlove/modules/Calc.lua +++ /dev/null @@ -1,385 +0,0 @@ ---- Utility module for parsing and evaluating CSS-like calc() expressions ---- Supports arithmetic operations (+, -, *, /) with mixed units (px, %, vw, vh) ----@class Calc -local Calc = {} - ---- Initialize Calc module with dependencies ----@param deps CalcDependencies Dependencies: { ErrorHandler = ErrorHandler? } -function Calc.init(deps) - Calc._ErrorHandler = deps.ErrorHandler -end - ---- Token types for lexical analysis -local TokenType = { - NUMBER = "NUMBER", - UNIT = "UNIT", - PLUS = "PLUS", - MINUS = "MINUS", - MULTIPLY = "MULTIPLY", - DIVIDE = "DIVIDE", - LPAREN = "LPAREN", - RPAREN = "RPAREN", - EOF = "EOF", -} - ---- Tokenize a calc expression string into tokens ----@param expr string The expression to tokenize (e.g., "50% - 10vw") ----@return CalcToken[]? tokens Array of tokens with type, value, unit ----@return string? error Error message if tokenization fails -local function tokenize(expr) - local tokens = {} - local i = 1 - local len = #expr - - while i <= len do - local char = expr:sub(i, i) - - -- Skip whitespace - if char:match("%s") then - i = i + 1 - -- Number (including decimals, but NOT negative - handled separately below) - elseif char:match("%d") or (char == "." and expr:sub(i + 1, i + 1):match("%d")) then - local numStr = "" - - -- Parse integer and decimal parts - while i <= len and (expr:sub(i, i):match("%d") or expr:sub(i, i) == ".") do - numStr = numStr .. expr:sub(i, i) - i = i + 1 - end - - local num = tonumber(numStr) - if not num then - return nil, "Invalid number: " .. numStr - end - - -- Check for unit following the number - local unitStr = "" - while i <= len and expr:sub(i, i):match("[%a%%]") do - unitStr = unitStr .. expr:sub(i, i) - i = i + 1 - end - - -- Default to px if no unit - if unitStr == "" then - unitStr = "px" - end - - -- Validate unit - local validUnits = { px = true, ["%"] = true, vw = true, vh = true } - if not validUnits[unitStr] then - return nil, "Invalid unit: " .. unitStr - end - - table.insert(tokens, { - type = TokenType.NUMBER, - value = num, - unit = unitStr, - }) - -- Operators - elseif char == "+" then - table.insert(tokens, { type = TokenType.PLUS }) - i = i + 1 - elseif char == "-" then - -- Check if this is a negative number or subtraction - -- It's a negative number if previous token is an operator or opening paren - local prevToken = tokens[#tokens] - if - not prevToken - or prevToken.type == TokenType.PLUS - or prevToken.type == TokenType.MINUS - or prevToken.type == TokenType.MULTIPLY - or prevToken.type == TokenType.DIVIDE - or prevToken.type == TokenType.LPAREN - then - -- This is a negative number, continue to number parsing - local numStr = "-" - i = i + 1 - - -- Parse integer and decimal parts - while i <= len and (expr:sub(i, i):match("%d") or expr:sub(i, i) == ".") do - numStr = numStr .. expr:sub(i, i) - i = i + 1 - end - - local num = tonumber(numStr) - if not num then - return nil, "Invalid number: " .. numStr - end - - -- Check for unit following the number - local unitStr = "" - while i <= len and expr:sub(i, i):match("[%a%%]") do - unitStr = unitStr .. expr:sub(i, i) - i = i + 1 - end - - -- Default to px if no unit - if unitStr == "" then - unitStr = "px" - end - - -- Validate unit - local validUnits = { px = true, ["%"] = true, vw = true, vh = true } - if not validUnits[unitStr] then - return nil, "Invalid unit: " .. unitStr - end - - table.insert(tokens, { - type = TokenType.NUMBER, - value = num, - unit = unitStr, - }) - else - -- This is subtraction operator - table.insert(tokens, { type = TokenType.MINUS }) - i = i + 1 - end - elseif char == "*" then - table.insert(tokens, { type = TokenType.MULTIPLY }) - i = i + 1 - elseif char == "/" then - table.insert(tokens, { type = TokenType.DIVIDE }) - i = i + 1 - elseif char == "(" then - table.insert(tokens, { type = TokenType.LPAREN }) - i = i + 1 - elseif char == ")" then - table.insert(tokens, { type = TokenType.RPAREN }) - i = i + 1 - else - return nil, "Unexpected character: " .. char - end - end - - table.insert(tokens, { type = TokenType.EOF }) - return tokens -end - ---- Parser for calc expressions using recursive descent ----@class Parser ----@field tokens CalcToken[] Array of tokens ----@field pos number Current token position -local Parser = {} -Parser.__index = Parser - ---- Create a new parser ----@param tokens CalcToken[] Array of tokens ----@return Parser -function Parser.new(tokens) - local self = setmetatable({}, Parser) - self.tokens = tokens - self.pos = 1 - return self -end - ---- Get current token ----@return CalcToken token Current token -function Parser:current() - return self.tokens[self.pos] -end - ---- Advance to next token -function Parser:advance() - self.pos = self.pos + 1 -end - ---- Parse expression (handles + and -) ----@return CalcASTNode ast Abstract syntax tree node -function Parser:parseExpression() - local left = self:parseTerm() - - while self:current().type == TokenType.PLUS or self:current().type == TokenType.MINUS do - local op = self:current().type - self:advance() - local right = self:parseTerm() - left = { - type = op == TokenType.PLUS and "add" or "subtract", - left = left, - right = right, - } - end - - return left -end - ---- Parse term (handles * and /) ----@return CalcASTNode ast Abstract syntax tree node -function Parser:parseTerm() - local left = self:parseFactor() - - while self:current().type == TokenType.MULTIPLY or self:current().type == TokenType.DIVIDE do - local op = self:current().type - self:advance() - local right = self:parseFactor() - left = { - type = op == TokenType.MULTIPLY and "multiply" or "divide", - left = left, - right = right, - } - end - - return left -end - ---- Parse factor (handles numbers and parentheses) ----@return CalcASTNode ast Abstract syntax tree node -function Parser:parseFactor() - local token = self:current() - - if token.type == TokenType.NUMBER then - self:advance() - return { - type = "number", - value = token.value, - unit = token.unit, - } - elseif token.type == TokenType.LPAREN then - self:advance() - local expr = self:parseExpression() - if self:current().type ~= TokenType.RPAREN then - error("Expected closing parenthesis") - end - self:advance() - return expr - else - error("Unexpected token: " .. token.type) - end -end - ---- Parse the tokens into an AST ----@return CalcASTNode ast Abstract syntax tree -function Parser:parse() - local ast = self:parseExpression() - if self:current().type ~= TokenType.EOF then - error("Unexpected tokens after expression") - end - return ast -end - ---- Create a calc expression object that can be resolved later ---- This is the main API function that users call ----@param expr string The calc expression (e.g., "50% - 10vw") ----@return CalcObject calcObject A calc expression object with AST -function Calc.new(expr) - -- Tokenize - local tokens, err = tokenize(expr) - if not tokens then - if Calc._ErrorHandler then - Calc._ErrorHandler:warn("Calc", "VAL_006", { - expression = expr, - error = err, - }) - end - -- Return a fallback calc object that resolves to 0 - return { - _isCalc = true, - _expr = expr, - _ast = nil, - _error = err, - } - end - - -- Parse - local parser = Parser.new(tokens) - local success, ast = pcall(function() - return parser:parse() - end) - - if not success then - if Calc._ErrorHandler then - Calc._ErrorHandler:warn("Calc", "VAL_006", { - expression = expr, - error = ast, -- ast contains error message on failure - }) - end - -- Return a fallback calc object that resolves to 0 - return { - _isCalc = true, - _expr = expr, - _ast = nil, - _error = ast, - } - end - - return { - _isCalc = true, - _expr = expr, - _ast = ast, - } -end - ---- Check if a value is a calc expression ----@param value any The value to check ----@return boolean isCalc True if value is a calc expression -function Calc.isCalc(value) - return type(value) == "table" and value._isCalc == true -end - ---- Resolve a calc expression to pixel value ----@param calcObj CalcObject The calc expression object ----@param viewportWidth number Viewport width in pixels ----@param viewportHeight number Viewport height in pixels ----@param parentSize number? Parent dimension for percentage units ----@return number resolvedValue Resolved pixel value -function Calc.resolve(calcObj, viewportWidth, viewportHeight, parentSize) - if not calcObj._ast then - -- Error during parsing, return 0 - return 0 - end - - --- Evaluate AST node recursively - ---@param node table AST node - ---@return number value Evaluated value in pixels - local function evaluate(node) - if node.type == "number" then - -- Convert unit to pixels - local value = node.value - local unit = node.unit - - if unit == "px" then - return value - elseif unit == "%" then - if not parentSize then - if Calc._ErrorHandler then - Calc._ErrorHandler:warn("Calc", "LAY_003", { - unit = "%", - issue = "parent dimension not available", - }) - end - return 0 - end - return (value / 100) * parentSize - elseif unit == "vw" then - return (value / 100) * viewportWidth - elseif unit == "vh" then - return (value / 100) * viewportHeight - else - return 0 - end - elseif node.type == "add" then - return evaluate(node.left) + evaluate(node.right) - elseif node.type == "subtract" then - return evaluate(node.left) - evaluate(node.right) - elseif node.type == "multiply" then - return evaluate(node.left) * evaluate(node.right) - elseif node.type == "divide" then - local divisor = evaluate(node.right) - if divisor == 0 then - if Calc._ErrorHandler then - Calc._ErrorHandler:warn("Calc", "VAL_006", { - expression = calcObj._expr, - error = "Division by zero", - }) - end - return 0 - end - return evaluate(node.left) / divisor - else - return 0 - end - end - - return evaluate(calcObj._ast) -end - -return Calc diff --git a/libs/flexlove/modules/Color.lua b/libs/flexlove/modules/Color.lua deleted file mode 100644 index 1ea41d37..00000000 --- a/libs/flexlove/modules/Color.lua +++ /dev/null @@ -1,346 +0,0 @@ ----@class Color -local Color = {} -Color.__index = Color - ---- Initialize module with shared dependencies ----@param deps table Dependencies {ErrorHandler} -function Color.init(deps) - if type(deps) == "table" then - Color._ErrorHandler = deps.ErrorHandler - end -end - ---- Build type-safe color objects with automatic validation and clamping ---- Use this to avoid invalid color values and ensure consistent LÖVE-compatible colors (0-1 range) ----@param r number? Red component (0-1), defaults to 0 ----@param g number? Green component (0-1), defaults to 0 ----@param b number? Blue component (0-1), defaults to 0 ----@param a number? Alpha component (0-1), defaults to 1 ----@return Color color The new color instance -function Color.new(r, g, b, a) - -- Sanitize and clamp color components - local _, sanitizedR = Color.validateColorChannel(r or 0, 1) - local _, sanitizedG = Color.validateColorChannel(g or 0, 1) - local _, sanitizedB = Color.validateColorChannel(b or 0, 1) - local _, sanitizedA = Color.validateColorChannel(a or 1, 1) - - -- FFI structs don't support metatables/methods without wrapping - -- The wrapping overhead negates the FFI benefits - local self = setmetatable({}, Color) - self.r = sanitizedR or 0 - self.g = sanitizedG or 0 - self.b = sanitizedB or 0 - self.a = sanitizedA or 1 - return self -end - ---- Extract individual color channels for use with love.graphics.setColor() ---- Use this to pass colors to LÖVE's rendering functions ----@return number r Red component (0-1) ----@return number g Green component (0-1) ----@return number b Blue component (0-1) ----@return number a Alpha component (0-1) -function Color:toRGBA() - return self.r, self.g, self.b, self.a -end - ---- Parse CSS-style hex colors into Color objects for designer-friendly workflows ---- Use this to work with colors from design tools that export hex values ----@param hexWithTag string Hex color string (e.g. "#RRGGBB" or "#RRGGBBAA") ----@return Color color The parsed color (returns white on error with warning) -function Color.fromHex(hexWithTag) - -- Validate input type - if type(hexWithTag) ~= "string" then - Color._ErrorHandler:warn("Color", "VAL_004", { - input = tostring(hexWithTag), - issue = "not a string", - fallback = "white (#FFFFFF)", - }) - return Color.new(1, 1, 1, 1) - end - - local hex = hexWithTag:gsub("#", "") - if #hex == 6 then - local r = tonumber("0x" .. hex:sub(1, 2)) - local g = tonumber("0x" .. hex:sub(3, 4)) - local b = tonumber("0x" .. hex:sub(5, 6)) - if not r or not g or not b then - Color._ErrorHandler:warn("Color", "VAL_004", { - input = hexWithTag, - issue = "invalid hex digits", - fallback = "white (#FFFFFF)", - }) - return Color.new(1, 1, 1, 1) -- Return white as fallback - end - return Color.new(r / 255, g / 255, b / 255, 1) - elseif #hex == 8 then - local r = tonumber("0x" .. hex:sub(1, 2)) - local g = tonumber("0x" .. hex:sub(3, 4)) - local b = tonumber("0x" .. hex:sub(5, 6)) - local a = tonumber("0x" .. hex:sub(7, 8)) - if not r or not g or not b or not a then - Color._ErrorHandler:warn("Color", "VAL_004", { - input = hexWithTag, - issue = "invalid hex digits", - fallback = "white (#FFFFFFFF)", - }) - return Color.new(1, 1, 1, 1) -- Return white as fallback - end - return Color.new(r / 255, g / 255, b / 255, a / 255) - else - Color._ErrorHandler:warn("Color", "VAL_004", { - input = hexWithTag, - expected = "#RRGGBB or #RRGGBBAA", - hexLength = #hex, - fallback = "white (#FFFFFF)", - }) - return Color.new(1, 1, 1, 1) -- Return white as fallback - end -end - ---- Verify and sanitize individual color components to prevent rendering errors ---- Use this to safely process user input or external color data ----@param value any Value to validate ----@param max number? Maximum value (255 for 0-255 range, 1 for 0-1 range), defaults to 1 ----@return boolean valid True if valid ----@return number? clamped Clamped value in 0-1 range, nil if invalid -function Color.validateColorChannel(value, max) - max = max or 1 - - if type(value) ~= "number" then - return false, nil - end - - -- Check for NaN - if value ~= value then - return false, nil - end - - -- Check for Infinity - if value == math.huge or value == -math.huge then - return false, nil - end - - -- Normalize to 0-1 range - local normalized = value - if max == 255 then - normalized = value / 255 - end - - -- Clamp to valid range - normalized = math.max(0, math.min(1, normalized)) - - return true, normalized -end - ---- Validate hex color format ----@param hex string Hex color string (with or without #) ----@return boolean valid True if valid format ----@return string? error Error message if invalid, nil if valid -function Color.validateHexColor(hex) - if type(hex) ~= "string" then - return false, "Hex color must be a string" - end - - -- Remove # prefix - local cleanHex = hex:gsub("^#", "") - - -- Check length (3, 6, or 8 characters) - if #cleanHex ~= 3 and #cleanHex ~= 6 and #cleanHex ~= 8 then - return false, string.format("Invalid hex length: %d. Expected 3, 6, or 8 characters", #cleanHex) - end - - -- Check for valid hex characters - if not cleanHex:match("^[0-9A-Fa-f]+$") then - return false, "Invalid hex characters. Use only 0-9, A-F" - end - - return true, nil -end - ---- Validate RGB/RGBA color values ----@param r number Red component ----@param g number Green component ----@param b number Blue component ----@param a number? Alpha component (optional, defaults to max) ----@param max number? Maximum value (255 or 1), defaults to 1 ----@return boolean valid True if valid ----@return string? error Error message if invalid, nil if valid -function Color.validateRGBColor(r, g, b, a, max) - max = max or 1 - a = a or max - - local rValid = Color.validateColorChannel(r, max) - local gValid = Color.validateColorChannel(g, max) - local bValid = Color.validateColorChannel(b, max) - local aValid = Color.validateColorChannel(a, max) - - if not rValid then - return false, string.format("Invalid red channel: %s", tostring(r)) - end - if not gValid then - return false, string.format("Invalid green channel: %s", tostring(g)) - end - if not bValid then - return false, string.format("Invalid blue channel: %s", tostring(b)) - end - if not aValid then - return false, string.format("Invalid alpha channel: %s", tostring(a)) - end - - return true, nil -end - ---- Check if a value is a valid color format ----@param value any Value to check ----@return string? format Format type ("hex", "named", "table"), nil if invalid -function Color.isValidColorFormat(value) - local valueType = type(value) - - -- Check for hex string - if valueType == "string" then - if value:match("^#?[0-9A-Fa-f]+$") then - local valid = Color.validateHexColor(value) - if valid then - return "hex" - end - end - - return nil - end - - -- Check for table format - if valueType == "table" then - -- Check for Color instance - if getmetatable(value) == Color then - return "table" - end - - -- Check for array format {r, g, b, a} - if value[1] and value[2] and value[3] then - local valid = Color.validateRGBColor(value[1], value[2], value[3], value[4]) - if valid then - return "table" - end - end - - -- Check for named format {r=, g=, b=, a=} - if value.r and value.g and value.b then - local valid = Color.validateRGBColor(value.r, value.g, value.b, value.a) - if valid then - return "table" - end - end - - return nil - end - - return nil -end - ---- Convert any color format to a valid Color object with graceful fallbacks ---- Use this to robustly handle colors from any source without crashes ----@param value any Color value to sanitize (hex, named, table, or Color instance) ----@param default Color? Default color if invalid (defaults to black) ----@return Color color Sanitized color instance (guaranteed non-nil) -function Color.sanitizeColor(value, default) - default = default or Color.new(0, 0, 0, 1) - - local format = Color.isValidColorFormat(value) - - if not format then - return default - end - - -- Handle hex format - if format == "hex" then - local cleanHex = value:gsub("^#", "") - - -- Expand 3-digit hex to 6-digit - if #cleanHex == 3 then - cleanHex = cleanHex:gsub("(.)", "%1%1") - end - - -- Try to parse - local success, result = pcall(Color.fromHex, "#" .. cleanHex) - if success then - return result - else - return default - end - end - - if format == "table" then - -- Color instance - if getmetatable(value) == Color then - return value - end - - -- Array format - if value[1] then - local _, r = Color.validateColorChannel(value[1], 1) - local _, g = Color.validateColorChannel(value[2], 1) - local _, b = Color.validateColorChannel(value[3], 1) - local _, a = Color.validateColorChannel(value[4] or 1, 1) - - if r and g and b and a then - return Color.new(r, g, b, a) - end - end - - -- Named format - if value.r then - local _, r = Color.validateColorChannel(value.r, 1) - local _, g = Color.validateColorChannel(value.g, 1) - local _, b = Color.validateColorChannel(value.b, 1) - local _, a = Color.validateColorChannel(value.a or 1, 1) - - if r and g and b and a then - return Color.new(r, g, b, a) - end - end - end - - return default -end - ---- Universally convert any color format (hex, named, table) into a Color object ---- Use this as your main color input handler to accept flexible color specifications ----@param value any Color value (hex string, named color, table, or Color instance) ----@return Color color Parsed color instance (defaults to black on error) -function Color.parse(value) - return Color.sanitizeColor(value, Color.new(0, 0, 0, 1)) -end - ---- Smoothly transition between two colors for animations and gradients ---- Use this to create color-based animations without manual channel calculations ----@param colorA Color Starting color ----@param colorB Color Ending color ----@param t number Interpolation factor (0-1) ----@return Color color Interpolated color -function Color.lerp(colorA, colorB, t) - -- Sanitize inputs - if type(colorA) ~= "table" or getmetatable(colorA) ~= Color then - colorA = Color.new(0, 0, 0, 1) - end - if type(colorB) ~= "table" or getmetatable(colorB) ~= Color then - colorB = Color.new(0, 0, 0, 1) - end - if type(t) ~= "number" or t ~= t or t == math.huge or t == -math.huge then - t = 0 - end - - -- Clamp t to 0-1 range - t = math.max(0, math.min(1, t)) - - -- Linear interpolation for each channel - local oneMinusT = 1 - t - local r = colorA.r * oneMinusT + colorB.r * t - local g = colorA.g * oneMinusT + colorB.g * t - local b = colorA.b * oneMinusT + colorB.b * t - local a = colorA.a * oneMinusT + colorB.a * t - - return Color.new(r, g, b, a) -end - -return Color diff --git a/libs/flexlove/modules/Context.lua b/libs/flexlove/modules/Context.lua deleted file mode 100644 index 10f53015..00000000 --- a/libs/flexlove/modules/Context.lua +++ /dev/null @@ -1,596 +0,0 @@ ----@class Context -local modulePath = (...):match("(.-)[^%.]+$") -local ZIndex = require(modulePath .. "ZIndex") -local Element = require(modulePath .. "Element") -local Context = { - topElements = {}, - -- Base scale configuration - baseScale = nil, -- {width: number, height: number} - -- Current scale factors - scaleFactors = { x = 1.0, y = 1.0 }, - defaultTheme = nil, - _focusedElement = nil, - _focusedElementId = nil, -- Stable id used to rehydrate focus across immediate-mode frames - _activeEventElement = nil, - _cachedViewport = { width = 0, height = 0 }, - -- Immediate mode state - _immediateMode = false, - _frameNumber = 0, - _currentFrameElements = {}, - _immediateModeState = nil, -- Will be initialized if immediate mode is enabled - _frameStarted = false, - _autoBeganFrame = false, - -- Z-index ordered element tracking for immediate mode - _zIndexOrderedElements = {}, -- Array of elements sorted by z-index (lowest to highest) - -- Focus management guard - _settingFocus = false, - -- Hook called whenever focus changes: function(element) or nil - _onFocusChanged = nil, - - -- Navigation state - _navigationContext = { - lastFocusedElement = nil, -- For returning from modals - navigationMode = "sequential", -- "sequential" or "directional" - containerElement = nil, -- Current navigation container - }, - - initialized = false, - - -- Expose internal hit-testing helpers for unit testing only. - -- These are populated below after their local definitions. They are NOT part - -- of the public API and must not be relied on by callers; they exist so the - -- shared hit-test core (the single place display:none guarding lives) can be - -- exercised directly by the test suite. Subsequent unified-event-routing - -- tasks consume these locals through the mode-agnostic query functions. - _test = { - pointHitsElement = nil, - elementHasScrollableOverflow = nil, - }, - - -- Debug draw overlay - _debugDraw = false, - _debugDrawKey = nil, - - -- Initialization state tracking - ---@type "uninitialized"|"initializing"|"ready" - _initState = "uninitialized", - ---@type table[] Queue of {props: ElementProps, callback: function(element)|nil} - _initQueue = {}, - - -- Per-frame cache for findInteractiveAtPosition so Clickable.onUpdate's - -- per-element call (unified-event-routing task 05) doesn't re-walk the tree - -- + realloc + sort for every interactive element sharing the same cursor. - -- Invalidated explicitly by Context.clearInteractiveCache() at the start of - -- each flexlove.update (both modes) and in clearFrameElements (immediate - -- mid-frame rebuild). It also self-invalidates when the topElements table - -- reference changes (tests replace it per-case; immediate-mode beginFrame - -- reassigns it each frame), so direct callers that never go through - -- flexlove.update still see fresh results across tree swaps. - _interactiveLookupCache = { - valid = false, - x = nil, - y = nil, - result = nil, - topElementsRef = nil, - frameNumber = -1, - }, -} - ---- Check if a point hits an element, accounting for scroll offsets and display:none. ---- All mode-agnostic query functions use this as their single hit-test entry point, ---- ensuring fixes like display:none guarding apply everywhere. ---- ---- This is the single canonical place where `element.display == false` short- ---- circuits hit testing. Parent-chain clipping/scroll-offset accumulation is ---- the caller's responsibility: callers walk the parent chain (using ---- `elementHasScrollableOverflow` to decide which ancestors clip) and pass the ---- accumulated scroll offset in here. Keeping the parent walk outside this core ---- lets retained-mode (recursive tree descent) and immediate-mode (flat ---- z-index list) callers share the exact same primitive bounds/display logic. ----@param element Element ----@param mx number Screen X coordinate ----@param my number Screen Y coordinate ----@param scrollOffsetX number? Accumulated scroll offset from parent chain ----@param scrollOffsetY number? Accumulated scroll offset from parent chain ----@return boolean hits -local function pointHitsElement(element, mx, my, scrollOffsetX, scrollOffsetY) - scrollOffsetX = scrollOffsetX or 0 - scrollOffsetY = scrollOffsetY or 0 - - -- Skip display:none elements entirely - if element.display == false then - return false - end - - local bx = element.x - local by = element.y - local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom) - - local adjustedX = mx + scrollOffsetX - local adjustedY = my + scrollOffsetY - - return adjustedX >= bx and adjustedX <= bx + bw and adjustedY >= by and adjustedY <= by + bh -end - ---- Check if an element has scrollable/clipped overflow (for scroll offset accumulation). ---- Returns true for `scroll`, `auto`, and `hidden` on either axis. These are the ---- overflow values that clip/translate descendant content and therefore require ---- scroll-offset compensation when hit testing descendants. ----@param element Element ----@return boolean -local function elementHasScrollableOverflow(element) - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - return overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" - or overflowX == "hidden" - or overflowY == "hidden" -end - --- Expose the two core helpers for unit testing only (see Context._test above). -Context._test.pointHitsElement = pointHitsElement -Context._test.elementHasScrollableOverflow = elementHasScrollableOverflow - --- Public exposure of the canonical hit-test primitive so other modules --- (e.g. FlexLove's `getElementAtPosition` / `_getTouchElementAtPosition` --- tree walks) can share the single implementation of bounds + display:none --- guarding instead of duplicating the `display == false` check inline. --- This keeps "display == false" in exactly one place for hit-testing. -Context.pointHitsElement = pointHitsElement -Context.elementHasScrollableOverflow = elementHasScrollableOverflow - ---- Find the first scrollable element at a screen position, regardless of mode. ---- This is the mode-agnostic successor to the two duplicated scrollable lookups ---- that previously lived inline in `flexlove.wheelmoved`: ---- * immediate mode — walked `Context._zIndexOrderedElements` in reverse and ---- re-implemented bounds + parent-chain clipping + scroll-offset math; and ---- * retained mode — recursed through `Context.topElements` with a private ---- `findScrollableAtPosition(elements, x, y)` helper. ---- Both paths now collapse into this single function, which routes every ---- hit test through `pointHitsElement` (the single place `display == false` ---- is guarded) and every scroll-offset decision through ---- `elementHasScrollableOverflow`. As a result display:none elements are never ---- returned in either mode, fixing the latent bug where the immediate-mode ---- path's `isPointInElement` did not skip display:none elements. ---- ---- The retained-mode branch intentionally mirrors the original ---- `findScrollableAtPosition` helper's tree walk (deepest scrollable wins, ---- children checked before self) but is upgraded to thread accumulated scroll ---- offsets through `pointHitsElement` so nested scrolled containers are tested ---- against their visible position. The original helper is removed once ---- `flexlove.wheelmoved` is rerouted onto this function in task 04. ----@param x number Screen X coordinate ----@param y number Screen Y coordinate ----@return Element|nil The scrollable element, or nil -function Context.findScrollableAtPosition(x, y) - if Context.isImmediateMode() then - -- Immediate mode: iterate the z-index ordered list (reverse order = - -- topmost first). pointHitsElement supplies the bounds + display guard. - for i = #Context._zIndexOrderedElements, 1, -1 do - local element = Context._zIndexOrderedElements[i] - if pointHitsElement(element, x, y) then - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - if - (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") - and (element._overflowX or element._overflowY) - then - return element - end - end - end - return nil - else - -- Retained mode: recursive tree walk from topElements. Children are - -- checked before self (deepest scrollable wins); accumulated scroll - -- offsets are threaded through pointHitsElement so descendants of - -- scrolled containers are hit-tested against their translated position. - local function findInTree(elements, scrollOffsetX, scrollOffsetY) - scrollOffsetX = scrollOffsetX or 0 - scrollOffsetY = scrollOffsetY or 0 - for i = #elements, 1, -1 do - local element = elements[i] - if pointHitsElement(element, x, y, scrollOffsetX, scrollOffsetY) then - if #element.children > 0 then - local childScrollOffsetX = scrollOffsetX - local childScrollOffsetY = scrollOffsetY - if elementHasScrollableOverflow(element) then - childScrollOffsetX = childScrollOffsetX + (element._scrollX or 0) - childScrollOffsetY = childScrollOffsetY + (element._scrollY or 0) - end - local childResult = findInTree(element.children, childScrollOffsetX, childScrollOffsetY) - if childResult then - return childResult - end - end - -- No descendant was scrollable — check self. - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - if - (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") - and (element._overflowX or element._overflowY) - then - return element - end - end - end - return nil - end - return findInTree(Context.topElements) - end -end - ---- Check whether immediate mode is active. ---- This is the single canonical accessor for the mode flag consumed throughout ---- the framework. Mode-aware branches elsewhere call this instead of reading ---- `Context._immediateMode` directly, so the literal mode flag only appears ---- here (its definition) and in StateManager (its mirrored storage) — never ---- scattered across Element / behaviors / managers (behavior-mode-unification ---- task 11). ----@return boolean -function Context.isImmediateMode() - return Context._immediateMode -end - ----@return number, number -- scaleX, scaleY -function Context.getScaleFactors() - return Context.scaleFactors.x, Context.scaleFactors.y -end - ---- Register an element in the z-index ordered tree (for immediate mode) ----@param element Element The element to register -function Context.registerElement(element) - if not Context.isImmediateMode() then - return - end - - table.insert(Context._zIndexOrderedElements, element) -end - -function Context.clearFrameElements() - Context._zIndexOrderedElements = {} - Context.clearInteractiveCache() -end - ---- Compute the composite z-index key for an element. ---- rootZ * ROOT_WEIGHT + depth * DEPTH_WEIGHT + ownZ ---- ---- ROOT_WEIGHT (10^10) gives the top-level ancestor's z-index 10 digits of significance. ---- DEPTH_WEIGHT (10^3) gives nesting depth 3 digits, ensuring children always sort above ---- their ancestors. The element's own z (capped to ±999 by ZIndex.clamp) fits within the ---- remaining 3 digits without interfering with the depth component. ---- ---- These weights assume |z| <= ZIndex.MAX_Z and practical tree depths (< 10^7), which ---- keeps the composite key well within Lua's exact integer range (2^53 ≈ 9 × 10^15). ---- ---- This is the SINGLE canonical z-index ordering function, used by both ---- sortElementsByZIndex (the immediate-mode flat list sort) and ---- findInteractiveAtPosition (the mode-agnostic occlusion sort). Keeping them ---- on the same key ensures the interactive topmost element matches the visual ---- draw order — a button in a z=50 MainMenu window must occlude a button in a ---- z=0 BottomBar even when both buttons default to own z=0. -local function getEffectiveZIndex(elem) - local ownZ = elem.z or 0 - local rootZ = ownZ - local depth = 0 - local current = elem.parent - while current do - rootZ = current.z or 0 - depth = depth + 1 - current = current.parent - end - return rootZ * ZIndex.ROOT_WEIGHT + depth * ZIndex.DEPTH_WEIGHT + ownZ -end - --- Public exposure so FlexLove.getElementAtPosition shares the single --- implementation instead of duplicating the parent-chain walk as a closure. -Context.getEffectiveZIndex = getEffectiveZIndex - ---- Sort elements by z-index (called after all elements are registered) -function Context.sortElementsByZIndex() - -- Precompute the composite key ONCE per element so the sort comparator is a - -- pure table lookup (O(1)) instead of re-walking the parent chain on every - -- O(N log N) comparison. This function runs every frame in immediate mode. - local elements = Context._zIndexOrderedElements - local zIndices = {} - for i = 1, #elements do - zIndices[elements[i]] = getEffectiveZIndex(elements[i]) - end - table.sort(elements, function(a, b) - return zIndices[a] < zIndices[b] - end) -end - ---- Find the topmost interactive element at a screen position, regardless of mode. ---- Replaces the former immediate-mode-only `Context.getTopElementAt()` (removed ---- in unified-event-routing task 05) and the retained-mode `_activeEventElement` ---- mechanism — both are now funneled through this single entry point. ---- ---- In immediate mode this replaces Context.getTopElementAt() (which only worked ---- in immediate mode). In retained mode this provides the same role as the ---- _activeEventElement set by flexlove.getElementAtPosition(). ---- ---- An element is "interactive" if it has an onEvent handler, themeComponent, or is editable. ----@param x number Screen X coordinate ----@param y number Screen Y coordinate ----@return Element|nil The topmost interactive element, or nil -function Context.findInteractiveAtPosition(x, y) - -- Per-frame cache: Clickable.onUpdate runs this for every interactive - -- element under the same cursor, but the result for a given (x,y) is - -- identical across all of them within a single update pass. Returning a - -- cached element restores the old 1x/frame cost of the _activeEventElement - -- mechanism that task 05 replaced. Cache auto-invalidates when the - -- topElements table reference changes (so tests and mid-frame rebuilds get - -- fresh results) and is cleared explicitly per-frame in flexlove.update. - local cache = Context._interactiveLookupCache - if - cache.valid - and cache.x == x - and cache.y == y - and cache.topElementsRef == Context.topElements - and cache.frameNumber == Context._frameNumber - then - return cache.result - end - - local interactiveCandidates = {} - - local function collectInteractive(element, scrollOffsetX, scrollOffsetY) - scrollOffsetX = scrollOffsetX or 0 - scrollOffsetY = scrollOffsetY or 0 - - if not pointHitsElement(element, x, y, scrollOffsetX, scrollOffsetY) then - return - end - - -- Check if this element is interactive - if element.onEvent or element.themeComponent or element.editable then - table.insert(interactiveCandidates, element) - end - - -- Recurse into children with accumulated scroll offset - local childScrollOffsetX = scrollOffsetX - local childScrollOffsetY = scrollOffsetY - if elementHasScrollableOverflow(element) then - childScrollOffsetX = childScrollOffsetX + (element._scrollX or 0) - childScrollOffsetY = childScrollOffsetY + (element._scrollY or 0) - end - - for _, child in ipairs(element.children) do - collectInteractive(child, childScrollOffsetX, childScrollOffsetY) - end - end - - -- Always traverse the tree (works in both modes — topElements exists always) - for _, element in ipairs(Context.topElements) do - collectInteractive(element) - end - - -- Sort by composite z-index descending — topmost wins. The composite key - -- (rootZ * ROOT_WEIGHT + depth * DEPTH_WEIGHT + ownZ) matches the ordering - -- used by sortElementsByZIndex / _zIndexOrderedElements, so the interactive - -- topmost element matches the visual draw order. This is critical for the - -- game's multi-window layout: a button inside a z=50 MainMenu window must - -- occlude a button inside a z=0 BottomBar even when both buttons default to - -- own z=0. Sorting by own-z alone (the original implementation) couldn't - -- distinguish them, so the wrong window's button could win, leaving the - -- visible button's isActiveElement=false and clicks/hover dead. - local zIndices = {} - for _, el in ipairs(interactiveCandidates) do - zIndices[el] = getEffectiveZIndex(el) - end - table.sort(interactiveCandidates, function(a, b) - return zIndices[a] > zIndices[b] - end) - - local result = interactiveCandidates[1] - - cache.x = x - cache.y = y - cache.result = result - cache.topElementsRef = Context.topElements - cache.frameNumber = Context._frameNumber - cache.valid = true - - return result -end - ---- Invalidate the per-frame `findInteractiveAtPosition` cache. ---- Called once at the top of `flexlove.update` (the natural per-frame boundary ---- in both modes) and from `clearFrameElements` (immediate-mode mid-frame ---- rebuild). After invalidation the next lookup recomputes fresh. -function Context.clearInteractiveCache() - local cache = Context._interactiveLookupCache - cache.valid = false - cache.x = nil - cache.y = nil - cache.result = nil - cache.topElementsRef = nil - cache.frameNumber = -1 -end - ---- Set the focused element (centralizes focus management) ---- Automatically blurs the previously focused element if different ----@param element Element|nil The element to focus (nil to clear focus) -function Context.setFocused(element) - if Context._focusedElement == element then - return -- Already focused - end - - -- Prevent re-entry during focus change - if Context._settingFocus then - return - end - Context._settingFocus = true - - -- Save reference to previously focused element before updating - local oldFocusedElement = Context._focusedElement - - -- Blur previously focused element - if oldFocusedElement and oldFocusedElement ~= element then - if oldFocusedElement._textEditor then - oldFocusedElement._textEditor:blur(oldFocusedElement) - end - end - - -- Set new focused element and persist its id for immediate-mode rehydration - Context._focusedElement = element - Context._focusedElementId = element and (element.id ~= "" and element.id or nil) or nil - - -- Notify any registered focus change hook (e.g. FocusIndicator) - if Context._onFocusChanged then - Context._onFocusChanged(element) - end - - -- Focus the new element's text editor if it has one - if element and element._textEditor then - element._textEditor._focused = true - end - - Context._settingFocus = false -end - ---- Recursively search for an element by id in an element tree ----@param root Element The root element to start searching from ----@param targetId string The id to search for ----@return Element|nil The element with the matching id, or nil if not found -local function findElementById(root, targetId) - if root.id == targetId then - return root - end - for _, child in ipairs(root.children or {}) do - local found = findElementById(child, targetId) - if found then - return found - end - end - return nil -end - ---- Rehydrate _focusedElement from _focusedElementId by scanning live elements. ---- Called at the start of getFocused() in immediate mode so stale references ---- are always replaced with the current-frame object before use. -function Context._rehydrateFocus() - if not Context._focusedElementId then - Context._focusedElement = nil - return - end - - -- First, try a fast linear search through all registered elements - for _, elem in ipairs(Context._zIndexOrderedElements) do - if elem.id == Context._focusedElementId then - Context._focusedElement = elem - return - end - end - - -- If not found, recursively search from top-level elements - -- This handles cases where elements may not be in _zIndexOrderedElements - for _, topLevel in ipairs(Context.topElements or {}) do - local found = findElementById(topLevel, Context._focusedElementId) - if found then - Context._focusedElement = found - return - end - end - - -- Element with that id is not present this frame (e.g. screen changed) - Context._focusedElement = nil -end - ---- Get the currently focused element ----@return Element|nil The focused element, or nil if none -function Context.getFocused() - if Context.isImmediateMode() then - Context._rehydrateFocus() - end - return Context._focusedElement -end - ---- Clear focus from any element -function Context.clearFocus() - Context._focusedElementId = nil - Context.setFocused(nil) -end - ---- Get all focusable elements in tab order, regardless of mode. ---- In immediate mode this extracts from _zIndexOrderedElements (flat, z-sorted). ---- In retained mode it walks the element tree (DOM order). ---- In both modes, display:none elements are excluded. ----@return table List of focusable elements in tab order -function Context.getFocusableElements() - local focusable = {} - - local function isFocusable(elem) - if elem.display == false then - return false - end - -- Use Element:isFocusable() for consistent behavior - return Element.isFocusable(elem) - end - - local function collectFromTree(elements) - for _, elem in ipairs(elements) do - if isFocusable(elem) then - table.insert(focusable, elem) - end - if #elem.children > 0 then - collectFromTree(elem.children) - end - end - end - - if Context._immediateMode then - -- Immediate mode: _zIndexOrderedElements is already in z-index order (lowest first), - -- which approximates tab order for most UIs. - for _, elem in ipairs(Context._zIndexOrderedElements) do - if isFocusable(elem) then - table.insert(focusable, elem) - end - end - else - -- Retained mode: walk the top element trees in DOM order - collectFromTree(Context.topElements) - end - - return focusable -end - --- ==================== --- Navigation Context --- ==================== - ---- Push current focus onto stack (for modals/dialogs) ----@param element Element? -function Context.pushFocusStack(element) - Context._navigationContext.lastFocusedElement = Context._focusedElement - if element then - Context.setFocused(element) - end -end - ---- Pop focus from stack (return from modal) ----@return Element? -function Context.popFocusStack() - local previous = Context._navigationContext.lastFocusedElement - Context._navigationContext.lastFocusedElement = nil - Context.setFocused(previous) - return previous -end - ---- Set navigation container (scope for tab navigation) ----@param element Element? -function Context.setNavigationContainer(element) - Context._navigationContext.containerElement = element -end - ---- Get navigation container ----@return Element? -function Context.getNavigationContainer() - return Context._navigationContext.containerElement -end - -return Context diff --git a/libs/flexlove/modules/Element.lua b/libs/flexlove/modules/Element.lua deleted file mode 100644 index 5a2da8db..00000000 --- a/libs/flexlove/modules/Element.lua +++ /dev/null @@ -1,3904 +0,0 @@ ----@class Element ----@field id string ----@field children Element[] ----@field parent Element|nil ----@field userdata any|nil ----@field onEvent fun(self: Element, event: table)|nil ----@field onEventDeferred boolean|nil ----@field onFocus fun(self: Element)|nil ----@field onFocusDeferred boolean ----@field dropFocusOnSelection boolean|nil ----@field onBlur fun(self: Element)|nil ----@field onBlurDeferred boolean ----@field onTextInput fun(self: Element, text: string)|nil ----@field onTextInputDeferred boolean ----@field onTextChange fun(self: Element, text: string)|nil ----@field onTextChangeDeferred boolean ----@field onEnter fun(self: Element)|nil ----@field onEnterDeferred boolean ----@field customDraw fun(self: Element)|nil ----@field onTouchEvent fun(self: Element, event: table)|nil ----@field onTouchEventDeferred boolean ----@field onGesture fun(self: Element, gesture: table)|nil ----@field onGestureDeferred boolean ----@field touchEnabled boolean ----@field multiTouchEnabled boolean ----@field theme table|nil ----@field themeComponent string|nil ----@field disabled boolean ----@field active boolean ----@field disableHighlight boolean ----@field contentAutoSizingMultiplier number[]|nil ----@field scaleCorners boolean|nil ----@field scalingAlgorithm string|nil ----@field contentBlur {radius:number, quality?:number}|nil ----@field backdropBlur {radius:number, quality?:number}|nil ----@field editable boolean ----@field multiline boolean ----@field passwordMode boolean ----@field textWrap string|boolean ----@field maxLines number|nil ----@field maxLength number|nil ----@field placeholder string|nil ----@field inputType string ----@field textOverflow string ----@field scrollable boolean ----@field autoGrow boolean ----@field selectOnFocus boolean ----@field cursorColor Color|nil ----@field selectionColor Color|nil ----@field cursorBlinkRate number ----@field selectParent Element|nil ----@field selectOption table|nil ----@field onChange fun(self: Element, value: any, option: Element)|nil ----@field border number|table|nil ----@field borderColor Color ----@field backgroundColor Color ----@field opacity number ----@field visibility string ----@field display boolean ----@field transform table|nil ----@field cornerRadius number|{topLeft:number, topRight:number, bottomLeft:number, bottomRight:number}|nil ----@field text string|nil ----@field textAlign string|table|nil ----@field textAlignHorizontal string ----@field textAlignVertical string ----@field imagePath string|nil ----@field image table|nil ----@field objectFit string ----@field objectPosition string ----@field imageOpacity number ----@field imageRepeat string ----@field imageTint Color|nil ----@field onImageLoad fun(self: Element, image: table)|nil ----@field onImageLoadDeferred boolean ----@field onImageError fun(self: Element, err: string)|nil ----@field onImageErrorDeferred boolean ----@field prevGameSize {width:number, height:number} ----@field autosizing {width:boolean, height:boolean} ----@field units table ----@field minTextSize number|nil ----@field maxTextSize number|nil ----@field autoScaleText boolean ----@field fontFamily string|nil ----@field textSize number ----@field width number ----@field height number ----@field x number ----@field y number ----@field z number ----@field gap number ----@field flexGrow number ----@field flexShrink number ----@field flexBasis number|string ----@field padding {top:number, right:number, bottom:number, left:number} ----@field margin {top:number, right:number, bottom:number, left:number} ----@field tabIndex number|nil ----@field textColor Color ----@field positioning string ----@field top number|nil ----@field right number|nil ----@field bottom number|nil ----@field left number|nil ----@field flexDirection string|nil ----@field flexWrap string|nil ----@field justifyContent string|nil ----@field alignItems string|nil ----@field alignContent string|nil ----@field justifySelf string|nil ----@field alignSelf string ----@field gridRows number|nil ----@field gridColumns number|nil ----@field columnGap number|nil ----@field rowGap number|nil ----@field transition table ----@field transitions table|nil ----@field animation table|nil ----@field overflow string|nil ----@field overflowX string|nil ----@field overflowY string|nil ----@field scrollbarWidth number|nil ----@field scrollbarColor Color|nil ----@field scrollbarBackgroundColor Color|nil ----@field scrollbarTrackColor Color|nil ----@field scrollbarRadius number|nil ----@field scrollbarPadding number|nil ----@field scrollSpeed number|nil ----@field invertScroll boolean|nil ----@field scrollBarStyle string|nil ----@field scrollbarKnobOffset number|nil ----@field hideScrollbars boolean|nil ----@field scrollbarPlacement string|nil ----@field scrollbarBalance number|nil ----@field borderWidth number|nil ----@field fontSize number|nil ----@field lineHeight number|nil -local Element = {} -Element.__index = Element - --- Forward declarations for the special-handler binding helpers used by --- Element:_applyProps (behavior-mode-unification task 08 capstone). These --- absorb the former subsystem-init and visual-state phase bodies (ThemeManager --- creation + theme-field exposure + editable/text/scroll/autoGrow/select-field --- defaults + parent assignment, and border/cornerRadius/display/text/textAlign --- normalization). They are now private implementation of the props-binding phase --- rather than standalone Element methods, so there are no longer per-capability --- init phases on Element. -local bindThemeAndFields, bindVisualState - --- NOTE: There is intentionally NO custom Element.__newindex for dimension properties. --- Lua's __newindex fires ONLY when the key is ABSENT from the raw table, but width/ --- height/x/y are all assigned during Element.new, so they already exist post- --- construction. A __newindex handler therefore CANNOT intercept retained-mode bare --- writes like `element.width = "42%"` (it just rawsets the broken string). --- Dimensions are instead validated lazily in Element:_checkDimensionTypes() at the --- start of each reflow, and must be changed via :setProperty() for resolution + --- layout invalidation. Keeping the metatable free of __newindex also avoids a --- per-field-write function call on every absent-key assignment (perf). - -local MAX_DEFER_RETRIES = 10 -local MAX_DEFERRED_METHODS = 100 -local _DEFERRED_NIL = {} -local unpack = table.unpack or unpack - ----Initialize Element module with required dependencies ----@param deps table Dependency table containing all required modules -function Element.init(deps) - Element._ErrorHandler = deps.ErrorHandler - Element._Color = deps.Color - Element._Context = deps.Context - Element._Units = deps.Units - Element._Calc = deps.Calc - Element._utils = deps.utils - Element._InputEvent = deps.InputEvent - Element._EventHandler = deps.EventHandler - Element._Renderer = deps.Renderer - Element._LayoutEngine = deps.LayoutEngine - Element._TextEditor = deps.TextEditor - Element._ScrollManager = deps.ScrollManager - Element._Theme = deps.Theme - Element._RoundedRect = deps.RoundedRect - Element._NinePatch = deps.NinePatch - Element._ImageRenderer = deps.ImageRenderer - Element._ImageCache = deps.ImageCache - Element._ImageScaler = deps.ImageScaler - Element._Blur = deps.Blur - Element._Transform = deps.Transform - Element._Grid = deps.Grid - Element._StateManager = deps.StateManager - Element._GestureRecognizer = deps.GestureRecognizer - Element._Performance = deps.Performance - Element._Animation = deps.Animation - Element._ZIndex = deps.ZIndex - Element._Select = deps.Select - Element._PropertySchema = deps.PropertySchema or require("modules.PropertySchema") - Element._Select.init({ - ErrorHandler = Element._ErrorHandler, - Context = Element._Context, - StateManager = Element._StateManager, - utils = Element._utils, - Element = Element, - }) - Element._ScrollManager.init({ - ErrorHandler = Element._ErrorHandler, - Context = Element._Context, - StateManager = Element._StateManager, - }) - - -- Bind Element scroll/scrollbar API directly onto ScrollManager. - -- ScrollManager owns all scroll interaction logic; Element retains only - -- 1-line delegates (no hand-written sync/nil-guard boilerplate). - local SM = Element._ScrollManager - Element._syncScrollManagerState = SM.syncToElement - Element._detectOverflow = SM._detectOverflow - Element.setScrollPosition = SM.setScrollPosition - Element._calculateScrollbarDimensions = SM._calculateScrollbarDimensions - Element._getScrollbarAtPosition = SM._getScrollbarAtPosition - Element._handleScrollbarPress = SM._handleScrollbarPress - Element._handleScrollbarDrag = SM._handleScrollbarDrag - Element._handleScrollbarRelease = SM._handleScrollbarRelease - Element._handleWheelScroll = SM._handleWheelScroll - Element.getScrollPosition = SM.getScrollPosition - Element.getMaxScroll = SM.elementGetMaxScroll - Element.getScrollPercentage = SM.elementGetScrollPercentage - Element.hasOverflow = SM.elementHasOverflow - Element.getContentSize = SM.elementGetContentSize - Element.scrollBy = SM.elementScrollBy - Element.scrollToTop = SM.scrollToTop - Element.scrollToBottom = SM.scrollToBottom - Element.scrollToLeft = SM.scrollToLeft - Element.scrollToRight = SM.scrollToRight - - -- Hoist subsystem dependency tables: created once at init time, not rebuilt - -- per Element.new() call. Staged initializers reference these directly. - Element._eventHandlerDeps = { - InputEvent = Element._InputEvent, - Context = Element._Context, - utils = Element._utils, - } - - -- Behavior registry (behavior-mode-unification). Concrete behaviors live in - -- modules/behaviors/ and auto-attach during Element.new when their - -- shouldAttach(props) predicate returns true. Element.update/draw/save-restore - -- dispatch over `element.behaviors` instead of branching on capability flags. - -- Registry order matters for onDraw layering: Themed (core Renderer:draw) must - -- run before Clickable (pressed overlay) so pressed feedback paints on top. - -- Imageable (image config) runs last. Animated (task 06) is late-attach-only. - -- Task 02 wires Clickable; task 05 Selectable; task 06 Animated; task 07 - -- Themed + Imageable; task 04 TextEditable (cursor blink + TextEditor - -- ownership + the 27 text-delegate forwarders). Scrollable is pending. - Element._behaviorRegistry = deps.behaviors or deps.clickableBehaviors or {} - -- TextEditable module reference: Element's 1-line text-delegate forwarders - -- route through `Element._TextEditable.(self, ...)` (task 04). Resolved - -- from deps (wired by FlexLove alongside the behavior registry) so minimal - -- builds without TextEditable leave forwarders inert (guarded by their - -- callers / the behavior's nil-checks). - Element._TextEditable = deps.TextEditable - -- Cached lookup of the Animated behavior instance for late-attach. Resolved - -- lazily (behaviors are optional in minimal builds) the first time an - -- animation is created on an element. - Element._animatedBehavior = nil - Element._rendererDeps = { - Color = Element._Color, - RoundedRect = Element._RoundedRect, - NinePatch = Element._NinePatch, - ImageRenderer = Element._ImageRenderer, - ImageCache = Element._ImageCache, - Theme = Element._Theme, - Blur = Element._Blur, - Transform = Element._Transform, - utils = Element._utils, - } - Element._layoutEngineDeps = { - utils = Element._utils, - Grid = Element._Grid, - Units = Element._Units, - Context = Element._Context, - ErrorHandler = Element._ErrorHandler, - } - Element._textEditorDeps = { - Context = Element._Context, - StateManager = Element._StateManager, - Color = Element._Color, - utils = Element._utils, - } - Element._scrollManagerDeps = { - utils = Element._utils, - Color = Element._Color, - } -end - --- Module-level helper: resolve a dimensional property with CSS-like unit support (px, %, vw, vh, calc) --- Defined once (not inside new()) to avoid per-element closure allocation. --- Handles parsing, defensive checks, and storage in both self and self.units tables. ----@param self table Element instance ----@param raw any Raw property value (string, number, CalcObject, or nil) ----@param key string Field name on self and self.units (e.g., "width", "x") ----@param ref number Reference dimension for percentage resolution ----@param ctx {vw:number, vh:number, sx:number, sy:number} Viewport and scale context ----@param opts {offset?: number, scaleAxis?: "x"|"y", default?: number, nullable?: boolean}? ----@return number? resolved Resolved pixel value (or nil if opts.nullable and input is missing/invalid) -local function _resolveUnit(self, raw, key, ref, ctx, opts) - opts = opts or {} - if raw == nil then - if opts.nullable then - return nil - end - local default = opts.default or 0 - self[key] = (opts.offset or 0) + default - self.units[key] = { value = default, unit = "px" } - return self[key] - end - local isCalc = Element._Calc and Element._Calc.isCalc(raw) - if type(raw) == "string" or isCalc then - local value, unit = Element._Units.parse(raw) - local resolved = Element._Units.resolve(value, unit, ctx.vw, ctx.vh, ref) - if type(resolved) ~= "number" then - if opts.nullable then - return nil - end - Element._ErrorHandler:warn("Element", "LAY_003", { - issue = key .. " resolution returned non-number value", - type = type(resolved), - value = tostring(resolved), - }) - resolved = 0 - end - self.units[key] = { value = value, unit = unit } - self[key] = (opts.offset or 0) + resolved - else - local val = raw - if opts.scaleAxis and Element._Context.baseScale then - val = raw * (opts.scaleAxis == "x" and ctx.sx or ctx.sy) - end - self[key] = (opts.offset or 0) + val - self.units[key] = { value = raw, unit = "px" } - end - return self[key] -end - --- Module-level helper: re-resolve a stored unit spec against a new viewport/parent reference. --- Used by resize() to refresh min/max constraints declared with %/vw/vh units. -local function _refreshUnit(self, key, ref, ctx, scaleAxis) - local u = self.units[key] - if not u or u.value == nil then - return - end - if u.unit == "px" then - self[key] = Element._Context.baseScale and (u.value * (scaleAxis == "x" and ctx.sx or ctx.sy)) or u.value - return - end - local resolved = Element._Units.resolve(u.value, u.unit, ctx.vw, ctx.vh, ref) - self[key] = type(resolved) == "number" and resolved or nil -end - --- --------------------------------------------------------------------------- --- Consolidated warn helpers (Task 11). --- Each duplicated "expecting X, got Y" / guard instrumentation block lived at --- its own use site; these single-reference helpers centralize the emit so call --- sites are thin invocations. Validation semantics (warn+fallback vs. throw) are --- preserved exactly — instrumentation is consolidated, not deleted. --- --------------------------------------------------------------------------- - --- Emit a VAL_001 invalid-enum warn for a textAlign sub-field and return the --- fallback. textAlign's schema entry is type "any" (string | table | compound), --- so this IS the boundary validator for the 4 textAlign parse branches in the --- props-phase visual-state helper. -local function _warnTextAlign(field, expected, got, fallback) - Element._ErrorHandler:warn("Element", "VAL_001", { - property = field, - expected = expected, - got = tostring(got), - }) - return fallback -end - --- Emit a FLEX_00x warn for an invalid flexGrow/flexShrink/flexBasis and return --- the fallback value. These props are SPECIAL_PROPS (warn+fallback, not throw) --- so this is their boundary validator. -local function _warnFlexInvalid(self, code, issue, value, fallback) - Element._ErrorHandler:warn("Element", code, { - element = self.id or "unnamed", - issue = issue, - value = tostring(value), - }) - return fallback -end - --- Emit an ELEM_010/011/012 warn for malformed declarative children entries. -local function _warnChildrenInvalid(self, code, issue, value) - local details = { element = self.id or "unnamed", issue = issue } - if value ~= nil then - details.value = tostring(value) - end - Element._ErrorHandler:warn("Element", code, details) -end - --- Emit LAY_011 when CSS positioning props (top/right/bottom/left) are supplied --- without absolute positioning. Called from both the no-parent and with-parent --- branches of _initPositioning. -local function _warnCssPositioningWithoutAbsolute(self, props) - local properties = {} - if props.top then - table.insert(properties, "top") - end - if props.bottom then - table.insert(properties, "bottom") - end - if props.left then - table.insert(properties, "left") - end - if props.right then - table.insert(properties, "right") - end - Element._ErrorHandler:warn("Element", "LAY_011", { - element = self.id or "unnamed", - positioning = self._originalPositioning or "relative", - properties = table.concat(properties, ", "), - }) -end - --- Emit an ELEM_003/004/005 guard warn for the animation/transition public API --- (deps-missing, non-table arg, invalid duration, non-table property list). --- All warn + fall back rather than throw. `value` nil => warn carries no details. -local function _warnAnimApi(code, value) - if value ~= nil then - Element._ErrorHandler:warn("Element", code, { value = tostring(value) }) - else - Element._ErrorHandler:warn("Element", code) - end -end - --- Image loading + image callback firing now live in the Imageable behavior --- (modules/behaviors/Imageable.lua) — moved out of Element per --- behavior-mode-unification task 07. Element is decoupled from image concern; --- the Imageable behavior enriches `element._renderer` with image config, runs --- the deferred load pipeline, and persists `_loadedImage` across immediate-mode --- frames. The fire-callback helper (formerly `_fireImageCallback` here) is --- reproduced inside Imageable as `fireImageCallback`. - --- --------------------------------------------------------------------------- --- Data-driven prop binding (Task 03) --- --------------------------------------------------------------------------- --- SPECIAL_PROPS is the documented boundary of the schema-driven _applyProps --- loop. Props listed here are bound explicitly by bindThemeAndFields / --- bindVisualState / the staged initializers instead of the generic registry --- loop, for one of five load-bearing reasons (none represent unfinished --- migration — moving them into the generic loop would require extending the --- PropertySchema DSL, which is deliberately kept small and declarative): --- --- 1. SUBSYSTEM ORDERING — the prop needs a subsystem constructed first. --- Theme props (theme/themeComponent/disabled/active/...) depend on the --- ThemeManager being alive so their defaults can be read from it; the --- generic loop runs before subsystem creation in _attachBehaviors. --- --- 2. NON-LITERAL DEFAULTS — the default is not a static value the schema's --- `default:` field can express. borderColor/backgroundColor/textColor --- default to Color.new(...); text defaults to "" only when editable; --- scrollable/autoGrow default from multiline. The schema only stores --- literal defaults (pure-Lua constraint; see PropertySchema.lua header). --- --- 3. UNIT RESOLUTION / VIEWPORT CONTEXT — dimension props (width/height/x/y --- /gap/padding/...) accept unit strings ("50%", "10px") or CalcObjects that --- resolve against parent size and viewport, which a pure-Lua schema cannot --- see. setProperty routes these via the `isDimension` flag at runtime, but --- construction-time binding needs the sizing context from _initSizingContext. --- --- 4. WARN-AND-FALLBACK vs. THROW — display and the flex props validate with a --- non-throwing warn+fallback path; the schema's `validator` field throws --- (VAL_001) for invalid enum/range values. These need their own boundary --- validators (_warnFlexInvalid / the display type-check). --- --- 5. SUBSYSTEM OWNERSHIP — overflow/scrollbar* are owned by ScrollManager, --- selectParent/selectOption by the Select subsystem, border/cornerRadius --- use schema normalizers but bind with a special shape (all-false→nil). --- These props' storage is owned by their subsystem, not the element core. --- --- Adding a new SIMPLE prop requires only a PropertySchema entry; adding a prop --- that needs any of the above additionally requires listing it here and binding --- it in the matching special-handler phase. Props NOT listed here (e.g. --- callbacks, editable, multiline, passwordMode, autoScaleText, cursorColor, --- selectionColor, opacity, visibility, transform, imagePath/objectFit/..., --- minTextSize/maxTextSize, alignSelf, transition) are bound generically by --- _applyProps (defaults + normalizers + validators + onX/onXDeferred --- auto-wiring). -local function _set(...) - local t = {} - for _, name in ipairs({ ... }) do - t[name] = true - end - return t -end - -local SPECIAL_PROPS = _set( - -- identity / tree - "id", - "parent", - "children", - -- theme-driven (ThemeManager owns these / computes defaults) - "theme", - "themeComponent", - "disabled", - "isDisabled", - "active", - "disableHighlight", - "themeStateLock", - "themeComponentDisabledStates", - "scaleCorners", - "scalingAlgorithm", - "contentAutoSizingMultiplier", - -- color defaults that require the Color module - "borderColor", - "backgroundColor", - "textColor", - -- display: non-throwing warn+fallback (unlike throwing range/enum validators) - "display", - -- text editing (validation side-effects / computed defaults) - "textWrap", - "scrollable", - "autoGrow", - "text", - "textAlign", - "textAlignHorizontal", - "textAlignVertical", - "textSize", - "fontFamily", - -- box model / dimensions (unit resolution) - "width", - "height", - "x", - "y", - "z", - "minWidth", - "maxWidth", - "minHeight", - "maxHeight", - "gap", - "top", - "right", - "bottom", - "left", - "columnGap", - "rowGap", - "padding", - "margin", - -- layout enums (positioning-mode validation + LayoutEngine config) - "positioning", - "flexDirection", - "flexWrap", - "justifyContent", - "alignItems", - "alignContent", - "justifySelf", - "gridRows", - "gridColumns", - -- flex shorthand + validated numerics (custom FLEX_xx warnings) - "flex", - "flexGrow", - "flexShrink", - "flexBasis", - -- scroll / scrollbar (ScrollManager owns these) - "overflow", - "overflowX", - "overflowY", - "scrollbarWidth", - "scrollbarColor", - "scrollbarTrackColor", - "scrollbarRadius", - "scrollbarPadding", - "scrollSpeed", - "invertScroll", - "smoothScrollEnabled", - "scrollBarStyle", - "scrollbarKnobOffset", - "hideScrollbars", - "scrollbarPlacement", - "scrollbarBalance", - "_scrollX", - "_scrollY", - -- select (Select subsystem owns these) - "selectParent", - "selectOption", - -- border / cornerRadius use schema normalizers but are bound as special handlers - "border", - "cornerRadius", - -- misc instance-only fields derived during construction - "tabIndex" -) - ---- Bind every schema-driven, side-effect-free prop onto `self` in one pass. ---- Iterates PropertySchema entries: applies defaults, normalizers, validators, ---- and auto-wires `onX` + `onXDeferred` companion pairs. Props listed in ---- SPECIAL_PROPS are skipped (they are handled explicitly in Element.new). ----@param props table Element construction props -function Element:_applyProps(props) - local schema = Element._PropertySchema - local registry = schema.all() - for name, meta in pairs(registry) do - -- Skip deferred companion entries (auto-wired by their base callback's - -- hasDeferred branch below) and SPECIAL_PROPS (handled in Element.new). - if not (name:match("Deferred$") or SPECIAL_PROPS[name]) then - local value = props[name] - if value == nil then - value = meta.default - end - if meta.normalizer then - value = meta.normalizer(value) - end - if meta.validator and value ~= nil and not meta.validator(value) then - -- Mirror the legacy throwing validateRange/validateEnum behavior: invalid - -- enum/range values error during construction (validated props: opacity, - -- imageOpacity, objectFit, imageRepeat). display is a special handler that - -- warns + falls back instead. - Element._ErrorHandler:error("Element", "VAL_001", { - property = name, - expected = meta.type, - got = tostring(value), - }) - end - local key = meta.storageKey or name - self[key] = value - -- Auto-wire deferred companion for callbacks that declare hasDeferred. - if meta.hasDeferred then - local deferredName = name .. "Deferred" - local deferredValue = props[deferredName] - self[deferredName] = deferredValue ~= nil and deferredValue or false - end - end - end - - -- Special-handler binding (behavior-mode-unification task 08 capstone): the - -- props below need side-effects, ordering relative to subsystems, non-literal - -- defaults, or unit resolution, so they cannot be bound by the generic schema - -- loop above. The two helpers below fold in the former subsystem-init phase - -- (ThemeManager creation + theme-field exposure + editable/multiline/ - -- passwordMode validation + textWrap/scrollable/autoGrow defaults + - -- selectParent/selectOption/_selectState + parent assignment) and visual-state - -- phase (border/cornerRadius/display/text/textAlign normalization). Subsystem - -- CREATION (EventHandler / TextEditor / ScrollManager / Renderer) is owned by - -- behavior onAttach hooks dispatched in _attachBehaviors; these helpers only - -- bind FIELDS that the core sizing/box/positioning phases and the behavior - -- onAttach hooks read. - bindThemeAndFields(self, props) - bindVisualState(self, props) -end - ----@param props ElementProps ----@return Element ---- Construct a new Element. Orchestrator only; real work is in the staged ---- initializers below (Task 10). No single phase exceeds ~400 LOC. -function Element.new(props) - -- Staged initializers (behavior-mode-unification task 08 capstone). The - -- orchestrator is a thin dispatcher: it runs core-data phases only - -- (construct → props → sizing → box model → positioning → finalize), then - -- attaches behaviors. The former behavioral phases (subsystem-init, visual- - -- state, image/renderer, scroll-manager) are deleted: their field-binding logic - -- folded into _applyProps and their subsystem creation logic moved into - -- behavior onAttach hooks (Clickable / TextEditable / Selectable / Themed / - -- Imageable / Scrollable). _attachBehaviors runs at the tail so - -- Selectable.onAttach can re-scan declarative children built by - -- _finalizeConstruction and so onAttach sees all element fields bound. - local self = Element:_construct(props) - self:_applyProps(props) - self:_initSizingContext(props) - self:_initBoxModel(props) - self:_initPositioning(props) - self:_finalizeConstruction(props) - self:_attachBehaviors(props) - return self -end - ---- Phase 1: metatable, schema-driven prop normalization (for special-handler ---- props), default tables (children, _deferredMethods), and ID generation. ---- Schema-driven binding (_applyProps) is invoked separately by Element.new. -function Element:_construct(props) - local instance = setmetatable({}, Element) - - -- Stash the construction props so behavior onAttach hooks (which receive only - -- the element per the locked `(element, ...)` signature) can read SPECIAL_PROPS - -- config that is NOT bound onto the element by the schema-driven _applyProps - -- loop (e.g. the scrollbar config consumed by Scrollable.onAttach). Prefixed - -- with `_` so the immediate-mode saveState public-prop scan skips it. - instance._initProps = props - - -- Apply schema-driven shape normalizers to props for the special-handler - -- props (padding/margin/flexDirection) whose downstream unit-resolution logic - -- reads from `props` directly. Generic props are bound by _applyProps below. - local schema = Element._PropertySchema - props.flexDirection = schema.get("flexDirection").normalizer(props.flexDirection) - props.padding = schema.get("padding").normalizer(props.padding) - props.margin = schema.get("margin").normalizer(props.margin) - - -- Behavior registry (Task 01 of behavior-mode-unification). Concrete - -- behaviors (Clickable, Scrollable, ...) are attached here in later tasks; - -- Element:update/draw/save-restore dispatch over this table instead of - -- branching on individual capability flags. Initially empty so existing - -- behavior is identical to pre-refactor until behaviors are wired in. - instance.children = {} - instance.behaviors = {} - instance._deferredMethods = {} - - -- Track whether ID was auto-generated (before ID assignment) - local idWasAutoGenerated = not props.id or props.id == "" - - -- Auto-generate ID if not provided (for all elements) - if idWasAutoGenerated then - instance.id = Element._StateManager.generateID(props, props.parent) - else - instance.id = props.id - end - - -- Initialize state manager ID for immediate mode (use self.id which may be auto-generated) - instance._stateId = instance.id - - -- Register with StateManager for state access (both immediate and retained modes) - if instance._stateId and instance._stateId ~= "" then - Element._StateManager.registerStateful(instance._stateId, instance) - end - return instance -end - ---- Attach behaviors whose shouldAttach(props) predicate matches this element's ---- props. Runs after prop binding + subsystem init (so Deferred flags and the ---- Select subsystem are in place) and dispatches onAttach for each match. The ---- EventHandler (Clickable) is created here rather than in the former subsystem- ---- init phase so Element never needs to know what an individual behavior does — ---- it only iterates the registry (behavior-mode-unification task 02). -function Element:_attachBehaviors(props) - local registry = Element._behaviorRegistry - if registry then - for _, behavior in ipairs(registry) do - if behavior.shouldAttach(props) then - table.insert(self.behaviors, behavior) - behavior.onAttach(self) - end - end - end -end - ---- Resolve the (lazily cached) Animated behavior instance from the registry. --- task 09: since the behavior loop no longer excludes Animated, elements WITH --- the behavior attached get the loop dispatch and `_dispatchAnimatedUpdate` no-ops --- for them. --- task 09 consolidation: `_ensureAnimatedAttached` / `_isAnimatedBehavior` --- (dead code) were removed; Animated.ensureAttached remains the late-attach --- entry point for callers that route through it. -function Element._resolveAnimatedBehavior() - local animated = Element._animatedBehavior - if animated == nil then - local registry = Element._behaviorRegistry - if registry then - for _, behavior in ipairs(registry) do - -- Animated exposes ensureAttached; Clickable/Themed/Imageable do not. - if type(behavior.ensureAttached) == "function" then - animated = behavior - break - end - end - end - Element._animatedBehavior = animated or false - end - return animated -end - ---- Dispatch Animated.onUpdate for an element EARLY in Element:update (before ---- the behavior loop) so animated geometry (x/y/width/height) is current for ---- Clickable hit-testing and Scrollable interaction this frame. NO-OPS for ---- elements that already have the Animated behavior attached (the loop ---- dispatches those) to avoid a double update — animation:update(dt) is not ---- idempotent within a frame. This handles the direct-assignment path ---- (`element.animation = ...` / `anim:apply`) that bypasses ---- Animated.ensureAttached; for elements with no animation the behavior's ---- onUpdate reads `element.animation` and returns. Not a behavioral capability ---- branch — iterates `element.behaviors`. (behavior-mode-unification task 09.) -function Element._dispatchAnimatedUpdate(element, dt) - if not element then - return - end - local animated = Element._resolveAnimatedBehavior() - if not animated then - return - end - -- Already attached? The behavior loop will dispatch it; bail to avoid a - -- double update (animation:update advances twice if called twice). - local behaviors = element.behaviors - if behaviors then - for i = 1, #behaviors do - if behaviors[i] == animated then - return - end - end - end - animated.onUpdate(element, dt) -end - ---- Special-handler binding helper for the props phase (behavior-mode- ---- unification task 08). Formerly the Element subsystem-init phase. Binds the ---- ThemeManager (or no-op fallback) + exposes theme fields, validates ---- editable/multiline/passwordMode combos, sets textWrap/scrollable/autoGrow ---- defaults, initializes selectParent/selectOption/_selectState fields (the ---- Select subsystem itself is initialized by Selectable.onAttach), and assigns ---- self.parent. EventHandler creation is owned by Clickable.onAttach and ---- TextEditor creation by TextEditable.onAttach — both run in _attachBehaviors at ---- the tail of Element.new, so this helper does not touch either subsystem. -bindThemeAndFields = function(self, props) - if Element._Theme then - self._themeManager = Element._Theme.Manager.new({ - theme = props.theme or Element._Context.defaultTheme, - themeComponent = props.themeComponent or nil, - disabled = props.isDisabled or props.disabled or false, - active = props.active or false, - disableHighlight = props.disableHighlight, - themeStateLock = props.themeStateLock or false, - themeComponentDisabledStates = props.themeComponentDisabledStates, - scaleCorners = props.scaleCorners, - scalingAlgorithm = props.scalingAlgorithm, - }) - else - -- Theme module absent (minimal build) — plain no-op ThemeManager - local noPadding = { top = 0, right = 0, bottom = 0, left = 0 } - self._themeManager = { - theme = nil, - themeComponent = props.themeComponent or nil, - disabled = props.isDisabled or props.disabled or false, - active = props.active or false, - themeComponentDisabledStates = {}, - scaleCorners = props.scaleCorners, - scalingAlgorithm = props.scalingAlgorithm, - validateThemeStateLock = function() end, - getState = function() - return "normal" - end, - setState = function() end, - updateState = function() - return false - end, - hasThemeComponent = function() - return false - end, - getTheme = function() - return nil - end, - getComponent = function() - return nil - end, - getStateComponent = function() - return nil - end, - getScrollbarComponent = function() - return nil - end, - getDefaultFontFamily = function() - return nil - end, - getContentAutoSizingMultiplier = function() - return nil - end, - getScaledContentPadding = function() - return noPadding - end, - getScaledContentPaddingForState = function() - return noPadding - end, - _getScaledContentPaddingForState = function() - return noPadding - end, - getStyle = function() - return nil - end, - } - end - - -- Validate themeStateLock after ThemeManager is created - if props.themeStateLock and props.themeComponent then - self._themeManager:validateThemeStateLock() - end - - -- Expose theme properties for backward compatibility - self.theme = self._themeManager.theme - self.themeComponent = self._themeManager.themeComponent - self.disabled = self._themeManager.disabled - self.active = self._themeManager.active - self._themeState = self._themeManager:getState() - - -- disableHighlight defaults to true when using themeComponent (themes handle their own visual feedback) - -- Can be explicitly overridden by setting props.disableHighlight - if props.disableHighlight ~= nil then - self.disableHighlight = props.disableHighlight - else - self.disableHighlight = self.themeComponent ~= nil - end - - -- Initialize contentAutoSizingMultiplier after theme is set - -- Priority: element props > theme component > theme default - if props.contentAutoSizingMultiplier then - self.contentAutoSizingMultiplier = props.contentAutoSizingMultiplier - else - local multiplier = self._themeManager:getContentAutoSizingMultiplier() - self.contentAutoSizingMultiplier = multiplier or { 1, 1 } - end - - -- Expose 9-patch corner scaling properties for backward compatibility - self.scaleCorners = self._themeManager.scaleCorners - self.scalingAlgorithm = self._themeManager.scalingAlgorithm - - self._blurInstance = nil - - -- editable/multiline/passwordMode are bound by _applyProps (default false). - -- Validate combinations: passwordMode disables multiline. - if self.passwordMode and self.multiline then - Element._ErrorHandler:warn("Element", "ELEM_006") - self.multiline = false - elseif self.passwordMode then - self.multiline = false - end - - self.textWrap = props.textWrap - if self.textWrap == nil then - self.textWrap = self.multiline and "word" or false - end - - self.scrollable = props.scrollable - if self.scrollable == nil then - self.scrollable = self.multiline - end - -- autoGrow defaults to true for multiline, false for single-line - if props.autoGrow ~= nil then - self.autoGrow = props.autoGrow - else - self.autoGrow = self.multiline - end - - self.selectParent = nil - self.selectOption = nil - self._selectState = nil - - if type(props.selectParent) == "table" then - self.selectParent = props.selectParent - end - - if type(props.selectOption) == "table" then - self.selectOption = props.selectOption - end - - -- TextEditor creation + immediate-mode state restore is owned by the - -- TextEditable behavior's onAttach (task 04), which runs in _attachBehaviors - -- at the tail of Element.new (after this helper has bound self.text and the - -- schema-driven callback fields). This subsystem-init helper no longer touches - -- the TextEditor. - - -- Set parent first so it's available for size calculations - self.parent = props.parent -end - ---- Special-handler binding helper for the props phase (behavior-mode- ---- unification task 08). Formerly the Element visual-state phase. Normalizes ---- border/cornerRadius/display/text/textAlign. The branch count comment below ---- refers to the 4 textAlign parse branches (table / simple-string / compound- ---- string / invalid). -bindVisualState = function(self, props) - local schema = Element._PropertySchema - ------ add non-hereditary ------ - --- self drawing --- - -- Border shape-normalization via the schema normalizer (special handler: the - -- number-vs-table-vs-nil shape and the all-false→nil collapse are intentional). - self.border = schema.get("border").normalizer(props.border) - self.borderColor = props.borderColor or Element._Color.new(0, 0, 0, 1) - self.backgroundColor = props.backgroundColor or Element._Color.new(0, 0, 0, 0) - - -- cornerRadius shape-normalization via the schema normalizer (special handler: - -- number-vs-table-vs-nil and the all-zero→nil collapse are intentional). - self.cornerRadius = schema.get("cornerRadius").normalizer(props.cornerRadius) - - -- display: default true; invalid (non-boolean) warns + falls back to true - -- (non-throwing, unlike range/enum validators). - if props.display ~= nil then - if type(props.display) == "boolean" then - self.display = props.display - else - self.display = true - Element._ErrorHandler:warn( - "Element", - "ELEM_010", - "display must be a boolean (true/false), got " .. type(props.display) .. ". Defaulting to true." - ) - end - else - self.display = true - end - - -- For editable elements, default text to empty string if not provided - if self.editable and props.text == nil then - self.text = "" - else - self.text = props.text - end - - -- Validate and set textAlign (supports simple string, compound string, or - -- table format). Enum membership is checked via PropertySchema validators - -- (the valid H/V sets live there, matching the objectFit/imageRepeat pattern); - -- compound-string parsing and warn+fallback stay here because they need - -- ErrorHandler, which the pure-Lua schema cannot depend on. - local textAlignMeta = schema.get("textAlign") - local textVAlignMeta = schema.get("textAlignVertical") - local textAlignDefault = textAlignMeta.default - local vAlignDefault = textVAlignMeta.default - - self.textAlign = props.textAlign or textAlignDefault - self.textAlignHorizontal = textAlignDefault - self.textAlignVertical = vAlignDefault - - if props.textAlign ~= nil then - if type(props.textAlign) == "table" then - -- Table format: {horizontal = "start", vertical = "center"} - local hAlign = props.textAlign.horizontal or textAlignDefault - local vAlign = props.textAlign.vertical or vAlignDefault - - if not textAlignMeta.validator(hAlign) then - hAlign = _warnTextAlign("textAlign.horizontal", "valid TextAlign value", hAlign, textAlignDefault) - end - if not textVAlignMeta.validator(vAlign) then - vAlign = _warnTextAlign("textAlign.vertical", "valid TextAlignVertical value", vAlign, vAlignDefault) - end - - self.textAlignHorizontal = hAlign - self.textAlignVertical = vAlign - elseif type(props.textAlign) == "string" then - if textAlignMeta.validator(props.textAlign) then - -- Known simple TextAlign value (backward compatible) - self.textAlignHorizontal = props.textAlign - self.textAlignVertical = vAlignDefault - else - -- Treat as compound string: "top-left" through "bottom-right" - local parts = {} - for part in props.textAlign:gmatch("[^-]+") do - table.insert(parts, part) - end - - if #parts == 2 then - local verticalMap = { top = "start", center = "center", bottom = "end" } - local horizontalMap = { left = "start", center = "center", right = "end" } - - local vStr = parts[1]:lower() - local hStr = parts[2]:lower() - local resolvedV = verticalMap[vStr] - local resolvedH = horizontalMap[hStr] - - if resolvedV and resolvedH then - self.textAlignHorizontal = resolvedH - self.textAlignVertical = resolvedV - else - _warnTextAlign( - "textAlign", - "valid compound string (e.g., 'top-left', 'center-right')", - props.textAlign, - nil - ) - end - else - _warnTextAlign("textAlign", "valid TextAlign value or compound string", props.textAlign, nil) - end - end - end - end -end - ---- Phase 5 (image + renderer init) is owned by the Themed and Imageable ---- behaviors (modules/behaviors/), attached in _attachBehaviors. Themed.onAttach ---- creates the Renderer (theme/blur config); Imageable.onAttach enriches it with ---- image config + deferred image loading. There is no longer a stub Element ---- phase for this — the behavior onAttach hooks ARE the phase ---- (behavior-mode-unification task 07/08). - ---- Phase 6a: viewport/scale context, LayoutEngine (defaults), unit specs table, ---- fontFamily, and textSize resolution. -function Element:_initSizingContext(props) - --- self positioning --- - local viewportWidth, viewportHeight = Element._Units.getViewport() - - ---- Sizing ---- - local gw, gh = love.window.getMode() - self.prevGameSize = { width = gw, height = gh } - self.autosizing = { width = false, height = false } - - -- Initialize LayoutEngine early with default values for auto-sizing calculations - -- It will be re-configured later with actual layout properties - self._layoutEngine = Element._LayoutEngine.new({ - positioning = Element._utils.enums.Positioning.RELATIVE, - flexDirection = Element._utils.enums.FlexDirection.HORIZONTAL, - flexWrap = Element._utils.enums.FlexWrap.NOWRAP, - justifyContent = Element._utils.enums.JustifyContent.FLEX_START, - alignItems = Element._utils.enums.AlignItems.STRETCH, - alignContent = Element._utils.enums.AlignContent.STRETCH, - gap = 0, - gridRows = 1, - gridColumns = 1, - - columnGap = 0, - rowGap = 0, - }, Element._layoutEngineDeps) - self._layoutEngine:initialize(self) - - -- Store unit specifications for responsive behavior - self.units = { - width = { value = nil, unit = "px" }, - height = { value = nil, unit = "px" }, - x = { value = nil, unit = "px" }, - y = { value = nil, unit = "px" }, - textSize = { value = nil, unit = "px" }, - gap = { value = nil, unit = "px" }, - flexBasis = { value = nil, unit = "auto" }, - padding = { - top = { value = nil, unit = "px" }, - right = { value = nil, unit = "px" }, - bottom = { value = nil, unit = "px" }, - left = { value = nil, unit = "px" }, - horizontal = { value = nil, unit = "px" }, -- Shorthand for left/right - vertical = { value = nil, unit = "px" }, -- Shorthand for top/bottom - }, - margin = { - top = { value = nil, unit = "px" }, - right = { value = nil, unit = "px" }, - bottom = { value = nil, unit = "px" }, - left = { value = nil, unit = "px" }, - horizontal = { value = nil, unit = "px" }, -- Shorthand for left/right - vertical = { value = nil, unit = "px" }, -- Shorthand for top/bottom - }, - } - - local _, scaleY = Element._Context.getScaleFactors() - - -- minTextSize/maxTextSize/autoScaleText are bound by _applyProps (autoScaleText - -- defaults true). They are needed before textSize processing below. - - -- Handle fontFamily (can be font name from theme or direct path to font file) - -- Priority: explicit props.fontFamily > parent fontFamily > theme default - if props.fontFamily then - -- Explicitly set fontFamily takes highest priority - self.fontFamily = props.fontFamily - elseif self.parent and self.parent.fontFamily then - -- Inherit from parent if parent has fontFamily set - self.fontFamily = self.parent.fontFamily - elseif props.themeComponent then - -- If using themeComponent, try to get default from theme via ThemeManager - local defaultFont = self._themeManager:getDefaultFontFamily() - self.fontFamily = defaultFont and "default" or nil - else - self.fontFamily = nil - end - - -- Handle textSize BEFORE width/height calculation (needed for auto-sizing) - if props.textSize then - if type(props.textSize) == "string" then - -- Check if it's a preset first - local presetValue, presetUnit = Element._utils.resolveTextSizePreset(props.textSize) - local value, unit - - if presetValue then - -- It's a preset, use the preset value and unit - value, unit = presetValue, presetUnit - self.units.textSize = { value = value, unit = unit } - else - -- Not a preset, parse normally - value, unit = Element._Units.parse(props.textSize) - self.units.textSize = { value = value, unit = unit } - end - - -- Resolve textSize based on unit type - if unit == "%" or unit == "vh" then - -- Percentage and vh are relative to viewport height - self.textSize = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight) - elseif unit == "vw" then - -- vw is relative to viewport width - self.textSize = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth) - elseif unit == "px" then - -- Pixel units - self.textSize = value - else - Element._ErrorHandler:error("Element", "ELEM_002", { - unit = unit, - }) - end - else - -- Validate pixel textSize value - if props.textSize <= 0 then - Element._ErrorHandler:error("Element", "ELEM_001", { - value = tostring(props.textSize), - }) - end - - -- Pixel textSize value - if self.autoScaleText and Element._Context.baseScale then - -- With base scaling: store original pixel value and scale relative to base resolution - self.units.textSize = { value = props.textSize, unit = "px" } - self.textSize = props.textSize * scaleY - elseif self.autoScaleText then - -- Without base scaling: convert to viewport units for auto-scaling - -- Calculate what percentage of viewport height this represents - local vhValue = (props.textSize / viewportHeight) * 100 - self.units.textSize = { value = vhValue, unit = "vh" } - self.textSize = props.textSize -- Initial size is the specified pixel value - else - -- No auto-scaling: apply base scaling if set, otherwise use raw value - self.textSize = Element._Context.baseScale and (props.textSize * scaleY) or props.textSize - self.units.textSize = { value = props.textSize, unit = "px" } - end - end - else - -- No textSize specified - use auto-scaling default - if self.autoScaleText and Element._Context.baseScale then - -- With base scaling: use 12px as default and scale - self.units.textSize = { value = 12, unit = "px" } - self.textSize = 12 * scaleY - elseif self.autoScaleText then - -- Without base scaling: default to 1.5vh (1.5% of viewport height) - self.units.textSize = { value = 1.5, unit = "vh" } - self.textSize = (1.5 / 100) * viewportHeight - else - -- No auto-scaling: use 12px with optional base scaling - self.textSize = Element._Context.baseScale and (12 * scaleY) or 12 - self.units.textSize = { value = nil, unit = "px" } - end - end -end - ---- Phase 6b: width/height/min-max/clamp, gap, flex shorthand/grow/shrink/basis, ---- 9-patch/border-box model, and padding/margin resolution + unit storage. -function Element:_initBoxModel(props) - local viewportWidth, viewportHeight = Element._Units.getViewport() - local scaleX, scaleY = Element._Context.getScaleFactors() - local _ctx = { vw = viewportWidth, vh = viewportHeight, sx = scaleX, sy = scaleY } - -- Handle width (both w and width properties, prefer w if both exist) - -- "auto" is treated as content-sized (same as omitting the property), per CSS semantics. - local widthProp = props.width - if widthProp == "auto" then - widthProp = nil - end - local tempWidth -- Temporary width for padding resolution - if widthProp then - local parentWidth = self.parent and self.parent.width or viewportWidth - tempWidth = _resolveUnit(self, widthProp, "width", parentWidth, _ctx, { scaleAxis = "x" }) - else - self.autosizing.width = true - -- Special case: if textWrap is enabled and parent exists, constrain width to parent - -- Text wrapping requires a width constraint, so use parent's content width - if props.textWrap and self.parent and self.parent.width then - tempWidth = self.parent.width - self.width = tempWidth - self.units.width = { value = 100, unit = "%" } -- Mark as parent-constrained - self.autosizing.width = false -- Not truly autosizing, constrained by parent - else - tempWidth = self:calculateAutoWidth() - self.width = tempWidth - self.units.width = { value = nil, unit = "auto" } -- Mark as auto-sized - end - end - - -- Handle height (both h and height properties, prefer h if both exist) - -- "auto" is treated as content-sized (same as omitting the property), per CSS semantics. - local heightProp = props.height - if heightProp == "auto" then - heightProp = nil - end - local tempHeight -- Temporary height for padding resolution - if heightProp then - local parentHeight = self.parent and self.parent.height or viewportHeight - tempHeight = _resolveUnit(self, heightProp, "height", parentHeight, _ctx, { scaleAxis = "y" }) - else - self.autosizing.height = true - -- Calculate auto-height without padding first - tempHeight = self:calculateAutoHeight() - self.height = tempHeight - self.units.height = { value = nil, unit = "auto" } -- Mark as auto-sized - end - - local constraintParentW = self.parent and self.parent.width or viewportWidth - local constraintParentH = self.parent and self.parent.height or viewportHeight - _resolveUnit(self, props.minWidth, "minWidth", constraintParentW, _ctx, { scaleAxis = "x", nullable = true }) - _resolveUnit(self, props.maxWidth, "maxWidth", constraintParentW, _ctx, { scaleAxis = "x", nullable = true }) - _resolveUnit(self, props.minHeight, "minHeight", constraintParentH, _ctx, { scaleAxis = "y", nullable = true }) - _resolveUnit(self, props.maxHeight, "maxHeight", constraintParentH, _ctx, { scaleAxis = "y", nullable = true }) - - if not self.autosizing.width then - self.width = Element._utils.clamp(tempWidth, self.minWidth, self.maxWidth) - tempWidth = self.width - else - self.width = Element._utils.clamp(self.width, self.minWidth, self.maxWidth) - end - if not self.autosizing.height then - self.height = Element._utils.clamp(tempHeight, self.minHeight, self.maxHeight) - tempHeight = self.height - else - self.height = Element._utils.clamp(self.height, self.minHeight, self.maxHeight) - end - - --- child positioning --- - if props.gap then - local flexDir = props.flexDirection or Element._utils.enums.FlexDirection.HORIZONTAL - local isHorizontalDir = flexDir == Element._utils.enums.FlexDirection.HORIZONTAL - or flexDir == Element._utils.enums.FlexDirection.HORIZONTAL_REVERSE - local containerSize = isHorizontalDir and self.width or self.height - _resolveUnit(self, props.gap, "gap", containerSize, _ctx) - else - self.gap = 0 - self.units.gap = { value = 0, unit = "px" } - end - - -- Handle flex shorthand property (sets flexGrow, flexShrink, flexBasis) - if props.flex ~= nil then - local grow, shrink, basis = Element._Units.parseFlexShorthand(props.flex) - - -- Only set individual properties if they weren't explicitly provided - if props.flexGrow == nil then - props.flexGrow = grow - end - if props.flexShrink == nil then - props.flexShrink = shrink - end - if props.flexBasis == nil then - props.flexBasis = basis - end - end - - -- Track whether flex-shrink was explicitly provided (directly or via flex shorthand) - self._hasExplicitFlexShrink = props.flexShrink ~= nil - - -- Handle flexGrow property - if props.flexGrow ~= nil then - if type(props.flexGrow) == "number" and props.flexGrow >= 0 then - self.flexGrow = props.flexGrow - else - self.flexGrow = _warnFlexInvalid(self, "FLEX_001", "flexGrow must be a non-negative number", props.flexGrow, 0) - end - else - self.flexGrow = 0 - end - - -- Handle flexShrink property - if props.flexShrink ~= nil then - if type(props.flexShrink) == "number" and props.flexShrink >= 0 then - self.flexShrink = props.flexShrink - else - self.flexShrink = - _warnFlexInvalid(self, "FLEX_002", "flexShrink must be a non-negative number", props.flexShrink, 1) - end - else - self.flexShrink = 1 - end - - -- Handle flexBasis property - if props.flexBasis ~= nil then - local isCalc = Element._Calc and Element._Calc.isCalc(props.flexBasis) - if props.flexBasis == "auto" then - self.flexBasis = "auto" - self.units.flexBasis = { value = nil, unit = "auto" } - elseif type(props.flexBasis) == "string" or isCalc then - local value, unit = Element._Units.parse(props.flexBasis) - self.units.flexBasis = { value = value, unit = unit } - -- Don't resolve yet - LayoutEngine will handle this during layout - self.flexBasis = props.flexBasis - elseif type(props.flexBasis) == "number" then - self.flexBasis = props.flexBasis - self.units.flexBasis = { value = props.flexBasis, unit = "px" } - else - self.flexBasis = - _warnFlexInvalid(self, "FLEX_003", "flexBasis must be a number, string, or 'auto'", props.flexBasis, "auto") - self.units.flexBasis = { value = nil, unit = "auto" } - end - else - self.flexBasis = "auto" - self.units.flexBasis = { value = nil, unit = "auto" } - end - - -- BORDER-BOX MODEL: For auto-sizing, we need to add padding to content dimensions - -- For explicit sizing, width/height already include padding (border-box) - - -- Check if we should use 9-patch content padding for auto-sizing - local use9PatchPadding = false - local ninePatchContentPadding = nil - if self._themeManager:hasThemeComponent() then - local component = self._themeManager:getComponent() - if component and component._ninePatchData and component._ninePatchData.contentPadding then - -- Only use 9-patch padding if no explicit padding was provided - if - not props.padding - or ( - not props.padding.top - and not props.padding.right - and not props.padding.bottom - and not props.padding.left - and not props.padding.horizontal - and not props.padding.vertical - ) - then - use9PatchPadding = true - ninePatchContentPadding = component._ninePatchData.contentPadding - end - end - end - - -- First, resolve padding using temporary dimensions - -- For auto-sized elements, this is content width; for explicit sizing, this is border-box width - local tempPadding - if use9PatchPadding then - -- tempWidth/tempHeight are guaranteed numbers by _resolveUnit (which warns + - -- clamps non-numbers) and calculateAutoWidth/Height; the prior defensive - -- re-check duplicated that boundary validation (Task 11). - - -- Get scaled 9-patch content padding from ThemeManager - local scaledPadding = self._themeManager:getScaledContentPadding(tempWidth, tempHeight) - if scaledPadding then - tempPadding = scaledPadding - else - -- Fallback if scaling fails - tempPadding = { - left = ninePatchContentPadding.left, - top = ninePatchContentPadding.top, - right = ninePatchContentPadding.right, - bottom = ninePatchContentPadding.bottom, - } - end - else - tempPadding = Element._Units.resolveSpacing(props.padding, self.width, self.height) - end - - -- Margin percentages are relative to parent's dimensions (CSS spec) - local parentWidth = self.parent and self.parent.width or viewportWidth - local parentHeight = self.parent and self.parent.height or viewportHeight - self.margin = Element._Units.resolveSpacing(props.margin, parentWidth, parentHeight) - - -- For auto-sized elements, add padding to get border-box dimensions - if self.autosizing.width then - self._borderBoxWidth = self.width + tempPadding.left + tempPadding.right - else - -- For explicit sizing, width is already border-box - self._borderBoxWidth = self.width - end - - if self.autosizing.height then - self._borderBoxHeight = self.height + tempPadding.top + tempPadding.bottom - else - -- For explicit sizing, height is already border-box - self._borderBoxHeight = self.height - end - - -- Set final padding - if use9PatchPadding then - -- Use 9-patch content padding - self.padding = { - left = ninePatchContentPadding.left, - top = ninePatchContentPadding.top, - right = ninePatchContentPadding.right, - bottom = ninePatchContentPadding.bottom, - } - else - -- Re-resolve padding based on final border-box dimensions (important for percentage padding) - self.padding = Element._Units.resolveSpacing(props.padding, self._borderBoxWidth, self._borderBoxHeight) - end - - -- Calculate final content dimensions by subtracting padding from border-box - self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right) - self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom) - - -- Re-resolve textSize presets now that width/height are set - -- (presets like "vw" need the viewport; others are resolved during constructor) - - -- Apply min/max constraints (also scaled) - local minSize = self.minTextSize and (Element._Context.baseScale and (self.minTextSize * scaleY) or self.minTextSize) - local maxSize = self.maxTextSize and (Element._Context.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize) - - if minSize and self.textSize < minSize then - self.textSize = minSize - end - if maxSize and self.textSize > maxSize then - self.textSize = maxSize - end - - -- Protect against too-small text sizes (minimum 1px) - if self.textSize < 1 then - self.textSize = 1 -- Minimum 1px - end - - -- Store original spacing values for proper resize handling - -- Store spacing unit specs (padding + margin share identical structure) - local sides = { "top", "right", "bottom", "left" } - for _, kind in ipairs({ "padding", "margin" }) do - local src = props[kind] - if src then - for _, axis in ipairs({ "horizontal", "vertical" }) do - if src[axis] then - if type(src[axis]) == "string" then - local value, unit = Element._Units.parse(src[axis]) - self.units[kind][axis] = { value = value, unit = unit } - else - self.units[kind][axis] = { value = src[axis], unit = "px" } - end - end - end - end - for _, side in ipairs(sides) do - if src and src[side] then - if type(src[side]) == "string" then - local value, unit = Element._Units.parse(src[side]) - self.units[kind][side] = { value = value, unit = unit, explicit = true } - else - self.units[kind][side] = { value = src[side], unit = "px", explicit = true } - end - else - self.units[kind][side] = { value = self[kind][side], unit = "px", explicit = false } - end - end - end - - -- Grid properties are set later in the constructor -end - ---- Phase 7: hereditary positioning (no-parent and with-parent), flex/grid ---- container properties, select-frame adopt, and LayoutEngine config update. -function Element:_initPositioning(props) - local viewportWidth, viewportHeight = Element._Units.getViewport() - local scaleX, scaleY = Element._Context.getScaleFactors() - local _ctx = { vw = viewportWidth, vh = viewportHeight, sx = scaleX, sy = scaleY } - ------ add hereditary ------ - if props.parent == nil then - table.insert(Element._Context.topElements, self) - - -- Handle x position with units - _resolveUnit(self, props.x, "x", viewportWidth, _ctx, { scaleAxis = "x", default = 0 }) - - -- Handle y position with units - _resolveUnit(self, props.y, "y", viewportHeight, _ctx, { scaleAxis = "y", default = 0 }) - - self.z = Element._ZIndex.clamp(props.z or 0) - self.tabIndex = props.tabIndex -- nil/0 = document order, >0 = explicit order, -1 = excluded from keyboard nav - - -- Set textColor with priority: props > theme text color > black - if props.textColor then - self.textColor = props.textColor - else - -- Try to get text color from theme via ThemeManager - local themeToUse = self._themeManager:getTheme() - if themeToUse and themeToUse.colors and themeToUse.colors.text then - self.textColor = themeToUse.colors.text - else - -- Fallback to black - self.textColor = Element._Color.new(0, 0, 0, 1) - end - end - - -- Track if positioning was explicitly set - if props.positioning then - Element._utils.validateEnum(props.positioning, Element._utils.enums.Positioning, "positioning") - self.positioning = props.positioning - self._originalPositioning = props.positioning - self._explicitlyAbsolute = (props.positioning == Element._utils.enums.Positioning.ABSOLUTE) - else - self.positioning = Element._utils.enums.Positioning.RELATIVE - self._originalPositioning = nil -- No explicit positioning - self._explicitlyAbsolute = false - end - - -- Handle positioning properties for elements without parent - -- Warn if CSS positioning properties are supplied but will be ignored. - -- Relative elements honor the offsets as visual deltas (see - -- _applyRelativeOffsets); absolute elements use applyPositioningOffsets. - -- Only flex-participating children (positioning coerced to ABSOLUTE but not - -- explicitly absolute) actually drop the offsets and warrant the warning. - if - (props.top or props.bottom or props.left or props.right) - and not self._explicitlyAbsolute - and self.positioning ~= Element._utils.enums.Positioning.RELATIVE - then - _warnCssPositioningWithoutAbsolute(self, props) - end - - -- Handle top/right/bottom/left positioning with units - if props.top then - _resolveUnit(self, props.top, "top", viewportHeight, _ctx) - end - if props.right then - _resolveUnit(self, props.right, "right", viewportWidth, _ctx) - end - if props.bottom then - _resolveUnit(self, props.bottom, "bottom", viewportHeight, _ctx) - end - if props.left then - _resolveUnit(self, props.left, "left", viewportWidth, _ctx) - end - - -- position: relative offsets are applied as visual deltas in - -- LayoutEngine:layoutChildren (after the flex flow places children), so - -- they survive the addChild -> layoutChildren re-entry here. - else - -- Set positioning first and track if explicitly set - self._originalPositioning = props.positioning -- Track original intent - if props.positioning == Element._utils.enums.Positioning.ABSOLUTE then - self.positioning = Element._utils.enums.Positioning.ABSOLUTE - self._explicitlyAbsolute = true -- Explicitly set to absolute by user - elseif props.positioning == Element._utils.enums.Positioning.FLEX then - self.positioning = Element._utils.enums.Positioning.FLEX - self._explicitlyAbsolute = false - elseif props.positioning == Element._utils.enums.Positioning.GRID then - self.positioning = Element._utils.enums.Positioning.GRID - self._explicitlyAbsolute = false - else - -- Default: children in flex/grid containers participate in parent's layout - -- children in relative/absolute containers default to relative - if - self.parent.positioning == Element._utils.enums.Positioning.FLEX - or self.parent.positioning == Element._utils.enums.Positioning.GRID - then - self.positioning = Element._utils.enums.Positioning.ABSOLUTE -- They are positioned BY flex/grid, not AS flex/grid - self._explicitlyAbsolute = false -- Participate in parent's layout - else - self.positioning = Element._utils.enums.Positioning.RELATIVE - self._explicitlyAbsolute = false -- Default for relative/absolute containers - end - end - - -- Set initial position - local parentPadding = self.parent.padding or { left = 0, top = 0 } - if self.positioning == Element._utils.enums.Positioning.ABSOLUTE then - -- Absolute positioning is relative to parent's content area (padding box) - local baseX = self.parent.x + parentPadding.left - local baseY = self.parent.y + parentPadding.top - - -- Handle x/y position with units - _resolveUnit(self, props.x, "x", self.parent.width, _ctx, { scaleAxis = "x", offset = baseX, default = 0 }) - _resolveUnit(self, props.y, "y", self.parent.height, _ctx, { scaleAxis = "y", offset = baseY, default = 0 }) - - self.z = Element._ZIndex.clamp(props.z or 0) - self.tabIndex = props.tabIndex - else - -- Children in flex containers start at parent position but will be repositioned by layoutChildren - -- Children in absolute/relative containers start at parent's content area (accounting for padding) - local baseX = self.parent.x + parentPadding.left - local baseY = self.parent.y + parentPadding.top - - -- Warn if explicit x/y is set on a child that will be positioned by flex layout - -- This position will be overridden unless the child has positioning="absolute" - local parentWillUseFlex = self.parent.positioning ~= "grid" - local childIsRelative = self.positioning ~= "absolute" or not self._explicitlyAbsolute - if parentWillUseFlex and childIsRelative and (props.x or props.y) then - Element._ErrorHandler:warn("Element", "LAY_008", { - element = self.id or "unnamed", - parent = self.parent.id or "unnamed", - properties = (props.x and props.y) and "x, y" or (props.x and "x" or "y"), - }) - end - - _resolveUnit(self, props.x, "x", self.parent.width, _ctx, { scaleAxis = "x", offset = baseX, default = 0 }) - _resolveUnit(self, props.y, "y", self.parent.height, _ctx, { scaleAxis = "y", offset = baseY, default = 0 }) - - self.z = Element._ZIndex.clamp(props.z or self.parent.z or 0) - self.tabIndex = props.tabIndex - end - - if props.textColor then - self.textColor = props.textColor - elseif self.parent.textColor then - self.textColor = self.parent.textColor - else - local themeToUse = self._themeManager:getTheme() - if themeToUse and themeToUse.colors and themeToUse.colors.text then - self.textColor = themeToUse.colors.text - else - -- Fallback to black - self.textColor = Element._Color.new(0, 0, 0, 1) - end - end - - -- Handle positioning properties BEFORE adding to parent (so they're available during layout) - -- Warn if CSS positioning properties are supplied but will be ignored. - -- Relative elements honor the offsets as visual deltas (see - -- _applyRelativeOffsets); absolute elements use applyPositioningOffsets. - -- Only flex-participating children (positioning coerced to ABSOLUTE but not - -- explicitly absolute) actually drop the offsets and warrant the warning. - if - (props.top or props.bottom or props.left or props.right) - and not self._explicitlyAbsolute - and self.positioning ~= Element._utils.enums.Positioning.RELATIVE - then - _warnCssPositioningWithoutAbsolute(self, props) - end - - -- Handle top/right/bottom/left positioning with units - if props.top then - _resolveUnit(self, props.top, "top", viewportHeight, _ctx) - end - if props.right then - _resolveUnit(self, props.right, "right", viewportWidth, _ctx) - end - if props.bottom then - _resolveUnit(self, props.bottom, "bottom", viewportHeight, _ctx) - end - if props.left then - _resolveUnit(self, props.left, "left", viewportWidth, _ctx) - end - - -- position: relative offsets are applied as visual deltas in - -- LayoutEngine:layoutChildren (after the flex flow places children), so - -- they survive the addChild -> layoutChildren re-entry here. - - props.parent:addChild(self) - end - - if self.positioning == Element._utils.enums.Positioning.FLEX then - -- Validate enum properties - if props.flexDirection then - Element._utils.validateEnum(props.flexDirection, Element._utils.enums.FlexDirection, "flexDirection") - end - if props.flexWrap then - Element._utils.validateEnum(props.flexWrap, Element._utils.enums.FlexWrap, "flexWrap") - end - if props.justifyContent then - Element._utils.validateEnum(props.justifyContent, Element._utils.enums.JustifyContent, "justifyContent") - end - if props.alignItems then - Element._utils.validateEnum(props.alignItems, Element._utils.enums.AlignItems, "alignItems") - end - if props.alignContent then - Element._utils.validateEnum(props.alignContent, Element._utils.enums.AlignContent, "alignContent") - end - if props.justifySelf then - Element._utils.validateEnum(props.justifySelf, Element._utils.enums.JustifySelf, "justifySelf") - end - - -- Warn if grid properties are set with flex positioning - if props.gridRows or props.gridColumns then - Element._ErrorHandler:warn("Element", "LAY_010", { - element = self.id or "unnamed", - positioning = "flex", - properties = "gridRows/gridColumns", - }) - end - - self.flexDirection = props.flexDirection or Element._utils.enums.FlexDirection.HORIZONTAL - self.flexWrap = props.flexWrap or Element._utils.enums.FlexWrap.NOWRAP - self.justifyContent = props.justifyContent or Element._utils.enums.JustifyContent.FLEX_START - self.alignItems = props.alignItems or Element._utils.enums.AlignItems.STRETCH - self.alignContent = props.alignContent or Element._utils.enums.AlignContent.STRETCH - self.justifySelf = props.justifySelf or Element._utils.enums.JustifySelf.AUTO - end - - -- Grid container properties - if self.positioning == Element._utils.enums.Positioning.GRID then - -- Warn if flex properties are set with grid positioning - if props.flexDirection or props.flexWrap or props.justifyContent then - Element._ErrorHandler:warn("Element", "LAY_009", { - element = self.id or "unnamed", - positioning = "grid", - properties = "flexDirection/flexWrap/justifyContent", - }) - end - - self.gridRows = props.gridRows - self.gridColumns = props.gridColumns - self.alignItems = props.alignItems or Element._utils.enums.AlignItems.STRETCH - - -- Handle columnGap and rowGap - _resolveUnit(self, props.columnGap, "columnGap", self.width, _ctx, { default = 0 }) - _resolveUnit(self, props.rowGap, "rowGap", self.height, _ctx, { default = 0 }) - end - - -- alignSelf is bound by _applyProps (default "auto"). - - -- Update the LayoutEngine with actual layout properties - -- (it was initialized early with defaults for auto-sizing calculations) - self._layoutEngine.positioning = self.positioning - if self.flexDirection then - self._layoutEngine.flexDirection = self.flexDirection - end - if self.flexWrap then - self._layoutEngine.flexWrap = self.flexWrap - end - if self.justifyContent then - self._layoutEngine.justifyContent = self.justifyContent - end - if self.alignItems then - self._layoutEngine.alignItems = self.alignItems - end - if self.alignContent then - self._layoutEngine.alignContent = self.alignContent - end - if self.gap then - self._layoutEngine.gap = self.gap - end - if self.gridRows then - self._layoutEngine.gridRows = self.gridRows - end - if self.gridColumns then - self._layoutEngine.gridColumns = self.gridColumns - end - - if self.columnGap then - self._layoutEngine.columnGap = self.columnGap - end - if self.rowGap then - self._layoutEngine.rowGap = self.rowGap - end - - -- transform is bound by _applyProps; transition is bound by _applyProps (default {}). - -- (Previously set inline here; both are now registry-driven.) -end - ---- Phase 8 (ScrollManager instantiation + immediate-mode scrollbar restore) is ---- owned by the Scrollable behavior (modules/behaviors/Scrollable.lua), attached ---- in _attachBehaviors. There is no longer an Element phase for this — the ---- behavior onAttach hook IS the phase (behavior-mode-unification task 03/08). ---- `overflow` / `overflowX` / `overflowY` are bound onto the element as plain ---- fields by bindThemeAndFields/`_applyProps` so that `Element:addChild`'s ---- scroll-container auto-size guard sees them during declarative-children ---- processing in _finalizeConstruction (which runs before _attachBehaviors); ---- Scrollable.onAttach then overwrites them with the ScrollManager's normalized ---- values, matching the legacy field-exposure order. - ---- Phase 9: immediate-mode registration, dirty flags, debug draw color, ---- declarative children tree, onCreate callback, and constructed flag. -function Element:_finalizeConstruction(props) - -- Register element in z-index tracking. registerElement is a mode-aware - -- no-op outside immediate mode, so no mode check is needed here - -- (behavior-mode-unification task 11). - Element._Context.registerElement(self) - - -- Performance optimization: dirty flags for layout tracking - -- These flags help skip unnecessary layout recalculations - self._dirty = false -- Element properties have changed, needs layout - self._childrenDirty = false -- Children have changed, needs layout - - -- Debug draw: assign a deterministic color for element boundary visualization - -- Uses a hash of the element ID to produce a stable hue, so colors don't flash each frame - local function hashStringToHue(str) - local hash = 5381 - for i = 1, #str do - hash = ((hash * 33) + string.byte(str, i)) % 360 - end - return hash - end - local hue = hashStringToHue(self.id or tostring(self)) - local function hslToRgb(h) - local s, l = 0.9, 0.55 - local c = (1 - math.abs(2 * l - 1)) * s - local x = c * (1 - math.abs((h / 60) % 2 - 1)) - local m = l - c / 2 - local r, g, b - if h < 60 then - r, g, b = c, x, 0 - elseif h < 120 then - r, g, b = x, c, 0 - elseif h < 180 then - r, g, b = 0, c, x - elseif h < 240 then - r, g, b = 0, x, c - elseif h < 300 then - r, g, b = x, 0, c - else - r, g, b = c, 0, x - end - return r + m, g + m, b + m - end - local dr, dg, db = hslToRgb(hue) - self._debugColor = { dr, dg, db } - - -- Process declarative children prop: build child tree from property tables - -- Placed after all self properties are initialized so children can safely access parent state - if props.children then - if type(props.children) ~= "table" then - _warnChildrenInvalid(self, "ELEM_010", "children must be a table array", props.children) - else - for i = 1, #props.children do - local childProps = props.children[i] - if childProps == nil then - _warnChildrenInvalid(self, "ELEM_011", "nil entry in children array, skipping", nil) - elseif type(childProps) ~= "table" then - _warnChildrenInvalid(self, "ELEM_012", "non-table entry in children array, skipping", childProps) - else - local childCopy = {} - for k, v in pairs(childProps) do - childCopy[k] = v - end - childCopy.parent = self - local child = Element.new(childCopy) - - -- Set up state management for declarative children so mutations - -- made in event callbacks persist across frames. Mode-aware via - -- StateManager.isImmediateMode (behavior-mode-unification task 11): - -- this whole block is immediate-mode-only frame bookkeeping. - if Element._StateManager.isImmediateMode() then - if not child.id or child.id == "" then - child.id = Element._StateManager.generateID(childCopy, self) - end - local childState = Element._StateManager.getState(child.id, {}) - Element._StateManager.markStateUsed(child.id) - child:restoreState(childState) - child._stateId = child.id - - -- Restore theme state from event handler state - if child.themeComponent then - local eventState = childState.eventHandler or {} - if child.disabled or eventState.disabled then - child._themeState = "disabled" - elseif child.active or eventState.active then - child._themeState = "active" - elseif eventState._pressed and next(eventState._pressed) then - child._themeState = "pressed" - elseif eventState._hovered then - child._themeState = "hover" - else - child._themeState = "normal" - end - end - - -- Add to current frame elements for saveState tracking - if Element._Context._currentFrameElements then - table.insert(Element._Context._currentFrameElements, child) - end - end - end - end - end - end - - -- Fire onCreate callback if provided - if self.onCreate then - if self.onCreateDeferred then - local FlexLove = package.loaded["FlexLove"] or package.loaded["libs.FlexLove"] - if FlexLove and FlexLove.deferCallback then - FlexLove.deferCallback(function() - self.onCreate(self, props) - end) - else - self.onCreate(self, props) - end - else - self.onCreate(self, props) - end - end - - -- Mark element as fully constructed. - -- NOTE: no longer gates an __newindex dimension warning (removed — see comment - -- at top of file). Retained lazily in case future write-interception is added. - self._constructed = true -end - ---- Retrieve the element's screen-space rectangle for collision detection and positioning calculations ---- Use this for custom layout logic, tooltips, or detecting overlaps between elements ----@return { x:number, y:number, width:number, height:number } -function Element:getBounds() - return { x = self.x, y = self.y, width = self:getBorderBoxWidth(), height = self:getBorderBoxHeight() } -end - ---- Test if a screen coordinate falls within the element's clickable area ---- Use this for custom hit detection or determining which element the mouse is over ---- @param x number ---- @param y number ---- @return boolean -function Element:contains(x, y) - local bounds = self:getBounds() - return bounds.x <= x and bounds.y <= y and bounds.x + bounds.width >= x and bounds.y + bounds.height >= y -end - ---- Get the element's total width including padding for layout calculations ---- Use this when you need the full visual width rather than just content width ----@return number -function Element:getBorderBoxWidth() - return self._borderBoxWidth or (self.width + self.padding.left + self.padding.right) -end - ---- Get the element's total height including padding for layout calculations ---- Use this when you need the full visual height rather than just content height ----@return number -function Element:getBorderBoxHeight() - return self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom) -end - ---- Get computed box dimensions (content area position and size) ---- Returns the position and size of the content area (inside padding) ----@return {x: number, y: number, width: number, height: number} -function Element:getComputedBox() - return { - x = self.x + self.padding.left, - y = self.y + self.padding.top, - width = self.width, - height = self.height, - } -end - ---- Mark this element and its ancestors as dirty, requiring layout recalculation ---- Call this when element properties change that affect layout -function Element:invalidateLayout() - self._dirty = true - - -- Invalidate dimension caches - self._borderBoxWidthCache = nil - self._borderBoxHeightCache = nil - - -- Mark parent as having dirty children - if self.parent then - self.parent._childrenDirty = true - -- Propagate up the tree (parents need to know their descendants changed) - local ancestor = self.parent - while ancestor do - ancestor._childrenDirty = true - ancestor = ancestor.parent - end - end -end - --- Scroll / scrollbar methods (_syncScrollManagerState, _detectOverflow, setScrollPosition, --- _calculateScrollbarDimensions, _getScrollbarAtPosition, _handleScrollbarPress/Drag/Release, --- _handleWheelScroll, getScrollPosition, getMaxScroll, getScrollPercentage, hasOverflow, --- getContentSize, scrollBy, scrollToTop) are bound to ScrollManager in Element.init. ScrollManager --- owns all scrollbar interaction logic; Element retains only 1-line delegates (see ScrollManager.lua). - ---- Mark a method for deferred retry during the update phase. ---- Methods that depend on layout calculations (e.g., scroll, sizing) ---- can defer themselves when preconditions aren't met. They'll be ---- retried automatically each frame in update() until they succeed. ----@param methodName string The method name to retry ----@param ... any? Arguments to forward on retry -function Element:_deferMethod(methodName, ...) - if type(self[methodName]) ~= "function" then - Element._ErrorHandler:warn("Element", "CORE_005", { - element = self.id, - method = tostring(methodName), - }) - return - end - - if #self._deferredMethods >= MAX_DEFERRED_METHODS then - Element._ErrorHandler:warn("Element", "CORE_004", { - element = self.id, - method = tostring(methodName), - retryCount = MAX_DEFERRED_METHODS, - }) - return - end - - local argc = select("#", ...) - local args = {} - for i = 1, argc do - local val = select(i, ...) - args[i] = val == nil and _DEFERRED_NIL or val - end - table.insert(self._deferredMethods, { - methodName = methodName, - args = args, - argc = argc, - retryCount = 0, - }) -end - --- Deferred image loading is owned by the Imageable behavior --- (modules/behaviors/Imageable.lua). Imageable.onAttach installs an instance --- closure on `element._loadImage` and defers it via _deferMethod; the deferred- --- method dispatcher (which resolves `self[methodName]`) invokes that closure. --- Element no longer owns the load logic itself and has zero image-branch logic. --- (behavior-mode-unification task 07) - --- scrollToBottom / scrollToLeft / scrollToRight are bound to ScrollManager in Element.init. - ---- Get the current state's scaled content padding ---- Returns the contentPadding for the current theme state, scaled to the element's size ----@return table|nil -- {left, top, right, bottom} or nil if no contentPadding -function Element:getScaledContentPadding() - local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right) - local borderBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom) - return self._themeManager:getScaledContentPadding(borderBoxWidth, borderBoxHeight) -end - ---- Get draw-time content offset from state-specific theme padding changes ----@return number offsetX, number offsetY -function Element:getContentStateOffset() - local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right) - local borderBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom) - - local currentPadding = self:getScaledContentPadding() - local basePadding = self._themeManager:_getScaledContentPaddingForState("normal", borderBoxWidth, borderBoxHeight) - - if not currentPadding or not basePadding then - return 0, 0 - end - - local offsetX = currentPadding.left - basePadding.left - local offsetY = currentPadding.top - basePadding.top - - if math.abs(offsetX) < 0.001 then - offsetX = 0 - end - if math.abs(offsetY) < 0.001 then - offsetY = 0 - end - - return offsetX, offsetY -end - ---- Get or create blur instance for this element ----@return table? -- Blur instance or nil if no blur configured -function Element:getBlurInstance() - -- Determine quality from contentBlur or backdropBlur - local quality = 5 -- Default quality - if self.contentBlur and self.contentBlur.quality then - quality = self.contentBlur.quality - elseif self.backdropBlur and self.backdropBlur.quality then - quality = self.backdropBlur.quality - end - - -- Create blur instance if needed - if not self._blurInstance or self._blurInstance.quality ~= quality then - self._blurInstance = Element._Blur.new({ quality = quality }) - end - - return self._blurInstance -end - ---- Get available content width for children (accounting for 9-patch content padding) ---- This is the width that children should use when calculating percentage widths ----@return number -function Element:getAvailableContentWidth() - local availableWidth = self.width - - local scaledContentPadding = self:getScaledContentPadding() - if scaledContentPadding then - -- Check if the element is using the scaled 9-patch contentPadding as its padding - -- Allow small floating point differences (within 0.1 pixels) - local usingContentPaddingAsPadding = ( - math.abs(self.padding.left - scaledContentPadding.left) < 0.1 - and math.abs(self.padding.right - scaledContentPadding.right) < 0.1 - ) - - if not usingContentPaddingAsPadding then - -- Element has explicit padding different from contentPadding - -- Subtract scaled contentPadding to get the area children should use - availableWidth = availableWidth - scaledContentPadding.left - scaledContentPadding.right - end - end - - return math.max(0, availableWidth) -end - ---- Get available content height for children (accounting for 9-patch content padding) ---- This is the height that children should use when calculating percentage heights ----@return number -function Element:getAvailableContentHeight() - local availableHeight = self.height - - local scaledContentPadding = self:getScaledContentPadding() - if scaledContentPadding then - -- Check if the element is using the scaled 9-patch contentPadding as its padding - -- Allow small floating point differences (within 0.1 pixels) - local usingContentPaddingAsPadding = ( - math.abs(self.padding.top - scaledContentPadding.top) < 0.1 - and math.abs(self.padding.bottom - scaledContentPadding.bottom) < 0.1 - ) - - if not usingContentPaddingAsPadding then - -- Element has explicit padding different from contentPadding - -- Subtract scaled contentPadding to get the area children should use - availableHeight = availableHeight - scaledContentPadding.top - scaledContentPadding.bottom - end - end - - return math.max(0, availableHeight) -end - -function Element:openSelect() - Element._Select.openSelect(self) -end - -function Element:closeSelect() - Element._Select.closeSelect(self) -end - -function Element:toggleSelect() - Element._Select.toggleSelect(self) -end - ----@return boolean -function Element:isSelectOpen() - return Element._Select.isSelectOpen(self) -end - ----@return any -function Element:getSelectValue() - return Element._Select.getSelectValue(self) -end - ----@return string? -function Element:getSelectLabel() - return Element._Select.getSelectLabel(self) -end - ----@return boolean -function Element:isSelectedSelectOption() - return Element._Select.isSelectedOption(self) -end - ----@param value any ----@param optionElement Element? -function Element:setSelectValue(value, optionElement) - Element._Select.setSelectValue(self, value, optionElement) -end - -function Element:_handleSelectRelease() - Element._Select.handleRelease(self) -end - ---- Dynamically insert a child element into the hierarchy for runtime UI construction ---- Use this to build interfaces procedurally or add elements based on application state ----@param child Element -function Element:addChild(child) - if self._managedSelectFrame and child.selectOption and self._managedSelectOwner then - child._selectParentHint = self._managedSelectOwner - end - - child.parent = self - - -- Re-evaluate positioning now that we have a parent - -- If child was created without explicit positioning, inherit from parent - if child._originalPositioning == nil then - -- No explicit positioning was set during construction - if - self.positioning == Element._utils.enums.Positioning.FLEX - or self.positioning == Element._utils.enums.Positioning.GRID - then - child.positioning = Element._utils.enums.Positioning.ABSOLUTE -- They are positioned BY flex/grid, not AS flex/grid - child._explicitlyAbsolute = false -- Participate in parent's layout - else - child.positioning = Element._utils.enums.Positioning.RELATIVE - child._explicitlyAbsolute = false -- Default for relative/absolute containers - end - end - -- If child._originalPositioning is set, it means explicit positioning was provided - -- and _explicitlyAbsolute was already set correctly during construction - - table.insert(self.children, child) - Element._Select.registerWithSelectParent(child) - - -- Mark parent as having dirty children to trigger layout recalculation - self._childrenDirty = true - - -- Only recalculate auto-sizing if the child participates in layout - -- (CSS: absolutely positioned children don't affect parent auto-sizing) - if not child._explicitlyAbsolute then - local sizeChanged = false - - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - local isScrollContainer = overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" - - if self.autosizing.height and not isScrollContainer then - local oldHeight = self.height - local contentHeight = self:calculateAutoHeight() - -- BORDER-BOX MODEL: Add padding to get border-box, then subtract to get content - self._borderBoxHeight = contentHeight + self.padding.top + self.padding.bottom - self.height = contentHeight - if oldHeight ~= self.height then - sizeChanged = true - end - end - if self.autosizing.width and not isScrollContainer then - local oldWidth = self.width - local contentWidth = self:calculateAutoWidth() - -- BORDER-BOX MODEL: Add padding to get border-box, then subtract to get content - self._borderBoxWidth = contentWidth + self.padding.left + self.padding.right - self.width = contentWidth - if oldWidth ~= self.width then - sizeChanged = true - end - end - - -- Propagate size change up the tree - if sizeChanged and self.parent and (self.parent.autosizing.width or self.parent.autosizing.height) then - -- Trigger parent to recalculate its size by re-adding this child's contribution - -- This ensures grandparents are notified of size changes - if self.parent.autosizing.height then - local contentHeight = self.parent:calculateAutoHeight() - self.parent._borderBoxHeight = contentHeight + self.parent.padding.top + self.parent.padding.bottom - self.parent.height = contentHeight - end - if self.parent.autosizing.width then - local contentWidth = self.parent:calculateAutoWidth() - self.parent._borderBoxWidth = contentWidth + self.parent.padding.left + self.parent.padding.right - self.parent.width = contentWidth - end - end - end - - -- Layout is deferred to FlexLove.endFrame in immediate mode (all elements - -- for the frame must exist before layout). shouldLayout() encapsulates the - -- mode check (behavior-mode-unification task 11). - if Element._StateManager.shouldLayout() then - self:layoutChildren() - end - - if - self._selectState - and self._selectState.selectFrame - and child.selectOption - and child ~= self._selectState.selectFrame - then - Element._Select.attachOptionToManagedFrame(child) - end -end - ---- Remove a child element from the hierarchy to dynamically update UIs ---- Use this to delete elements when they're no longer needed or respond to user actions ----@param child Element -function Element:removeChild(child) - for i, c in ipairs(self.children) do - if c == child then - Element._Select.handleChildRemoved(self, child) - Element._Select.unregisterFromSelectParent(child) - table.remove(self.children, i) - child.parent = nil - - -- Recalculate auto-sizing if needed - if self.autosizing.width or self.autosizing.height then - if self.autosizing.width then - local contentWidth = self:calculateAutoWidth() - self._borderBoxWidth = contentWidth + self.padding.left + self.padding.right - self.width = contentWidth - end - if self.autosizing.height then - local contentHeight = self:calculateAutoHeight() - self._borderBoxHeight = contentHeight + self.padding.top + self.padding.bottom - self.height = contentHeight - end - end - - -- Re-layout children after removal (deferred in immediate mode). - if Element._StateManager.shouldLayout() then - self:layoutChildren() - end - - break - end - end -end - ---- Reparent this element to a new parent, properly detaching from the current location ---- and inserting into the new parent's children hierarchy with correct layout and alignment. ---- If newParent is nil, the element becomes a top-level element. ---- Works whether the element was originally created with or without a parent. ----@param newParent Element? -function Element:setParent(newParent) - local expectedManagedSelectParent = nil - if self._managedSelectFrame and self._managedSelectOwner then - expectedManagedSelectParent = self._managedSelectOwner - if self._managedSelectOwner._selectState and self._managedSelectOwner._selectState.selectAnchor then - expectedManagedSelectParent = self._managedSelectOwner._selectState.selectAnchor - end - end - - if self._managedSelectFrame and self._managedSelectOwner and newParent ~= expectedManagedSelectParent then - Element._Select.warnSelectFrame(self._managedSelectOwner, "ELEM_009", { - element = self._managedSelectOwner.id, - frame = self.id, - expectedParent = expectedManagedSelectParent and expectedManagedSelectParent.id or nil, - actualParent = newParent and newParent.id or nil, - }) - end - - if self.parent == newParent then - return -- Already at this parent, no-op - end - - -- Remove from current location - if self.parent then - -- removeChild sets child.parent = nil and recalculates parent layout - self.parent:removeChild(self) - else - -- Remove from topElements (element was created without a parent) - for i, elem in ipairs(Element._Context.topElements) do - if elem == self then - table.remove(Element._Context.topElements, i) - break - end - end - self.parent = nil - end - - if newParent then - -- addChild handles: setting self.parent, re-evaluating positioning, - -- inserting into children, marking dirty, auto-sizing, and layoutChildren - newParent:addChild(self) - else - -- Become a top-level element - self.parent = nil - self.x = self.x or 0 - self.y = self.y or 0 - self.z = Element._ZIndex.clamp(self.z or 0) - table.insert(Element._Context.topElements, self) - end -end - ---- Delete all child elements at once for resetting containers or clearing lists ---- Use this to efficiently empty containers when rebuilding UI from scratch -function Element:clearChildren() - -- Clear parent references for all children - for _, child in ipairs(self.children) do - Element._Select.unregisterFromSelectParent(child) - child.parent = nil - end - - -- Clear the children table - self.children = {} - - -- Recalculate auto-sizing if needed - if self.autosizing.width or self.autosizing.height then - if self.autosizing.width then - local contentWidth = self:calculateAutoWidth() - self._borderBoxWidth = contentWidth + self.padding.left + self.padding.right - self.width = contentWidth - end - if self.autosizing.height then - local contentHeight = self:calculateAutoHeight() - self._borderBoxHeight = contentHeight + self.padding.top + self.padding.bottom - self.height = contentHeight - end - end - - -- Re-layout (though there are no children now; deferred in immediate mode). - if Element._StateManager.shouldLayout() then - self:layoutChildren() - end -end - ---- Get the number of children this element has ----@return number -function Element:getChildCount() - return #self.children -end - ---- Apply positioning offsets (top, right, bottom, left) to an element --- @param element The element to apply offsets to -function Element:applyPositioningOffsets(element) - -- Delegate to LayoutEngine - self._layoutEngine:applyPositioningOffsets(element) -end - -function Element:layoutChildren() - -- Check performance warnings (only on root elements to avoid spam) - if not self.parent then - self:_checkPerformanceWarnings() - end - - -- Catch stale bare dimension writes that bypassed setProperty (e.g. - -- `element.width = "42%"` stores a raw string). Lua __newindex cannot intercept - -- these at write time (the keys exist post-construction), so we validate lazily - -- here, once per element per property, only when a reflow is already pending. - if self._dirty then - self:_checkDimensionTypes() - end - - -- Delegate layout to LayoutEngine - self._layoutEngine:layoutChildren() -end - ---- Warn once per stale dimension property that holds a non-number value, which ---- indicates a bare write (e.g. `element.width = "42%"`) bypassed setProperty. ---- Bare dimension writes neither resolve unit strings nor invalidate layout; ---- the element renders with the wrong size until :setProperty() is used. -function Element:_checkDimensionTypes() - if not self._dimWarned then - self._dimWarned = {} - end - for _, prop in ipairs({ "width", "height", "x", "y" }) do - local v = self[prop] - if v ~= nil and type(v) ~= "number" then - if not self._dimWarned[prop] then - self._dimWarned[prop] = true - Element._ErrorHandler:warn("Element", "ELM_001", { - property = prop, - message = string.format( - 'element.%s holds a non-number value (%s); a bare write bypassed setProperty and was not resolved to pixels. Use element:setProperty("%s", value) instead.', - prop, - type(v), - prop - ), - }) - end - end - end -end - ---- Warn about percentage sizing with auto-sizing parent ----@param child Element ----@param axis string "width" or "height" -function Element:_warnIfPercentageWithAutoSizing(child, axis) - if self._managedSelectFrame then - return - end - Element._ErrorHandler:warn("LayoutEngine", "LAY_004", { - child = child.id or "unnamed", - issue = "percentage " .. axis .. " with parent auto-sizing", - }) -end - ---- Whether element needs cross-axis percentage dimension syncing ---- Managed select frames sync percentage children with container dimensions ----@return boolean -function Element:_shouldSyncPercentageDimensions() - return self._managedSelectFrame == true -end - ---- Adjust cross-axis percentage width for managed select minimum ----@param child Element ----@param newBorderBoxWidth number ----@return number -function Element:_adjustCrossAxisPercentageWidth(child, newBorderBoxWidth) - if self._managedSelectFrame and self.autosizing and self.autosizing.width then - local intrinsicBorderBoxWidth = child:calculateAutoWidth() + child.padding.left + child.padding.right - return math.max(newBorderBoxWidth, intrinsicBorderBoxWidth) - end - return newBorderBoxWidth -end - ---- Layout-path delegate: adjust child border-box width for a managed-select frame. ---- Owned by Select; routed through here so the layout path stays free of dropdown details. ----@param child Element ----@param childBorderBoxWidth number ----@return number -function Element:_adjustAutoWidthChildBorderBoxForManagedSelect(child, childBorderBoxWidth) - return Element._Select.adjustAutoWidthChild(self, child, childBorderBoxWidth) -end - ---- Destroy element and its children -function Element:destroy() - -- Remove from global elements list - for i, win in ipairs(Element._Context.topElements) do - if win == self then - table.remove(Element._Context.topElements, i) - break - end - end - - if self.parent then - for i, child in ipairs(self.parent.children) do - if child == self then - Element._Select.unregisterFromSelectParent(self) - table.remove(self.parent.children, i) - break - end - end - self.parent = nil - end - - -- Destroy all children - for _, child in ipairs(self.children) do - child:destroy() - end - - -- Clear children table - self.children = {} - - -- Clear parent reference - if self.parent then - self.parent = nil - end - - -- Clear animation reference - self.animation = nil - - -- Clear onEvent to prevent closure leaks - self.onEvent = nil - - -- Clear touch callbacks to prevent closure leaks - self.onTouchEvent = nil - self.onGesture = nil - - Element._Select.cleanupDestroy(self) -end - ---- Retry deferred methods queued via `_deferMethod` during this frame. Each ---- pending entry is invoked through pcall; failures are reported to the ---- ErrorHandler instead of aborting the frame, and entries that re-defer are ---- retried next frame with an incremented retry count up to MAX_DEFER_RETRIES. ---- Extracted from the tail of Element:update so update stays a thin ---- behavior-dispatch orchestrator (behavior-mode-unification task 09). -function Element:_processDeferredMethods() - if #self._deferredMethods == 0 then - return - end - local pending = self._deferredMethods - self._deferredMethods = {} - for _, entry in ipairs(pending) do - if entry.retryCount >= MAX_DEFER_RETRIES then - Element._ErrorHandler:warn("Element", "CORE_004", { - element = self.id, - method = tostring(entry.methodName), - retryCount = entry.retryCount, - }) - else - local beforeCount = #self._deferredMethods - local callArgs = {} - for j = 1, entry.argc do - local val = entry.args[j] - if val == _DEFERRED_NIL then - callArgs[j] = nil - else - callArgs[j] = val - end - end - local success, err = pcall(function() - self[entry.methodName](self, unpack(callArgs, 1, entry.argc)) - end) - if not success then - Element._ErrorHandler:warn("Element", "CORE_002", { - element = self.id, - method = tostring(entry.methodName), - error = tostring(err), - }) - end - -- Propagate retry count to any new deferred entry for the same method - for i = beforeCount + 1, #self._deferredMethods do - if self._deferredMethods[i].methodName == entry.methodName then - self._deferredMethods[i].retryCount = entry.retryCount + 1 - end - end - end - end -end - ---- Draw element and its children -function Element:draw(backdropCanvas) - -- Early exit if element is display:none or invisible (optimization) - if self.display == false or self.opacity <= 0 or self.visibility == "hidden" then - return - end - - -- Background behaviors (drawLayer ~= "overlay") render BEFORE children in - -- registry order: Themed (core Renderer:draw), Clickable (pressed overlay), - -- ... Overlay behaviors (Scrollable scrollbars) render AFTER children below. - local drawCtx = { backdropCanvas = backdropCanvas } - local behaviors = self.behaviors - for i = 1, #behaviors do - local b = behaviors[i] - if b.drawLayer ~= "overlay" then - b.onDraw(self, drawCtx) - end - end - - -- Core child hierarchy rendering (clipping, sorting, scroll offset, blur). - -- Stays in Element: it is structural, not a per-capability behavior. - self:_drawChildren(backdropCanvas) - - -- Overlay behaviors (drawLayer == "overlay") render AFTER children so they - -- paint on top, e.g. Scrollable's scrollbars (behavior-mode-unification 09). - for i = 1, #behaviors do - local b = behaviors[i] - if b.drawLayer == "overlay" then - b.onDraw(self, drawCtx) - end - end -end - ---- Core child-drawing pipeline extracted from Element:draw so the draw entry ---- point stays a thin behavior-dispatch orchestrator (task 09). Owns z-sort, ---- rounded-corner/overflow clipping (stencil > scissor), scroll/content offset, ---- optional content-blur application, and recursive child:draw. Not a behavior ---- — this is structural hierarchy rendering shared by every element. -function Element:_drawChildren(backdropCanvas) - local sortedChildren = {} - for _, child in ipairs(self.children) do - table.insert(sortedChildren, child) - end - if #sortedChildren == 0 then - return - end - table.sort(sortedChildren, function(a, b) - return a.z < b.z - end) - - local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right) - local borderBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom) - - -- Check if we need to clip children to rounded corners - local hasRoundedCorners = false - if self.cornerRadius then - if type(self.cornerRadius) == "number" then - hasRoundedCorners = self.cornerRadius > 0 - else - hasRoundedCorners = self.cornerRadius.topLeft > 0 - or self.cornerRadius.topRight > 0 - or self.cornerRadius.bottomLeft > 0 - or self.cornerRadius.bottomRight > 0 - end - end - - -- Render the (possibly clipped + offset) child layer, applying content blur - -- when configured. The inner closure performs clipping/offset/draw; blur - -- wraps it in a region pass when a blur instance is available. - local function renderChildLayer() - local contentOffsetX, contentOffsetY = self:getContentStateOffset() - - -- Determine overflow behavior per axis (matches HTML/CSS behavior) - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - local needsOverflowClipping = (overflowX ~= "visible" or overflowY ~= "visible") - and (overflowX ~= nil or overflowY ~= nil) - - -- Apply scroll/content offset after clipping is set - local hasScrollOffset = needsOverflowClipping and (self._scrollX ~= 0 or self._scrollY ~= 0) - local hasContentOffset = contentOffsetX ~= 0 or contentOffsetY ~= 0 - local hasOffset = hasScrollOffset or hasContentOffset - - -- Set up clipping: rounded-corners (stencil) > overflow (scissor) > none - local clipMode = "none" - if hasRoundedCorners then - local roundedBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right) - local roundedBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom) - local stencilFunc = - Element._RoundedRect.stencilFunction(self.x, self.y, roundedBoxWidth, roundedBoxHeight, self.cornerRadius) - local currentCanvas = love.graphics.getCanvas() - love.graphics.setCanvas() - love.graphics.stencil(stencilFunc, "replace", 1) - love.graphics.setCanvas(currentCanvas) - love.graphics.setStencilTest("greater", 0) - clipMode = "stencil" - elseif needsOverflowClipping then - love.graphics.setScissor(self.x + self.padding.left, self.y + self.padding.top, self.width, self.height) - clipMode = "scissor" - end - - if hasOffset then - love.graphics.push() - love.graphics.translate( - (hasScrollOffset and -self._scrollX or 0) + contentOffsetX, - (hasScrollOffset and -self._scrollY or 0) + contentOffsetY - ) - end - - for _, child in ipairs(sortedChildren) do - child:draw(backdropCanvas) - end - - if hasOffset then - love.graphics.pop() - end - - -- Restore clipping state - if clipMode == "stencil" then - love.graphics.setStencilTest() - elseif clipMode == "scissor" then - love.graphics.setScissor() - end - end - - -- Apply content blur if configured - if self.contentBlur and self.contentBlur.radius > 0 then - local blurInstance = self:getBlurInstance() - if blurInstance then - Element._Blur.applyToRegion( - blurInstance, - self.contentBlur.radius, - self.x, - self.y, - borderBoxWidth, - borderBoxHeight, - renderChildLayer - ) - else - renderChildLayer() - end - else - renderChildLayer() - end -end - ---- Update element (propagate to children) ----@param dt number -function Element:update(dt) - if self.display == false then - return - end - if not self.parent then - self:_trackActiveAnimations() - end - for _, child in ipairs(self.children) do - child:update(dt) - end - -- Advance direct-assignment animations before the loop so geometry is current - -- for hit-testing; no-ops when the Animated behavior is already attached. - Element._dispatchAnimatedUpdate(self, dt) - for _, b in ipairs(self.behaviors) do - b.onUpdate(self, dt) - end - self:_processDeferredMethods() -end - ---- Handle a touch event directly (for external touch routing) ---- Invokes both onEvent and onTouchEvent callbacks if set ----@param touchEvent InputEvent The touch event to handle -function Element:handleTouchEvent(touchEvent) - if not self.touchEnabled or self.disabled then - return - end - if self._eventHandler then - self._eventHandler:_invokeCallback(self, touchEvent) - self._eventHandler:_invokeTouchCallback(self, touchEvent) - end -end - ---- Handle a gesture event (from GestureRecognizer or external routing) ----@param gesture table The gesture data (type, position, velocity, etc.) -function Element:handleGesture(gesture) - if not self.touchEnabled or self.disabled then - return - end - if self._eventHandler then - self._eventHandler:_invokeGestureCallback(self, gesture) - end -end - ---- Get active touches currently tracked on this element ----@return table Active touches keyed by touch ID -function Element:getTouches() - if self._eventHandler then - return self._eventHandler:getActiveTouches() - end - return {} -end - ----@param newViewportWidth number ----@param newViewportHeight number -function Element:recalculateUnits(newViewportWidth, newViewportHeight) - self._layoutEngine:recalculateUnits(newViewportWidth, newViewportHeight) -end - ---- Resize element and its children based on game window size change ----@param newGameWidth number ----@param newGameHeight number -function Element:resize(newGameWidth, newGameHeight) - self:recalculateUnits(newGameWidth, newGameHeight) - self:_refreshSizeConstraints(newGameWidth, newGameHeight) - - -- For non-auto-sized elements with viewport/percentage units, update content dimensions from border-box - if not self.autosizing.width and self._borderBoxWidth and self.units.width.unit ~= "px" then - self._borderBoxWidth = Element._utils.clamp(self._borderBoxWidth, self.minWidth, self.maxWidth) - self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right) - end - if not self.autosizing.height and self._borderBoxHeight and self.units.height.unit ~= "px" then - self._borderBoxHeight = Element._utils.clamp(self._borderBoxHeight, self.minHeight, self.maxHeight) - self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom) - end - - -- Update children - for _, child in ipairs(self.children) do - child:resize(newGameWidth, newGameHeight) - end - - -- Recalculate auto-sized dimensions after children are resized - if self.autosizing.width then - local contentWidth = self:calculateAutoWidth() - -- BORDER-BOX MODEL: Add padding to get border-box, then subtract to get content - self._borderBoxWidth = - Element._utils.clamp(contentWidth + self.padding.left + self.padding.right, self.minWidth, self.maxWidth) - self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right) - -- CONTENT-LEVEL CLAMP: CSS min-width/max-width also bound the content width. - -- Subtracting padding from the clamped border-box can drop the content width - -- below minWidth (e.g. minWidth=200, horizontal padding=100 => content=100), - -- so re-clamp the content dimension with the shared size-clamping utility. - self.width = Element._utils.clampSize(self.width, self.minWidth, self.maxWidth) - end - if self.autosizing.height then - local contentHeight = self:calculateAutoHeight() - -- BORDER-BOX MODEL: Add padding to get border-box, then subtract to get content - self._borderBoxHeight = - Element._utils.clamp(contentHeight + self.padding.top + self.padding.bottom, self.minHeight, self.maxHeight) - self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom) - -- CONTENT-LEVEL CLAMP: CSS min-height/max-height also bound the content height. - -- Subtracting padding from the clamped border-box can drop the content height - -- below minHeight (e.g. minHeight=200, vertical padding=100 => content=100), - -- so re-clamp the content dimension with the shared size-clamping utility. - self.height = Element._utils.clampSize(self.height, self.minHeight, self.maxHeight) - end - - -- Re-resolve textSize if it uses viewport-relative units after dimensions are finalized - - self:layoutChildren() - self.prevGameSize.width = newGameWidth - self.prevGameSize.height = newGameHeight -end - -function Element:_refreshSizeConstraints(newViewportWidth, newViewportHeight) - local scaleX, scaleY = Element._Context.getScaleFactors() - local ctx = { vw = newViewportWidth, vh = newViewportHeight, sx = scaleX, sy = scaleY } - local parentW = self.parent and self.parent.width or newViewportWidth - local parentH = self.parent and self.parent.height or newViewportHeight - _refreshUnit(self, "minWidth", parentW, ctx, "x") - _refreshUnit(self, "maxWidth", parentW, ctx, "x") - _refreshUnit(self, "minHeight", parentH, ctx, "y") - _refreshUnit(self, "maxHeight", parentH, ctx, "y") -end - ---- Calculate text width for button ----@return number -function Element:calculateTextWidth() - if self.text == nil then - return 0 - end - - local font = Element._utils.getFont(self.textSize, self.fontFamily, self.themeComponent, self._themeManager) - local width = font:getWidth(self.text) - return Element._utils.applyContentMultiplier(width, self.contentAutoSizingMultiplier, "width") -end - ----@return number -function Element:calculateTextHeight() - if self.text == nil then - return 0 - end - - local font = Element._utils.getFont(self.textSize, self.fontFamily, self.themeComponent, self._themeManager) - local height = font:getHeight() - - if self.textWrap and (self.textWrap == "word" or self.textWrap == "char" or self.textWrap == true) then - local availableWidth = self.width - - if (not availableWidth or availableWidth <= 0) and self.parent then - availableWidth = self.parent.width - end - - if availableWidth and availableWidth > 0 then - local _, wrappedLines = font:getWrap(self.text, availableWidth) - height = height * #wrappedLines - end - end - - return Element._utils.applyContentMultiplier(height, self.contentAutoSizingMultiplier, "height") -end - -function Element:calculateAutoWidth() - local contentWidth = self._layoutEngine:calculateAutoWidth() - if self._managedSelectMinimumBorderBoxWidth then - local minimumContentWidth = - math.max(0, self._managedSelectMinimumBorderBoxWidth - self.padding.left - self.padding.right) - contentWidth = math.max(contentWidth, minimumContentWidth) - end - return contentWidth -end - ---- Calculate auto height based on children -function Element:calculateAutoHeight() - return self._layoutEngine:calculateAutoHeight() -end - ----@param newText string ----@param autoresize boolean? --default: false -function Element:updateText(newText, autoresize) - self.text = newText or self.text - if autoresize then - self.width = self:calculateTextWidth() - self.height = self:calculateTextHeight() - end -end - ----@param newOpacity number -function Element:updateOpacity(newOpacity) - self.opacity = newOpacity - for _, child in ipairs(self.children) do - child:updateOpacity(newOpacity) - end -end - ---- same as calling updateOpacity(0) -function Element:hide() - self:updateOpacity(0) -end - ---- same as calling updateOpacity(1) -function Element:show() - self:updateOpacity(1) -end - --- ==================== --- Input Handling - Text Editing (behavior-delegated, task 04) --- ==================== --- All text-editor operations are dispatched through the TextEditable behavior --- (modules/behaviors/TextEditable.lua). Element retains only thin 1-line --- forwarders for backward-compat with external callers (EventHandler, --- KeyboardNavigation, Renderer, game UI). The behavior owns the TextEditor --- subsystem (onAttach creates it, onUpdate drives cursor blink, saveState / --- restoreState persist it) AND implements the delegate bodies (text sync, --- auto-grow, nil-guarding element._textEditor) — so Element carries zero --- text-editor nil-guard branches and zero text-editor logic. --- --- `_wrapLine` / `_getFont` remain here: they are RENDERER forwarders (not --- TextEditor delegates), and the TextEditor has its own implementations. --- `updateText` (above) is a plain-label text setter, not a TextEditor delegate. --- ==================== - ---- Set cursor position (delegates to TextEditable behavior) ----@param position number -- Character index (0-based) -function Element:setCursorPosition(position) - return Element._TextEditable.setCursorPosition(self, position) -end - ---- Get cursor position (delegates to TextEditable behavior) ----@return number -- Character index (0-based) -function Element:getCursorPosition() - return Element._TextEditable.getCursorPosition(self) -end - ---- Move cursor by delta characters (delegates to TextEditable behavior) ----@param delta number -- Number of characters to move (positive or negative) -function Element:moveCursorBy(delta) - return Element._TextEditable.moveCursorBy(self, delta) -end - ---- Move cursor to start of text (delegates to TextEditable behavior) -function Element:moveCursorToStart() - return Element._TextEditable.moveCursorToStart(self) -end - ---- Move cursor to end of text (delegates to TextEditable behavior) -function Element:moveCursorToEnd() - return Element._TextEditable.moveCursorToEnd(self) -end - ---- Move cursor to start of current line (delegates to TextEditable behavior) -function Element:moveCursorToLineStart() - return Element._TextEditable.moveCursorToLineStart(self) -end - ---- Move cursor to end of current line (delegates to TextEditable behavior) -function Element:moveCursorToLineEnd() - return Element._TextEditable.moveCursorToLineEnd(self) -end - ---- Move cursor to start of previous word (delegates to TextEditable behavior) -function Element:moveCursorToPreviousWord() - return Element._TextEditable.moveCursorToPreviousWord(self) -end - ---- Move cursor to start of next word (delegates to TextEditable behavior) -function Element:moveCursorToNextWord() - return Element._TextEditable.moveCursorToNextWord(self) -end - ---- Set selection range (delegates to TextEditable behavior) ----@param startPos number -- Start position (inclusive) ----@param endPos number -- End position (inclusive) -function Element:setSelection(startPos, endPos) - return Element._TextEditable.setSelection(self, startPos, endPos) -end - ---- Get selection range (delegates to TextEditable behavior) ----@return number?, number? -- Start and end positions, or nil if no selection -function Element:getSelection() - return Element._TextEditable.getSelection(self) -end - ---- Check if there is an active selection (delegates to TextEditable behavior) ----@return boolean -function Element:hasSelection() - return Element._TextEditable.hasSelection(self) -end - ---- Clear selection (delegates to TextEditable behavior) -function Element:clearSelection() - return Element._TextEditable.clearSelection(self) -end - ---- Select all text (delegates to TextEditable behavior) -function Element:selectAll() - return Element._TextEditable.selectAll(self) -end - ---- Get selected text (delegates to TextEditable behavior) ----@return string? -- Selected text or nil if no selection -function Element:getSelectedText() - return Element._TextEditable.getSelectedText(self) -end - ---- Delete selected text (delegates to TextEditable behavior, which owns text sync + auto-grow) ----@return boolean -- True if text was deleted -function Element:deleteSelection() - return Element._TextEditable.deleteSelection(self) -end - ---- Give this element keyboard focus to enable text input or keyboard navigation ---- Use this to automatically focus text fields when showing forms or dialogs -function Element:focus() - return Element._TextEditable.focus(self) -end - ---- Remove keyboard focus to stop capturing input events ---- Use this when closing popups or switching focus to other elements -function Element:blur() - return Element._TextEditable.blur(self) -end - ---- Query focus state to conditionally render focus indicators or handle keyboard input ---- Use this to style focused elements or determine which element receives keyboard events ----@return boolean -function Element:isFocused() - return Element._TextEditable.isFocused(self) -end - ---- Retrieve the element's current text content for processing or validation ---- Use this to read user input from text fields or get display text ----@return string -function Element:getText() - return Element._TextEditable.getText(self) -end - ---- Update the element's text content programmatically for dynamic labels or resetting inputs ---- Use this to change text without user input, like clearing fields or updating status messages ----@param text string -function Element:setText(text) - return Element._TextEditable.setText(self, text) -end - ---- Programmatically insert text at any position for autocomplete or text manipulation ---- Use this to implement suggestions, templates, or text snippets ----@param text string -- Text to insert ----@param position number? -- Position to insert at (default: cursor position) -function Element:insertText(text, position) - return Element._TextEditable.insertText(self, text, position) -end - ----@param startPos number -- Start position (inclusive) ----@param endPos number -- End position (inclusive) -function Element:deleteText(startPos, endPos) - return Element._TextEditable.deleteText(self, startPos, endPos) -end - ---- Replace text in range ----@param startPos number -- Start position (inclusive) ----@param endPos number -- End position (inclusive) ----@param newText string -- Replacement text -function Element:replaceText(startPos, endPos, newText) - return Element._TextEditable.replaceText(self, startPos, endPos, newText) -end - ---- Wrap a single line of text ----@param line string -- Line to wrap ----@param maxWidth number -- Maximum width in pixels ----@return table -- Array of wrapped line parts -function Element:_wrapLine(line, maxWidth) - return self._renderer:wrapLine(self, line, maxWidth) -end - ----@return love.Font -function Element:_getFont() - return self._renderer:getFont(self) -end - --- ==================== --- Input Handling - Mouse Selection --- ==================== - ---- Handle mouse click on text (set cursor position or start selection) ---- Delegates to the TextEditable behavior, which owns drag tracking. ----@param mouseX number -- Mouse X coordinate ----@param mouseY number -- Mouse Y coordinate ----@param clickCount number -- Number of clicks (1=single, 2=double, 3=triple) -function Element:_handleTextClick(mouseX, mouseY, clickCount) - return Element._TextEditable._handleTextClick(self, mouseX, mouseY, clickCount) -end - ---- Handle mouse drag for text selection ---- Delegates to the TextEditable behavior, which owns drag tracking. ----@param mouseX number -- Mouse X coordinate ----@param mouseY number -- Mouse Y coordinate -function Element:_handleTextDrag(mouseX, mouseY) - return Element._TextEditable._handleTextDrag(self, mouseX, mouseY) -end - --- ==================== --- Input Handling - Keyboard Input (behavior-delegated, task 04) --- ==================== - ---- Handle text input (character input) — delegates to the TextEditable behavior. ----@param text string -- Character(s) to insert -function Element:textinput(text) - return Element._TextEditable.textinput(self, text) -end - ---- Handle key press (special keys) — delegates to the TextEditable behavior. ----@param key string -- Key name ----@param scancode string -- Scancode ----@param isrepeat boolean -- Whether this is a key repeat -function Element:keypressed(key, scancode, isrepeat) - return Element._TextEditable.keypressed(self, key, scancode, isrepeat) -end - --- ==================== --- Performance Monitoring --- ==================== - ---- Get hierarchy depth of this element ----@return number depth Depth in the element tree (0 for root) -function Element:getHierarchyDepth() - local depth = 0 - local current = self.parent - while current do - depth = depth + 1 - current = current.parent - end - return depth -end - ---- Count total elements in this tree ----@return number count Total number of elements including this one and all descendants -function Element:countElements() - local count = 1 -- Count self - for _, child in ipairs(self.children) do - count = count + child:countElements() - end - return count -end - -function Element:_checkPerformanceWarnings() - if not Element._Performance or not Element._Performance.warningsEnabled then - return - end - - -- Check hierarchy depth - local depth = self:getHierarchyDepth() - if depth >= 15 then - Element._Performance:logWarning( - string.format("hierarchy_depth_%s", self.id), - "Element", - string.format("Element hierarchy depth is %d levels for element '%s'", depth, self.id or "unnamed"), - { depth = depth, elementId = self.id or "unnamed" }, - "Deep nesting can impact performance. Consider flattening the structure or using absolute positioning" - ) - end - - -- Check total element count (only for root elements) - if not self.parent then - local totalElements = self:countElements() - if totalElements >= 1000 then - Element._Performance:logWarning( - "element_count_high", - "Element", - string.format("UI contains %d+ elements", totalElements), - { elementCount = totalElements }, - "Large element counts may impact performance. Consider virtualization for long lists or pagination for large datasets" - ) - end - end -end - ---- Count active animations in tree ----@return number count Number of active animations -function Element:_countActiveAnimations() - local count = self.animation and 1 or 0 - for _, child in ipairs(self.children) do - count = count + child:_countActiveAnimations() - end - return count -end - ---- Track active animations and warn if too many -function Element:_trackActiveAnimations() - -- Get Performance instance from deps if available - if not Element._Performance or not Element._Performance.warningsEnabled then - return - end - - local animCount = self:_countActiveAnimations() - if animCount >= 50 then - Element._Performance:logWarning( - "animation_count_high", - "Element", - string.format("%d+ animations running simultaneously", animCount), - { animationCount = animCount }, - "High animation counts may impact frame rate. Consider reducing concurrent animations or using CSS-style transitions" - ) - end -end - ---- Change the tint color of an image element dynamically for hover effects or state indication ---- Use this to recolor images without replacing the asset, like highlighting selected items ----@param color Color Color to tint the image -function Element:setImageTint(color) - self.imageTint = color -end - ---- Adjust image transparency independently from the element for fade effects ---- Use this to create image-specific fade animations or disabled states ----@param opacity number Opacity 0-1 -function Element:setImageOpacity(opacity) - if opacity ~= nil then - Element._utils.validateRange(opacity, 0, 1, "imageOpacity") - end - self.imageOpacity = opacity -end - ---- Set image repeat mode ----@param repeatMode string Repeat mode: "no-repeat", "repeat", "repeat-x", "repeat-y", "space", "round" -function Element:setImageRepeat(repeatMode) - local validImageRepeat = { - ["no-repeat"] = "no-repeat", - ["repeat"] = "repeat", - ["repeat-x"] = "repeat-x", - ["repeat-y"] = "repeat-y", - space = "space", - round = "round", - } - Element._utils.validateEnum(repeatMode, validImageRepeat, "imageRepeat") - self.imageRepeat = repeatMode -end - ---- Apply rotation transform to create spinning animations or rotated layouts ---- Use this for loading spinners, compass needles, or angled UI elements ----@param angle number Angle in radians -function Element:rotate(angle) - if not self.transform then - self.transform = Element._Transform.new({}) - end - self.transform.rotate = angle -end - ---- Resize element visually using scale transforms for zoom effects ---- Use this for hover magnification, shrinking animations, or responsive scaling ----@param scaleX number X-axis scale ----@param scaleY number? Y-axis scale (defaults to scaleX) -function Element:scale(scaleX, scaleY) - if not self.transform then - self.transform = Element._Transform.new({}) - end - self.transform.scaleX = scaleX - self.transform.scaleY = scaleY or scaleX -end - ---- Offset element position using transforms for smooth movement without layout recalculation ---- Use this for parallax effects, draggable elements, or position animations ----@param x number X translation ----@param y number Y translation -function Element:translate(x, y) - if not self.transform then - self.transform = Element._Transform.new({}) - end - self.transform.translateX = x - self.transform.translateY = y -end - ---- Define the pivot point for rotation and scaling transforms ---- Use this to rotate around corners, edges, or custom points rather than the center ----@param originX number X origin (0-1, where 0.5 is center) ----@param originY number Y origin (0-1, where 0.5 is center) -function Element:setTransformOrigin(originX, originY) - if not self.transform then - self.transform = Element._Transform.new({}) - end - self.transform.originX = originX - self.transform.originY = originY -end - ---- Animate element to new property values with automatic transition ---- Captures current values as start, uses provided values as final, and applies the animation ----@param props table Target property values ----@param duration number? Animation duration in seconds (default: 0.3) ----@param easing string? Easing function name (default: "linear") ----@return Element self For method chaining -function Element:animateTo(props, duration, easing) - if not Element._Animation then - _warnAnimApi("ELEM_003") - return self - end - - if type(props) ~= "table" then - _warnAnimApi("ELEM_003") - return self - end - - duration = duration or 0.3 - easing = easing or "linear" - - -- Collect current values as start - local startValues = {} - for key, _ in pairs(props) do - startValues[key] = self[key] - end - - -- Create and apply animation - local anim = Element._Animation.new({ - duration = duration, - start = startValues, - final = props, - easing = easing, - }) - - anim:apply(self) - return self -end - ---- Fade element to full opacity ----@param duration number? Duration in seconds (default: 0.3) ----@param easing string? Easing function name ----@return Element self For method chaining -function Element:fadeIn(duration, easing) - return self:animateTo({ opacity = 1 }, duration or 0.3, easing) -end - ---- Fade element to zero opacity ----@param duration number? Duration in seconds (default: 0.3) ----@param easing string? Easing function name ----@return Element self For method chaining -function Element:fadeOut(duration, easing) - return self:animateTo({ opacity = 0 }, duration or 0.3, easing) -end - ---- Scale element to target scale value using transforms ----@param targetScale number Target scale multiplier ----@param duration number? Duration in seconds (default: 0.3) ----@param easing string? Easing function name ----@return Element self For method chaining -function Element:scaleTo(targetScale, duration, easing) - if not Element._Animation or not Element._Transform then - _warnAnimApi("ELEM_003") - return self - end - - -- Ensure element has a transform - if not self.transform then - self.transform = Element._Transform.new({}) - end - - local currentScaleX = self.transform.scaleX or 1 - local currentScaleY = self.transform.scaleY or 1 - - local anim = Element._Animation.new({ - duration = duration or 0.3, - start = { scaleX = currentScaleX, scaleY = currentScaleY }, - final = { scaleX = targetScale, scaleY = targetScale }, - easing = easing or "linear", - }) - - anim:apply(self) - return self -end - ---- Move element to target position ----@param x number Target x position ----@param y number Target y position ----@param duration number? Duration in seconds (default: 0.3) ----@param easing string? Easing function name ----@return Element self For method chaining -function Element:moveTo(x, y, duration, easing) - return self:animateTo({ x = x, y = y }, duration or 0.3, easing) -end - ---- Set transition configuration for a property ----@param property string Property name or "all" for all properties ----@param config table Transition config {duration, easing, delay, onComplete} -function Element:setTransition(property, config) - if not self.transitions then - self.transitions = {} - end - - if type(config) ~= "table" then - _warnAnimApi("ELEM_003") - config = {} - end - - -- Validate config - if config.duration and (type(config.duration) ~= "number" or config.duration < 0) then - _warnAnimApi("ELEM_004", config.duration) - config.duration = 0.3 - end - - self.transitions[property] = { - duration = config.duration or 0.3, - easing = config.easing or "easeOutQuad", - delay = config.delay or 0, - onComplete = config.onComplete, - } -end - ---- Set transition configuration for multiple properties ----@param groupName string Name for this transition group ----@param config table Transition config {duration, easing, delay, onComplete} ----@param properties table Array of property names -function Element:setTransitionGroup(_, config, properties) - if type(properties) ~= "table" then - _warnAnimApi("ELEM_005") - return - end - - for _, prop in ipairs(properties) do - self:setTransition(prop, config) - end -end - ---- Remove transition configuration for a property ----@param property string Property name or "all" to remove all -function Element:removeTransition(property) - if not self.transitions then - return - end - - if property == "all" then - self.transitions = {} - else - self.transitions[property] = nil - end -end - ---- Resolve a unit-based dimension property (width/height) from a string or CalcObject ---- Parses the value, updates self.units, resolves to pixels, and updates border-box dimensions ----@param property string "width" or "height" ----@param value string|table The unit string (e.g., "50%", "10vw") or CalcObject ----@return number resolvedValue The resolved pixel value -function Element:_resolveDimensionProperty(property, value) - local viewportWidth, viewportHeight = Element._Units.getViewport() - local parsedValue, parsedUnit = Element._Units.parse(value) - self.units[property] = { value = parsedValue, unit = parsedUnit } - - local parentDimension - if property == "width" then - parentDimension = self.parent and self.parent.width or viewportWidth - else - parentDimension = self.parent and self.parent.height or viewportHeight - end - - local resolved = Element._Units.resolve(parsedValue, parsedUnit, viewportWidth, viewportHeight, parentDimension) - - if type(resolved) ~= "number" then - Element._ErrorHandler:warn("Element", "LAY_003", { - issue = string.format("%s resolution returned non-number value", property), - type = type(resolved), - value = tostring(resolved), - }) - resolved = 0 - end - - self[property] = resolved - - if property == "width" then - if self.autosizing and self.autosizing.width then - self._borderBoxWidth = resolved + self.padding.left + self.padding.right - else - self._borderBoxWidth = resolved - end - else - if self.autosizing and self.autosizing.height then - self._borderBoxHeight = resolved + self.padding.top + self.padding.bottom - else - self._borderBoxHeight = resolved - end - end - - return resolved -end - ---- Resolve a dimension (width/height) prop given a unit-string/Calc value. ---- Handles the unit-sameness short-circuit, transition-on-resolved-pixel-value ---- semantics, and layout invalidation. Exits setProperty (caller returns). -local function _setDimensionWithUnit(self, property, value, transitionConfig) - -- Check if the unit specification is the same (compare against stored units) - local currentUnits = self.units[property] - local newValue, newUnit = Element._Units.parse(value) - if currentUnits and currentUnits.value == newValue and currentUnits.unit == newUnit then - return - end - - if transitionConfig then - -- For transitions, resolve the target value and transition the pixel value - local currentPixelValue = self[property] - local resolvedTarget = self:_resolveDimensionProperty(property, value) - - if currentPixelValue ~= nil and currentPixelValue ~= resolvedTarget then - -- Reset to current value before animating - self[property] = currentPixelValue - local Animation = require("modules.Animation") - local anim = Animation.new({ - duration = transitionConfig.duration, - start = { [property] = currentPixelValue }, - final = { [property] = resolvedTarget }, - easing = transitionConfig.easing, - onComplete = transitionConfig.onComplete, - }) - anim:apply(self) - end - else - self:_resolveDimensionProperty(property, value) - end - - self:invalidateLayout() -end - ---- Apply a transition animation from the current value to `value` for `property`. ---- Falls back to a direct write when there is no current value to animate from. -local function _animatePropertyTo(self, property, value, transitionConfig) - local currentValue = self[property] - if currentValue ~= nil then - local Animation = require("modules.Animation") - local anim = Animation.new({ - duration = transitionConfig.duration, - start = { [property] = currentValue }, - final = { [property] = value }, - easing = transitionConfig.easing, - onComplete = transitionConfig.onComplete, - }) - anim:apply(self) - else - self[property] = value - end -end - --- Explicit handler map for the few props with genuinely-different setProperty --- semantics that cannot be expressed via schema flags alone. Adding a new prop --- with ordinary behavior requires NO new entry here — it flows through the --- generic flagged dispatch below. Handlers signal a full-handled early return. -local _specialSetHandlers = { - parent = function(self, value) - self:setParent(value) - return true - end, - themeComponent = function(self, value) - self.themeComponent = value - self:_syncThemeAndRenderer("themeComponent", value) - return true - end, - -- imagePath / image: setting these must re-run the Imageable load pipeline - -- (recompute `_loadedImage`, fire onImageLoad/onImageError, defer I/O). The - -- Imageable behavior installs `element._reloadImage` at construction; if it - -- is absent the element has no image concern (Imageable only attaches when - -- imagePath/image is declared at construction), so the field is set but no - -- load occurs — late image-concern acquisition requires re-attaching the - -- behavior, which is outside the attach-at-construction contract. - imagePath = function(self, value) - self.imagePath = value - if self._reloadImage then - self:_reloadImage() - end - return true - end, - image = function(self, value) - self.image = value - if self._reloadImage then - self:_reloadImage() - end - return true - end, -} - ---- Set property with automatic transition. ---- Dispatch is registry-driven: dimension/unit props route through ---- `_setDimensionWithUnit`, the genuinely-special props (parent, ---- themeComponent, imagePath, image) route through `_specialSetHandlers`, and ---- everything else is a single generic path that consults schema flags ---- (`affectsLayout` / `syncsTheme`) for layout invalidation and theme sync. No ---- inline hardcoded property-name branches and no per-call table allocation. ----@param property string Property name ----@param value any New value -function Element:setProperty(property, value) - local transitionConfig - if self.transitions then - transitionConfig = self.transitions[property] or self.transitions["all"] - end - - local schema = Element._PropertySchema - - -- 1. Dimension prop with a unit string / CalcObject: resolve to pixels. - if schema.isDimension(property) and (type(value) == "string" or (Element._Calc and Element._Calc.isCalc(value))) then - _setDimensionWithUnit(self, property, value, transitionConfig) - return - end - - -- 2. Genuinely-special props (parent reparenting, themeComponent sync). - local handler = _specialSetHandlers[property] - if handler then - handler(self, value) - return - end - - -- 3. Generic flagged dispatch. - -- Skip write/transition/layout work for unchanged values, but still sync - -- theme state: disabled/active must reach setThemeState even when the value is - -- unchanged (renderer/theme state may have been reset out-of-band). - if self[property] ~= value then - if transitionConfig then - _animatePropertyTo(self, property, value, transitionConfig) - else - self[property] = value - end - if schema.affectsLayout(property) then - self:invalidateLayout() - end - end - if schema.syncsTheme(property) then - self:_syncThemeAndRenderer(property, value) - end -end - ----Sync ThemeManager and Renderer when properties change that affect rendering ----@param property string The property name that changed ----@param value any The new value -function Element:_syncThemeAndRenderer(property, value) - -- Visual props (backgroundColor/borderColor/cornerRadius/opacity) and callbacks - -- (onEvent/onTouchEvent/onGesture) are intentionally NOT synced here: Renderer:draw - -- and EventHandler dispatch read them from the element as source of truth, so a - -- bare `element. = v` write is immediately consistent with setProperty(...). - -- Only stateful side effects (theme-state machine + themeManager component) remain. - if property == "disabled" then - if self._themeManager then - self._themeManager.disabled = value - end - if self._renderer then - self._renderer:setThemeState(value and "disabled" or "normal") - end - elseif property == "active" then - if self._themeManager then - self._themeManager.active = value - end - if self._renderer then - self._renderer:setThemeState(value and "active" or "normal") - end - elseif property == "themeComponent" then - if self._themeManager then - self._themeManager.themeComponent = value - end - end -end - --- ==================== --- State Persistence (behavior-mode-unification task 12) --- ==================== - ---- Save all element state for immediate-mode persistence. ---- Each attached behavior owns its own state extraction (saveState hook) and ---- returns a snapshot (or nil) merged into the consolidated state table: ---- Clickable → `eventHandler`, Scrollable → `scrollManager`, TextEditable → ---- `textEditor` + drag tracking, Selectable → `select`, Themed → `blur`, ---- Persistable → `_props` (public scalar mutations). Element owns ZERO ---- per-subsystem extraction logic — this method is a pure dispatch loop. ----@return ElementStateData state Complete state snapshot -function Element:saveState() - local state = {} - for i = 1, #self.behaviors do - local bstate = self.behaviors[i].saveState(self) - if bstate ~= nil then - for k, v in pairs(bstate) do - state[k] = v - end - end - end - return state -end - ---- Restore all element state from StateManager. ---- Each attached behavior owns its own hydration (restoreState hook) and reads ---- only its own slice from the full state table. Registry order places ---- Persistable last so `_props` overrides subsystem-hydrated state, preserving ---- the legacy restore ordering. Element owns ZERO per-subsystem hydration. ----@param state ElementStateData State to restore -function Element:restoreState(state) - if not state then - return - end - for i = 1, #self.behaviors do - self.behaviors[i].restoreState(self, state) - end -end - ---- Cleanup method to break circular references (immediate-mode frame end). ---- Iterates each attached behavior's `onDetach` hook so every behavior tears ---- down what its `onAttach` created (Clickable releases the EventHandler, ---- TextEditable the TextEditor, Themed the Renderer, Selectable the select ---- fields, Imageable the image callbacks), then clears the behaviors list and ---- unregisters from StateManager. Does NOT clear onEvent / onTouchEvent / ---- onGesture — the Renderer/EventHandler read those directly from the element ---- (not the cache), so clearing them here would break retained mode. -function Element:_cleanup() - for i = 1, #self.behaviors do - self.behaviors[i].onDetach(self) - end - self.behaviors = {} - -- onCreate fires once at construction (already invoked by now); release it. - self.onCreate = nil - if self._stateId and self._stateId ~= "" then - Element._StateManager.unregisterStateful(self._stateId) - end -end - --- ==================== --- Keyboard Navigation --- ==================== - ---- Check if this element can receive keyboard focus ----@return boolean -function Element:isFocusable() - if self.disabled then - return false - end - -- Capability query: an element is keyboard-focusable when it is editable, has - -- an event/text handler, participates in the Select subsystem, or is a - -- touch-interactive element with callbacks. Expressed as a single boolean - -- expression (not a dispatch branch) because focusability is a query, not a - -- per-frame behavior. - return not not ( - self.editable - or type(self.onEvent) == "function" - or self._selectState - or self.selectOption - or self.onTextInput - or (self.touchEnabled and (self.onTouchEvent or self.onGesture)) - ) -end - ---- Get all focusable children in DOM/document order (depth-first traversal) ---- Elements are collected in the order they appear in the children array, ---- with nested children collected after their parent. This matches standard ---- browser tab order behavior where elements are ordered by document position. ----@return Element[] -function Element:getFocusableChildren() - local focusable = {} - - local function collectFocusable(elem) - for _, child in ipairs(elem.children) do - -- Check self first - if child:isFocusable() then - table.insert(focusable, child) - end - - -- Then recurse (depth-first) - collectFocusable(child) - end - end - - collectFocusable(self) - return focusable -end - ---- Get next focusable element in sequence ----@param container Element The container element ----@param currentElement Element? Current focused element ----@param wrap boolean? Whether to wrap around ----@return Element? -function Element.getNextFocusable(container, currentElement, wrap) - local focusable = container:getFocusableChildren() - if #focusable == 0 then - return nil - end - - -- Find current index - local currentIndex = 0 - if currentElement then - for i, elem in ipairs(focusable) do - if elem == currentElement then - currentIndex = i - break - end - end - end - - -- Find next - local nextIndex = currentIndex + 1 - if nextIndex > #focusable then - if wrap then - nextIndex = 1 - else - return nil - end - end - - return focusable[nextIndex] -end - ---- Get previous focusable element in sequence ----@param container Element The container element ----@param currentElement Element? Current focused element ----@param wrap boolean? Whether to wrap around ----@return Element? -function Element.getPreviousFocusable(container, currentElement, wrap) - local focusable = container:getFocusableChildren() - if #focusable == 0 then - return nil - end - - -- Find current index - local currentIndex = #focusable + 1 - if currentElement then - for i, elem in ipairs(focusable) do - if elem == currentElement then - currentIndex = i - break - end - end - end - - -- Find previous - local prevIndex = currentIndex - 1 - if prevIndex < 1 then - if wrap then - prevIndex = #focusable - else - return nil - end - end - - return focusable[prevIndex] -end - -return Element diff --git a/libs/flexlove/modules/Enums.lua b/libs/flexlove/modules/Enums.lua deleted file mode 100644 index 9e94b4c1..00000000 --- a/libs/flexlove/modules/Enums.lua +++ /dev/null @@ -1,171 +0,0 @@ --- Layout, flex, text, image, and ARIA enums used across FlexLove. --- Extracted from utils so utils stays under its LOC budget; re-exported as --- `utils.enums` for backward compatibility. - -local enums = { - ---@enum TextAlign - TextAlign = { START = "start", CENTER = "center", END = "end", JUSTIFY = "justify" }, - ---@enum TextAlignVertical - TextAlignVertical = { START = "start", CENTER = "center", END = "end" }, - ---@enum Positioning - Positioning = { ABSOLUTE = "absolute", RELATIVE = "relative", FLEX = "flex", GRID = "grid" }, - ---@enum FlexDirection - FlexDirection = { - HORIZONTAL = "horizontal", - VERTICAL = "vertical", - ROW = "row", - COLUMN = "column", - HORIZONTAL_REVERSE = "horizontal-reverse", - VERTICAL_REVERSE = "vertical-reverse", - ROW_REVERSE = "row-reverse", - COLUMN_REVERSE = "column-reverse", - }, - ---@enum JustifyContent - JustifyContent = { - FLEX_START = "flex-start", - CENTER = "center", - SPACE_AROUND = "space-around", - FLEX_END = "flex-end", - SPACE_EVENLY = "space-evenly", - SPACE_BETWEEN = "space-between", - }, - ---@enum JustifySelf - JustifySelf = { - AUTO = "auto", - FLEX_START = "flex-start", - CENTER = "center", - FLEX_END = "flex-end", - SPACE_AROUND = "space-around", - SPACE_EVENLY = "space-evenly", - SPACE_BETWEEN = "space-between", - }, - ---@enum AlignItems - AlignItems = { - STRETCH = "stretch", - FLEX_START = "flex-start", - FLEX_END = "flex-end", - CENTER = "center", - BASELINE = "baseline", - }, - ---@enum AlignSelf - AlignSelf = { - AUTO = "auto", - STRETCH = "stretch", - FLEX_START = "flex-start", - FLEX_END = "flex-end", - CENTER = "center", - BASELINE = "baseline", - }, - ---@enum AlignContent - AlignContent = { - STRETCH = "stretch", - FLEX_START = "flex-start", - FLEX_END = "flex-end", - CENTER = "center", - SPACE_BETWEEN = "space-between", - SPACE_AROUND = "space-around", - }, - ---@enum FlexWrap - FlexWrap = { NOWRAP = "nowrap", WRAP = "wrap", WRAP_REVERSE = "wrap-reverse" }, - ---@enum TextSize - TextSize = { - XXS = "xxs", - XS = "xs", - SM = "sm", - MD = "md", - LG = "lg", - XL = "xl", - XXL = "xxl", - XL3 = "3xl", - XL4 = "4xl", - }, - ---@enum ImageRepeat - ImageRepeat = { - NO_REPEAT = "no-repeat", - REPEAT = "repeat", - REPEAT_X = "repeat-x", - REPEAT_Y = "repeat-y", - SPACE = "space", - ROUND = "round", - }, - - ---@enum ARIA Role (accessibility roles for screen readers) - ARIA = { - -- Widget roles - BUTTON = "button", - CHECKBOX = "checkbox", - LINK = "link", - MENUITEM = "menuitem", - MENUITEMCHECKBOX = "menuitemcheckbox", - MENUITEMRADIO = "menuitemradio", - PROGRESSBAR = "progressbar", - RADIO = "radio", - SCROLLBAR = "scrollbar", - SLIDER = "slider", - SPINBUTTON = "spinbutton", - SWITCH = "switch", - TAB = "tab", - TABLIST = "tablist", - TABPANEL = "tabpanel", - TEXTBOX = "textbox", - TOOLTIP = "tooltip", - TREEITEM = "treeitem", - COMBOBOX = "combobox", - GRID = "grid", - GRIDCELL = "gridcell", - LISTBOX = "listbox", - LISTITEM = "listitem", - MENU = "menu", - MENUBAR = "menubar", - TREE = "tree", - TREEGRID = "treegrid", - WINDOW = "window", - DIALOG = "dialog", - ALERTDIALOG = "alertdialog", - - -- Landmark roles - BANNER = "banner", - COMPLEMENTARY = "complementary", - CONTENTINFO = "contentinfo", - FORM = "form", - MAIN = "main", - NAVIGATION = "navigation", - REGION = "region", - SEARCH = "search", - - -- Live region roles - ALERT = "alert", - LOG = "log", - MARQUEE = "marquee", - STATUS = "status", - TIMERTIME = "timer", - - -- Document structure roles - ARTICLE = "article", - BLOCKQUOTEBLOCKQUOTE = "blockquote", - CAPTION = "caption", - CODE = "code", - DEFINITION = "definition", - DELETED = "deletion", - DIRECTORY = "directory", - DIVISION = "division", - EMphasis = "emphasis", - HEADING = "heading", - INSERTED = "insertion", - LIST = "list", - MARK = "mark", - MATH = "math", - NONE = "none", - PARAGRAPH = "paragraph", - PRESENTATION = "presentation", - SEPARATOR = "separator", - STRONG = "strong", - SUBSCRIPT = "subscript", - SUPERSCRIPT = "superscript", - TERM = "term", - TIME = "time", - VARIABLE = "variable", - }, -} - -return { enums = enums } diff --git a/libs/flexlove/modules/ErrorHandler.lua b/libs/flexlove/modules/ErrorHandler.lua deleted file mode 100644 index 2f930619..00000000 --- a/libs/flexlove/modules/ErrorHandler.lua +++ /dev/null @@ -1,1042 +0,0 @@ ----@class ErrorCodes ----@field categories table ----@field codes table -local ErrorCodes = { - categories = { - VAL = "Validation", - LAY = "Layout", - REN = "Render", - THM = "Theme", - EVT = "Event", - RES = "Resource", - SYS = "System", - }, - codes = { - -- Validation Errors (VAL_001 - VAL_099) - VAL_001 = { - code = "FLEXLOVE_VAL_001", - category = "VAL", - description = "Invalid property type", - suggestion = "Check the property type matches the expected type (e.g., number, string, table)", - }, - VAL_002 = { - code = "FLEXLOVE_VAL_002", - category = "VAL", - description = "Property value out of range", - suggestion = "Ensure the value is within the allowed min/max range", - }, - VAL_003 = { - code = "FLEXLOVE_VAL_003", - category = "VAL", - description = "Required property missing", - suggestion = "Provide the required property in your element definition", - }, - VAL_004 = { - code = "FLEXLOVE_VAL_004", - category = "VAL", - description = "Invalid color format", - suggestion = "Use valid color format: {r, g, b, a} with values 0-1, hex string, or Color object", - }, - VAL_005 = { - code = "FLEXLOVE_VAL_005", - category = "VAL", - description = "Invalid unit format", - suggestion = "Use valid unit format: number (px), '50%', '10vw', '5vh', etc.", - }, - VAL_006 = { - code = "FLEXLOVE_VAL_006", - category = "VAL", - description = "Invalid calc() expression or calculation error", - suggestion = "Check calc() syntax and ensure no division by zero. Format: calc('value1 operator value2') with operators: +, -, *, / and units: px, %, vw, vh", - }, - VAL_007 = { - code = "FLEXLOVE_VAL_007", - category = "VAL", - description = "Invalid enum value", - suggestion = "Use one of the allowed enum values for this property", - }, - VAL_008 = { - code = "FLEXLOVE_VAL_008", - category = "VAL", - description = "Invalid text input", - suggestion = "Ensure text meets validation requirements (length, pattern, allowed characters)", - }, - - -- Layout Errors (LAY_001 - LAY_099) - LAY_001 = { - code = "FLEXLOVE_LAY_001", - category = "LAY", - description = "Invalid flex direction", - suggestion = "Use 'horizontal' or 'vertical' for flexDirection", - }, - LAY_002 = { - code = "FLEXLOVE_LAY_002", - category = "LAY", - description = "Circular dependency detected", - suggestion = "Remove circular references in element hierarchy or layout constraints", - }, - LAY_003 = { - code = "FLEXLOVE_LAY_003", - category = "LAY", - description = "Invalid dimensions (negative or NaN)", - suggestion = "Ensure width and height are positive numbers", - }, - LAY_004 = { - code = "FLEXLOVE_LAY_004", - category = "LAY", - description = "Layout calculation overflow", - suggestion = "Reduce complexity of layout or increase recursion limit", - }, - LAY_005 = { - code = "FLEXLOVE_LAY_005", - category = "LAY", - description = "Invalid alignment value", - suggestion = "Use valid alignment values (flex-start, center, flex-end, etc.)", - }, - LAY_006 = { - code = "FLEXLOVE_LAY_006", - category = "LAY", - description = "Invalid positioning mode", - suggestion = "Use 'absolute', 'relative', 'flex', or 'grid' for positioning", - }, - LAY_007 = { - code = "FLEXLOVE_LAY_007", - category = "LAY", - description = "Grid layout error", - suggestion = "Check grid template columns/rows and item placement", - }, - LAY_008 = { - code = "FLEXLOVE_LAY_008", - category = "LAY", - description = "Explicit position will be ignored by flex layout", - suggestion = "Remove x/y properties (flex layout controls position), OR set positioning='absolute' with left/top/right/bottom properties. Additionally, you can use margin/padding for positional offsets in flex layouts.", - }, - LAY_009 = { - code = "FLEXLOVE_LAY_009", - category = "LAY", - description = "Flex layout properties ignored with grid positioning", - suggestion = "Remove flexDirection/justifyContent/alignItems properties, or change positioning to 'flex' or 'relative'", - }, - LAY_010 = { - code = "FLEXLOVE_LAY_010", - category = "LAY", - description = "Grid layout properties ignored without grid positioning", - suggestion = "Set positioning='grid' to use grid layout properties, or remove grid properties", - }, - LAY_011 = { - code = "FLEXLOVE_LAY_011", - category = "LAY", - description = "CSS positioning properties ignored", - suggestion = "Set positioning='absolute' to use top/bottom/left/right properties", - }, - - -- Rendering Errors (REN_001 - REN_099) - REN_001 = { - code = "FLEXLOVE_REN_001", - category = "REN", - description = "Invalid render state", - suggestion = "Ensure element is properly initialized before rendering", - }, - REN_002 = { - code = "FLEXLOVE_REN_002", - category = "REN", - description = "Texture loading failed", - suggestion = "Check image path and format, ensure file exists", - }, - REN_003 = { - code = "FLEXLOVE_REN_003", - category = "REN", - description = "Font loading failed", - suggestion = "Check font path and format, ensure file exists", - }, - REN_004 = { - code = "FLEXLOVE_REN_004", - category = "REN", - description = "Invalid color value", - suggestion = "Color components must be numbers between 0 and 1", - }, - REN_005 = { - code = "FLEXLOVE_REN_005", - category = "REN", - description = "Clipping stack overflow", - suggestion = "Reduce nesting depth or check for missing scissor pops", - }, - REN_006 = { - code = "FLEXLOVE_REN_006", - category = "REN", - description = "Shader compilation failed", - suggestion = "Check shader code for syntax errors", - }, - REN_007 = { - code = "FLEXLOVE_REN_007", - category = "REN", - description = "Invalid nine-patch configuration", - suggestion = "Check nine-patch slice values and image dimensions", - }, - - -- Theme Errors (THM_001 - THM_099) - THM_001 = { - code = "FLEXLOVE_THM_001", - category = "THM", - description = "Theme file not found", - suggestion = "Check theme file path and ensure file exists", - }, - THM_002 = { - code = "FLEXLOVE_THM_002", - category = "THM", - description = "Invalid theme structure", - suggestion = "Theme must return a table with 'name' and component styles", - }, - THM_003 = { - code = "FLEXLOVE_THM_003", - category = "THM", - description = "Required theme property missing", - suggestion = "Ensure theme has required properties (name, base styles, etc.)", - }, - THM_004 = { - code = "FLEXLOVE_THM_004", - category = "THM", - description = "Invalid component style", - suggestion = "Component styles must be tables with valid properties", - }, - THM_005 = { - code = "FLEXLOVE_THM_005", - category = "THM", - description = "Theme loading failed", - suggestion = "Check theme file for Lua syntax errors", - }, - THM_006 = { - code = "FLEXLOVE_THM_006", - category = "THM", - description = "Invalid theme color", - suggestion = "Theme colors must be valid color values (hex, rgba, Color object)", - }, - THM_007 = { - code = "FLEXLOVE_THM_007", - category = "THM", - description = "themeStateLock has no effect without a valid theme component", - suggestion = "Ensure themeComponent is set and valid when using themeStateLock", - }, - THM_008 = { - code = "FLEXLOVE_THM_008", - category = "THM", - description = "Theme component has no state variants", - suggestion = "themeStateLock has no effect on components without state variants", - }, - THM_009 = { - code = "FLEXLOVE_THM_009", - category = "THM", - description = "Requested theme state does not exist", - suggestion = "Use one of the available theme states or set themeStateLock to false", - }, - THM_010 = { - code = "FLEXLOVE_THM_010", - category = "THM", - description = "Invalid themeStateLock type", - suggestion = "themeStateLock must be boolean or string (state name)", - }, - - -- Event Errors (EVT_001 - EVT_099) - EVT_001 = { - code = "FLEXLOVE_EVT_001", - category = "EVT", - description = "Invalid event type", - suggestion = "Use valid event types (mousepressed, textinput, etc.)", - }, - EVT_002 = { - code = "FLEXLOVE_EVT_002", - category = "EVT", - description = "Event handler error", - suggestion = "Check event handler function for errors", - }, - EVT_003 = { - code = "FLEXLOVE_EVT_003", - category = "EVT", - description = "Event propagation error", - suggestion = "Check event bubbling/capturing logic", - }, - EVT_004 = { - code = "FLEXLOVE_EVT_004", - category = "EVT", - description = "Invalid event target", - suggestion = "Ensure event target element exists and is valid", - }, - EVT_005 = { - code = "FLEXLOVE_EVT_005", - category = "EVT", - description = "Event handler not a function", - suggestion = "Event handlers must be functions", - }, - - -- Resource Errors (RES_001 - RES_099) - RES_001 = { - code = "FLEXLOVE_RES_001", - category = "RES", - description = "File not found", - suggestion = "Check file path and ensure file exists in the filesystem", - }, - RES_002 = { - code = "FLEXLOVE_RES_002", - category = "RES", - description = "Permission denied", - suggestion = "Check file permissions and access rights", - }, - RES_003 = { - code = "FLEXLOVE_RES_003", - category = "RES", - description = "Invalid file format", - suggestion = "Ensure file format is supported (png, jpg, ttf, etc.)", - }, - RES_004 = { - code = "FLEXLOVE_RES_004", - category = "RES", - description = "Resource loading failed", - suggestion = "Check file integrity and format compatibility", - }, - RES_005 = { - code = "FLEXLOVE_RES_005", - category = "RES", - description = "Image cache error", - suggestion = "Clear image cache or check memory availability", - }, - - -- System Errors (SYS_001 - SYS_099) - SYS_001 = { - code = "FLEXLOVE_SYS_001", - category = "SYS", - description = "Memory allocation failed", - suggestion = "Reduce memory usage or check available memory", - }, - SYS_002 = { - code = "FLEXLOVE_SYS_002", - category = "SYS", - description = "Stack overflow", - suggestion = "Reduce recursion depth or check for infinite loops", - }, - SYS_003 = { - code = "FLEXLOVE_SYS_003", - category = "SYS", - description = "Invalid state", - suggestion = "Check initialization order and state management", - }, - SYS_004 = { - code = "FLEXLOVE_SYS_004", - category = "SYS", - description = "Module initialization failed", - suggestion = "Check module dependencies and initialization order", - }, - - -- Performance Warnings (PERF_001 - PERF_099) - PERF_001 = { - code = "FLEXLOVE_PERF_001", - category = "PERF", - description = "Performance threshold exceeded", - suggestion = "Operation took longer than recommended. Monitor for patterns.", - }, - PERF_002 = { - code = "FLEXLOVE_PERF_002", - category = "PERF", - description = "Critical performance threshold exceeded", - suggestion = "Operation is causing frame drops. Consider optimizing or reducing frequency.", - }, - PERF_003 = { - code = "FLEXLOVE_PERF_003", - category = "PERF", - description = "Large blur area in immediate mode", - suggestion = "Consider using retained mode for this component to avoid recreating blur effects every frame.", - }, - - -- Memory Warnings (MEM_001 - MEM_099) - MEM_001 = { - code = "FLEXLOVE_MEM_001", - category = "MEM", - description = "Memory leak detected", - suggestion = "Table is growing consistently. Review cache eviction policies and ensure objects are properly released.", - }, - - -- State Management Warnings (STATE_001 - STATE_099) - STATE_001 = { - code = "FLEXLOVE_STATE_001", - category = "STATE", - description = "CallSite counters accumulating", - suggestion = "This indicates incrementFrame() may not be called properly. Check immediate mode frame management.", - }, - - -- Animation Errors (ANIM_001 - ANIM_099) - ANIM_001 = { - code = "FLEXLOVE_ANIM_001", - category = "VAL", - description = "Invalid animation configuration", - suggestion = "Animation.new() requires a table argument with duration, start, and final properties", - }, - ANIM_002 = { - code = "FLEXLOVE_ANIM_002", - category = "VAL", - description = "Invalid animation duration", - suggestion = "Animation duration must be a positive number in seconds", - }, - ANIM_003 = { - code = "FLEXLOVE_ANIM_003", - category = "VAL", - description = "Invalid animation target", - suggestion = "Animation can only be applied to table elements", - }, - ANIM_004 = { - code = "FLEXLOVE_ANIM_004", - category = "VAL", - description = "Invalid animation chain", - suggestion = "chain() requires an Animation object or function", - }, - ANIM_005 = { - code = "FLEXLOVE_ANIM_005", - category = "VAL", - description = "Invalid animation delay", - suggestion = "delay() requires a non-negative number in seconds", - }, - ANIM_006 = { - code = "FLEXLOVE_ANIM_006", - category = "VAL", - description = "Invalid repeat count", - suggestion = "repeatCount() requires a non-negative number", - }, - ANIM_007 = { - code = "FLEXLOVE_ANIM_007", - category = "VAL", - description = "Invalid keyframes configuration", - suggestion = "Animation.keyframes() requires a table with duration and keyframes array", - }, - ANIM_008 = { - code = "FLEXLOVE_ANIM_008", - category = "VAL", - description = "Insufficient keyframes", - suggestion = "Keyframe animations require at least 2 keyframes", - }, - ANIM_009 = { - code = "FLEXLOVE_ANIM_009", - category = "VAL", - description = "Invalid animation group configuration", - suggestion = "AnimationGroup.new() requires a table with animations array", - }, - ANIM_010 = { - code = "FLEXLOVE_ANIM_010", - category = "VAL", - description = "Empty animation group", - suggestion = "AnimationGroup requires at least one animation", - }, - ANIM_011 = { - code = "FLEXLOVE_ANIM_011", - category = "VAL", - description = "Invalid animation group mode", - suggestion = "AnimationGroup mode must be 'parallel' or 'sequence'", - }, - - -- Blur Errors (BLUR_001 - BLUR_099) - BLUR_001 = { - code = "FLEXLOVE_BLUR_001", - category = "VAL", - description = "Missing draw function", - suggestion = "applyToRegion requires a draw function to render the content to be blurred", - }, - BLUR_002 = { - code = "FLEXLOVE_BLUR_002", - category = "VAL", - description = "Missing backdrop canvas", - suggestion = "applyBackdrop requires a backdrop canvas parameter", - }, - - -- FlexLove Core Errors (CORE_001 - CORE_099) - CORE_001 = { - code = "FLEXLOVE_CORE_001", - category = "VAL", - description = "Invalid callback function", - suggestion = "deferCallback expects a function argument", - }, - CORE_002 = { - code = "FLEXLOVE_CORE_002", - category = "SYS", - description = "Deferred callback execution failed", - suggestion = "Check the callback function for errors. Error details included in message.", - }, - CORE_003 = { - code = "FLEXLOVE_CORE_003", - category = "VAL", - description = "Invalid garbage collection strategy", - suggestion = "GC strategy must be one of: 'default', 'aggressive', 'conservative'", - }, - CORE_004 = { - code = "FLEXLOVE_CORE_004", - category = "SYS", - description = "Deferred method retry limit exceeded", - suggestion = "A deferred method has been retried too many times without succeeding. Check that preconditions are eventually met.", - }, - CORE_005 = { - code = "FLEXLOVE_CORE_005", - category = "VAL", - description = "Invalid deferred method", - suggestion = "The method name provided to _deferMethod does not exist on the element.", - }, - - -- Element Errors (ELEM_001 - ELEM_099) - ELEM_001 = { - code = "FLEXLOVE_ELEM_001", - category = "VAL", - description = "Invalid text size", - suggestion = "textSize must be greater than 0", - }, - ELEM_002 = { - code = "FLEXLOVE_ELEM_002", - category = "VAL", - description = "Invalid text size unit", - suggestion = "textSize unit must be one of: px, %, vw, vh, or presets: xs, sm, md, lg, xl, xxl, 2xl, 3xl, 4xl", - }, - ELEM_003 = { - code = "FLEXLOVE_ELEM_003", - category = "VAL", - description = "Invalid transition configuration", - suggestion = "setTransition() requires a table with transition properties", - }, - ELEM_004 = { - code = "FLEXLOVE_ELEM_004", - category = "VAL", - description = "Invalid transition duration", - suggestion = "Transition duration must be a non-negative number in seconds", - }, - ELEM_005 = { - code = "FLEXLOVE_ELEM_005", - category = "VAL", - description = "Invalid transition group", - suggestion = "setTransitionGroup() requires an array of property names", - }, - ELEM_006 = { - code = "FLEXLOVE_ELEM_006", - category = "VAL", - description = "Incompatible element configuration", - suggestion = "passwordMode and multiline cannot be used together. Multiline will be disabled.", - }, - ELEM_007 = { - code = "FLEXLOVE_ELEM_007", - category = "VAL", - description = "Invalid select frame configuration", - suggestion = "Pass a fully instantiated Element as selectParent.selectFrame. Create it unattached so the owning select can adopt it safely.", - }, - ELEM_008 = { - code = "FLEXLOVE_ELEM_008", - category = "VAL", - description = "Select frame was already parented before adoption", - suggestion = "Create the selectFrame without a parent, or explicitly accept that the select will reparent it during adoption.", - }, - ELEM_009 = { - code = "FLEXLOVE_ELEM_009", - category = "VAL", - description = "Managed select frame was reparented unexpectedly", - suggestion = "Avoid moving a managed selectFrame outside its owning select after adoption. Let the select own the frame lifecycle.", - }, - ELEM_010 = { - code = "FLEXLOVE_ELEM_010", - category = "VAL", - description = "Invalid display property value", - suggestion = "The display property accepts only boolean values (true/false). Pass `true` to show the element or `false` to hide it from layout, rendering, and hit testing.", - }, - - -- Module Loader Warnings (MOD_001 - MOD_099) - MOD_001 = { - code = "FLEXLOVE_MOD_001", - category = "RES", - description = "Optional module not found", - suggestion = "Using stub implementation for optional module. This is expected if the module is not required.", - }, - - -- Utility Errors (UTIL_001 - UTIL_099) - UTIL_001 = { - code = "FLEXLOVE_UTIL_001", - category = "VAL", - description = "Text truncation warning", - suggestion = "Text was truncated to fit within the maximum allowed length", - }, - - -- Image/Rendering Errors (IMG_001 - IMG_099) - IMG_001 = { - code = "FLEXLOVE_IMG_001", - category = "REN", - description = "Stencil buffer not available", - suggestion = "Cannot apply corner radius to image without stencil buffer support. Check graphics capabilities.", - }, - - -- Navigation Errors (NAV_001 - NAV_099) - NAV_001 = { - code = "FLEXLOVE_NAV_001", - category = "EVT", - description = "Element focus callback error", - suggestion = "Check the onFocus callback function for errors. Error details included in message.", - }, - NAV_002 = { - code = "FLEXLOVE_NAV_002", - category = "EVT", - description = "Element activation callback error", - suggestion = "Check the onEvent callback function for errors. Error details included in message.", - }, - NAV_003 = { - code = "FLEXLOVE_NAV_003", - category = "EVT", - description = "Element dismiss callback error", - suggestion = "Check the onDismiss callback function for errors. Error details included in message.", - }, - }, -} - ---- Get error information by code ---- @param code string Error code (e.g., "VAL_001" or "FLEXLOVE_VAL_001") ---- @return table? errorInfo Error information or nil if not found -function ErrorCodes.get(code) - -- Handle both short and full format - local shortCode = code:gsub("^FLEXLOVE_", "") - return ErrorCodes.codes[shortCode] -end - ---- Get human-readable description for error code ---- @param code string Error code ---- @return string description Error description -function ErrorCodes.describe(code) - local info = ErrorCodes.get(code) - if info then - return info.description - end - return "Unknown error code: " .. code -end - ---- Search error codes by keyword ---- @param keyword string Keyword to search for ---- @return table codes Matching error codes -function ErrorCodes.search(keyword) - keyword = keyword:lower() - local result = {} - for code, info in pairs(ErrorCodes.codes) do - local searchText = (code .. " " .. info.description .. " " .. info.suggestion):lower() - if searchText:find(keyword, 1, true) then - table.insert(result, { - code = code, - fullCode = info.code, - description = info.description, - suggestion = info.suggestion, - category = ErrorCodes.categories[info.category], - }) - end - end - return result -end - ---- Format error message with code ---- @param code string Error code ---- @param message string Error message ---- @return string formattedMessage Formatted error message with code -function ErrorCodes.formatMessage(code, message) - local info = ErrorCodes.get(code) - if info then - return string.format("[%s] %s", info.code, message) - end - return message -end - ---- Validate that all error codes are unique and properly formatted ---- @return boolean, string? Returns true if valid, or false with error message -function ErrorCodes.validate() - local seen = {} - local fullCodes = {} - - for code, info in pairs(ErrorCodes.codes) do - -- Check for duplicates - if seen[code] then - return false, "Duplicate error code: " .. code - end - seen[code] = true - - if fullCodes[info.code] then - return false, "Duplicate full error code: " .. info.code - end - fullCodes[info.code] = true - - -- Check format - if not code:match("^[A-Z]+_[0-9]+$") then - return false, "Invalid code format: " .. code .. " (expected CATEGORY_NUMBER)" - end - - -- Check full code format - local expectedFullCode = "FLEXLOVE_" .. code - if info.code ~= expectedFullCode then - return false, "Mismatched full code for " .. code .. ": expected " .. expectedFullCode .. ", got " .. info.code - end - - -- Check required fields - if not info.description or info.description == "" then - return false, "Missing description for " .. code - end - if not info.suggestion or info.suggestion == "" then - return false, "Missing suggestion for " .. code - end - if not info.category or info.category == "" then - return false, "Missing category for " .. code - end - end - - return true, nil -end - ----@enum LOG_LEVEL -local LOG_LEVEL = { - CRITICAL = 1, - ERROR = 2, - WARNING = 3, - INFO = 4, - DEBUG = 5, -} - ----@enum LOG_TARGET -local LOG_TARGET = { - CONSOLE = "console", - FILE = "file", - BOTH = "both", - NONE = "none", -} - ----@class ErrorHandler ----@field errorCodes ErrorCodes ----@field includeStackTrace boolean -- Default: false ----@field logLevel LOG_LEVEL --Default: LOG_LEVEL.WARNING ----@field logTarget "console" | "file" | "both" ----@field logFile string ----@field maxLogSize number in bytes ----@field maxLogFiles number files to rotate ----@field enableRotation boolean see maxLogFiles ----@field _currentLogSize number private ----@field _logFileHandle file* private -local ErrorHandler = { - errorCodes = ErrorCodes, -} -ErrorHandler.__index = ErrorHandler - ----@type ErrorHandler|nil -local instance = nil - ----@param config { includeStackTrace?: boolean, logLevel?: LOG_LEVEL, logTarget?: "console" | "file" | "both", logFile?: string, maxLogSize?: number, maxLogFiles?: number, enableRotation?: boolean }|nil ----@return ErrorHandler -function ErrorHandler.init(config) - if instance == nil then - local self = setmetatable({}, ErrorHandler) - self.includeStackTrace = config and config.includeStackTrace or false - self.logLevel = config and config.logLevel or LOG_LEVEL.WARNING - self.logTarget = config and config.logTarget or LOG_TARGET.CONSOLE - self.logFile = config and config.logFile or "flexlove-errors.log" - self.maxLogSize = config and config.maxLogSize or 10 * 1024 * 1024 - self.maxLogFiles = config and config.maxLogFiles or 5 - self.enableRotation = config and config.enableRotation or true - self._currentLogSize = 0 - self._logFileHandle = nil - instance = self - end - return instance -end - ---- Get the singleton instance (lazily initializes if needed) ----@return ErrorHandler -function ErrorHandler.getInstance() - if instance == nil then - ErrorHandler.init() - end - return instance -end - ---- Get current timestamp with milliseconds ----@return string|osdate Formatted timestamp -function ErrorHandler:_getTimestamp() - local time = os.time() - local date = os.date("%Y-%m-%d %H:%M:%S", time) - -- Note: Lua doesn't have millisecond precision by default, so we approximate - return date -end - ---- Rotate log file if needed -function ErrorHandler:_rotateLogIfNeeded() - if not self.enableRotation then - return - end - if self._currentLogSize < self.maxLogSize then - return - end - - -- Close current log - if self._logFileHandle then - self._logFileHandle:close() - self._logFileHandle = nil - end - - -- Rotate existing logs - for i = self.maxLogFiles - 1, 1, -1 do - local oldName = self.logFile .. "." .. i - local newName = self.logFile .. "." .. (i + 1) - os.rename(oldName, newName) -- Will fail silently if file doesn't exist - end - - -- Move current log to .1 - os.rename(self.logFile, self.logFile .. ".1") - - -- Create new log file - self._logFileHandle = io.open(self.logFile, "a") - self._currentLogSize = 0 -end - ---- Escape string for JSON ----@param str string String to escape ----@return string Escaped string -function ErrorHandler:_escapeJson(str) - str = tostring(str) - str = str:gsub("\\", "\\\\") - str = str:gsub('"', '\\"') - str = str:gsub("\n", "\\n") - str = str:gsub("\r", "\\r") - str = str:gsub("\t", "\\t") - return str -end - ---- Format details as JSON object ----@param details table|nil Details object ----@return string JSON string -function ErrorHandler:_formatDetailsJson(details) - if not details or type(details) ~= "table" then - return "{}" - end - - local parts = {} - for key, value in pairs(details) do - local jsonKey = self:_escapeJson(tostring(key)) - local jsonValue = self:_escapeJson(tostring(value)) - table.insert(parts, string.format('"%s":"%s"', jsonKey, jsonValue)) - end - - return "{" .. table.concat(parts, ",") .. "}" -end - ---- Format details object as readable key-value pairs ----@param details table|nil Details object ----@return string Formatted details -function ErrorHandler:_formatDetails(details) - if not details or type(details) ~= "table" then - return "" - end - - local lines = {} - for key, value in pairs(details) do - local formattedKey = tostring(key):gsub("^%l", string.upper) - local formattedValue = tostring(value) - -- Truncate very long values - if #formattedValue > 100 then - formattedValue = formattedValue:sub(1, 97) .. "..." - end - table.insert(lines, string.format(" %s: %s", formattedKey, formattedValue)) - end - - if #lines > 0 then - return "\n\nDetails:\n" .. table.concat(lines, "\n") - end - return "" -end - ---- Extract and format stack trace ----@param level number Stack level to start from ----@return string Formatted stack trace -function ErrorHandler:_formatStackTrace(level) - if not self.includeStackTrace then - return "" - end - - local lines = {} - local currentLevel = level or 3 - - while true do - local info = debug.getinfo(currentLevel, "Sl") - if not info then - break - end - - -- Skip internal Lua files - if info.source:match("^@") and not info.source:match("loveStub") then - local source = info.source:sub(2) -- Remove @ prefix - local location = string.format("%s:%d", source, info.currentline) - table.insert(lines, " " .. location) - end - - currentLevel = currentLevel + 1 - if currentLevel > level + 10 then - break - end -- Limit depth - end - - if #lines > 0 then - return "\n\nStack trace:\n" .. table.concat(lines, "\n") - end - return "" -end - ---- Format an error or warning message using error code lookup ----@param module string The module name (e.g., "Element", "Units", "Theme") ----@param level string "Error" or "Warning" ----@param code string Error code (e.g., "VAL_001") ----@param details table|nil Optional details object ----@return string Formatted message -function ErrorHandler:_formatMessage(module, level, code, details) - local codeInfo = ErrorCodes.get(code) - - if not codeInfo then - return string.format("[FlexLove - %s] %s: Unknown error code: %s", module, level, code) - end - - -- Build formatted message - local parts = {} - - -- Header: [FlexLove - Module] Level [CODE]: Description - table.insert(parts, string.format("[FlexLove - %s] %s [%s]: %s", module, level, codeInfo.code, codeInfo.description)) - - -- Details section - if details and type(details) == "table" then - table.insert(parts, self:_formatDetails(details)) - end - - -- Suggestion section - if codeInfo.suggestion and codeInfo.suggestion ~= "" then - table.insert(parts, string.format("\n\nSuggestion: %s", codeInfo.suggestion)) - end - - return table.concat(parts, "") -end - ---- Write log entry to file and/or console ----@param level string Log level ----@param levelNum number Log level number ----@param module string Module name ----@param code string|nil Error code ----@param message string Message ----@param details table|nil Details ----@param suggestion string|nil Suggestion -function ErrorHandler:_writeLog(level, levelNum, module, code, message, details, suggestion) - -- Check if we should log this level - if not levelNum or not self.logLevel or levelNum > self.logLevel then - return - end - - local timestamp = self:_getTimestamp() - local logEntry - - local jsonParts = { - string.format('"timestamp":"%s"', self:_escapeJson(timestamp)), - string.format('"level":"%s"', level), - string.format('"module":"%s"', self:_escapeJson(module)), - string.format('"message":"%s"', self:_escapeJson(message)), - } - - if code then - table.insert(jsonParts, string.format('"code":"%s"', self:_escapeJson(code))) - end - - if details then - table.insert(jsonParts, string.format('"details":%s', self:_formatDetailsJson(details))) - end - - if suggestion then - table.insert(jsonParts, string.format('"suggestion":"%s"', self:_escapeJson(suggestion))) - end - - logEntry = "{" .. table.concat(jsonParts, ",") .. "}\n" - - if self.logTarget == "console" or self.logTarget == "both" then - io.write(logEntry) - io.flush() - end - - -- Write to file - if self.logTarget == "file" or self.logTarget == "both" then - -- Lazy file opening: open on first write - if not self._logFileHandle then - self._logFileHandle = io.open(self.logFile, "a") - if self._logFileHandle then - -- Get current file size - local currentPos = self._logFileHandle:seek("end") - self._currentLogSize = currentPos or 0 - end - end - - if self._logFileHandle then - self:_rotateLogIfNeeded() - - -- Reopen if rotation closed it - if not self._logFileHandle then - self._logFileHandle = io.open(self.logFile, "a") - end - - if self._logFileHandle then - self._logFileHandle:write(logEntry) - self._logFileHandle:flush() - self._currentLogSize = self._currentLogSize + #logEntry - end - end - end -end - ---- Throw a critical error (stops execution) ----@param module string The module name ----@param code string Error code (e.g., "VAL_001") ----@param details table|nil Optional details object -function ErrorHandler:error(module, code, details) - local formattedMessage = self:_formatMessage(module, "Error", code, details) - - local codeInfo = ErrorCodes.get(code) - local message = codeInfo and codeInfo.description or code - local suggestion = codeInfo and codeInfo.suggestion or nil - - -- Log the error - self:_writeLog("ERROR", LOG_LEVEL.ERROR, module, code, message, details, suggestion) - - if self.includeStackTrace then - formattedMessage = formattedMessage .. self:_formatStackTrace(3) - end - - error(formattedMessage, 2) -end - ---- Print a warning (non-critical, continues execution) ----@param module string The module name ----@param code string Warning code (e.g., "VAL_001") ----@param details table|nil Optional details object -function ErrorHandler:warn(module, code, details) - local codeInfo = ErrorCodes.get(code) - local message = codeInfo and codeInfo.description or code - local suggestion = codeInfo and codeInfo.suggestion or nil - - -- Log the warning - self:_writeLog("WARNING", LOG_LEVEL.WARNING, module, code, message, details, suggestion) -end - ---- Validate that a value is not nil ----@param module string The module name ----@param value any The value to check ----@param paramName string The parameter name ----@return boolean True if valid -function ErrorHandler:assertNotNil(module, value, paramName) - if value == nil then - self:error(module, "VAL_003", "Required parameter missing", { - parameter = paramName, - }) - return false - end - return true -end - ---- Validate that a value is of the expected type ----@param module string The module name ---- Warn if a value is deprecated ----@param module string The module name ----@param oldName string The deprecated name ----@param newName string The new name to use -function ErrorHandler:warnDeprecated(module, oldName, newName) - self:warn(module, string.format("'%s' is deprecated. Use '%s' instead", oldName, newName)) -end - -return ErrorHandler diff --git a/libs/flexlove/modules/EventHandler.lua b/libs/flexlove/modules/EventHandler.lua deleted file mode 100644 index 2631fc47..00000000 --- a/libs/flexlove/modules/EventHandler.lua +++ /dev/null @@ -1,843 +0,0 @@ ----@class EventHandler ----@field onEvent fun(element:Element, event:InputEvent)? ----@field onEventDeferred boolean? ----@field onTouchEvent fun(element:Element, touchEvent:InputEvent)? -- Touch-specific callback ----@field onTouchEventDeferred boolean? -- Whether onTouchEvent is deferred ----@field onGesture fun(element:Element, gesture:table)? -- Gesture callback ----@field onGestureDeferred boolean? -- Whether onGesture is deferred ----@field touchEnabled boolean -- Whether touch events are processed (default: true) ----@field multiTouchEnabled boolean -- Whether multi-touch is supported (default: false) ----@field _pressed table ----@field _lastClickTime number? ----@field _lastClickButton number? ----@field _clickCount number ----@field _dragStartX table ----@field _dragStartY table ----@field _lastMouseX table ----@field _lastMouseY table ----@field _touches table -- Multi-touch state per touch ID ----@field _touchStartPositions table -- Touch start positions ----@field _lastTouchPositions table -- Last touch positions for delta ----@field _touchHistory table -- Touch position history for gestures (last 5) ----@field _hovered boolean ----@field _scrollbarPressHandled boolean ----@field _InputEvent table ----@field _utils table ----@field _Performance Performance? Performance module dependency ----@field _ErrorHandler ErrorHandler -local EventHandler = {} -EventHandler.__index = EventHandler - ---- Initialize module with shared dependencies ----@param deps table Dependencies {Performance, ErrorHandler, InputEvent, Context, utils} -function EventHandler.init(deps) - EventHandler._Performance = deps.Performance - EventHandler._ErrorHandler = deps.ErrorHandler - EventHandler._InputEvent = deps.InputEvent - EventHandler._utils = deps.utils - EventHandler._Context = deps.Context -end - ----@param config table Configuration options ----@return EventHandler -function EventHandler.new(config) - config = config or {} - local self = setmetatable({}, EventHandler) - - self.onEvent = config.onEvent - self.onEventDeferred = config.onEventDeferred - self.onTouchEvent = config.onTouchEvent - self.onTouchEventDeferred = config.onTouchEventDeferred or false - self.onGesture = config.onGesture - self.onGestureDeferred = config.onGestureDeferred or false - self.touchEnabled = config.touchEnabled ~= false -- Default true - self.multiTouchEnabled = config.multiTouchEnabled or false -- Default false - - self._pressed = config._pressed or {} - - self._lastClickTime = config._lastClickTime - self._lastClickButton = config._lastClickButton - self._clickCount = config._clickCount or 0 - - -- FocusIndicator reference (set after initialization) - self._FocusIndicator = nil - - self._dragStartX = config._dragStartX or {} - self._dragStartY = config._dragStartY or {} - self._lastMouseX = config._lastMouseX or {} - self._lastMouseY = config._lastMouseY or {} - - -- Multi-touch tracking - self._touches = config._touches or {} - self._touchStartPositions = config._touchStartPositions or {} - self._lastTouchPositions = config._lastTouchPositions or {} - self._touchHistory = config._touchHistory or {} - - self._hovered = config._hovered or false - - self._scrollbarPressHandled = false - - return self -end - ---- Get state for persistence (for immediate mode) ----@return table State data -function EventHandler:getState() - return { - _pressed = self._pressed, - _lastClickTime = self._lastClickTime, - _lastClickButton = self._lastClickButton, - _clickCount = self._clickCount, - _dragStartX = self._dragStartX, - _dragStartY = self._dragStartY, - _lastMouseX = self._lastMouseX, - _lastMouseY = self._lastMouseY, - _touches = self._touches, - _touchStartPositions = self._touchStartPositions, - _lastTouchPositions = self._lastTouchPositions, - _touchHistory = self._touchHistory, - _hovered = self._hovered, - } -end - ---- Restore state from persistence (for immediate mode) ----@param state table State data -function EventHandler:setState(state) - if not state then - return - end - - self._pressed = state._pressed or {} - self._lastClickTime = state._lastClickTime - self._lastClickButton = state._lastClickButton - self._clickCount = state._clickCount or 0 - self._dragStartX = state._dragStartX or {} - self._dragStartY = state._dragStartY or {} - self._lastMouseX = state._lastMouseX or {} - self._lastMouseY = state._lastMouseY or {} - self._touches = state._touches or {} - self._touchStartPositions = state._touchStartPositions or {} - self._lastTouchPositions = state._lastTouchPositions or {} - self._touchHistory = state._touchHistory or {} - self._hovered = state._hovered or false -end - ---- Process mouse button events in the update cycle ----@param element Element The parent element ----@param mx number Mouse X position ----@param my number Mouse Y position ----@param isHovering boolean Whether mouse is over element ----@param isActiveElement boolean Whether this is the top element at mouse position -function EventHandler:processMouseEvents(element, mx, my, isHovering, isActiveElement) - -- Start performance timing - -- Performance accessed via EventHandler._Performance - if EventHandler._Performance and EventHandler._Performance.enabled then - EventHandler._Performance:startTimer("event_mouse") - end - - -- Check if currently dragging (allows drag continuation even if occluded) - local isDragging = false - for _, button in ipairs({ 1, 2, 3 }) do - if self._pressed[button] and love.mouse.isDown(button) then - isDragging = true - break - end - end - - -- Check if any button is currently pressed (tracked state) - local hasTrackedPress = false - for _, button in ipairs({ 1, 2, 3 }) do - if self._pressed[button] then - hasTrackedPress = true - break - end - end - - -- Can only process events if we have handler, element is enabled, and is active or dragging or has tracked press - -- Read onEvent from element (source of truth), fallback to handler cache for backwards compat - local canProcessEvents = ( - element.onEvent - or self.onEvent - or element.editable - or element._selectState - or element.selectOption - ) - and element.visibility ~= "hidden" - and not element.disabled - and (isActiveElement or isDragging or hasTrackedPress) - - if not canProcessEvents then - -- If not hovering and no buttons are physically pressed, reset all pressed states - -- This ensures the pressed state is cleared when mouse leaves without button held - if not isHovering and not isDragging then - for _, button in ipairs({ 1, 2, 3 }) do - if self._pressed[button] and not love.mouse.isDown(button) then - self._pressed[button] = false - self._dragStartX[button] = nil - self._dragStartY[button] = nil - end - end - end - - -- Track hover state changes even when events can't be processed - -- Fire synthetic unhover when element becomes disabled while hovered - if element.disabled and self._hovered then - self._hovered = false - if element.onEvent or self.onEvent then - local modifiers = EventHandler._utils.getModifiers() - local unhoverEvent = EventHandler._InputEvent.new({ - type = "unhover", - button = 0, - x = mx, - y = my, - modifiers = modifiers, - clickCount = 0, - }) - self:_invokeCallback(element, unhoverEvent) - end - elseif self._hovered and not isHovering then - self._hovered = false - if element.onEvent or self.onEvent then - local modifiers = EventHandler._utils.getModifiers() - local unhoverEvent = EventHandler._InputEvent.new({ - type = "unhover", - button = 0, - x = mx, - y = my, - modifiers = modifiers, - clickCount = 0, - }) - self:_invokeCallback(element, unhoverEvent) - end - end - - if EventHandler._Performance and EventHandler._Performance.enabled then - EventHandler._Performance:stopTimer("event_mouse") - end - return - end - - -- Track hover state changes and fire hover/unhover events BEFORE button processing - -- This ensures hover fires before press when mouse first enters element - local wasHovered = self._hovered - local isHoveringAndActive = isHovering and isActiveElement - - if isHoveringAndActive and not wasHovered then - -- Just started hovering - fire hover event - self._hovered = true - local modifiers = EventHandler._utils.getModifiers() - local hoverEvent = EventHandler._InputEvent.new({ - type = "hover", - button = 0, - x = mx, - y = my, - modifiers = modifiers, - clickCount = 0, - }) - self:_invokeCallback(element, hoverEvent) - elseif not isHoveringAndActive and wasHovered then - -- Just stopped hovering - fire unhover event - self._hovered = false - local modifiers = EventHandler._utils.getModifiers() - local unhoverEvent = EventHandler._InputEvent.new({ - type = "unhover", - button = 0, - x = mx, - y = my, - modifiers = modifiers, - clickCount = 0, - }) - self:_invokeCallback(element, unhoverEvent) - end - - -- Process all three mouse buttons - local buttons = { 1, 2, 3 } -- left, right, middle - - for _, button in ipairs(buttons) do - -- Check if this button was tracked as pressed - local wasPressed = self._pressed[button] - local isPhysicallyPressed = love.mouse.isDown(button) - - if isHovering or isDragging or wasPressed then - if isPhysicallyPressed then - -- Button is pressed down - if not wasPressed then - -- Just pressed - fire press event (only if hovering) - if isHovering then - self:_handleMousePress(element, mx, my, button) - end - else - -- Button is still pressed - check for drag - self:_handleMouseDrag(element, mx, my, button, isHovering) - end - elseif wasPressed then - -- Button was just released - -- Only fire click and release events if mouse is still hovering AND element is active - -- (not occluded by another element) - if isHovering and isActiveElement then - self:_handleMouseRelease(element, mx, my, button) - else - -- Mouse left before release OR element is occluded - just clear the pressed state without firing events - self._pressed[button] = false - self._dragStartX[button] = nil - self._dragStartY[button] = nil - end - end - end - end - - -- After processing events, reset pressed states for buttons that are no longer held - -- This handles the case where mouse leaves while button is held, then released - if not isHovering and not isDragging then - for _, button in ipairs({ 1, 2, 3 }) do - if self._pressed[button] and not love.mouse.isDown(button) then - self._pressed[button] = false - self._dragStartX[button] = nil - self._dragStartY[button] = nil - end - end - end - - -- Stop performance timing - if EventHandler._Performance and EventHandler._Performance.enabled then - EventHandler._Performance:stopTimer("event_mouse") - end -end - ---- Handle mouse button press ----@param element Element The parent element ----@param mx number Mouse X position ----@param my number Mouse Y position ----@param button number Mouse button (1=left, 2=right, 3=middle) -function EventHandler:_handleMousePress(element, mx, my, button) - -- Check if press is on scrollbar first (skip if already handled) - if button == 1 and not self._scrollbarPressHandled and element._handleScrollbarPress then - if element:_handleScrollbarPress(mx, my, button) then - -- Scrollbar consumed the event, mark as pressed to prevent onEvent - self._pressed[button] = true - self._scrollbarPressHandled = true - return - end - end - - -- Fire press event - local modifiers = EventHandler._utils.getModifiers() - local pressEvent = EventHandler._InputEvent.new({ - type = "press", - button = button, - x = mx, - y = my, - modifiers = modifiers, - clickCount = 1, - }) - self:_invokeCallback(element, pressEvent) - - self._pressed[button] = true - - -- On left click, set keyboard focus to any focusable element (not just editable). - -- Clear the focus indicator since mouse navigation doesn't use it. - local isFocusable - if type(element.isFocusable) == "function" then - isFocusable = element:isFocusable() - else - isFocusable = (element.editable == true) - or (type(element.onEvent) == "function") - or element._selectState ~= nil - or element.selectOption ~= nil - end - - if button == 1 and EventHandler._Context and isFocusable then - EventHandler._Context.setFocused(element) - -- Hide focus indicator - it's only for keyboard navigation - if EventHandler._FocusIndicator then - EventHandler._FocusIndicator.setFocused(nil) - end - end - -- Set mouse down position for text selection on left click - if button == 1 and element._textEditor then - element._mouseDownPosition = element._textEditor:mouseToTextPosition(element, mx, my) - element._textDragOccurred = false -- Reset drag flag on press - end - - -- Record drag start position per button - self._dragStartX[button] = mx - self._dragStartY[button] = my - self._lastMouseX[button] = mx - self._lastMouseY[button] = my -end - ---- Handle mouse drag (while button is pressed and mouse moves) ----@param element Element The parent element ----@param mx number Mouse X position ----@param my number Mouse Y position ----@param button number Mouse button ----@param isHovering boolean Whether mouse is over element -function EventHandler:_handleMouseDrag(element, mx, my, button, isHovering) - local lastX = self._lastMouseX[button] or mx - local lastY = self._lastMouseY[button] or my - - if lastX ~= mx or lastY ~= my then - -- Handle scrollbar drag if scrollbar was pressed - if button == 1 and self._scrollbarPressHandled and element._handleScrollbarDrag then - element:_handleScrollbarDrag(mx, my) - self._lastMouseX[button] = mx - self._lastMouseY[button] = my - return -- Don't process other drag events while dragging scrollbar - end - - -- Mouse has moved - fire drag event only if still hovering - if isHovering then - local modifiers = EventHandler._utils.getModifiers() - local dx = mx - self._dragStartX[button] - local dy = my - self._dragStartY[button] - - local dragEvent = EventHandler._InputEvent.new({ - type = "drag", - button = button, - x = mx, - y = my, - dx = dx, - dy = dy, - modifiers = modifiers, - clickCount = 1, - }) - self:_invokeCallback(element, dragEvent) - end - - -- Handle text selection drag for editable elements - if button == 1 and element.editable and element._focused and element._handleTextDrag then - element:_handleTextDrag(mx, my) - end - - -- Update last known position for this button - self._lastMouseX[button] = mx - self._lastMouseY[button] = my - end -end - ---- Handle mouse button release ----@param mx number Mouse X position ----@param my number Mouse Y position ----@param button number Mouse button -function EventHandler:_handleMouseRelease(element, mx, my, button) - local currentTime = love.timer.getTime() - local modifiers = EventHandler._utils.getModifiers() - - -- Handle scrollbar release if scrollbar was pressed - if button == 1 and self._scrollbarPressHandled and element._handleScrollbarRelease then - element:_handleScrollbarRelease(button) - self._scrollbarPressHandled = false -- Reset flag - self._pressed[button] = false - self._dragStartX[button] = nil - self._dragStartY[button] = nil - return -- Don't process click events for scrollbar release - end - - -- Determine click count (double-click detection) - local clickCount - local doubleClickThreshold = 0.3 -- 300ms for double-click - - if - self._lastClickTime - and self._lastClickButton == button - and (currentTime - self._lastClickTime) < doubleClickThreshold - then - clickCount = self._clickCount + 1 - else - clickCount = 1 - end - - self._clickCount = clickCount - self._lastClickTime = currentTime - self._lastClickButton = button - - -- Determine event type based on button - local eventType = "click" - if button == 2 then - eventType = "rightclick" - elseif button == 3 then - eventType = "middleclick" - end - - -- Fire click event - local clickEvent = EventHandler._InputEvent.new({ - type = eventType, - button = button, - x = mx, - y = my, - modifiers = modifiers, - clickCount = clickCount, - }) - self:_invokeCallback(element, clickEvent) - - self._pressed[button] = false - - -- Clean up drag tracking - self._dragStartX[button] = nil - self._dragStartY[button] = nil - - -- Clean up text selection drag tracking - if button == 1 then - element._mouseDownPosition = nil - end - - -- Focus editable elements on left click - if button == 1 and element.editable then - -- Only focus if not already focused (to avoid moving cursor to end) - local wasFocused = element:isFocused() - if not wasFocused then - element:focus() - end - - -- Handle text click for cursor positioning and word selection - -- Only process click if no text drag occurred (to preserve drag selection) - if element._handleTextClick and not element._textDragOccurred then - element:_handleTextClick(mx, my, clickCount) - end - - -- Reset drag flag after release - element._textDragOccurred = false - end - - -- Fire release event - local releaseEvent = EventHandler._InputEvent.new({ - type = "release", - button = button, - x = mx, - y = my, - modifiers = modifiers, - clickCount = clickCount, - }) - self:_invokeCallback(element, releaseEvent) - - if button == 1 and element._handleSelectRelease then - element:_handleSelectRelease() - end -end - ---- Process touch events in the update cycle ----@param element Element The parent element -function EventHandler:processTouchEvents(element) - -- Start performance timing - if EventHandler._Performance and EventHandler._Performance.enabled then - EventHandler._Performance:startTimer("event_touch") - end - - -- Check if element can process events - local canProcessEvents = ( - element.onEvent - or self.onEvent - or element.onTouchEvent - or self.onTouchEvent - or element.editable - ) - and not element.disabled - and self.touchEnabled - - if not canProcessEvents then - if EventHandler._Performance and EventHandler._Performance.enabled then - EventHandler._Performance:stopTimer("event_touch") - end - return - end - - local bx = element.x - local by = element.y - local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom) - - -- Get current active touches from LÖVE - local activeTouches = {} - local touches = love.touch.getTouches() - for _, id in ipairs(touches) do - activeTouches[tostring(id)] = true - end - - -- Count active tracked touches for multi-touch filtering - local trackedTouchCount = 0 - for _ in pairs(self._touches) do - trackedTouchCount = trackedTouchCount + 1 - end - - -- Process active touches - for _, id in ipairs(touches) do - local touchId = tostring(id) - local tx, ty = love.touch.getPosition(id) - local pressure = 1.0 -- LÖVE doesn't provide pressure by default - - -- Check if touch is within element bounds - local isInside = tx >= bx and tx <= bx + bw and ty >= by and ty <= by + bh - - if isInside then - if not self._touches[touchId] then - -- Multi-touch filtering: reject new touches when multiTouchEnabled=false - -- and we already have an active touch - if self.multiTouchEnabled or trackedTouchCount == 0 then - -- New touch began - self:_handleTouchBegan(element, touchId, tx, ty, pressure) - trackedTouchCount = trackedTouchCount + 1 - end - else - -- Touch moved - self:_handleTouchMoved(element, touchId, tx, ty, pressure) - end - elseif self._touches[touchId] then - -- Touch moved outside or ended - if activeTouches[touchId] then - -- Still active but outside - fire moved event - self:_handleTouchMoved(element, touchId, tx, ty, pressure) - else - -- Touch ended - self:_handleTouchEnded(element, touchId, tx, ty, pressure) - end - end - end - - -- Check for ended touches (touches that were tracked but are no longer active) - for touchId, _ in pairs(self._touches) do - if not activeTouches[touchId] then - -- Touch ended or cancelled - local lastPos = self._lastTouchPositions[touchId] - if lastPos then - self:_handleTouchEnded(element, touchId, lastPos.x, lastPos.y, 1.0) - else - -- Cleanup orphaned touch - self:_cleanupTouch(touchId) - end - end - end - - -- Stop performance timing - if EventHandler._Performance and EventHandler._Performance.enabled then - EventHandler._Performance:stopTimer("event_touch") - end -end - ---- Handle touch began event ----@param element Element The parent element ----@param touchId string Touch identifier ----@param x number Touch X position ----@param y number Touch Y position ----@param pressure number Touch pressure (0-1) -function EventHandler:_handleTouchBegan(element, touchId, x, y, pressure) - -- Create touch state - self._touches[touchId] = { - x = x, - y = y, - pressure = pressure, - timestamp = love.timer.getTime(), - phase = "began", - } - - -- Record start position - self._touchStartPositions[touchId] = { x = x, y = y } - self._lastTouchPositions[touchId] = { x = x, y = y } - - -- Initialize touch history - self._touchHistory[touchId] = { { x = x, y = y, timestamp = love.timer.getTime() } } - - -- Create and fire touch press event - local touchEvent = EventHandler._InputEvent.fromTouch(touchId, x, y, "began", pressure) - touchEvent.type = "touchpress" - touchEvent.dx = 0 - touchEvent.dy = 0 - self:_invokeCallback(element, touchEvent) - self:_invokeTouchCallback(element, touchEvent) -end - ---- Handle touch moved event ----@param element Element The parent element ----@param touchId string Touch identifier ----@param x number Touch X position ----@param y number Touch Y position ----@param pressure number Touch pressure (0-1) -function EventHandler:_handleTouchMoved(element, touchId, x, y, pressure) - local touchState = self._touches[touchId] - - if not touchState then - -- Touch not tracked, ignore - return - end - - local lastPos = self._lastTouchPositions[touchId] - if not lastPos or lastPos.x ~= x or lastPos.y ~= y then - -- Touch position changed - local startPos = self._touchStartPositions[touchId] - local dx = x - startPos.x - local dy = y - startPos.y - - -- Update touch state - touchState.x = x - touchState.y = y - touchState.pressure = pressure - touchState.phase = "moved" - - -- Update last position - self._lastTouchPositions[touchId] = { x = x, y = y } - - -- Add to touch history (keep last 5 positions) - local history = self._touchHistory[touchId] or {} - table.insert(history, { x = x, y = y, timestamp = love.timer.getTime() }) - if #history > 5 then - table.remove(history, 1) - end - self._touchHistory[touchId] = history - - -- Create and fire touch move event - local touchEvent = EventHandler._InputEvent.fromTouch(touchId, x, y, "moved", pressure) - touchEvent.type = "touchmove" - touchEvent.dx = dx - touchEvent.dy = dy - self:_invokeCallback(element, touchEvent) - self:_invokeTouchCallback(element, touchEvent) - end -end - ---- Handle touch ended event ----@param element Element The parent element ----@param touchId string Touch identifier ----@param x number Touch X position ----@param y number Touch Y position ----@param pressure number Touch pressure (0-1) -function EventHandler:_handleTouchEnded(element, touchId, x, y, pressure) - local touchState = self._touches[touchId] - - if not touchState then - -- Touch not tracked, ignore - return - end - - local startPos = self._touchStartPositions[touchId] - local dx = x - startPos.x - local dy = y - startPos.y - - -- Create and fire touch release event - local touchEvent = EventHandler._InputEvent.fromTouch(touchId, x, y, "ended", pressure) - touchEvent.type = "touchrelease" - touchEvent.dx = dx - touchEvent.dy = dy - self:_invokeCallback(element, touchEvent) - self:_invokeTouchCallback(element, touchEvent) - - -- Cleanup touch state - self:_cleanupTouch(touchId) -end - ---- Cleanup touch state ----@param touchId string Touch ID -function EventHandler:_cleanupTouch(touchId) - self._touches[touchId] = nil - self._touchStartPositions[touchId] = nil - self._lastTouchPositions[touchId] = nil - self._touchHistory[touchId] = nil -end - ---- Get active touches on this element ----@return table Active touches -function EventHandler:getActiveTouches() - return self._touches -end - ---- Reset scrollbar press flag (called each frame) -function EventHandler:resetScrollbarPressFlag() - self._scrollbarPressHandled = false -end - ---- Check if any mouse button is pressed ----@return boolean True if any button is pressed -function EventHandler:isAnyButtonPressed() - for _, pressed in pairs(self._pressed) do - if pressed then - return true - end - end - return false -end - ---- Check if a specific button is pressed ----@param button number Mouse button (1=left, 2=right, 3=middle) ----@return boolean True if button is pressed -function EventHandler:isButtonPressed(button) - return self._pressed[button] == true -end - ---- Invoke the onEvent callback, optionally deferring it if onEventDeferred is true ----@param element Element The element that triggered the event ----@param event InputEvent The event data -function EventHandler:_invokeCallback(element, event) - -- Read onEvent from element (source of truth), fallback to handler cache for backwards compat - local callback = element.onEvent or self.onEvent - if not callback then - return - end - - if self.onEventDeferred then - -- Get FlexLove module to defer the callback - local FlexLove = package.loaded["FlexLove"] or package.loaded["libs.FlexLove"] - if FlexLove and FlexLove.deferCallback then - FlexLove.deferCallback(function() - callback(element, event) - end) - else - EventHandler._ErrorHandler:error("EventHandler", "SYS_003", { - eventType = event.type, - }) - end - else - callback(element, event) - end -end - ---- Invoke the onTouchEvent callback, optionally deferring it ----@param element Element The element that triggered the event ----@param event InputEvent The touch event data -function EventHandler:_invokeTouchCallback(element, event) - -- Read onTouchEvent from element (source of truth), fallback to handler cache for backwards compat - local callback = element.onTouchEvent or self.onTouchEvent - if not callback then - return - end - - if self.onTouchEventDeferred then - local FlexLove = package.loaded["FlexLove"] or package.loaded["libs.FlexLove"] - if FlexLove and FlexLove.deferCallback then - FlexLove.deferCallback(function() - callback(element, event) - end) - else - EventHandler._ErrorHandler:error("EventHandler", "SYS_003", { - eventType = event.type, - }) - end - else - callback(element, event) - end -end - ---- Invoke the onGesture callback, optionally deferring it ----@param element Element The element that triggered the event ----@param gesture table The gesture data from GestureRecognizer -function EventHandler:_invokeGestureCallback(element, gesture) - -- Read onGesture from element (source of truth), fallback to handler cache for backwards compat - local callback = element.onGesture or self.onGesture - if not callback then - return - end - - if self.onGestureDeferred then - local FlexLove = package.loaded["FlexLove"] or package.loaded["libs.FlexLove"] - if FlexLove and FlexLove.deferCallback then - FlexLove.deferCallback(function() - callback(element, gesture) - end) - else - EventHandler._ErrorHandler:error("EventHandler", "SYS_003", { - gestureType = gesture.type, - }) - end - else - callback(element, gesture) - end -end - -return EventHandler diff --git a/libs/flexlove/modules/FocusIndicator.lua b/libs/flexlove/modules/FocusIndicator.lua deleted file mode 100644 index ebbac8ad..00000000 --- a/libs/flexlove/modules/FocusIndicator.lua +++ /dev/null @@ -1,232 +0,0 @@ -local packageName = ... or "FocusIndicator" -local modulePath = packageName:match("(.-)[^%.]+$") - -local function req(name) - return require(modulePath .. name) -end - -local FocusIndicator = {} - ---- Configuration ----@type KeyboardNavigationFocusIndicatorConfig -FocusIndicator.config = { - enabled = true, - - --- Custom draw function to override default rendering - ---@type function|nil - --- Called with: element, bounds, style - return true to skip default drawing - draw = nil, - - -- Appearance - color = { 0.2, 0.6, 1.0, 0.8 }, -- Blue with 80% opacity - lineWidth = 2, - inset = -3, -- Negative value extends beyond element - borderRadius = 4, - - -- Animation - animationDuration = 0.15, -- Seconds for focus animation - pulseEnabled = false, -- Enable pulsing animation - pulseDuration = 1.0, -- Seconds per pulse cycle - pulseScaleMin = 0.95, -- Minimum scale during pulse - pulseScaleMax = 1.05, -- Maximum scale during pulse -} - ---- State -FocusIndicator._focusedElement = nil -FocusIndicator._animationProgress = 0 -FocusIndicator._pulsePhase = 0 -FocusIndicator._hidden = true -FocusIndicator._deps = nil - ---- Initialize FocusIndicator module ----@param deps table Dependencies table containing Context and Color modules ----@field deps.Context table Context module for getting focused element ----@field deps.Color table Color module for color manipulation -function FocusIndicator.init(deps) - FocusIndicator._deps = deps - FocusIndicator._Context = deps.Context - FocusIndicator._Color = deps.Color -end - ---- Update animation state for entrance and pulse effects ----@param dt number Delta time in seconds since last frame -function FocusIndicator:update(dt) - if not FocusIndicator.config.enabled then - return - end - - -- Update focus entrance animation - if FocusIndicator._animationProgress < 1 then - FocusIndicator._animationProgress = - math.min(1, FocusIndicator._animationProgress + (dt / FocusIndicator.config.animationDuration)) - end - - -- Update pulse animation - if FocusIndicator.config.pulseEnabled then - FocusIndicator._pulsePhase = (FocusIndicator._pulsePhase + dt) % FocusIndicator.config.pulseDuration - end -end - ---- Set the focused element to render indicator around ----@param element Element? The element to show focus indicator around, or nil to hide -function FocusIndicator.setFocused(element) - FocusIndicator._focusedElement = element - FocusIndicator._hidden = element == nil - -- Reset animation when focus changes - if element then - FocusIndicator._animationProgress = 0 - end -end - ---- Get the current scale factor for animations ---- Combines entrance scale (0.8 to 1.0) with optional pulse scale ----@return number Scale factor (typically 0.8-1.05 range) -function FocusIndicator:getScale() - local scale = 1 - - -- Apply entrance animation (scale up from 0.8) - local entranceScale = 0.8 + (0.2 * FocusIndicator._animationProgress) - scale = scale * entranceScale - - -- Apply pulse animation - if FocusIndicator.config.pulseEnabled then - local pulseProgress = FocusIndicator._pulsePhase / FocusIndicator.config.pulseDuration - -- Smooth sine wave pulse - local pulseScale = FocusIndicator.config.pulseScaleMin - + (FocusIndicator.config.pulseScaleMax - FocusIndicator.config.pulseScaleMin) - * (0.5 + 0.5 * math.sin(2 * math.pi * pulseProgress)) - scale = scale * pulseScale - end - - return scale -end - ---- Get the current opacity for the indicator ---- Applies entrance animation fade-in to the configured alpha ----@return number Alpha value (0-1 range) -function FocusIndicator:getOpacity() - -- Fade in on focus - return FocusIndicator.config.color[4] * FocusIndicator._animationProgress -end - ---- Draw the focus indicator around the focused element ---- Renders a rounded rectangle border, or calls custom draw function if configured ---- Should be called from within love.draw() after all elements are drawn -function FocusIndicator:draw() - if not FocusIndicator.config.enabled then - return - end - - if FocusIndicator._hidden then - return - end - - -- In immediate mode the stored element reference is stale (recreated every frame). - -- Always resolve through Context so we get the live object with up-to-date positions. - local element - if FocusIndicator._Context then - element = FocusIndicator._Context.getFocused() - else - element = FocusIndicator._focusedElement - end - - if not element then - return - end - - -- Get element dimensions (use border-box size which includes padding) - local x = element.x or 0 - local y = element.y or 0 - local w = element._borderBoxWidth - or (element.width + (element.padding and (element.padding.left + element.padding.right) or 0)) - local h = element._borderBoxHeight - or (element.height + (element.padding and (element.padding.top + element.padding.bottom) or 0)) - - if w == 0 or h == 0 then - return - end - - -- Calculate indicator dimensions with inset and scale - local inset = FocusIndicator.config.inset - local scale = self:getScale() - - local indicatorX = x + inset - local indicatorY = y + inset - local indicatorW = w - 2 * inset - local indicatorH = h - 2 * inset - - -- Center the scale around the element - local offsetX = (indicatorW * (1 - scale)) / 2 - local offsetY = (indicatorH * (1 - scale)) / 2 - - indicatorX = indicatorX + offsetX - indicatorY = indicatorY + offsetY - indicatorW = indicatorW * scale - indicatorH = indicatorH * scale - - -- Get color with animated opacity - local r, g, b = FocusIndicator.config.color[1], FocusIndicator.config.color[2], FocusIndicator.config.color[3] - local a = self:getOpacity() - - -- Build style table for custom draw callback - local bounds = { - x = indicatorX, - y = indicatorY, - width = indicatorW, - height = indicatorH, - } - - local style = { - color = { r = r, g = g, b = b, a = a }, - lineWidth = FocusIndicator.config.lineWidth, - borderRadius = FocusIndicator.config.borderRadius, - scale = scale, - opacity = a, - } - - -- Check for custom draw callback - if FocusIndicator.config.draw then - local skipDefault = FocusIndicator.config.draw(element, bounds, style) - if skipDefault then - return - end - end - - -- Save current love.graphics state - local prevBlend, prevAlphaMode = love.graphics.getBlendMode() - local prevR, prevG, prevB, prevA = love.graphics.getColor() - local prevLineWidth = love.graphics.getLineWidth() - - -- Set blend mode for transparency - love.graphics.setBlendMode("alpha") - - -- Draw rounded rectangle border - love.graphics.setColor(r, g, b, a) - love.graphics.setLineWidth(FocusIndicator.config.lineWidth) - - -- Draw the rounded rectangle border - local borderRadius = FocusIndicator.config.borderRadius - love.graphics.rectangle("line", indicatorX, indicatorY, indicatorW, indicatorH, borderRadius) - - -- Restore love.graphics state - love.graphics.setBlendMode(prevBlend, prevAlphaMode) - love.graphics.setColor(prevR, prevG, prevB, prevA) - love.graphics.setLineWidth(prevLineWidth) -end - ---- Set the indicator color ----@param r number Red component (0-1 range) ----@param g number Green component (0-1 range) ----@param b number Blue component (0-1 range) ----@param a number|nil Alpha component (0-1 range), defaults to current alpha if omitted -function FocusIndicator.setColor(r, g, b, a) - FocusIndicator.config.color = { r, g, b, a or FocusIndicator.config.color[4] } -end - ---- Set the stroke width for the indicator border ----@param width number Line width in pixels -function FocusIndicator.setLineWidth(width) - FocusIndicator.config.lineWidth = width -end - -return FocusIndicator diff --git a/libs/flexlove/modules/FontCache.lua b/libs/flexlove/modules/FontCache.lua deleted file mode 100644 index 7aef441c..00000000 --- a/libs/flexlove/modules/FontCache.lua +++ /dev/null @@ -1,269 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local function req(name) - return require(modulePath .. name) -end - --- Font cache with LRU eviction, font resolution, and cache management. --- `ErrorHandler` and `resolveImagePath` are injected via init() to avoid --- a cross-import into utils (utils re-exports the cache via aliases). - --- Font cache with LRU eviction -local FONT_CACHE = {} -local FONT_CACHE_MAX_SIZE = 50 -local FONT_CACHE_STATS = { - hits = 0, - misses = 0, - evictions = 0, - size = 0, -} - -local ErrorHandler = nil -local resolveImagePath = nil - ---- Initialize dependencies ----@param deps table Dependencies: { ErrorHandler = ErrorHandler, resolveImagePath = function } -local function init(deps) - if type(deps) == "table" then - ErrorHandler = deps.ErrorHandler - resolveImagePath = deps.resolveImagePath - end -end - --- LRU tracking: each entry has {font, lastUsed, accessCount} -local function updateCacheAccess(cacheKey) - local entry = FONT_CACHE[cacheKey] - if entry then - entry.lastUsed = love.timer.getTime() - entry.accessCount = entry.accessCount + 1 - end -end - -local function evictLRU() - local oldestKey = nil - local oldestTime = math.huge - - for key, entry in pairs(FONT_CACHE) do - -- Skip methods (get, getFont) - only evict cache entries (tables with lastUsed) - if type(entry) == "table" and entry.lastUsed then - if entry.lastUsed < oldestTime then - oldestTime = entry.lastUsed - oldestKey = key - end - end - end - - if oldestKey then - FONT_CACHE[oldestKey] = nil - FONT_CACHE_STATS.evictions = FONT_CACHE_STATS.evictions + 1 - FONT_CACHE_STATS.size = FONT_CACHE_STATS.size - 1 - end -end - ---- Create or get a font from cache ----@param size number ----@param fontPath string? ----@return love.Font -function FONT_CACHE.get(size, fontPath) - -- Bucket font sizes for better cache reuse (reduces unique cache entries) - -- Small sizes (< 20): round to nearest 2 - -- Medium sizes (20-40): round to nearest 4 - -- Large sizes (> 40): round to nearest 8 - if size < 20 then - size = math.floor((size + 1) / 2) * 2 - elseif size < 40 then - size = math.floor((size + 2) / 4) * 4 - else - size = math.floor((size + 4) / 8) * 8 - end - - local cacheKey = fontPath and (fontPath .. ":" .. tostring(size)) or ("default:" .. tostring(size)) - - if FONT_CACHE[cacheKey] then - -- Cache hit - FONT_CACHE_STATS.hits = FONT_CACHE_STATS.hits + 1 - updateCacheAccess(cacheKey) - return FONT_CACHE[cacheKey].font - end - - -- Cache miss - FONT_CACHE_STATS.misses = FONT_CACHE_STATS.misses + 1 - - local font - if fontPath then - local resolvedPath = resolveImagePath(fontPath) - local success, result = pcall(love.graphics.newFont, resolvedPath, size) - if success then - font = result - else - if ErrorHandler then - ErrorHandler:warn("utils", "RES_004", { - resourceType = "font", - path = fontPath, - }) - end - font = love.graphics.newFont(size) - end - else - font = love.graphics.newFont(size) - end - - -- Add to cache with LRU metadata - FONT_CACHE[cacheKey] = { - font = font, - lastUsed = love.timer.getTime(), - accessCount = 1, - } - FONT_CACHE_STATS.size = FONT_CACHE_STATS.size + 1 - - -- Evict if cache is full - if FONT_CACHE_STATS.size > FONT_CACHE_MAX_SIZE then - evictLRU() - end - - return font -end - ---- Get font for text size (cached) ----@param textSize number? ----@param fontPath string? ----@return love.Font -function FONT_CACHE.getFont(textSize, fontPath) - if textSize then - return FONT_CACHE.get(textSize, fontPath) - else - return love.graphics.getFont() - end -end - --- Font resolution utilities - ---- Resolve font path from fontFamily and theme ----@param fontFamily string? Font family name or direct path ----@param themeComponent string? Theme component name ----@param themeManager table? ThemeManager instance ----@return string? Resolved font path or nil -local function resolveFontPath(fontFamily, themeComponent, themeManager) - if fontFamily then - -- Check if fontFamily is a theme font name - local themeToUse = themeManager and themeManager:getTheme() - if themeToUse and themeToUse.fonts and themeToUse.fonts[fontFamily] then - return themeToUse.fonts[fontFamily] - else - -- Treat as direct path to font file - return fontFamily - end - elseif themeComponent and themeManager then - -- If using themeComponent but no fontFamily specified, check for default font in theme - return themeManager:getDefaultFontFamily() - end - return nil -end - ---- Get font for element (resolves from theme or fontFamily) ----@param textSize number? Text size in pixels ----@param fontFamily string? Font family name or direct path ----@param themeComponent string? Theme component name ----@param themeManager table? ThemeManager instance ----@return love.Font -local function getFont(textSize, fontFamily, themeComponent, themeManager) - local fontPath = resolveFontPath(fontFamily, themeComponent, themeManager) - return FONT_CACHE.getFont(textSize, fontPath) -end - --- Font cache management - ---- Get font cache statistics ----@return table stats {hits, misses, evictions, size, hitRate} -local function getFontCacheStats() - local total = FONT_CACHE_STATS.hits + FONT_CACHE_STATS.misses - local hitRate = total > 0 and (FONT_CACHE_STATS.hits / total) or 0 - return { - hits = FONT_CACHE_STATS.hits, - misses = FONT_CACHE_STATS.misses, - evictions = FONT_CACHE_STATS.evictions, - size = FONT_CACHE_STATS.size, - hitRate = hitRate, - } -end - ---- Set maximum font cache size ----@param maxSize number Maximum number of fonts to cache -local function setFontCacheSize(maxSize) - FONT_CACHE_MAX_SIZE = math.max(1, maxSize) - - -- Evict entries if cache is now over limit - while FONT_CACHE_STATS.size > FONT_CACHE_MAX_SIZE do - evictLRU() - end -end - ---- Clear font cache -local function clearFontCache() - -- Clear cache entries but preserve methods (get, getFont) - for key, entry in pairs(FONT_CACHE) do - if type(entry) == "table" and entry.lastUsed then - FONT_CACHE[key] = nil - end - end - FONT_CACHE_STATS.size = 0 - FONT_CACHE_STATS.evictions = 0 -end - ---- Preload font at multiple sizes ----@param fontPath string? Path to font file (nil for default font) ----@param sizes table Array of font sizes to preload -local function preloadFont(fontPath, sizes) - for _, size in ipairs(sizes) do - -- Round size to reduce cache entries - size = math.floor(size + 0.5) - - local cacheKey = fontPath and (fontPath .. ":" .. tostring(size)) or ("default:" .. tostring(size)) - - if not FONT_CACHE[cacheKey] then - local font - if fontPath then - local resolvedPath = resolveImagePath(fontPath) - local success, result = pcall(love.graphics.newFont, resolvedPath, size) - if success then - font = result - else - font = love.graphics.newFont(size) - end - else - font = love.graphics.newFont(size) - end - - FONT_CACHE[cacheKey] = { - font = font, - lastUsed = love.timer.getTime(), - accessCount = 1, - } - FONT_CACHE_STATS.size = FONT_CACHE_STATS.size + 1 - FONT_CACHE_STATS.misses = FONT_CACHE_STATS.misses + 1 - - -- Evict if cache is full - if FONT_CACHE_STATS.size > FONT_CACHE_MAX_SIZE then - evictLRU() - end - end - end -end - ---- Reset font cache statistics -local function resetFontCacheStats() - FONT_CACHE_STATS.hits = 0 - FONT_CACHE_STATS.misses = 0 - FONT_CACHE_STATS.evictions = 0 -end - -return { - FONT_CACHE = FONT_CACHE, - init = init, - resolveFontPath = resolveFontPath, - getFont = getFont, - getFontCacheStats = getFontCacheStats, - setFontCacheSize = setFontCacheSize, - clearFontCache = clearFontCache, - preloadFont = preloadFont, - resetFontCacheStats = resetFontCacheStats, -} diff --git a/libs/flexlove/modules/GestureRecognizer.lua b/libs/flexlove/modules/GestureRecognizer.lua deleted file mode 100644 index 5e324f09..00000000 --- a/libs/flexlove/modules/GestureRecognizer.lua +++ /dev/null @@ -1,583 +0,0 @@ ----@class GestureRecognizer ----@field _touches table -- Current touch states ----@field _gestureStates table -- Active gesture states ----@field _config table -- Gesture configuration (thresholds, etc.) ----@field _InputEvent table ----@field _utils table -local GestureRecognizer = {} -GestureRecognizer.__index = GestureRecognizer - --- Gesture types enum -local GestureType = { - TAP = "tap", - DOUBLE_TAP = "double_tap", - LONG_PRESS = "long_press", - SWIPE = "swipe", - PAN = "pan", - PINCH = "pinch", - ROTATE = "rotate", -} - --- Gesture states -local GestureState = { - POSSIBLE = "possible", - BEGAN = "began", - CHANGED = "changed", - ENDED = "ended", - CANCELLED = "cancelled", - FAILED = "failed", -} - --- Default configuration -local defaultConfig = { - -- Tap gesture - tapMaxDuration = 0.3, -- seconds - tapMaxMovement = 10, -- pixels - - -- Double-tap gesture - doubleTapInterval = 0.3, -- seconds between taps - - -- Long-press gesture - longPressMinDuration = 0.5, -- seconds - longPressMaxMovement = 10, -- pixels - - -- Swipe gesture - swipeMinDistance = 50, -- pixels - swipeMaxDuration = 0.2, -- seconds - swipeMinVelocity = 200, -- pixels per second - - -- Pan gesture - panMinMovement = 5, -- pixels to start pan - - -- Pinch gesture - pinchMinScaleChange = 0.1, -- 10% scale change - - -- Rotate gesture - rotateMinAngleChange = 5, -- degrees -} - ---- Create a new GestureRecognizer instance ----@param config table? Optional configuration options ----@param deps table Dependencies {InputEvent, utils} ----@return GestureRecognizer -function GestureRecognizer.new(config, deps) - config = config or {} - - local self = setmetatable({}, GestureRecognizer) - - self._InputEvent = deps.InputEvent - self._utils = deps.utils - - -- Merge configuration with defaults - self._config = {} - for key, value in pairs(defaultConfig) do - self._config[key] = config[key] or value - end - - self._touches = {} - self._gestureStates = { - tap = nil, - doubleTap = { lastTapTime = 0, tapCount = 0 }, - longPress = {}, - swipe = {}, - pan = {}, - pinch = {}, - rotate = {}, - } - - return self -end - ---- Update gesture recognizer with touch event ----@param event InputEvent Touch event -function GestureRecognizer:processTouchEvent(event) - if not event.touchId then - return nil - end - - local touchId = event.touchId - local gestures = {} - - -- Update touch state - if event.type == "touchpress" then - self._touches[touchId] = { - startX = event.x, - startY = event.y, - x = event.x, - y = event.y, - startTime = event.timestamp, - lastTime = event.timestamp, - phase = "began", - } - - -- Initialize gesture detection - self:_detectTapBegan(touchId, event) - self:_detectLongPressBegan(touchId, event) - elseif event.type == "touchmove" then - local touch = self._touches[touchId] - if touch then - touch.x = event.x - touch.y = event.y - touch.lastTime = event.timestamp - touch.phase = "moved" - - -- Update gesture detection - local panGesture = self:_detectPan(touchId, event) - if panGesture then - table.insert(gestures, panGesture) - end - local swipeGesture = self:_detectSwipe(touchId, event) - if swipeGesture then - table.insert(gestures, swipeGesture) - end - - -- Multi-touch gestures - if self:_getTouchCount() >= 2 then - local pinchGesture = self:_detectPinch(event) - if pinchGesture then - table.insert(gestures, pinchGesture) - end - local rotateGesture = self:_detectRotate(event) - if rotateGesture then - table.insert(gestures, rotateGesture) - end - end - end - elseif event.type == "touchrelease" then - local touch = self._touches[touchId] - if touch then - touch.phase = "ended" - - -- Finalize gesture detection - local tapGesture = self:_detectTapEnded(touchId, event) - if tapGesture then - table.insert(gestures, tapGesture) - end - local swipeGesture = self:_detectSwipeEnded(touchId, event) - if swipeGesture then - table.insert(gestures, swipeGesture) - end - local panGesture = self:_detectPanEnded(touchId, event) - if panGesture then - table.insert(gestures, panGesture) - end - - -- Cleanup touch - self._touches[touchId] = nil - end - elseif event.type == "touchcancel" then - -- Cancel all active gestures for this touch - self._touches[touchId] = nil - self:_cancelAllGestures() - end - - return #gestures > 0 and gestures or nil -end - ---- Get number of active touches ----@return number -function GestureRecognizer:_getTouchCount() - local count = 0 - for _ in pairs(self._touches) do - count = count + 1 - end - return count -end - ---- Detect tap gesture began ----@param touchId string ----@param event InputEvent -function GestureRecognizer:_detectTapBegan(touchId, event) - -- Tap detection happens on touch end - -- Just record the touch for now -end - ---- Detect tap gesture ended ----@param touchId string ----@param event InputEvent -function GestureRecognizer:_detectTapEnded(touchId, event) - local touch = self._touches[touchId] - if not touch then - return - end - - local duration = event.timestamp - touch.startTime - local dx = event.x - touch.startX - local dy = event.y - touch.startY - local distance = math.sqrt(dx * dx + dy * dy) - - -- Check if it's a valid tap - if duration < self._config.tapMaxDuration and distance < self._config.tapMaxMovement then - local currentTime = event.timestamp - local doubleTapState = self._gestureStates.doubleTap - - -- Check for double-tap - if currentTime - doubleTapState.lastTapTime < self._config.doubleTapInterval then - doubleTapState.tapCount = doubleTapState.tapCount + 1 - - if doubleTapState.tapCount >= 2 then - -- Fire double-tap gesture - return { - type = GestureType.DOUBLE_TAP, - state = GestureState.ENDED, - x = event.x, - y = event.y, - timestamp = event.timestamp, - } - end - else - doubleTapState.tapCount = 1 - end - - doubleTapState.lastTapTime = currentTime - - -- Fire tap gesture - return { - type = GestureType.TAP, - state = GestureState.ENDED, - x = event.x, - y = event.y, - timestamp = event.timestamp, - } - end -end - ---- Detect long-press gesture began ----@param touchId string ----@param event InputEvent -function GestureRecognizer:_detectLongPressBegan(touchId, event) - -- Long-press detection happens continuously during touch - self._gestureStates.longPress[touchId] = { - startX = event.x, - startY = event.y, - startTime = event.timestamp, - triggered = false, - } -end - ---- Detect pan gesture ----@param touchId string ----@param event InputEvent ----@return table? Gesture event -function GestureRecognizer:_detectPan(touchId, event) - local touch = self._touches[touchId] - if not touch then - return nil - end - - local dx = event.x - touch.startX - local dy = event.y - touch.startY - local distance = math.sqrt(dx * dx + dy * dy) - - local panState = self._gestureStates.pan[touchId] - - if not panState then - -- Check if pan should begin - if distance >= self._config.panMinMovement then - self._gestureStates.pan[touchId] = { - active = true, - lastX = touch.startX, - lastY = touch.startY, - } - panState = self._gestureStates.pan[touchId] - - return { - type = GestureType.PAN, - state = GestureState.BEGAN, - x = event.x, - y = event.y, - dx = dx, - dy = dy, - timestamp = event.timestamp, - } - end - else - -- Pan is active, fire changed event - local panDx = event.x - panState.lastX - local panDy = event.y - panState.lastY - - panState.lastX = event.x - panState.lastY = event.y - - return { - type = GestureType.PAN, - state = GestureState.CHANGED, - x = event.x, - y = event.y, - dx = panDx, - dy = panDy, - totalDx = dx, - totalDy = dy, - timestamp = event.timestamp, - } - end - - return nil -end - ---- Detect pan ended ----@param touchId string ----@param event InputEvent ----@return table? Gesture event -function GestureRecognizer:_detectPanEnded(touchId, event) - local panState = self._gestureStates.pan[touchId] - if panState and panState.active then - self._gestureStates.pan[touchId] = nil - - local touch = self._touches[touchId] - local dx = event.x - touch.startX - local dy = event.y - touch.startY - - return { - type = GestureType.PAN, - state = GestureState.ENDED, - x = event.x, - y = event.y, - dx = dx, - dy = dy, - timestamp = event.timestamp, - } - end - - return nil -end - ---- Detect swipe gesture ----@param touchId string ----@param event InputEvent -function GestureRecognizer:_detectSwipe(touchId, event) - -- Swipe detection happens on touch end -end - ---- Detect swipe ended ----@param touchId string ----@param event InputEvent ----@return table? Gesture event -function GestureRecognizer:_detectSwipeEnded(touchId, event) - local touch = self._touches[touchId] - if not touch then - return nil - end - - local duration = event.timestamp - touch.startTime - local dx = event.x - touch.startX - local dy = event.y - touch.startY - local distance = math.sqrt(dx * dx + dy * dy) - - -- Check if it's a valid swipe - if distance >= self._config.swipeMinDistance and duration <= self._config.swipeMaxDuration then - local velocity = distance / duration - - if velocity >= self._config.swipeMinVelocity then - -- Determine swipe direction - local angle = math.atan2(dy, dx) - local direction = "right" - - if angle >= -math.pi / 4 and angle < math.pi / 4 then - direction = "right" - elseif angle >= math.pi / 4 and angle < 3 * math.pi / 4 then - direction = "down" - elseif angle >= -3 * math.pi / 4 and angle < -math.pi / 4 then - direction = "up" - else - direction = "left" - end - - return { - type = GestureType.SWIPE, - state = GestureState.ENDED, - x = event.x, - y = event.y, - dx = dx, - dy = dy, - direction = direction, - velocity = velocity, - timestamp = event.timestamp, - } - end - end - - return nil -end - ---- Detect pinch gesture ----@param event InputEvent ----@return table? Gesture event -function GestureRecognizer:_detectPinch(event) - -- Get two touches for pinch - local touches = {} - for touchId, touch in pairs(self._touches) do - table.insert(touches, { id = touchId, touch = touch }) - if #touches >= 2 then - break - end - end - - if #touches < 2 then - return nil - end - - local t1 = touches[1].touch - local t2 = touches[2].touch - - -- Calculate current distance - local currentDx = t2.x - t1.x - local currentDy = t2.y - t1.y - local currentDistance = math.sqrt(currentDx * currentDx + currentDy * currentDy) - - -- Calculate initial distance - local initialDx = t2.startX - t1.startX - local initialDy = t2.startY - t1.startY - local initialDistance = math.sqrt(initialDx * initialDx + initialDy * initialDy) - - if initialDistance == 0 then - return nil - end - - -- Calculate scale - local scale = currentDistance / initialDistance - local pinchState = self._gestureStates.pinch - - if not pinchState.active then - -- Check if pinch should begin - if math.abs(scale - 1.0) >= self._config.pinchMinScaleChange then - pinchState.active = true - pinchState.initialScale = scale - pinchState.lastScale = scale - - -- Calculate center point - local centerX = (t1.x + t2.x) / 2 - local centerY = (t1.y + t2.y) / 2 - - return { - type = GestureType.PINCH, - state = GestureState.BEGAN, - scale = scale, - centerX = centerX, - centerY = centerY, - timestamp = event.timestamp, - } - end - else - -- Pinch is active, fire changed event - local centerX = (t1.x + t2.x) / 2 - local centerY = (t1.y + t2.y) / 2 - - local scaleChange = scale - pinchState.lastScale - pinchState.lastScale = scale - - return { - type = GestureType.PINCH, - state = GestureState.CHANGED, - scale = scale, - scaleChange = scaleChange, - centerX = centerX, - centerY = centerY, - timestamp = event.timestamp, - } - end - - return nil -end - ---- Detect rotate gesture ----@param event InputEvent ----@return table? Gesture event -function GestureRecognizer:_detectRotate(event) - -- Get two touches for rotation - local touches = {} - for touchId, touch in pairs(self._touches) do - table.insert(touches, { id = touchId, touch = touch }) - if #touches >= 2 then - break - end - end - - if #touches < 2 then - return nil - end - - local t1 = touches[1].touch - local t2 = touches[2].touch - - -- Calculate current angle - local currentAngle = math.atan2(t2.y - t1.y, t2.x - t1.x) - - -- Calculate initial angle - local initialAngle = math.atan2(t2.startY - t1.startY, t2.startX - t1.startX) - - -- Calculate rotation (in degrees) - local rotation = (currentAngle - initialAngle) * 180 / math.pi - - local rotateState = self._gestureStates.rotate - - if not rotateState.active then - -- Check if rotation should begin - if math.abs(rotation) >= self._config.rotateMinAngleChange then - rotateState.active = true - rotateState.initialRotation = rotation - rotateState.lastRotation = rotation - - -- Calculate center point - local centerX = (t1.x + t2.x) / 2 - local centerY = (t1.y + t2.y) / 2 - - return { - type = GestureType.ROTATE, - state = GestureState.BEGAN, - rotation = rotation, - centerX = centerX, - centerY = centerY, - timestamp = event.timestamp, - } - end - else - -- Rotation is active, fire changed event - local centerX = (t1.x + t2.x) / 2 - local centerY = (t1.y + t2.y) / 2 - - local rotationChange = rotation - rotateState.lastRotation - rotateState.lastRotation = rotation - - return { - type = GestureType.ROTATE, - state = GestureState.CHANGED, - rotation = rotation, - rotationChange = rotationChange, - centerX = centerX, - centerY = centerY, - timestamp = event.timestamp, - } - end - - return nil -end - ---- Cancel all active gestures -function GestureRecognizer:_cancelAllGestures() - for gestureType, state in pairs(self._gestureStates) do - if type(state) == "table" and state.active then - state.active = false - end - end -end - ---- Reset gesture recognizer state -function GestureRecognizer:reset() - self._touches = {} - self._gestureStates = { - tap = nil, - doubleTap = { lastTapTime = 0, tapCount = 0 }, - longPress = {}, - swipe = {}, - pan = {}, - pinch = { active = false }, - rotate = { active = false }, - } -end - --- Export gesture types and states -GestureRecognizer.GestureType = GestureType -GestureRecognizer.GestureState = GestureState - -return GestureRecognizer diff --git a/libs/flexlove/modules/Grid.lua b/libs/flexlove/modules/Grid.lua deleted file mode 100644 index d37600c7..00000000 --- a/libs/flexlove/modules/Grid.lua +++ /dev/null @@ -1,336 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local utils = require(modulePath .. "utils") -local enums = utils.enums -local Units = require(modulePath .. "Units") - -local Positioning = enums.Positioning -local AlignItems = enums.AlignItems - ---- Grid layout with variable column widths / row heights ---- Supports px, %, fr, auto, vw, vh, and calc track sizes -local Grid = {} - ---- Parse a single track spec into {type, value} ---- Uses the Units pipeline for standard CSS units (px, %, vw, vh, calc). ---- Grid-specific types (fr, auto) are handled directly. ----@param spec number|string Track specification: number (px), string ("100px", "50%", "10vw", "1fr", "auto") ----@param availableSize number Container size for % resolution ----@param viewportWidth number Viewport width for vw resolution ----@param viewportHeight number Viewport height for vh resolution ----@return table {type: "px"|"fr"|"auto", value: number} -function Grid._parseTrack(spec, availableSize, viewportWidth, viewportHeight) - -- Handle calc objects (tables with _isCalc flag from FlexLove.calc()) - if type(spec) == "table" then - local resolved = Units.resolve(spec, "calc", viewportWidth, viewportHeight, availableSize) - return { type = "px", value = resolved } - end - - if type(spec) == "number" then - return { type = "px", value = spec } - end - - if type(spec) == "string" then - if spec == "auto" then - return { type = "auto", value = 0 } - end - - -- Check for fr unit (grid-specific, not in Units pipeline) - local numStr, unit = spec:match("^([%-]?[%d%.]+)(.*)$") - if numStr and unit == "fr" then - local num = tonumber(numStr) - if num then - return { type = "fr", value = num } - end - end - - -- Delegate all other units to the Units pipeline (px, %, vw, vh, calc) - local parsedVal, parsedUnit = Units.parse(spec) - local resolved = Units.resolve(parsedVal, parsedUnit, viewportWidth, viewportHeight, availableSize) - return { type = "px", value = resolved } - end - - -- Default: 1fr - return { type = "fr", value = 1 } -end - ---- Build track list from gridColumns/gridRows or fall back to equal 1fr tracks ----@param spec number|table? Track count (number = equal 1fr tracks) or array of track specs (e.g., {"1fr", "2fr", "100px"}) ----@param availableSize number Container size for % resolution ----@param viewportWidth number Viewport width for vw resolution ----@param viewportHeight number Viewport height for vh resolution ----@return table Array of {type, value} track descriptors -function Grid._buildTracks(spec, availableSize, viewportWidth, viewportHeight) - if type(spec) == "table" and #spec > 0 then - local tracks = {} - for i, s in ipairs(spec) do - tracks[i] = Grid._parseTrack(s, availableSize, viewportWidth, viewportHeight) - end - return tracks - end - -- Fallback: equal 1fr tracks - local count = (type(spec) == "number" and spec > 0) and spec or 1 - local tracks = {} - for i = 1, count do - tracks[i] = { type = "fr", value = 1 } - end - return tracks -end - ---- Measure intrinsic content sizes for auto tracks ---- Maps children to their tracks and computes each child's max-content contribution. ---- For children with explicit dimensions (units unit ~= "auto"), uses the original ---- explicit size. For auto-sized children, uses calculated content size. ---- Stores the max per auto track. Matches CSS Grid auto sizing where tracks size ---- to the max-content contribution of their grid items. ----@param tracks table Array of {type, value} track descriptors ----@param children table Array of grid child elements ----@param axis "width"|"height" Dimension axis to measure -function Grid._measureAutoTracks(tracks, children, axis) - local trackSizes = {} - local numTracks = #tracks - - for i, child in ipairs(children) do - local index = i - 1 - local trackIdx = (index % numTracks) + 1 - - local intrinsicSize - if axis == "width" then - local unit = child.units and child.units.width and child.units.width.unit - if unit and unit ~= "auto" then - -- Explicit width: use original value + padding (not stretched border-box) - intrinsicSize = (child.units.width.value or 0) + child.padding.left + child.padding.right - else - -- Auto-sized: use calculated content size - intrinsicSize = child:calculateAutoWidth() - end - else - local unit = child.units and child.units.height and child.units.height.unit - if unit and unit ~= "auto" then - intrinsicSize = (child.units.height.value or 0) + child.padding.top + child.padding.bottom - else - intrinsicSize = child:calculateAutoHeight() - end - end - - if intrinsicSize > 0 then - trackSizes[trackIdx] = math.max(trackSizes[trackIdx] or 0, intrinsicSize) - end - end - - -- Apply measured sizes to auto tracks - for i, track in ipairs(tracks) do - if track.type == "auto" and trackSizes[i] then - track.value = trackSizes[i] - end - end -end - ---- Resolve track sizes: auto (content) first, then px (fixed), then fr (remaining) ---- CSS Grid algorithm: ---- 1. auto tracks size to their content (max-content) — measured by _measureAutoTracks ---- 2. px tracks consume their fixed size ---- 3. fr tracks consume remaining free space proportionally ---- 4. If no fr tracks exist, auto tracks share remaining space equally ---- Mutates tracks in-place, converting all to {type="px", value=number} ----@param tracks table Array of {type, value} track descriptors ----@param availableSize number Total space available for tracks ----@param gap number Gap between tracks -function Grid._resolveTracks(tracks, availableSize, gap) - local count = #tracks - local totalGaps = (count > 1 and (count - 1) * gap) or 0 - local remaining = math.max(0, availableSize - totalGaps) - - -- Pass 1: Treat auto tracks as fixed (content-measured) and subtract - for _, track in ipairs(tracks) do - if track.type == "px" then - remaining = remaining - track.value - elseif track.type == "auto" then - remaining = remaining - math.max(0, track.value) - end - end - - remaining = math.max(0, remaining) - - -- Pass 2: Count fr shares - local totalFr = 0 - local autoCount = 0 - for _, track in ipairs(tracks) do - if track.type == "fr" then - totalFr = totalFr + track.value - elseif track.type == "auto" then - autoCount = autoCount + 1 - end - end - - -- Pass 3: Distribute remaining space - if totalFr > 0 then - -- fr tracks consume all remaining free space - local frUnit = remaining / totalFr - for _, track in ipairs(tracks) do - if track.type == "fr" then - track.value = frUnit * track.value - track.type = "px" - end - end - elseif autoCount > 0 then - -- No fr tracks: auto tracks share remaining space equally (grow beyond content) - local extraPerAuto = math.max(0, remaining) / autoCount - for _, track in ipairs(tracks) do - if track.type == "auto" then - track.value = track.value + extraPerAuto - track.type = "px" - end - end - end -end - ---- Layout grid items within a grid container ---- Supports variable column widths and row heights via gridColumns/gridRows (number or track specs) ---- Falls back to equal-sized 1fr tracks when nil ----@param element Element -- Grid container element -function Grid.layoutGridItems(element) - -- Calculate space reserved by absolutely positioned siblings - local reservedLeft = 0 - local reservedRight = 0 - local reservedTop = 0 - local reservedBottom = 0 - - for _, child in ipairs(element.children) do - -- Only consider absolutely positioned children with explicit positioning and display != false - if child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute and child.display ~= false then - -- BORDER-BOX MODEL: Use border-box dimensions for space calculations - local childBorderBoxWidth = child:getBorderBoxWidth() - local childBorderBoxHeight = child:getBorderBoxHeight() - - if child.left then - reservedLeft = math.max(reservedLeft, child.left + childBorderBoxWidth) - end - if child.right then - reservedRight = math.max(reservedRight, child.right + childBorderBoxWidth) - end - if child.top then - reservedTop = math.max(reservedTop, child.top + childBorderBoxHeight) - end - if child.bottom then - reservedBottom = math.max(reservedBottom, child.bottom + childBorderBoxHeight) - end - end - end - - -- Calculate available space (accounting for padding and reserved space) - -- BORDER-BOX MODEL: element.width and element.height are already content dimensions - local availableWidth = math.max(0, element.width - reservedLeft - reservedRight) - local availableHeight = math.max(0, element.height - reservedTop - reservedBottom) - - -- Get gaps - local columnGap = element.columnGap or 0 - local rowGap = element.rowGap or 0 - - -- Collect grid children (exclude explicitly absolute and display=false) - local gridChildren = {} - for _, child in ipairs(element.children) do - if not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) and child.display ~= false then - table.insert(gridChildren, child) - end - end - - -- Get viewport dimensions for unit resolution (vw, vh, %) - local vpw, vph = Units.getViewport() - - -- Build tracks, measure auto tracks by content, then resolve sizes - local colTracks = Grid._buildTracks(element.gridColumns, availableWidth, vpw, vph) - local rowTracks = Grid._buildTracks(element.gridRows, availableHeight, vpw, vph) - - Grid._measureAutoTracks(colTracks, gridChildren, "width") - Grid._measureAutoTracks(rowTracks, gridChildren, "height") - - Grid._resolveTracks(colTracks, availableWidth, columnGap) - Grid._resolveTracks(rowTracks, availableHeight, rowGap) - - -- Compute column start positions (for positioning) - local colStarts = {} - local currentX = element.x + element.padding.left + reservedLeft - for col = 1, #colTracks do - colStarts[col] = currentX - currentX = currentX + colTracks[col].value + columnGap - end - - local rowStarts = {} - local currentY = element.y + element.padding.top + reservedTop - for row = 1, #rowTracks do - rowStarts[row] = currentY - currentY = currentY + rowTracks[row].value + rowGap - end - - local effectiveAlignItems = element.alignItems or AlignItems.STRETCH - - for i, child in ipairs(gridChildren) do - -- Calculate row and column (0-indexed for calculation) - local index = i - 1 - local col = index % #colTracks - local row = math.floor(index / #colTracks) - - if row >= #rowTracks then - break - end - - -- Get resolved cell position and size - local colIdx = col + 1 - local rowIdx = row + 1 - local cellX = colStarts[colIdx] - local cellY = rowStarts[rowIdx] - local cellWidth = colTracks[colIdx].value - local cellHeight = rowTracks[rowIdx].value - - -- Apply alignment within grid cell (default to stretch) - -- BORDER-BOX MODEL: Set border-box dimensions, content area adjusts automatically - if effectiveAlignItems == AlignItems.STRETCH or effectiveAlignItems == "stretch" then - child.x = cellX - child.y = cellY - child._borderBoxWidth = cellWidth - child._borderBoxHeight = cellHeight - child.width = math.max(0, cellWidth - child.padding.left - child.padding.right) - child.height = math.max(0, cellHeight - child.padding.top - child.padding.bottom) - -- Disable auto-sizing when stretched by grid - child.autosizing.width = false - child.autosizing.height = false - elseif effectiveAlignItems == AlignItems.CENTER or effectiveAlignItems == "center" then - local childBorderBoxWidth = child:getBorderBoxWidth() - local childBorderBoxHeight = child:getBorderBoxHeight() - child.x = cellX + (cellWidth - childBorderBoxWidth) / 2 - child.y = cellY + (cellHeight - childBorderBoxHeight) / 2 - elseif - effectiveAlignItems == AlignItems.FLEX_START - or effectiveAlignItems == "flex-start" - or effectiveAlignItems == "start" - then - child.x = cellX - child.y = cellY - elseif - effectiveAlignItems == AlignItems.FLEX_END - or effectiveAlignItems == "flex-end" - or effectiveAlignItems == "end" - then - local childBorderBoxWidth = child:getBorderBoxWidth() - local childBorderBoxHeight = child:getBorderBoxHeight() - child.x = cellX + cellWidth - childBorderBoxWidth - child.y = cellY + cellHeight - childBorderBoxHeight - else - child.x = cellX - child.y = cellY - child._borderBoxWidth = cellWidth - child._borderBoxHeight = cellHeight - child.width = math.max(0, cellWidth - child.padding.left - child.padding.right) - child.height = math.max(0, cellHeight - child.padding.top - child.padding.bottom) - -- Disable auto-sizing when stretched by grid - child.autosizing.width = false - child.autosizing.height = false - end - - if #child.children > 0 then - child:layoutChildren() - end - end -end - -return Grid diff --git a/libs/flexlove/modules/ImageCache.lua b/libs/flexlove/modules/ImageCache.lua deleted file mode 100644 index 206fa59c..00000000 --- a/libs/flexlove/modules/ImageCache.lua +++ /dev/null @@ -1,160 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local function req(name) - return require(modulePath .. name) -end - -local utils = req("utils") - --- ErrorHandler will be injected via init -local ErrorHandler = nil - ----@class ImageCache ----@field _cache table -local ImageCache = {} -ImageCache._cache = {} - ---- Initialize ImageCache with dependencies ----@param deps table Dependencies table with ErrorHandler -function ImageCache.init(deps) - if deps and deps.ErrorHandler then - ErrorHandler = deps.ErrorHandler - end -end - ---- Load an image from file path with caching ---- Returns cached image if already loaded, otherwise loads and caches it ----@param imagePath string -- Path to image file ----@param loadImageData boolean? -- Optional: also load ImageData for pixel access (default: false) ----@return love.Image|nil -- Image object or nil on error ----@return string|nil -- Error message if loading failed -function ImageCache.load(imagePath, loadImageData) - if not imagePath or type(imagePath) ~= "string" or imagePath == "" then - return nil, "Invalid image path: path must be a non-empty string" - end - - local normalizedPath = utils.normalizePath(imagePath) - - if ImageCache._cache[normalizedPath] then - return ImageCache._cache[normalizedPath].image, nil - end - - local success, imageOrError = pcall(love.graphics.newImage, normalizedPath) - if not success then - if ErrorHandler then - ErrorHandler:warn("ImageCache", "RES_004", { - resourceType = "image", - path = imagePath, - error = tostring(imageOrError), - }) - end - return nil, string.format("Failed to load image '%s': %s", imagePath, tostring(imageOrError)) - end - - local image = imageOrError - local imgData = nil - - if loadImageData then - local dataSuccess, dataOrError = pcall(love.image.newImageData, normalizedPath) - if dataSuccess then - imgData = dataOrError - elseif ErrorHandler then - ErrorHandler:warn("ImageCache", "RES_004", { - resourceType = "image data", - path = imagePath, - error = tostring(dataOrError), - }) - end - end - - ImageCache._cache[normalizedPath] = { - image = image, - imageData = imgData, - } - - return image, nil -end - ---- Get a cached image without loading ----@param imagePath string -- Path to image file ----@return love.Image|nil -- Cached image or nil if not found -function ImageCache.get(imagePath) - if not imagePath or type(imagePath) ~= "string" then - return nil - end - - local normalizedPath = utils.normalizePath(imagePath) - local cached = ImageCache._cache[normalizedPath] - return cached and cached.image or nil -end - ---- Get cached ImageData for an image ----@param imagePath string -- Path to image file ----@return love.ImageData|nil -- Cached ImageData or nil if not found -function ImageCache.getImageData(imagePath) - if not imagePath or type(imagePath) ~= "string" then - return nil - end - - local normalizedPath = utils.normalizePath(imagePath) - local cached = ImageCache._cache[normalizedPath] - return cached and cached.imageData or nil -end - ---- Remove a specific image from cache ----@param imagePath string -- Path to image file to remove ----@return boolean -- True if image was removed, false if not found -function ImageCache.remove(imagePath) - if not imagePath or type(imagePath) ~= "string" then - return false - end - - local normalizedPath = utils.normalizePath(imagePath) - if ImageCache._cache[normalizedPath] then - local cached = ImageCache._cache[normalizedPath] - if cached.image then - cached.image:release() - end - if cached.imageData then - cached.imageData:release() - end - ImageCache._cache[normalizedPath] = nil - return true - end - return false -end - ---- Clear all cached images -function ImageCache.clear() - for path, cached in pairs(ImageCache._cache) do - if cached.image then - cached.image:release() - end - if cached.imageData then - cached.imageData:release() - end - end - ImageCache._cache = {} -end - ---- Get cache statistics ----@return {count: number, memoryEstimate: number} -- Cache stats -function ImageCache.getStats() - local count = 0 - local memoryEstimate = 0 - - for path, cached in pairs(ImageCache._cache) do - count = count + 1 - if cached.image then - local w, h = cached.image:getDimensions() - -- Estimate: 4 bytes per pixel (RGBA) - memoryEstimate = memoryEstimate + (w * h * 4) - end - end - - return { - count = count, - memoryEstimate = memoryEstimate, - } -end - -return ImageCache diff --git a/libs/flexlove/modules/ImageRenderer.lua b/libs/flexlove/modules/ImageRenderer.lua deleted file mode 100644 index 886c30a2..00000000 --- a/libs/flexlove/modules/ImageRenderer.lua +++ /dev/null @@ -1,380 +0,0 @@ ----@class ImageRenderer -local ImageRenderer = {} - --- ErrorHandler and utils will be injected via init -local ErrorHandler = nil -local utils = nil - ---- Initialize ImageRenderer with dependencies ----@param deps table Dependencies table with ErrorHandler and utils -function ImageRenderer.init(deps) - if deps and deps.ErrorHandler then - ErrorHandler = deps.ErrorHandler - end - if deps and deps.utils then - utils = deps.utils - end -end - ---- Calculate rendering parameters for object-fit modes ---- Returns source and destination rectangles for rendering ----@param imageWidth number -- Natural width of the image ----@param imageHeight number -- Natural height of the image ----@param boundsWidth number -- Width of the bounds to fit within ----@param boundsHeight number -- Height of the bounds to fit within ----@param fitMode string? -- One of: "fill", "contain", "cover", "scale-down", "none" (default: "fill") ----@param objectPosition string? -- Position like "center center", "top left", "50% 50%" (default: "center center") ----@return {sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number, scaleX: number, scaleY: number} -function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, fitMode, objectPosition) - fitMode = fitMode or "fill" - objectPosition = objectPosition or "center center" - - if imageWidth <= 0 or imageHeight <= 0 or boundsWidth <= 0 or boundsHeight <= 0 then - ErrorHandler:error("ImageRenderer", "VAL_002", { - imageWidth = imageWidth, - imageHeight = imageHeight, - boundsWidth = boundsWidth, - boundsHeight = boundsHeight, - }) - end - - local result = { - sx = 0, -- Source X - sy = 0, -- Source Y - sw = imageWidth, -- Source width - sh = imageHeight, -- Source height - dx = 0, -- Destination X - dy = 0, -- Destination Y - dw = boundsWidth, -- Destination width - dh = boundsHeight, -- Destination height - scaleX = 1, -- Scale factor X - scaleY = 1, -- Scale factor Y - } - - if fitMode == "fill" then - -- Stretch to fill bounds (may distort) - result.scaleX = boundsWidth / imageWidth - result.scaleY = boundsHeight / imageHeight - result.dw = boundsWidth - result.dh = boundsHeight - elseif fitMode == "contain" then - -- Scale to fit within bounds (preserves aspect ratio) - local scale = math.min(boundsWidth / imageWidth, boundsHeight / imageHeight) - result.scaleX = scale - result.scaleY = scale - result.dw = imageWidth * scale - result.dh = imageHeight * scale - - -- Apply object-position for letterbox alignment - local posX, posY = ImageRenderer._parsePosition(objectPosition) - result.dx = (boundsWidth - result.dw) * posX - result.dy = (boundsHeight - result.dh) * posY - elseif fitMode == "cover" then - -- Scale to cover bounds (preserves aspect ratio, may crop) - local scale = math.max(boundsWidth / imageWidth, boundsHeight / imageHeight) - result.scaleX = scale - result.scaleY = scale - - local scaledWidth = imageWidth * scale - local scaledHeight = imageHeight * scale - - -- Apply object-position for crop alignment - local posX, posY = ImageRenderer._parsePosition(objectPosition) - - -- Calculate which part of the scaled image to show - local cropX = (scaledWidth - boundsWidth) * posX - local cropY = (scaledHeight - boundsHeight) * posY - - -- Convert back to source coordinates - result.sx = cropX / scale - result.sy = cropY / scale - result.sw = boundsWidth / scale - result.sh = boundsHeight / scale - - result.dx = 0 - result.dy = 0 - result.dw = boundsWidth - result.dh = boundsHeight - elseif fitMode == "none" then - -- Use natural size (no scaling) - result.scaleX = 1 - result.scaleY = 1 - result.dw = imageWidth - result.dh = imageHeight - - -- Apply object-position - local posX, posY = ImageRenderer._parsePosition(objectPosition) - result.dx = (boundsWidth - imageWidth) * posX - result.dy = (boundsHeight - imageHeight) * posY - elseif fitMode == "scale-down" then - -- Use none or contain, whichever is smaller - if imageWidth <= boundsWidth and imageHeight <= boundsHeight then - -- Image fits naturally, use "none" - return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "none", objectPosition) - else - -- Image too large, use "contain" - return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "contain", objectPosition) - end - else - ErrorHandler:warn("ImageRenderer", "VAL_007", { - fitMode = fitMode, - fallback = "fill", - }) - -- Use 'fill' as fallback - return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "fill", objectPosition) - end - - return result -end - ---- Parse object-position string into normalized coordinates (0-1) ---- Supports keywords (center, top, bottom, left, right) and percentages ----@param position string -- Position string like "center center", "top left", "50% 50%" ----@return number, number -- Normalized X and Y positions (0-1) -function ImageRenderer._parsePosition(position) - if not position or type(position) ~= "string" then - return 0.5, 0.5 -- Default to center - end - - -- Split into X and Y components - local parts = {} - for part in position:gmatch("%S+") do - table.insert(parts, part:lower()) - end - - -- If only one value, use it for both axes (with special handling) - if #parts == 1 then - local val = parts[1] - if val == "left" or val == "right" then - parts = { val, "center" } - elseif val == "top" or val == "bottom" then - parts = { "center", val } - else - parts = { val, val } - end - elseif #parts == 0 then - return 0.5, 0.5 -- Default to center - end - - local function parseValue(val) - -- Handle keywords - if val == "center" then - return 0.5 - elseif val == "left" or val == "top" then - return 0 - elseif val == "right" or val == "bottom" then - return 1 - end - - -- Handle percentages - local percent = val:match("^([%d%.]+)%%$") - if percent then - return tonumber(percent) / 100 - end - - -- Handle plain numbers (treat as percentage) - local num = tonumber(val) - if num then - return num / 100 - end - - -- Invalid value, default to center - return 0.5 - end - - local x = parseValue(parts[1]) - local y = parseValue(parts[2] or parts[1]) - - -- Clamp to 0-1 range - x = math.max(0, math.min(1, x)) - y = math.max(0, math.min(1, y)) - - return x, y -end - ---- Draw an image with specified object-fit mode ----@param image love.Image -- Image to draw ----@param x number -- X position of bounds ----@param y number -- Y position of bounds ----@param width number -- Width of bounds ----@param height number -- Height of bounds ----@param fitMode string? -- Object-fit mode (default: "fill") ----@param objectPosition string? -- Object-position (default: "center center") ----@param opacity number? -- Opacity 0-1 (default: 1) ----@param tintColor Color? -- Color to tint the image (default: white/no tint) -function ImageRenderer.draw(image, x, y, width, height, fitMode, objectPosition, opacity, tintColor) - if not image then - return -- Nothing to draw - end - - opacity = opacity or 1 - fitMode = fitMode or "fill" - objectPosition = objectPosition or "center center" - - local imgWidth, imgHeight = image:getDimensions() - local params = ImageRenderer.calculateFit(imgWidth, imgHeight, width, height, fitMode, objectPosition) - - -- Save current color - local r, g, b, a = love.graphics.getColor() - - -- Apply opacity and tint - if tintColor then - love.graphics.setColor(tintColor.r, tintColor.g, tintColor.b, tintColor.a * opacity) - else - love.graphics.setColor(1, 1, 1, opacity) - end - - -- Draw image - if params.sx ~= 0 or params.sy ~= 0 or params.sw ~= imgWidth or params.sh ~= imgHeight then - -- Need to use a quad for cropping - local quad = love.graphics.newQuad(params.sx, params.sy, params.sw, params.sh, imgWidth, imgHeight) - love.graphics.draw(image, quad, x + params.dx, y + params.dy, 0, params.dw / params.sw, params.dh / params.sh) - else - -- Simple draw with scaling - love.graphics.draw(image, x + params.dx, y + params.dy, 0, params.scaleX, params.scaleY) - end - - -- Restore color - love.graphics.setColor(r, g, b, a) -end - ---- Draw an image with tiling/repeat mode ----@param image love.Image -- Image to draw ----@param x number -- X position of bounds ----@param y number -- Y position of bounds ----@param width number -- Width of bounds ----@param height number -- Height of bounds ----@param repeatMode string? -- Repeat mode: "repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round" (default: "no-repeat") ----@param opacity number? -- Opacity 0-1 (default: 1) ----@param tintColor Color? -- Color to tint the image (default: white/no tint) -function ImageRenderer.drawTiled(image, x, y, width, height, repeatMode, opacity, tintColor) - if not image then - return -- Nothing to draw - end - - opacity = opacity or 1 - repeatMode = repeatMode or "no-repeat" - - local imgWidth, imgHeight = image:getDimensions() - - -- Save current color - local r, g, b, a = love.graphics.getColor() - - -- Apply opacity and tint - if tintColor then - love.graphics.setColor(tintColor.r, tintColor.g, tintColor.b, tintColor.a * opacity) - else - love.graphics.setColor(1, 1, 1, opacity) - end - - if repeatMode == "no-repeat" then - -- Just draw once, no tiling - love.graphics.draw(image, x, y) - elseif repeatMode == "repeat" then - -- Tile in both directions - local tilesX = math.ceil(width / imgWidth) - local tilesY = math.ceil(height / imgHeight) - - for tileY = 0, tilesY - 1 do - for tileX = 0, tilesX - 1 do - local drawX = x + (tileX * imgWidth) - local drawY = y + (tileY * imgHeight) - - -- Calculate how much of the tile to draw (for partial tiles at edges) - local drawWidth = math.min(imgWidth, width - (tileX * imgWidth)) - local drawHeight = math.min(imgHeight, height - (tileY * imgHeight)) - - if drawWidth < imgWidth or drawHeight < imgHeight then - -- Use quad for partial tile - local quad = love.graphics.newQuad(0, 0, drawWidth, drawHeight, imgWidth, imgHeight) - love.graphics.draw(image, quad, drawX, drawY) - else - -- Draw full tile - love.graphics.draw(image, drawX, drawY) - end - end - end - elseif repeatMode == "repeat-x" then - -- Tile horizontally only - local tilesX = math.ceil(width / imgWidth) - - for tileX = 0, tilesX - 1 do - local drawX = x + (tileX * imgWidth) - local drawWidth = math.min(imgWidth, width - (tileX * imgWidth)) - - if drawWidth < imgWidth then - -- Use quad for partial tile - local quad = love.graphics.newQuad(0, 0, drawWidth, imgHeight, imgWidth, imgHeight) - love.graphics.draw(image, quad, drawX, y) - else - -- Draw full tile - love.graphics.draw(image, drawX, y) - end - end - elseif repeatMode == "repeat-y" then - -- Tile vertically only - local tilesY = math.ceil(height / imgHeight) - - for tileY = 0, tilesY - 1 do - local drawY = y + (tileY * imgHeight) - local drawHeight = math.min(imgHeight, height - (tileY * imgHeight)) - - if drawHeight < imgHeight then - -- Use quad for partial tile - local quad = love.graphics.newQuad(0, 0, imgWidth, drawHeight, imgWidth, imgHeight) - love.graphics.draw(image, quad, x, drawY) - else - -- Draw full tile - love.graphics.draw(image, x, drawY) - end - end - elseif repeatMode == "space" then - -- Distribute tiles with even spacing - local tilesX = math.floor(width / imgWidth) - local tilesY = math.floor(height / imgHeight) - - if tilesX < 1 then - tilesX = 1 - end - if tilesY < 1 then - tilesY = 1 - end - - local spaceX = tilesX > 1 and (width - (tilesX * imgWidth)) / (tilesX - 1) or 0 - local spaceY = tilesY > 1 and (height - (tilesY * imgHeight)) / (tilesY - 1) or 0 - - for tileY = 0, tilesY - 1 do - for tileX = 0, tilesX - 1 do - local drawX = x + (tileX * (imgWidth + spaceX)) - local drawY = y + (tileY * (imgHeight + spaceY)) - love.graphics.draw(image, drawX, drawY) - end - end - elseif repeatMode == "round" then - -- Scale tiles to fit bounds exactly - local tilesX = math.max(1, utils.round(width / imgWidth)) - local tilesY = math.max(1, utils.round(height / imgHeight)) - - local scaleX = width / (tilesX * imgWidth) - local scaleY = height / (tilesY * imgHeight) - - for tileY = 0, tilesY - 1 do - for tileX = 0, tilesX - 1 do - local drawX = x + (tileX * imgWidth * scaleX) - local drawY = y + (tileY * imgHeight * scaleY) - love.graphics.draw(image, drawX, drawY, 0, scaleX, scaleY) - end - end - else - ErrorHandler:warn("ImageRenderer", "VAL_007", { - repeatMode = repeatMode, - fallback = "no-repeat", - }) - love.graphics.draw(image, x, y) - end - - -- Restore color - love.graphics.setColor(r, g, b, a) -end - -return ImageRenderer diff --git a/libs/flexlove/modules/ImageScaler.lua b/libs/flexlove/modules/ImageScaler.lua deleted file mode 100644 index cf510bf3..00000000 --- a/libs/flexlove/modules/ImageScaler.lua +++ /dev/null @@ -1,174 +0,0 @@ --- ==================== --- ImageScaler --- ==================== - -local ImageScaler = {} - --- ErrorHandler will be injected via init -local ErrorHandler = nil - ---- Initialize ImageScaler with dependencies ----@param deps table Dependencies table with ErrorHandler -function ImageScaler.init(deps) - if deps and deps.ErrorHandler then - ErrorHandler = deps.ErrorHandler - end -end - ---- Scale an ImageData region using nearest-neighbor sampling ---- Produces sharp, pixelated scaling - ideal for pixel art ----@param sourceImageData love.ImageData -- Source image data ----@param srcX number -- Source region X (0-based) ----@param srcY number -- Source region Y (0-based) ----@param srcW number -- Source region width ----@param srcH number -- Source region height ----@param destW number -- Destination width ----@param destH number -- Destination height ----@return love.ImageData -- Scaled image data -function ImageScaler.scaleNearest(sourceImageData, srcX, srcY, srcW, srcH, destW, destH) - if not sourceImageData then - ErrorHandler:error("ImageScaler", "VAL_001", { - parameter = "sourceImageData", - }) - end - - if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then - ErrorHandler:warn("ImageScaler", "VAL_002", { - srcW = srcW, - srcH = srcH, - destW = destW, - destH = destH, - fallback = "1x1 transparent image", - }) - -- Return a minimal 1x1 transparent image as fallback - local fallbackImageData = love.image.newImageData(1, 1) - fallbackImageData:setPixel(0, 0, 0, 0, 0, 0) - return fallbackImageData - end - - -- Create destination ImageData - local destImageData = love.image.newImageData(destW, destH) - - -- Calculate scale ratios (cached outside loops for performance) - local scaleX = srcW / destW - local scaleY = srcH / destH - - -- Nearest-neighbor sampling - for destY = 0, destH - 1 do - for destX = 0, destW - 1 do - -- Calculate source pixel coordinates using floor (nearest-neighbor) - local srcPixelX = math.floor(destX * scaleX) + srcX - local srcPixelY = math.floor(destY * scaleY) + srcY - - -- Clamp to source bounds (safety check) - srcPixelX = math.min(srcPixelX, srcX + srcW - 1) - srcPixelY = math.min(srcPixelY, srcY + srcH - 1) - - -- Sample source pixel - local r, g, b, a = sourceImageData:getPixel(srcPixelX, srcPixelY) - - -- Write to destination - destImageData:setPixel(destX, destY, r, g, b, a) - end - end - - return destImageData -end - ---- Linear interpolation helper ---- Blends between two values based on interpolation factor ----@param a number -- Start value ----@param b number -- End value ----@param t number -- Interpolation factor [0, 1] ----@return number -- Interpolated value -local function lerp(a, b, t) - return a + (b - a) * t -end - ---- Scale an ImageData region using bilinear interpolation ---- Produces smooth, filtered scaling - ideal for high-quality upscaling ----@param sourceImageData love.ImageData -- Source image data ----@param srcX number -- Source region X (0-based) ----@param srcY number -- Source region Y (0-based) ----@param srcW number -- Source region width ----@param srcH number -- Source region height ----@param destW number -- Destination width ----@param destH number -- Destination height ----@return love.ImageData -- Scaled image data -function ImageScaler.scaleBilinear(sourceImageData, srcX, srcY, srcW, srcH, destW, destH) - if not sourceImageData then - ErrorHandler:error("ImageScaler", "VAL_001", { - parameter = "sourceImageData", - }) - end - - if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then - ErrorHandler:warn("ImageScaler", "VAL_002", { - srcW = srcW, - srcH = srcH, - destW = destW, - destH = destH, - fallback = "1x1 transparent image", - }) - -- Return a minimal 1x1 transparent image as fallback - local fallbackImageData = love.image.newImageData(1, 1) - fallbackImageData:setPixel(0, 0, 0, 0, 0, 0) - return fallbackImageData - end - - -- Create destination ImageData - local destImageData = love.image.newImageData(destW, destH) - - -- Calculate scale ratios - local scaleX = srcW / destW - local scaleY = srcH / destH - - -- Bilinear interpolation - for destY = 0, destH - 1 do - for destX = 0, destW - 1 do - -- Calculate fractional source position - local srcXf = destX * scaleX - local srcYf = destY * scaleY - - -- Get integer coordinates for 2x2 sampling grid - local x0 = math.floor(srcXf) - local y0 = math.floor(srcYf) - local x1 = math.min(x0 + 1, srcW - 1) - local y1 = math.min(y0 + 1, srcH - 1) - - -- Get fractional parts for interpolation - local fx = srcXf - x0 - local fy = srcYf - y0 - - -- Sample 4 neighboring pixels (with source offset) - local r00, g00, b00, a00 = sourceImageData:getPixel(srcX + x0, srcY + y0) - local r10, g10, b10, a10 = sourceImageData:getPixel(srcX + x1, srcY + y0) - local r01, g01, b01, a01 = sourceImageData:getPixel(srcX + x0, srcY + y1) - local r11, g11, b11, a11 = sourceImageData:getPixel(srcX + x1, srcY + y1) - - -- Interpolate horizontally (top and bottom rows) - local rTop = lerp(r00, r10, fx) - local gTop = lerp(g00, g10, fx) - local bTop = lerp(b00, b10, fx) - local aTop = lerp(a00, a10, fx) - - local rBottom = lerp(r01, r11, fx) - local gBottom = lerp(g01, g11, fx) - local bBottom = lerp(b01, b11, fx) - local aBottom = lerp(a01, a11, fx) - - -- Interpolate vertically (final result) - local r = lerp(rTop, rBottom, fy) - local g = lerp(gTop, gBottom, fy) - local b = lerp(bTop, bBottom, fy) - local a = lerp(aTop, aBottom, fy) - - -- Write to destination - destImageData:setPixel(destX, destY, r, g, b, a) - end - end - - return destImageData -end - -return ImageScaler diff --git a/libs/flexlove/modules/InputEvent.lua b/libs/flexlove/modules/InputEvent.lua deleted file mode 100644 index 8f1be533..00000000 --- a/libs/flexlove/modules/InputEvent.lua +++ /dev/null @@ -1,88 +0,0 @@ ----@class InputEvent ----@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"|"hover"|"unhover"|"touchpress"|"touchmove"|"touchrelease"|"touchcancel" ----@field button number -- Mouse button: 1 (left), 2 (right), 3 (middle) ----@field x number -- Mouse/Touch X position ----@field y number -- Mouse/Touch Y position ----@field dx number? -- Delta X from drag/touch start (only for drag/touch events) ----@field dy number? -- Delta Y from drag/touch start (only for drag/touch events) ----@field modifiers {shift:boolean, ctrl:boolean, alt:boolean, super:boolean} ----@field clickCount number -- Number of clicks (for double/triple click detection) ----@field timestamp number -- Time when event occurred ----@field touchId string? -- Touch identifier (for multi-touch) ----@field pressure number? -- Touch pressure (0-1, defaults to 1.0) ----@field phase string? -- Touch phase: "began", "moved", "ended", "cancelled" -local InputEvent = {} -InputEvent.__index = InputEvent - ----@class InputEventProps ----@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"|"hover"|"unhover"|"touchpress"|"touchmove"|"touchrelease"|"touchcancel" ----@field button number ----@field x number ----@field y number ----@field dx number? ----@field dy number? ----@field modifiers {shift:boolean, ctrl:boolean, alt:boolean, super:boolean} ----@field clickCount number? ----@field timestamp number? ----@field touchId string? ----@field pressure number? ----@field phase string? - ---- Create a new input event ----@param props InputEventProps ----@return InputEvent -function InputEvent.new(props) - local self = setmetatable({}, InputEvent) - self.type = props.type - self.button = props.button - self.x = props.x - self.y = props.y - self.dx = props.dx - self.dy = props.dy - self.modifiers = props.modifiers - self.clickCount = props.clickCount or 1 - self.timestamp = props.timestamp or love.timer.getTime() - - -- Touch-specific properties - self.touchId = props.touchId - self.pressure = props.pressure or 1.0 - self.phase = props.phase - - return self -end - ---- Create an InputEvent from LÖVE touch data ----@param id userdata Touch ID from LÖVE ----@param x number Touch X position ----@param y number Touch Y position ----@param phase string Touch phase: "began", "moved", "ended", "cancelled" ----@param pressure number? Touch pressure (0-1, defaults to 1.0) ----@return InputEvent -function InputEvent.fromTouch(id, x, y, phase, pressure) - local touchIdStr = tostring(id) - local eventType = "touchpress" - if phase == "moved" then - eventType = "touchmove" - elseif phase == "ended" then - eventType = "touchrelease" - elseif phase == "cancelled" then - eventType = "touchcancel" - end - - return InputEvent.new({ - type = eventType, - button = 1, -- Treat touch as left button - x = x, - y = y, - dx = 0, - dy = 0, - modifiers = { shift = false, ctrl = false, alt = false, super = false }, - clickCount = 1, - timestamp = love.timer.getTime(), - touchId = touchIdStr, - pressure = pressure or 1.0, - phase = phase, - }) -end - -return InputEvent diff --git a/libs/flexlove/modules/KeyboardNavigation.lua b/libs/flexlove/modules/KeyboardNavigation.lua deleted file mode 100644 index 7122cd40..00000000 --- a/libs/flexlove/modules/KeyboardNavigation.lua +++ /dev/null @@ -1,748 +0,0 @@ -local packageName = ... or "KeyboardNavigation" -local modulePath = packageName:match("(.-)[^%.]+$") - -local function req(name) - return require(modulePath .. name) -end - ----@class KeyboardNavigation ----@field config KeyboardNavigationConfig -local KeyboardNavigation = { - - config = { - -- Global settings - enabled = true, - debugMode = false, - - -- Key bindings - keys = { - next = "tab", - previous = "shifttab", - up = "up", - down = "down", - left = "left", - right = "right", - activate = { "return", "space" }, - dismiss = "escape", - toggleDebug = "f12", - inspect = "i", - }, - - -- Navigation behavior - wrapAround = true, - directionalNavigation = true, - focusVisible = true, - autofocusOnCreate = false, - - --- Drop focus after pressing Enter/Space to activate an element - --- When false, focus remains on the element after activation - dropFocusOnSelection = true, - - -- Developer tools - developerTools = { - enabled = true, - showProperties = true, - highlightColor = { 1, 0.8, 0, 0.5 }, - }, - - -- Focus indicator style - focusIndicator = { - color = { 0.2, 0.6, 1.0, 0.8 }, - lineWidth = 2, - inset = -3, - borderRadius = 4, - animationDuration = 0.15, - }, - }, - - -- State - _navigationStack = {}, - _lastNavigationTime = 0, - _inspectMode = false, - _deps = nil, - - -- Spatial index for directional navigation (performance optimization) - _spatialIndex = { - enabled = false, - cellSize = 100, -- Grid cell size in pixels - grid = {}, -- Grid storing element references - elementPositions = {}, -- Cache of element positions {element = {x, y, w, h}} - lastUpdateFrame = 0, - }, -} - ---- Initialize KeyboardNavigation module ----@param deps table {Context, Element, ErrorHandler, utils, InputEvent} -function KeyboardNavigation.init(deps) - -- Validate required dependencies - local required = { Context = true, Element = true, ErrorHandler = true, utils = true, InputEvent = true } - for depName, _ in pairs(required) do - if not deps[depName] then - error(string.format("KeyboardNavigation.init: Missing required dependency: %s", depName)) - end - end - - KeyboardNavigation._deps = deps - KeyboardNavigation._ErrorHandler = deps.ErrorHandler - KeyboardNavigation._InputEvent = deps.InputEvent - KeyboardNavigation._Context = deps.Context - KeyboardNavigation._Element = deps.Element - KeyboardNavigation._utils = deps.utils -end - ---- Handle keyboard press for navigation ----@param key string ----@param scancode string ----@param isrepeat boolean ----@return boolean handled -function KeyboardNavigation:handleKeyPress(key, scancode, isrepeat) - if not KeyboardNavigation._Context then - return false - end - - -- Debug logging - if KeyboardNavigation.config.debugMode then - print( - string.format( - "[KeyboardNavigation] Key pressed: %s (scancode: %s, repeat: %s)", - key, - scancode, - tostring(isrepeat) - ) - ) - print(string.format("[KeyboardNavigation] Enabled: %s", tostring(KeyboardNavigation.config.enabled))) - end - - local config = KeyboardNavigation.config - local keys = config.keys - - -- Check for activation keys - for _, activateKey in ipairs(keys.activate) do - if key == activateKey then - return self:activateElement() - end - end - - -- Check for dismiss key - if key == keys.dismiss then - return self:dismissElement() - end - - -- Check for next/previous navigation - -- Tab with shift held = previous; Tab without shift = next - if key == keys.next then - if love.keyboard.isDown("lshift") or love.keyboard.isDown("rshift") then - return self:previousFocusable() - end - return self:nextFocusable() - end - - if key == keys.previous then - return self:previousFocusable() - end - - -- Check for directional navigation - if config.directionalNavigation then - if key == keys.up then - return self:navigateDirectional("up") - elseif key == keys.down then - return self:navigateDirectional("down") - elseif key == keys.left then - return self:navigateDirectional("left") - elseif key == keys.right then - return self:navigateDirectional("right") - end - end - - return false -end - ---- Find next focusable element in the focusable list ----@param focusableList table List of focusable elements in tab order ----@param current Element? Currently focused element ----@return Element? -function KeyboardNavigation:_findNextInList(focusableList, current) - local currentIndex = 0 - if current then - for i, elem in ipairs(focusableList) do - if elem.id == current.id then - currentIndex = i - break - end - end - end - - -- Search forward - if currentIndex < #focusableList then - return focusableList[currentIndex + 1] - end - - -- Wrap around if enabled - if KeyboardNavigation.config.wrapAround and #focusableList > 0 then - return focusableList[1] - end - - return nil -end - ---- Get the focusable element list scoped to the navigation container ----@return Element[] -function KeyboardNavigation:_getScopedFocusableList() - local Context = KeyboardNavigation._Context - local container = Context.getNavigationContainer() - if container then - return container:getFocusableChildren() - end - return Context.getFocusableElements() -end - ---- Navigate to next focusable element (Tab) ----@return boolean success -function KeyboardNavigation:nextFocusable() - local Context = KeyboardNavigation._Context - - local current = Context.getFocused() - if KeyboardNavigation.config.debugMode then - print( - string.format("[KeyboardNavigation] Tab pressed - Current focus: %s", tostring(current and current.id or "nil")) - ) - end - - local focusableList = self:_getScopedFocusableList() - local nextElem = self:_findNextInList(focusableList, current) - - if nextElem then - self:_focusElement(nextElem) - return true - end - - return false -end - ---- Find previous focusable element in the focusable list ----@param focusableList table List of focusable elements in tab order ----@param current Element? Currently focused element ----@return Element? -function KeyboardNavigation:_findPreviousInList(focusableList, current) - local currentIndex = #focusableList + 1 - if current then - for i, elem in ipairs(focusableList) do - if elem.id == current.id then - currentIndex = i - break - end - end - end - - -- Search backward - if currentIndex - 1 >= 1 then - return focusableList[currentIndex - 1] - end - - -- Wrap around if enabled - if KeyboardNavigation.config.wrapAround and #focusableList > 0 then - return focusableList[#focusableList] - end - - return nil -end - ---- Navigate to previous focusable element (Shift+Tab) ----@return boolean success -function KeyboardNavigation:previousFocusable() - local Context = KeyboardNavigation._Context - - local current = Context.getFocused() - - local focusableList = self:_getScopedFocusableList() - local prevElem = self:_findPreviousInList(focusableList, current) - - if prevElem then - self:_focusElement(prevElem) - return true - end - - return false -end - ---- Navigate using arrow keys ----@param direction "up"|"down"|"left"|"right" ----@return boolean success -function KeyboardNavigation:navigateDirectional(direction) - local Context = KeyboardNavigation._Context - local current = Context.getFocused() - - if not current then - return false - end - - local nextElem = KeyboardNavigation:_findDirectionalNeighbor(current, direction) - - if nextElem then - self:_focusElement(nextElem) - return true - end - - return false -end - ---- Find closest focusable element in the given direction ----@param current Element ----@param direction "up"|"down"|"left"|"right" ----@return Element? -function KeyboardNavigation:_findDirectionalNeighbor(current, direction) - -- Try spatial index first if enabled - if KeyboardNavigation._spatialIndex.enabled then - local spatialResult = self:_findDirectionalNeighborSpatial(current, direction) - if spatialResult then - return spatialResult - end - end - - -- Collect all focusable elements visible this frame - local Context = KeyboardNavigation._Context - local focusable = {} - - local function collectFocusable(elem) - if elem:isFocusable() and elem ~= current then - table.insert(focusable, elem) - end - for _, child in ipairs(elem.children) do - collectFocusable(child) - end - end - - -- Mode-agnostic: collect from Context's focusable list - local allFocusable = Context.getFocusableElements() - for _, elem in ipairs(allFocusable) do - if elem ~= current then - table.insert(focusable, elem) - end - end - - if #focusable == 0 then - return nil - end - - local currentRect = { - x = current.x, - y = current.y, - width = current.width or 0, - height = current.height or 0, - } - - local closest = nil - local closestDistance = math.huge - - for _, elem in ipairs(focusable) do - local elemRect = { - x = elem.x, - y = elem.y, - width = elem.width or 0, - height = elem.height or 0, - } - - local distance, isInDirection = self:_calculateDirectionalDistance(currentRect, elemRect, direction) - - if isInDirection and distance < closestDistance then - closest = elem - closestDistance = distance - end - end - - -- If no element found in exact direction, try with looser criteria - if not closest then - closest = self:_findClosestInDirection(current, focusable, direction) - end - - return closest -end - ---- Calculate distance and direction between elements ----@param from table {x, y, width, height} ----@param to table {x, y, width, height} ----@param direction string ----@return number distance, boolean isInDirection -function KeyboardNavigation:_calculateDirectionalDistance(from, to, direction) - -- Calculate bounding box edges - local fromLeft = from.x - local fromRight = from.x + from.width - local fromTop = from.y - local fromBottom = from.y + from.height - - local toLeft = to.x - local toRight = to.x + to.width - local toTop = to.y - local toBottom = to.y + to.height - - local distance = math.huge - local isInDirection = false - - if direction == "up" then - if toBottom < fromTop then - isInDirection = true - distance = fromTop - toBottom - end - elseif direction == "down" then - if toTop > fromBottom then - isInDirection = true - distance = toTop - fromBottom - end - elseif direction == "left" then - if toRight < fromLeft then - isInDirection = true - distance = fromLeft - toRight - end - elseif direction == "right" then - if toLeft > fromRight then - isInDirection = true - distance = toLeft - fromRight - end - end - - return distance, isInDirection -end - ---- Find closest element in direction using center-to-center distance ----@param current Element ----@param focusable Element[] ----@param direction string ----@return Element? -function KeyboardNavigation:_findClosestInDirection(current, focusable, direction) - local currentCenterX = current.x + (current.width or 0) / 2 - local currentCenterY = current.y + (current.height or 0) / 2 - - local closest = nil - local closestDistance = math.huge - - for _, elem in ipairs(focusable) do - if elem ~= current then - local elemCenterX = elem.x + (elem.width or 0) / 2 - local elemCenterY = elem.y + (elem.height or 0) / 2 - - local dx = elemCenterX - currentCenterX - local dy = elemCenterY - currentCenterY - - -- Check if element is generally in the right direction - local isInDirection = false - - if direction == "up" and dy < 0 then - isInDirection = true - elseif direction == "down" and dy > 0 then - isInDirection = true - elseif direction == "left" and dx < 0 then - isInDirection = true - elseif direction == "right" and dx > 0 then - isInDirection = true - end - - if isInDirection then - local distance = math.sqrt(dx * dx + dy * dy) - if distance < closestDistance then - closest = elem - closestDistance = distance - end - end - end - end - - return closest -end - ---- Focus an element ----@param element Element -function KeyboardNavigation:_focusElement(element) - local Context = KeyboardNavigation._Context - - if element and element:isFocusable() then - if KeyboardNavigation.config.debugMode then - print( - string.format( - "[KeyboardNavigation] Focusing element: %s (id: %s)", - element.themeComponent or "unknown", - tostring(element.id) - ) - ) - end - Context.setFocused(element) - - -- Update focus indicator - if KeyboardNavigation.FocusIndicator then - KeyboardNavigation.FocusIndicator.setFocused(element) - end - - -- Call onFocus callback if it exists - if element.onFocus then - local success, err = pcall(function() - if element.onFocusDeferred then - table.insert(Context._deferredCallbacks or {}, function() - element:onFocus(element) - end) - else - element:onFocus(element) - end - end) - - if not success then - KeyboardNavigation._ErrorHandler:warn("KeyboardNavigation", "NAV_001", { - elementId = element.id or "unknown", - error = tostring(err), - }) - end - end - end -end - ----@param element Element ----@return boolean -function KeyboardNavigation:_shouldDropFocusOnSelection(element) - if element and element.dropFocusOnSelection ~= nil then - return element.dropFocusOnSelection == true - end - - return KeyboardNavigation.config.dropFocusOnSelection == true -end - ---- Activate currently focused element ----@return boolean success -function KeyboardNavigation:activateElement() - local Context = KeyboardNavigation._Context - local focused = Context.getFocused() - - if not focused then - return false - end - - if focused.disabled then - return false - end - - -- Fire press and release events - if focused.onEvent then - local modifiers = KeyboardNavigation._utils.getModifiers() - local pressEvent = KeyboardNavigation._InputEvent.new({ - type = "press", - button = 1, - x = focused.x, - y = focused.y, - modifiers = modifiers, - clickCount = 1, - }) - - local releaseEvent = KeyboardNavigation._InputEvent.new({ - type = "release", - button = 1, - x = focused.x, - y = focused.y, - modifiers = modifiers, - clickCount = 1, - }) - - local success, err = pcall(function() - focused.onEvent(focused, pressEvent) - focused.onEvent(focused, releaseEvent) - end) - - if not success then - KeyboardNavigation._ErrorHandler:warn("KeyboardNavigation", "NAV_002", { - elementId = focused.id or "unknown", - error = tostring(err), - }) - end - - -- Drop focus after selection based on per-element override or global config. - if KeyboardNavigation:_shouldDropFocusOnSelection(focused) then - Context.clearFocus() - if KeyboardNavigation.FocusIndicator then - KeyboardNavigation.FocusIndicator.setFocused(nil) - end - end - - return true - end - - return false -end - ---- Dismiss currently focused element ----@return boolean success -function KeyboardNavigation:dismissElement() - local Context = KeyboardNavigation._Context - local focused = Context.getFocused() - - if not focused then - return false - end - - -- Check if element has a dismiss handler - if focused.onDismiss then - local success, err = pcall(function() - if focused.onDismissDeferred then - table.insert(Context._deferredCallbacks or {}, function() - focused:onDismiss(focused) - end) - else - focused:onDismiss(focused) - end - end) - - if not success then - KeyboardNavigation._ErrorHandler:warn("KeyboardNavigation", "NAV_003", { - elementId = focused.id or "unknown", - error = tostring(err), - }) - end - - return true -- Handler took care of dismissal - end - - -- Default behavior: blur the element (only if no onDismiss handler) - Context.clearFocus() - return true -end - ---- Update keyboard navigation (for animations, etc.) ----@param dt number -function KeyboardNavigation:update(dt) - -- Update focus indicator if it exists - if KeyboardNavigation.FocusIndicator then - KeyboardNavigation.FocusIndicator:update(dt) - end -end - ---- Push current focus onto stack (for modals/dialogs) ---- Saves current focus and sets new focus to the given element ----@param element Element? The element to focus (e.g., modal dialog) -function KeyboardNavigation:pushFocus(element) - local Context = KeyboardNavigation._Context - - table.insert(KeyboardNavigation._navigationStack, Context.getFocused()) - Context.pushFocusStack(element) -end - ---- Pop focus from stack (return from modal) ---- Restores previously focused element from the stack ----@return Element? The previously focused element, or nil if stack was empty -function KeyboardNavigation:popFocus() - local Context = KeyboardNavigation._Context - - local previous = Context.popFocusStack() - if #KeyboardNavigation._navigationStack > 0 then - previous = table.remove(KeyboardNavigation._navigationStack) - end - - return previous -end - --- ==================== --- Spatial Index (Performance Optimization) --- ==================== - ---- Enable spatial index for faster directional navigation ----@param enabled boolean -function KeyboardNavigation.enableSpatialIndex(enabled) - KeyboardNavigation._spatialIndex.enabled = enabled - if not enabled then - KeyboardNavigation:_clearSpatialIndex() - end -end - ---- Clear spatial index -function KeyboardNavigation:_clearSpatialIndex() - KeyboardNavigation._spatialIndex.grid = {} - KeyboardNavigation._spatialIndex.elementPositions = {} -end - ---- Find directional neighbor using spatial index ----@param current Element ----@param direction "up"|"down"|"left"|"right" ----@return Element? -function KeyboardNavigation:_findDirectionalNeighborSpatial(current, direction) - local index = KeyboardNavigation._spatialIndex - local cellSize = index.cellSize - - -- Get current element's grid position - local currentPos = index.elementPositions[current] - if not currentPos then - return nil - end - - local centerX = currentPos.x + currentPos.w / 2 - local centerY = currentPos.y + currentPos.h / 2 - local currentCellX = math.floor(centerX / cellSize) - local currentCellY = math.floor(centerY / cellSize) - - -- Search in direction, expanding outward - local maxSearchRadius = 20 -- Maximum cells to search - local visited = {} - - for radius = 1, maxSearchRadius do - local candidates = {} - - -- Get cells in the search ring - if direction == "up" then - table.insert(candidates, { currentCellX, currentCellY - radius }) - if radius > 1 then - table.insert(candidates, { currentCellX - 1, currentCellY - radius }) - table.insert(candidates, { currentCellX + 1, currentCellY - radius }) - end - elseif direction == "down" then - table.insert(candidates, { currentCellX, currentCellY + radius }) - if radius > 1 then - table.insert(candidates, { currentCellX - 1, currentCellY + radius }) - table.insert(candidates, { currentCellX + 1, currentCellY + radius }) - end - elseif direction == "left" then - table.insert(candidates, { currentCellX - radius, currentCellY }) - if radius > 1 then - table.insert(candidates, { currentCellX - radius, currentCellY - 1 }) - table.insert(candidates, { currentCellX - radius, currentCellY + 1 }) - end - elseif direction == "right" then - table.insert(candidates, { currentCellX + radius, currentCellY }) - if radius > 1 then - table.insert(candidates, { currentCellX + radius, currentCellY - 1 }) - table.insert(candidates, { currentCellX + radius, currentCellY + 1 }) - end - end - - -- Check each candidate cell - for _, cell in ipairs(candidates) do - local cellKey = string.format("%d,%d", cell[1], cell[2]) - local cellElements = index.grid[cellKey] - - if cellElements then - for _, elem in ipairs(cellElements) do - if elem ~= current and not visited[elem] then - visited[elem] = true - local elemPos = index.elementPositions[elem] - if elemPos then - local elemCenterX = elemPos.x + elemPos.w / 2 - local elemCenterY = elemPos.y + elemPos.h / 2 - - -- Check if element is in the correct direction - local isInDirection = false - if direction == "up" and elemCenterY < centerY then - isInDirection = true - elseif direction == "down" and elemCenterY > centerY then - isInDirection = true - elseif direction == "left" and elemCenterX < centerX then - isInDirection = true - elseif direction == "right" and elemCenterX > centerX then - isInDirection = true - end - - if isInDirection then - return elem - end - end - end - end - end - end - end - - return nil -end - -return KeyboardNavigation diff --git a/libs/flexlove/modules/LayoutEngine.lua b/libs/flexlove/modules/LayoutEngine.lua deleted file mode 100644 index cf4dec4d..00000000 --- a/libs/flexlove/modules/LayoutEngine.lua +++ /dev/null @@ -1,1714 +0,0 @@ ----@class LayoutEngine ----@field element Element? Reference to the parent element ----@field positioning Positioning Layout positioning mode ----@field flexDirection FlexDirection Direction of flex layout ----@field justifyContent JustifyContent Alignment of items along main axis ----@field alignItems AlignItems Alignment of items along cross axis ----@field alignContent AlignContent Alignment of lines in multi-line flex containers ----@field flexWrap FlexWrap Whether children wrap to multiple lines ----@field gap number Space between children elements ----@field gridRows number? Number of rows in the grid ----@field gridColumns number? Number of columns in the grid ----@field columnGap number? Gap between grid columns ----@field rowGap number? Gap between grid rows ----@field _Grid table ----@field _Units table ----@field _Context table ----@field _Positioning table ----@field _FlexDirection table ----@field _JustifyContent table ----@field _AlignContent table ----@field _AlignItems table ----@field _AlignSelf table ----@field _FlexWrap table ----@field _layoutCount number Track layout recalculations per frame ----@field _lastFrameCount number Last frame number for resetting counters ----@field _ErrorHandler ErrorHandler? ErrorHandler module dependency ----@field _Performance Performance? Performance module dependency -local LayoutEngine = {} -LayoutEngine.__index = LayoutEngine - ---- Recursively shift an element and all its descendants by (dx, dy). ---- Used by the row-reverse mirror pass and the `position: relative` offset ---- pass: both run after the rest of layout has placed the subtree, so a single ---- delta walk keeps descendants visually anchored to the parent. ----@param elem Element ----@param dx number ----@param dy number -local function shiftSubtree(elem, dx, dy) - elem.x = elem.x + dx - elem.y = elem.y + dy - for _, c in ipairs(elem.children) do - shiftSubtree(c, dx, dy) - end -end - ---- Initialize module with shared dependencies ----@param deps table Dependencies {ErrorHandler, Performance, utils} -function LayoutEngine.init(deps) - LayoutEngine._ErrorHandler = deps.ErrorHandler - LayoutEngine._Performance = deps.Performance - LayoutEngine._Utils = deps.utils -end - ----@class LayoutEngineProps ----@field positioning Positioning? Layout positioning mode (default: RELATIVE) ----@field flexDirection FlexDirection? Direction of flex layout (default: HORIZONTAL) ----@field justifyContent JustifyContent? Alignment of items along main axis (default: FLEX_START) ----@field alignItems AlignItems? Alignment of items along cross axis (default: STRETCH) ----@field alignContent AlignContent? Alignment of lines in multi-line flex containers (default: STRETCH) ----@field flexWrap FlexWrap? Whether children wrap to multiple lines (default: NOWRAP) ----@field gap number? Space between children elements (default: 10) ----@field gridRows number? Number of rows in the grid ----@field gridColumns number? Number of columns in the grid ----@field columnGap number? Gap between grid columns ----@field rowGap number? Gap between grid rows - ---- Create a new LayoutEngine instance ----@param props LayoutEngineProps ----@param deps table Dependencies {utils, Grid, Units, Context} ----@return LayoutEngine -function LayoutEngine.new(props, deps) - local enums = deps.utils.enums - local Positioning = enums.Positioning - local FlexDirection = enums.FlexDirection - local JustifyContent = enums.JustifyContent - local AlignContent = enums.AlignContent - local AlignItems = enums.AlignItems - local AlignSelf = enums.AlignSelf - local FlexWrap = enums.FlexWrap - - local self = setmetatable({}, LayoutEngine) - - -- Store dependencies for instance methods - self._Grid = deps.Grid - self._Units = deps.Units - self._Context = deps.Context - self._ErrorHandler = deps.ErrorHandler - self._Positioning = Positioning - self._FlexDirection = FlexDirection - self._JustifyContent = JustifyContent - self._AlignContent = AlignContent - self._AlignItems = AlignItems - self._AlignSelf = AlignSelf - self._FlexWrap = FlexWrap - - -- Layout configuration - self.positioning = props.positioning or Positioning.FLEX - self.flexDirection = props.flexDirection or FlexDirection.HORIZONTAL - self.justifyContent = props.justifyContent or JustifyContent.FLEX_START - self.alignItems = props.alignItems or AlignItems.STRETCH - self.alignContent = props.alignContent or AlignContent.STRETCH - self.flexWrap = props.flexWrap or FlexWrap.NOWRAP - self.gap = props.gap or 10 - - -- Grid layout configuration - self.gridRows = props.gridRows - self.gridColumns = props.gridColumns - - self.columnGap = props.columnGap - self.rowGap = props.rowGap - - -- Element reference (will be set via initialize) - self.element = nil - - -- Performance tracking - self._layoutCount = 0 - self._lastFrameCount = 0 - - -- Layout memoization cache - self._layoutCache = { - childrenCount = 0, - containerWidth = 0, - containerHeight = 0, - containerX = 0, - containerY = 0, - childrenHash = "", - } - - return self -end - ---- Initialize the LayoutEngine with its parent element ----@param element Element The parent element -function LayoutEngine:initialize(element) - self.element = element -end - ---- True for flex-direction `horizontal` or `horizontal-reverse` (and their ---- `row`/`row-reverse` aliases, which normalize to those at construction). ---- Routes every main-axis orientation check so reverse directions are ---- correctly classified as horizontal. ----@return boolean -function LayoutEngine:_isHorizontal() - return self.flexDirection == self._FlexDirection.HORIZONTAL - or self.flexDirection == self._FlexDirection.HORIZONTAL_REVERSE -end - ---- True for flex-direction `horizontal-reverse` or `vertical-reverse` ---- (and their `row-reverse`/`column-reverse` aliases). ----@return boolean -function LayoutEngine:_isReverse() - return self.flexDirection == self._FlexDirection.HORIZONTAL_REVERSE - or self.flexDirection == self._FlexDirection.VERTICAL_REVERSE -end - ---- Apply CSS positioning offsets (top, right, bottom, left) to a child element ----@param child Element The element to apply offsets to -function LayoutEngine:applyPositioningOffsets(child) - if not child then - return - end - - -- For CSS-style positioning, we need the parent's bounds - local parent = child.parent - if not parent then - return - end - - -- Only apply offsets to explicitly absolute children or children in relative/absolute containers - -- Flex/grid children ignore positioning offsets as they participate in layout - local isFlexChild = child.positioning == self._Positioning.FLEX - or child.positioning == self._Positioning.GRID - or (child.positioning == self._Positioning.ABSOLUTE and not child._explicitlyAbsolute) - - if not isFlexChild and child._explicitlyAbsolute then - -- Apply absolute positioning for explicitly absolute children - -- Apply top offset (distance from parent's content box top edge) - if child.top then - child.y = parent.y + parent.padding.top + child.top - end - - -- Apply bottom offset (distance from parent's content box bottom edge) - -- BORDER-BOX MODEL: Use border-box dimensions for positioning - if child.bottom then - local elementBorderBoxHeight = child:getBorderBoxHeight() - child.y = parent.y + parent.padding.top + parent.height - child.bottom - elementBorderBoxHeight - end - - -- Apply left offset (distance from parent's content box left edge) - if child.left then - child.x = parent.x + parent.padding.left + child.left - end - - -- Apply right offset (distance from parent's content box right edge) - -- BORDER-BOX MODEL: Use border-box dimensions for positioning - if child.right then - local elementBorderBoxWidth = child:getBorderBoxWidth() - child.x = parent.x + parent.padding.left + parent.width - child.right - elementBorderBoxWidth - end - end -end - ---- Calculate flex item sizes based on flexGrow, flexShrink, flexBasis ---- Implements CSS flexbox sizing algorithm ----@param children table Array of child elements in the flex line ----@param availableMainSize number Available space in main axis ----@param gap number Gap between items ----@param isHorizontal boolean Whether main axis is horizontal ----@param defaultFlexShrink number? Default flex-shrink to use when child.flexShrink is nil ----@return table mainSizes Array of calculated main sizes for each child -function LayoutEngine:_calculateFlexSizes(children, availableMainSize, gap, isHorizontal, defaultFlexShrink) - local implicitFlexShrink = defaultFlexShrink - if implicitFlexShrink == nil then - implicitFlexShrink = 1 - end - - local function getResolvedFlexShrink(child) - if child._hasExplicitFlexShrink then - return child.flexShrink - end - return implicitFlexShrink - end - - local childCount = #children - local totalGaps = math.max(0, childCount - 1) * gap - local availableForContent = availableMainSize - totalGaps - local viewportWidth, viewportHeight = self._Units.getViewport() - - -- Step 1: Calculate hypothetical main sizes (flex basis resolution) - local hypotheticalSizes = {} - local flexBases = {} - local totalFlexBasis = 0 - - local function resolveDeclaredMainSize(child) - local axisUnits = nil - if child.units then - axisUnits = isHorizontal and child.units.width or child.units.height - end - - if not axisUnits or axisUnits.unit == "auto" or axisUnits.value == nil then - return nil - end - - local resolved = - self._Units.resolve(axisUnits.value, axisUnits.unit, viewportWidth, viewportHeight, availableMainSize) - - if type(resolved) == "number" then - return math.max(0, resolved) - end - - return nil - end - - for i, child in ipairs(children) do - local flexBasis = child.flexBasis - local hypotheticalSize - - -- Resolve flex-basis - if flexBasis == "auto" then - -- Use declared main size to avoid reusing a previously flexed runtime size - hypotheticalSize = resolveDeclaredMainSize(child) - if hypotheticalSize == nil then - if isHorizontal then - hypotheticalSize = child:getBorderBoxWidth() - else - hypotheticalSize = child:getBorderBoxHeight() - end - end - elseif type(flexBasis) == "number" then - hypotheticalSize = flexBasis - elseif type(flexBasis) == "string" and child.units.flexBasis then - -- Parse and resolve flex-basis with units - local value, unit = child.units.flexBasis.value, child.units.flexBasis.unit - hypotheticalSize = self._Units.resolve(value, unit, viewportWidth, viewportHeight, availableMainSize) - else - -- Fallback to element's natural size - if isHorizontal then - hypotheticalSize = child:getBorderBoxWidth() - else - hypotheticalSize = child:getBorderBoxHeight() - end - end - - -- Add margins to hypothetical size - local childMargin = child.margin - if isHorizontal then - hypotheticalSize = hypotheticalSize + childMargin.left + childMargin.right - else - hypotheticalSize = hypotheticalSize + childMargin.top + childMargin.bottom - end - - flexBases[i] = hypotheticalSize - hypotheticalSizes[i] = hypotheticalSize - totalFlexBasis = totalFlexBasis + hypotheticalSize - end - - -- Step 2: Determine if we need to grow or shrink - local freeSpace = availableForContent - totalFlexBasis - - -- Step 3a: Handle positive free space (GROW) - if freeSpace > 0 then - local totalFlexGrow = 0 - for _, child in ipairs(children) do - totalFlexGrow = totalFlexGrow + (child.flexGrow or 0) - end - - if totalFlexGrow > 0 then - -- Distribute free space proportionally to flex-grow values - for i, child in ipairs(children) do - local flexGrow = child.flexGrow or 0 - if flexGrow > 0 then - local growAmount = (flexGrow / totalFlexGrow) * freeSpace - hypotheticalSizes[i] = hypotheticalSizes[i] + growAmount - end - end - end - -- Step 3b: Handle negative free space (SHRINK) - elseif freeSpace < 0 then - local totalFlexShrink = 0 - local totalScaledShrinkFactor = 0 - - for i, child in ipairs(children) do - local flexShrink = getResolvedFlexShrink(child) - totalFlexShrink = totalFlexShrink + flexShrink - -- Scaled shrink factor = flex-shrink × flex-basis - totalScaledShrinkFactor = totalScaledShrinkFactor + (flexShrink * flexBases[i]) - end - - if totalScaledShrinkFactor > 0 then - -- Distribute shrinkage proportionally to (flex-shrink × flex-basis) - for i, child in ipairs(children) do - local flexShrink = getResolvedFlexShrink(child) - if flexShrink > 0 then - local scaledShrinkFactor = flexShrink * flexBases[i] - local shrinkAmount = (scaledShrinkFactor / totalScaledShrinkFactor) * math.abs(freeSpace) - hypotheticalSizes[i] = math.max(0, hypotheticalSizes[i] - shrinkAmount) - end - end - end - end - - -- Step 4: Return final main sizes (excluding margins), clamped to per-child min/max - local mainSizes = {} - for i, child in ipairs(children) do - local childMargin = child.margin - local marginSum = isHorizontal and (childMargin.left + childMargin.right) or (childMargin.top + childMargin.bottom) - local minBound = isHorizontal and child.minWidth or child.minHeight - local maxBound = isHorizontal and child.maxWidth or child.maxHeight - mainSizes[i] = LayoutEngine._Utils.clamp(math.max(0, hypotheticalSizes[i] - marginSum), minBound, maxBound) - end - - return mainSizes -end - ---- Layout children within this element according to positioning mode -function LayoutEngine:layoutChildren() - -- Start performance timing first (before any early returns) - local timerName = nil - if LayoutEngine._Performance and LayoutEngine._Performance.enabled and self.element then - -- Use memory address to make timer name unique per element instance - timerName = "layout_" .. (self.element.id or tostring(self.element):match("0x%x+") or "unknown") - LayoutEngine._Performance:startTimer(timerName) - end - - if self.element == nil then - return - end - - -- Check if layout can be skipped (memoization optimization) - if self:_canSkipLayout() then - if timerName and LayoutEngine._Performance then - LayoutEngine._Performance:stopTimer(timerName) - end - return - end - - -- Track layout recalculations for performance warnings - self:_trackLayoutRecalculation() - - -- Handle grid layout - if self.positioning == self._Positioning.GRID then - self._Grid.layoutGridItems(self.element) - - -- Stop performance timing - if timerName and LayoutEngine._Performance then - LayoutEngine._Performance:stopTimer(timerName) - end - return - end - - local childCount = #self.element.children - - if childCount == 0 then - -- Stop performance timing - if timerName and LayoutEngine._Performance then - LayoutEngine._Performance:stopTimer(timerName) - end - return - end - - -- Get flex children (children that participate in flex layout) - -- Exclude display=false (CSS display:none) and explicitly absolute children - local flexChildren = {} - for _, child in ipairs(self.element.children) do - local isFlexChild = not (child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute) - and child.display ~= false - if isFlexChild then - table.insert(flexChildren, child) - - -- Warn if child uses percentage sizing but parent has autosizing - if child.units and child.units.width then - if child.units.width.unit == "%" and self.element.autosizing and self.element.autosizing.width then - self.element:_warnIfPercentageWithAutoSizing(child, "width") - end - end - if child.units and child.units.height then - if child.units.height.unit == "%" and self.element.autosizing and self.element.autosizing.height then - self.element:_warnIfPercentageWithAutoSizing(child, "height") - end - end - end - end - - -- CSS-compliant behavior: absolutely positioned elements are completely removed from normal flow - -- They do NOT reserve space or affect flex layout calculations at all - - -- If no flex children, skip flex layout but still position absolute children - if #flexChildren == 0 then - -- Position absolutely positioned children even when there are no flex children - for i, child in ipairs(self.element.children) do - if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute and child.display ~= false then - self:applyPositioningOffsets(child) - - -- If child has children, layout them after position change - if #child.children > 0 then - child:layoutChildren() - end - end - end - - -- Detect overflow after children positioning - if self.element._detectOverflow then - self.element:_detectOverflow() - end - - -- Stop performance timing - if timerName and LayoutEngine._Performance then - LayoutEngine._Performance:stopTimer(timerName) - end - return - end - - -- Calculate available space (accounting for padding only, NOT absolute children) - -- BORDER-BOX MODEL: element.width and element.height are already content dimensions (padding subtracted) - local availableMainSize = 0 - local availableCrossSize = 0 - - -- Reserve space for scrollbars if needed (reserve-space mode) - local scrollbarReservedWidth = 0 - local scrollbarReservedHeight = 0 - if self.element._scrollManager and self.element._scrollManager.scrollbarPlacement == "reserve-space" then - scrollbarReservedWidth, scrollbarReservedHeight = self.element._scrollManager:getReservedSpace(self.element) - end - - if self:_isHorizontal() then - availableMainSize = self.element.width - scrollbarReservedWidth - availableCrossSize = self.element.height - scrollbarReservedHeight - else - availableMainSize = self.element.height - scrollbarReservedHeight - availableCrossSize = self.element.width - scrollbarReservedWidth - end - - -- Keep percentage-sized children in sync when container dimensions change. - -- Managed select frames rely on this so `width = "100%"` options expand with the dropdown. - if scrollbarReservedWidth > 0 or scrollbarReservedHeight > 0 or self.element:_shouldSyncPercentageDimensions() then - local isHorizontal = self:_isHorizontal() - for _, child in ipairs(flexChildren) do - if isHorizontal then - -- Horizontal flex: main-axis is width, cross-axis is height - -- Adjust main-axis width if percentage-based - if child.units and child.units.width and child.units.width.unit == "%" then - local newBorderBoxWidth = LayoutEngine._Utils.clamp( - (child.units.width.value / 100) * availableMainSize, - child.minWidth, - child.maxWidth - ) - child._borderBoxWidth = newBorderBoxWidth - child.width = math.max(0, newBorderBoxWidth - child.padding.left - child.padding.right) - end - -- Adjust cross-axis height if percentage-based - if child.units and child.units.height and child.units.height.unit == "%" then - local newBorderBoxHeight = LayoutEngine._Utils.clamp( - (child.units.height.value / 100) * availableCrossSize, - child.minHeight, - child.maxHeight - ) - child._borderBoxHeight = newBorderBoxHeight - child.height = math.max(0, newBorderBoxHeight - child.padding.top - child.padding.bottom) - end - else - -- Vertical flex: main-axis is height, cross-axis is width - -- Adjust main-axis height if percentage-based - if child.units and child.units.height and child.units.height.unit == "%" then - local newBorderBoxHeight = LayoutEngine._Utils.clamp( - (child.units.height.value / 100) * availableMainSize, - child.minHeight, - child.maxHeight - ) - child._borderBoxHeight = newBorderBoxHeight - child.height = math.max(0, newBorderBoxHeight - child.padding.top - child.padding.bottom) - end - -- Adjust cross-axis width if percentage-based - if child.units and child.units.width and child.units.width.unit == "%" then - local rawBorderBoxWidth = (child.units.width.value / 100) * availableCrossSize - local newBorderBoxWidth = LayoutEngine._Utils.clamp( - self.element:_adjustCrossAxisPercentageWidth(child, rawBorderBoxWidth), - child.minWidth, - child.maxWidth - ) - child._borderBoxWidth = newBorderBoxWidth - child.width = math.max(0, newBorderBoxWidth - child.padding.left - child.padding.right) - end - end - end - end - - -- Handle flex wrap: create lines of children - local lines = {} - - if self.flexWrap == self._FlexWrap.NOWRAP then - -- All children go on one line - lines[1] = flexChildren - else - -- Wrap children into multiple lines - local currentLine = {} - local currentLineSize = 0 - - -- Performance optimization: hoist enum comparisons outside loop - local isHorizontal = self:_isHorizontal() - local gapSize = self.gap - local viewportWidth, viewportHeight = self._Units.getViewport() - - local function resolveDeclaredMainSizeForWrap(child) - local axisUnits = nil - if child.units then - axisUnits = isHorizontal and child.units.width or child.units.height - end - - if not axisUnits or axisUnits.unit == "auto" or axisUnits.value == nil then - return nil - end - - local resolved = - self._Units.resolve(axisUnits.value, axisUnits.unit, viewportWidth, viewportHeight, availableMainSize) - - if type(resolved) == "number" then - return math.max(0, resolved) - end - - return nil - end - - for _, child in ipairs(flexChildren) do - -- BORDER-BOX MODEL: Use border-box dimensions for layout calculations - -- Include margins in size calculations - -- Performance optimization: hoist margin table access - local childMargin = child.margin - local childMainSize = 0 - local childMainMargin = 0 - local declaredMainSize = resolveDeclaredMainSizeForWrap(child) - if isHorizontal then - childMainSize = declaredMainSize or child:getBorderBoxWidth() - childMainMargin = childMargin.left + childMargin.right - else - childMainSize = declaredMainSize or child:getBorderBoxHeight() - childMainMargin = childMargin.top + childMargin.bottom - end - local childTotalMainSize = childMainSize + childMainMargin - - -- Check if adding this child would exceed the available space - local lineSpacing = #currentLine > 0 and gapSize or 0 - if #currentLine > 0 and currentLineSize + lineSpacing + childTotalMainSize > availableMainSize then - -- Start a new line - if #currentLine > 0 then - table.insert(lines, currentLine) - end - currentLine = { child } - currentLineSize = childTotalMainSize - else - -- Add to current line - table.insert(currentLine, child) - currentLineSize = currentLineSize + lineSpacing + childTotalMainSize - end - end - - -- Add the last line if it has children - if #currentLine > 0 then - table.insert(lines, currentLine) - end - - -- Handle wrap-reverse: reverse the order of lines - if self.flexWrap == self._FlexWrap.WRAP_REVERSE then - local reversedLines = {} - for i = #lines, 1, -1 do - table.insert(reversedLines, lines[i]) - end - lines = reversedLines - end - end - - -- Apply flex sizing to each line BEFORE calculating line heights - -- Performance optimization: hoist enum comparison outside loop - local isHorizontal = self:_isHorizontal() - local mainAxisOverflow = nil - if self:_isHorizontal() then - mainAxisOverflow = self.element.overflowX or self.element.overflow - else - mainAxisOverflow = self.element.overflowY or self.element.overflow - end - local preserveMainAxisOverflow = (mainAxisOverflow == "scroll" or mainAxisOverflow == "auto") - local defaultFlexShrink = preserveMainAxisOverflow and 0 or 1 - - for lineIndex, line in ipairs(lines) do - -- Check if any child in this line needs flex sizing. - -- For scroll/auto in the main axis, keep implicit shrink at 0 so overflow can scroll. - local needsFlexSizing = false - for _, child in ipairs(line) do - local flexGrow = child.flexGrow or 0 - local flexBasis = child.flexBasis - local resolvedFlexShrink = defaultFlexShrink - if child._hasExplicitFlexShrink then - resolvedFlexShrink = child.flexShrink - end - - if flexGrow > 0 or (flexBasis and flexBasis ~= "auto") or resolvedFlexShrink > 0 then - needsFlexSizing = true - break - end - end - - -- Only apply flex sizing if needed - if needsFlexSizing then - -- Calculate flex sizes for this line - local mainSizes = self:_calculateFlexSizes(line, availableMainSize, self.gap, isHorizontal, defaultFlexShrink) - - -- Apply calculated sizes to children - for i, child in ipairs(line) do - local mainSize = mainSizes[i] - - if isHorizontal then - -- Update width for horizontal flex - child._borderBoxWidth = mainSize - child.width = math.max(0, mainSize - child.padding.left - child.padding.right) - -- Invalidate width cache - child._borderBoxWidthCache = nil - else - -- Update height for vertical flex - child._borderBoxHeight = mainSize - child.height = math.max(0, mainSize - child.padding.top - child.padding.bottom) - -- Invalidate height cache - child._borderBoxHeightCache = nil - end - - -- Trigger layout for child's children if any - if #child.children > 0 then - child:layoutChildren() - end - end - end - end - - -- Calculate line positions and heights (including child padding) - -- Performance optimization: preallocate array if possible - local lineHeights = table.create and table.create(#lines) or {} - local totalLinesHeight = 0 - - -- Performance optimization: hoist enum comparison outside loop (already hoisted above) - -- local isHorizontal = self.flexDirection == self._FlexDirection.HORIZONTAL - - for lineIndex, line in ipairs(lines) do - local maxCrossSize = 0 - for _, child in ipairs(line) do - -- BORDER-BOX MODEL: Use border-box dimensions for layout calculations - -- Include margins in cross-axis size calculations - -- Performance optimization: hoist margin table access - local childMargin = child.margin - local childCrossSize = 0 - local childCrossMargin = 0 - if isHorizontal then - childCrossSize = child:getBorderBoxHeight() - childCrossMargin = childMargin.top + childMargin.bottom - else - childCrossSize = child:getBorderBoxWidth() - childCrossMargin = childMargin.left + childMargin.right - end - local childTotalCrossSize = childCrossSize + childCrossMargin - maxCrossSize = math.max(maxCrossSize, childTotalCrossSize) - end - lineHeights[lineIndex] = maxCrossSize - totalLinesHeight = totalLinesHeight + maxCrossSize - end - - -- Account for gaps between lines - local lineGaps = math.max(0, #lines - 1) * self.gap - totalLinesHeight = totalLinesHeight + lineGaps - - -- For single line layouts, CENTER, FLEX_END and STRETCH should use full cross size - if #lines == 1 then - if - self.alignItems == self._AlignItems.STRETCH - or self.alignItems == self._AlignItems.CENTER - or self.alignItems == self._AlignItems.FLEX_END - then - -- STRETCH, CENTER, and FLEX_END should use full available cross size - lineHeights[1] = availableCrossSize - totalLinesHeight = availableCrossSize - end - -- CENTER and FLEX_END should preserve natural child dimensions - -- and only affect positioning within the available space - end - - -- Calculate starting position for lines based on alignContent - local lineStartPos = 0 - local lineSpacing = self.gap - local freeLineSpace = availableCrossSize - totalLinesHeight - - -- Apply AlignContent logic for both single and multiple lines - if self.alignContent == self._AlignContent.FLEX_START then - lineStartPos = 0 - elseif self.alignContent == self._AlignContent.CENTER then - lineStartPos = freeLineSpace / 2 - elseif self.alignContent == self._AlignContent.FLEX_END then - lineStartPos = freeLineSpace - elseif self.alignContent == self._AlignContent.SPACE_BETWEEN then - lineStartPos = 0 - if #lines > 1 then - lineSpacing = self.gap + (freeLineSpace / (#lines - 1)) - end - elseif self.alignContent == self._AlignContent.SPACE_AROUND then - local spaceAroundEach = freeLineSpace / #lines - lineStartPos = spaceAroundEach / 2 - lineSpacing = self.gap + spaceAroundEach - elseif self.alignContent == self._AlignContent.STRETCH then - lineStartPos = 0 - if #lines > 1 and freeLineSpace > 0 then - lineSpacing = self.gap + (freeLineSpace / #lines) - -- Distribute extra space to line heights (only if positive) - local extraPerLine = freeLineSpace / #lines - for i = 1, #lineHeights do - lineHeights[i] = lineHeights[i] + extraPerLine - end - end - end - - -- Position children within each line - local currentCrossPos = lineStartPos - - for lineIndex, line in ipairs(lines) do - local lineHeight = lineHeights[lineIndex] - - -- Calculate total size of children in this line (including padding and margins) - -- BORDER-BOX MODEL: Use border-box dimensions for layout calculations - -- Performance optimization: hoist flexDirection check outside loop - local isHorizontal = self:_isHorizontal() - local totalChildrenSize = 0 - for _, child in ipairs(line) do - local childMargin = child.margin - if isHorizontal then - totalChildrenSize = totalChildrenSize + child:getBorderBoxWidth() + childMargin.left + childMargin.right - else - totalChildrenSize = totalChildrenSize + child:getBorderBoxHeight() + childMargin.top + childMargin.bottom - end - end - - local totalGapSize = math.max(0, #line - 1) * self.gap - local totalContentSize = totalChildrenSize + totalGapSize - local freeSpace = availableMainSize - totalContentSize - - -- Calculate initial position and spacing based on justifyContent - local startPos = 0 - local itemSpacing = self.gap - - if self.justifyContent == self._JustifyContent.FLEX_START then - startPos = 0 - elseif self.justifyContent == self._JustifyContent.CENTER then - startPos = math.max(0, freeSpace / 2) - elseif self.justifyContent == self._JustifyContent.FLEX_END then - startPos = math.max(0, freeSpace) - elseif self.justifyContent == self._JustifyContent.SPACE_BETWEEN then - startPos = 0 - if #line > 1 and freeSpace > 0 then - itemSpacing = self.gap + (freeSpace / (#line - 1)) - end - elseif self.justifyContent == self._JustifyContent.SPACE_AROUND then - if freeSpace > 0 then - local spaceAroundEach = freeSpace / #line - startPos = spaceAroundEach / 2 - itemSpacing = self.gap + spaceAroundEach - end - elseif self.justifyContent == self._JustifyContent.SPACE_EVENLY then - if freeSpace > 0 then - local spaceBetween = freeSpace / (#line + 1) - startPos = spaceBetween - itemSpacing = self.gap + spaceBetween - end - end - - -- Position children in this line - local currentMainPos = startPos - - -- Performance optimization: hoist frequently accessed element properties - local elementX = self.element.x - local elementY = self.element.y - local elementPadding = self.element.padding - local elementPaddingLeft = elementPadding.left - local elementPaddingTop = elementPadding.top - local alignItems = self.alignItems - local alignSelf_AUTO = self._AlignSelf.AUTO - local alignItems_FLEX_START = self._AlignItems.FLEX_START - local alignItems_CENTER = self._AlignItems.CENTER - local alignItems_FLEX_END = self._AlignItems.FLEX_END - local alignItems_STRETCH = self._AlignItems.STRETCH - - for _, child in ipairs(line) do - -- Performance optimization: hoist child table accesses - local childMargin = child.margin - local childPadding = child.padding - local childAutosizing = child.autosizing - - -- Determine effective cross-axis alignment - local effectiveAlign = child.alignSelf - if effectiveAlign == nil or effectiveAlign == alignSelf_AUTO then - effectiveAlign = alignItems - end - - if self:_isHorizontal() then - -- Horizontal layout: main axis is X, cross axis is Y - -- Position child at border box (x, y represents top-left including padding) - -- CSS-compliant: absolute children don't affect flex positioning, so no reserved space offset - local childMarginLeft = childMargin.left - child.x = elementX + elementPaddingLeft + currentMainPos + childMarginLeft - - -- BORDER-BOX MODEL: Use border-box dimensions for alignment calculations - local childBorderBoxHeight = child:getBorderBoxHeight() - local childMarginTop = childMargin.top - local childMarginBottom = childMargin.bottom - local childTotalCrossSize = childBorderBoxHeight + childMarginTop + childMarginBottom - - if effectiveAlign == alignItems_FLEX_START then - child.y = elementY + elementPaddingTop + currentCrossPos + childMarginTop - elseif effectiveAlign == alignItems_CENTER then - child.y = elementY - + elementPaddingTop - + currentCrossPos - + ((lineHeight - childTotalCrossSize) / 2) - + childMarginTop - elseif effectiveAlign == alignItems_FLEX_END then - child.y = elementY + elementPaddingTop + currentCrossPos + lineHeight - childTotalCrossSize + childMarginTop - elseif effectiveAlign == alignItems_STRETCH then - -- STRETCH: Only apply if height was not explicitly set - if childAutosizing and childAutosizing.height then - -- STRETCH: Set border-box height to lineHeight minus margins, content area shrinks to fit - local availableHeight = LayoutEngine._Utils.clamp( - lineHeight - childMarginTop - childMarginBottom, - child.minHeight, - child.maxHeight - ) - child._borderBoxHeight = availableHeight - child.height = math.max(0, availableHeight - childPadding.top - childPadding.bottom) - end - child.y = elementY + elementPaddingTop + currentCrossPos + childMarginTop - end - - -- Apply positioning offsets (top, right, bottom, left) - self:applyPositioningOffsets(child) - - -- If child has children, re-layout them after position change - if #child.children > 0 then - child:layoutChildren() - end - - -- Advance position by child's border-box width plus margins - currentMainPos = currentMainPos + child:getBorderBoxWidth() + childMarginLeft + childMargin.right + itemSpacing - else - -- Vertical layout: main axis is Y, cross axis is X - -- Position child at border box (x, y represents top-left including padding) - -- CSS-compliant: absolute children don't affect flex positioning, so no reserved space offset - local childMarginTop = childMargin.top - child.y = elementY + elementPaddingTop + currentMainPos + childMarginTop - - -- BORDER-BOX MODEL: Use border-box dimensions for alignment calculations - local childBorderBoxWidth = child:getBorderBoxWidth() - local childMarginLeft = childMargin.left - local childMarginRight = childMargin.right - local childTotalCrossSize = childBorderBoxWidth + childMarginLeft + childMarginRight - local elementPaddingLeft = elementPadding.left - - if effectiveAlign == alignItems_FLEX_START then - child.x = elementX + elementPaddingLeft + currentCrossPos + childMarginLeft - elseif effectiveAlign == alignItems_CENTER then - child.x = elementX - + elementPaddingLeft - + currentCrossPos - + ((lineHeight - childTotalCrossSize) / 2) - + childMarginLeft - elseif effectiveAlign == alignItems_FLEX_END then - child.x = elementX + elementPaddingLeft + currentCrossPos + lineHeight - childTotalCrossSize + childMarginLeft - elseif effectiveAlign == alignItems_STRETCH then - -- STRETCH: Only apply if width was not explicitly set - if childAutosizing and childAutosizing.width then - -- STRETCH: Set border-box width to lineHeight minus margins, content area shrinks to fit - local availableWidth = - LayoutEngine._Utils.clamp(lineHeight - childMarginLeft - childMarginRight, child.minWidth, child.maxWidth) - child._borderBoxWidth = availableWidth - child.width = math.max(0, availableWidth - childPadding.left - childPadding.right) - end - child.x = elementX + elementPaddingLeft + currentCrossPos + childMarginLeft - end - - -- Apply positioning offsets (top, right, bottom, left) - self:applyPositioningOffsets(child) - - -- If child has children, re-layout them after position change - if #child.children > 0 then - child:layoutChildren() - end - - -- Advance position by child's border-box height plus margins - currentMainPos = currentMainPos - + child:getBorderBoxHeight() - + child.margin.top - + child.margin.bottom - + itemSpacing - end - end - - -- Move to next line position - currentCrossPos = currentCrossPos + lineHeight + lineSpacing - end - - -- Position explicitly absolute children after flex layout - for i, child in ipairs(self.element.children) do - if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute and child.display ~= false then - -- Apply positioning offsets (top, right, bottom, left) - self:applyPositioningOffsets(child) - - -- If child has children, layout them after position change - if #child.children > 0 then - child:layoutChildren() - end - end - end - - -- flex-direction: row-reverse / column-reverse — mirror the main-axis - -- position of each flex child relative to the container content area, and - -- shift the child's subtree by the same delta so descendants follow. - -- Cross-axis positions and absolute children are not affected. - if self:_isReverse() then - local parent = self.element - local padLeft = parent.padding.left - local padTop = parent.padding.top - local contentW = parent.width - local contentH = parent.height - local mirrorHorizontal = self:_isHorizontal() - - for _, child in ipairs(flexChildren) do - if mirrorHorizontal then - local distFromLeft = child.x - parent.x - padLeft - local childW = child:getBorderBoxWidth() - local newDistFromLeft = contentW - distFromLeft - childW - local dx = newDistFromLeft - distFromLeft - if dx ~= 0 then - shiftSubtree(child, dx, 0) - end - else - local distFromTop = child.y - parent.y - padTop - local childH = child:getBorderBoxHeight() - local newDistFromTop = contentH - distFromTop - childH - local dy = newDistFromTop - distFromTop - if dy ~= 0 then - shiftSubtree(child, 0, dy) - end - end - end - end - - -- position: relative — shift each in-flow child by (left or -right, - -- top or -bottom) after the flex flow (and row-reverse mirroring) has - -- placed it, so the offset is a pure visual delta that doesn't influence - -- siblings' flow positions. Per CSS, `top` wins over `bottom` and `left` - -- over `right` when both are set. Static/absolute children are unaffected - -- (absolute uses applyPositioningOffsets; flex-participating children - -- dropped the offsets and emitted LAY_011 at construction). Runs for every - -- container type so relative children in relative containers also honor offsets. - for _, child in ipairs(self.element.children) do - if child.positioning == self._Positioning.RELATIVE and child.display ~= false then - local dx, dy = 0, 0 - if child.top then - dy = child.top - elseif child.bottom then - dy = -child.bottom - end - if child.left then - dx = child.left - elseif child.right then - dx = -child.right - end - if dx ~= 0 or dy ~= 0 then - shiftSubtree(child, dx, dy) - end - end - end - - -- Detect overflow after children are laid out - if self.element._detectOverflow then - self.element:_detectOverflow() - end - - -- Stop performance timing - if timerName and LayoutEngine._Performance then - LayoutEngine._Performance:stopTimer(timerName) - end -end - ---- Simulate wrapping children into lines for auto-sizing calculations ----@param children table Array of child elements ----@param availableSize number Available space in main axis ----@param isHorizontal boolean True if flex direction is horizontal ----@return table Array of lines, where each line is an array of children -function LayoutEngine:_simulateWrap(children, availableSize, isHorizontal) - local lines = {} - local currentLine = {} - local currentLineSize = 0 - - for _, child in ipairs(children) do - -- Calculate child size in main axis (including margins) - local childMainSize = 0 - local childMainMargin = 0 - if isHorizontal then - childMainSize = child:getBorderBoxWidth() - if child.margin then - childMainMargin = child.margin.left + child.margin.right - end - else - childMainSize = child:getBorderBoxHeight() - if child.margin then - childMainMargin = child.margin.top + child.margin.bottom - end - end - local childTotalMainSize = childMainSize + childMainMargin - - -- Check if adding this child would exceed the available space - local lineSpacing = #currentLine > 0 and self.gap or 0 - if #currentLine > 0 and currentLineSize + lineSpacing + childTotalMainSize > availableSize then - -- Start a new line - table.insert(lines, currentLine) - currentLine = { child } - currentLineSize = childTotalMainSize - else - -- Add to current line - table.insert(currentLine, child) - currentLineSize = currentLineSize + lineSpacing + childTotalMainSize - end - end - - -- Add the last line if it has children - if #currentLine > 0 then - table.insert(lines, currentLine) - end - - return lines -end - ---- Calculate auto width based on children ----@return number -function LayoutEngine:calculateAutoWidth() - if self.element == nil then - return 0 - end - - -- BORDER-BOX MODEL: Calculate content width, caller will add padding to get border-box - local contentWidth = self.element:calculateTextWidth() - if not self.element.children or #self.element.children == 0 then - return contentWidth - end - - -- Get flex children (children that participate in flex layout) - -- Exclude display=false (CSS display:none) and explicitly absolute children - local flexChildren = {} - for _, child in ipairs(self.element.children) do - if not child._explicitlyAbsolute and child.display ~= false then - table.insert(flexChildren, child) - end - end - - if #flexChildren == 0 then - return contentWidth - end - - local isHorizontal = self:_isHorizontal() - - if isHorizontal then - -- HORIZONTAL flex with potential wrapping - if self.flexWrap ~= self._FlexWrap.NOWRAP and self.element.width and self.element.width > 0 then - -- Container has explicit width and wrapping enabled - calculate based on wrapped lines - local availableWidth = self.element.width - local lines = self:_simulateWrap(flexChildren, availableWidth, true) - - -- Find the widest line - local maxLineWidth = contentWidth - for _, line in ipairs(lines) do - local lineWidth = 0 - for i, child in ipairs(line) do - local childBorderBoxWidth = child:getBorderBoxWidth() - local childMarginH = 0 - if child.margin then - childMarginH = child.margin.left + child.margin.right - end - lineWidth = lineWidth + childBorderBoxWidth + childMarginH - if i < #line then - lineWidth = lineWidth + self.gap - end - end - maxLineWidth = math.max(maxLineWidth, lineWidth) - end - return maxLineWidth - else - -- No wrapping or no explicit width - sum all children on one line - local totalWidth = contentWidth - for i, child in ipairs(flexChildren) do - local childBorderBoxWidth = child:getBorderBoxWidth() - local childMarginH = 0 - if child.margin then - childMarginH = child.margin.left + child.margin.right - end - totalWidth = totalWidth + childBorderBoxWidth + childMarginH - if i < #flexChildren then - totalWidth = totalWidth + self.gap - end - end - return totalWidth - end - else - -- VERTICAL flex - return max child width (including margins) - local maxWidth = contentWidth - for _, child in ipairs(flexChildren) do - local childBorderBoxWidth = child:getBorderBoxWidth() - childBorderBoxWidth = self.element:_adjustAutoWidthChildBorderBoxForManagedSelect(child, childBorderBoxWidth) - local childMarginH = 0 - if child.margin then - childMarginH = child.margin.left + child.margin.right - end - maxWidth = math.max(maxWidth, childBorderBoxWidth + childMarginH) - end - return maxWidth - end -end - ----@return number -function LayoutEngine:calculateAutoHeight() - if self.element == nil then - return 0 - end - - local height = self.element:calculateTextHeight() - if not self.element.children or #self.element.children == 0 then - return height - end - - -- Get flex children (children that participate in flex layout) - -- Exclude display=false (CSS display:none) and explicitly absolute children - local flexChildren = {} - for _, child in ipairs(self.element.children) do - if not child._explicitlyAbsolute and child.display ~= false then - table.insert(flexChildren, child) - end - end - - if #flexChildren == 0 then - return height - end - - local isVertical = not self:_isHorizontal() - - if isVertical then - -- VERTICAL flex with potential wrapping - if self.flexWrap ~= self._FlexWrap.NOWRAP and self.element.height and self.element.height > 0 then - -- Container has explicit height and wrapping enabled - calculate based on wrapped lines - local availableHeight = self.element.height - local lines = self:_simulateWrap(flexChildren, availableHeight, false) - - -- Sum all line heights - local totalLinesHeight = height - for i, line in ipairs(lines) do - local lineHeight = 0 - for _, child in ipairs(line) do - local childBorderBoxHeight = child:getBorderBoxHeight() - local childMarginV = 0 - if child.margin then - childMarginV = child.margin.top + child.margin.bottom - end - lineHeight = math.max(lineHeight, childBorderBoxHeight + childMarginV) - end - totalLinesHeight = totalLinesHeight + lineHeight - if i < #lines then - totalLinesHeight = totalLinesHeight + self.gap - end - end - return totalLinesHeight - else - -- No wrapping or no explicit height - sum all children on one line - local totalHeight = height - for i, child in ipairs(flexChildren) do - local childBorderBoxHeight = child:getBorderBoxHeight() - local childMarginV = 0 - if child.margin then - childMarginV = child.margin.top + child.margin.bottom - end - totalHeight = totalHeight + childBorderBoxHeight + childMarginV - if i < #flexChildren then - totalHeight = totalHeight + self.gap - end - end - return totalHeight - end - else - -- HORIZONTAL flex with potential wrapping - if self.flexWrap ~= self._FlexWrap.NOWRAP and self.element.width and self.element.width > 0 then - -- Container has explicit width and wrapping enabled - calculate based on wrapped lines - local availableWidth = self.element.width - local lines = self:_simulateWrap(flexChildren, availableWidth, true) - - -- Sum all line heights (cross-axis for horizontal flex) - local totalLinesHeight = height - for i, line in ipairs(lines) do - local lineHeight = 0 - for _, child in ipairs(line) do - local childBorderBoxHeight = child:getBorderBoxHeight() - local childMarginV = 0 - if child.margin then - childMarginV = child.margin.top + child.margin.bottom - end - lineHeight = math.max(lineHeight, childBorderBoxHeight + childMarginV) - end - totalLinesHeight = totalLinesHeight + lineHeight - if i < #lines then - totalLinesHeight = totalLinesHeight + self.gap - end - end - return totalLinesHeight - else - -- No wrapping or no explicit width - return max child height (including margins) - local maxHeight = height - for _, child in ipairs(flexChildren) do - local childBorderBoxHeight = child:getBorderBoxHeight() - local childMarginV = 0 - if child.margin then - childMarginV = child.margin.top + child.margin.bottom - end - maxHeight = math.max(maxHeight, childBorderBoxHeight + childMarginV) - end - return maxHeight - end - end -end - ---- Recalculate units based on new viewport dimensions (for vw, vh, % units) ----@param newViewportWidth number ----@param newViewportHeight number -function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight) - if self.element == nil then - return - end - local Units = self._Units - - -- Get updated scale factors - local scaleX, scaleY = self._Context.getScaleFactors() - - -- Recalculate border-box width if using viewport or percentage units (skip auto-sized) - -- Store in _borderBoxWidth temporarily, will calculate content width after padding is resolved - if self.element.units.width.unit ~= "px" and self.element.units.width.unit ~= "auto" then - local parentWidth = self.element.parent and self.element.parent.width or newViewportWidth - self.element._borderBoxWidth = Units.resolve( - self.element.units.width.value, - self.element.units.width.unit, - newViewportWidth, - newViewportHeight, - parentWidth - ) - elseif self.element.units.width.unit == "px" and self.element.units.width.value and self._Context.baseScale then - -- Reapply base scaling to pixel widths (border-box) - self.element._borderBoxWidth = self.element.units.width.value * scaleX - end - - -- Recalculate border-box height if using viewport or percentage units (skip auto-sized) - -- Store in _borderBoxHeight temporarily, will calculate content height after padding is resolved - if self.element.units.height.unit ~= "px" and self.element.units.height.unit ~= "auto" then - local parentHeight = self.element.parent and self.element.parent.height or newViewportHeight - self.element._borderBoxHeight = Units.resolve( - self.element.units.height.value, - self.element.units.height.unit, - newViewportWidth, - newViewportHeight, - parentHeight - ) - elseif self.element.units.height.unit == "px" and self.element.units.height.value and self._Context.baseScale then - -- Reapply base scaling to pixel heights (border-box) - self.element._borderBoxHeight = self.element.units.height.value * scaleY - end - - -- Recalculate position if using viewport or percentage units - -- Skip position recalculation for flex children (non-explicitly-absolute children with a parent) - -- Their x/y is entirely controlled by the parent's layoutChildren() call - local isFlexChild = self.element.parent and not self.element._explicitlyAbsolute - if not isFlexChild then - if self.element.units.x.unit ~= "px" then - local parentWidth = self.element.parent and self.element.parent.width or newViewportWidth - local baseX = self.element.parent and self.element.parent.x or 0 - local offsetX = Units.resolve( - self.element.units.x.value, - self.element.units.x.unit, - newViewportWidth, - newViewportHeight, - parentWidth - ) - self.element.x = baseX + offsetX - else - -- For pixel units, update position relative to parent's new position (with base scaling) - if self.element.parent then - local baseX = self.element.parent.x - local scaledOffset = self._Context.baseScale and (self.element.units.x.value * scaleX) - or self.element.units.x.value - self.element.x = baseX + scaledOffset - elseif self._Context.baseScale then - -- Top-level element with pixel position - apply base scaling - self.element.x = self.element.units.x.value * scaleX - end - end - - if self.element.units.y.unit ~= "px" then - local parentHeight = self.element.parent and self.element.parent.height or newViewportHeight - local baseY = self.element.parent and self.element.parent.y or 0 - local offsetY = Units.resolve( - self.element.units.y.value, - self.element.units.y.unit, - newViewportWidth, - newViewportHeight, - parentHeight - ) - self.element.y = baseY + offsetY - else - -- For pixel units, update position relative to parent's new position (with base scaling) - if self.element.parent then - local baseY = self.element.parent.y - local scaledOffset = self._Context.baseScale and (self.element.units.y.value * scaleY) - or self.element.units.y.value - self.element.y = baseY + scaledOffset - elseif self._Context.baseScale then - -- Top-level element with pixel position - apply base scaling - self.element.y = self.element.units.y.value * scaleY - end - end - end - - -- Recalculate textSize if auto-scaling is enabled or using viewport/element-relative units - if self.element.autoScaleText and self.element.units.textSize.value then - local unit = self.element.units.textSize.unit - local value = self.element.units.textSize.value - - if unit == "px" and self._Context.baseScale then - -- With base scaling: scale pixel values relative to base resolution - self.element.textSize = value * scaleY - elseif unit == "px" then - -- Without base scaling but auto-scaling enabled: text doesn't scale - self.element.textSize = value - elseif unit == "%" or unit == "vh" then - -- Percentage and vh are relative to viewport height - self.element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportHeight) - elseif unit == "vw" then - -- vw is relative to viewport width - self.element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportWidth) - else - self.element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, nil) - end - - -- Apply min/max constraints (with base scaling) - local minSize = self.element.minTextSize - and (self._Context.baseScale and (self.element.minTextSize * scaleY) or self.element.minTextSize) - local maxSize = self.element.maxTextSize - and (self._Context.baseScale and (self.element.maxTextSize * scaleY) or self.element.maxTextSize) - - if minSize and self.element.textSize < minSize then - self.element.textSize = minSize - end - if maxSize and self.element.textSize > maxSize then - self.element.textSize = maxSize - end - - -- Protect against too-small text sizes (minimum 1px) - if self.element.textSize < 1 then - self.element.textSize = 1 -- Minimum 1px - end - elseif self.element.units.textSize.unit == "px" and self.element.units.textSize.value and self._Context.baseScale then - -- No auto-scaling but base scaling is set: reapply base scaling to pixel text sizes - self.element.textSize = self.element.units.textSize.value * scaleY - - -- Protect against too-small text sizes (minimum 1px) - if self.element.textSize < 1 then - self.element.textSize = 1 -- Minimum 1px - end - end - - -- Final protection: ensure textSize is always at least 1px (catches all edge cases) - if self.element.text and self.element.textSize and self.element.textSize < 1 then - self.element.textSize = 1 -- Minimum 1px - end - - -- Recalculate gap if using viewport or percentage units - if self.element.units.gap.unit ~= "px" then - local containerSize = (self:_isHorizontal()) - and (self.element.parent and self.element.parent.width or newViewportWidth) - or (self.element.parent and self.element.parent.height or newViewportHeight) - self.element.gap = Units.resolve( - self.element.units.gap.value, - self.element.units.gap.unit, - newViewportWidth, - newViewportHeight, - containerSize - ) - end - - -- Recalculate flexBasis if using viewport or percentage units - if - self.element.units.flexBasis - and self.element.units.flexBasis.unit ~= "auto" - and self.element.units.flexBasis.unit ~= "px" - then - local value, unit = self.element.units.flexBasis.value, self.element.units.flexBasis.unit - -- flexBasis uses parent main-axis size for percentage resolution. - local parentMainIsHorizontal = true - if self.element.parent and self.element.parent.flexDirection then - local pd = self.element.parent.flexDirection - parentMainIsHorizontal = pd == self._FlexDirection.HORIZONTAL or pd == self._FlexDirection.HORIZONTAL_REVERSE - end - local parentSize = newViewportWidth - if self.element.parent then - if parentMainIsHorizontal then - parentSize = self.element.parent.width - else - parentSize = self.element.parent.height - end - end - local resolvedBasis = Units.resolve(value, unit, newViewportWidth, newViewportHeight, parentSize) - if type(resolvedBasis) == "number" then - self.element.flexBasis = resolvedBasis - end - end - - -- Recalculate spacing (padding/margin) if using viewport or percentage units - -- For percentage-based padding: - -- - If element has a parent: use parent's border-box dimensions (CSS spec for child elements) - -- - If element has no parent: use element's own border-box dimensions (CSS spec for root elements) - local parentBorderBoxWidth = self.element.parent and self.element.parent._borderBoxWidth - or self.element._borderBoxWidth - or newViewportWidth - local parentBorderBoxHeight = self.element.parent and self.element.parent._borderBoxHeight - or self.element._borderBoxHeight - or newViewportHeight - - -- Handle shorthand properties first (horizontal/vertical) - local resolvedHorizontalPadding = nil - local resolvedVerticalPadding = nil - - if self.element.units.padding.horizontal and self.element.units.padding.horizontal.unit ~= "px" then - resolvedHorizontalPadding = Units.resolve( - self.element.units.padding.horizontal.value, - self.element.units.padding.horizontal.unit, - newViewportWidth, - newViewportHeight, - parentBorderBoxWidth - ) - elseif self.element.units.padding.horizontal and self.element.units.padding.horizontal.value then - resolvedHorizontalPadding = self.element.units.padding.horizontal.value - end - - if self.element.units.padding.vertical and self.element.units.padding.vertical.unit ~= "px" then - resolvedVerticalPadding = Units.resolve( - self.element.units.padding.vertical.value, - self.element.units.padding.vertical.unit, - newViewportWidth, - newViewportHeight, - parentBorderBoxHeight - ) - elseif self.element.units.padding.vertical and self.element.units.padding.vertical.value then - resolvedVerticalPadding = self.element.units.padding.vertical.value - end - -- Resolve individual padding sides (with fallback to shorthand) - for _, side in ipairs({ "top", "right", "bottom", "left" }) do - -- Check if this side was explicitly set or if we should use shorthand - local useShorthand = false - if not self.element.units.padding[side].explicit then - -- Not explicitly set, check if we have shorthand - if side == "left" or side == "right" then - useShorthand = resolvedHorizontalPadding ~= nil - elseif side == "top" or side == "bottom" then - useShorthand = resolvedVerticalPadding ~= nil - end - end - - if useShorthand then - -- Use shorthand value - if side == "left" or side == "right" then - self.element.padding[side] = resolvedHorizontalPadding - else - self.element.padding[side] = resolvedVerticalPadding - end - elseif self.element.units.padding[side].unit ~= "px" then - -- Recalculate non-pixel units - local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth - self.element.padding[side] = Units.resolve( - self.element.units.padding[side].value, - self.element.units.padding[side].unit, - newViewportWidth, - newViewportHeight, - parentSize - ) - end - -- If unit is "px" and not using shorthand, value stays the same - end - - -- Handle margin shorthand properties - local resolvedHorizontalMargin = nil - local resolvedVerticalMargin = nil - - if self.element.units.margin.horizontal and self.element.units.margin.horizontal.unit ~= "px" then - resolvedHorizontalMargin = Units.resolve( - self.element.units.margin.horizontal.value, - self.element.units.margin.horizontal.unit, - newViewportWidth, - newViewportHeight, - parentBorderBoxWidth - ) - elseif self.element.units.margin.horizontal and self.element.units.margin.horizontal.value then - resolvedHorizontalMargin = self.element.units.margin.horizontal.value - end - - if self.element.units.margin.vertical and self.element.units.margin.vertical.unit ~= "px" then - resolvedVerticalMargin = Units.resolve( - self.element.units.margin.vertical.value, - self.element.units.margin.vertical.unit, - newViewportWidth, - newViewportHeight, - parentBorderBoxHeight - ) - elseif self.element.units.margin.vertical and self.element.units.margin.vertical.value then - resolvedVerticalMargin = self.element.units.margin.vertical.value - end - - -- Resolve individual margin sides (with fallback to shorthand) - for _, side in ipairs({ "top", "right", "bottom", "left" }) do - -- Check if this side was explicitly set or if we should use shorthand - local useShorthand = false - if not self.element.units.margin[side].explicit then - -- Not explicitly set, check if we have shorthand - if side == "left" or side == "right" then - useShorthand = resolvedHorizontalMargin ~= nil - elseif side == "top" or side == "bottom" then - useShorthand = resolvedVerticalMargin ~= nil - end - end - - if useShorthand then - -- Use shorthand value - if side == "left" or side == "right" then - self.element.margin[side] = resolvedHorizontalMargin - else - self.element.margin[side] = resolvedVerticalMargin - end - elseif self.element.units.margin[side].unit ~= "px" then - -- Recalculate non-pixel units - local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth - self.element.margin[side] = Units.resolve( - self.element.units.margin[side].value, - self.element.units.margin[side].unit, - newViewportWidth, - newViewportHeight, - parentSize - ) - end - -- If unit is "px" and not using shorthand, value stays the same - end - - -- BORDER-BOX MODEL: Calculate content dimensions from border-box dimensions - -- For explicitly-sized elements (non-auto), _borderBoxWidth/_borderBoxHeight were set earlier - -- Now we calculate content width/height by subtracting padding - -- Only recalculate if using viewport/percentage units (where _borderBoxWidth actually changed) - if self.element.units.width.unit ~= "auto" and self.element.units.width.unit ~= "px" then - -- _borderBoxWidth was recalculated for viewport/percentage units - -- Calculate content width by subtracting padding - self.element.width = - math.max(0, self.element._borderBoxWidth - self.element.padding.left - self.element.padding.right) - elseif self.element.units.width.unit == "auto" then - -- For auto-sized elements, width is content width (calculated in resize method) - -- Update border-box to include padding - self.element._borderBoxWidth = self.element.width + self.element.padding.left + self.element.padding.right - end - -- For pixel units, width stays as-is (may have been manually modified) - - if self.element.units.height.unit ~= "auto" and self.element.units.height.unit ~= "px" then - -- _borderBoxHeight was recalculated for viewport/percentage units - -- Calculate content height by subtracting padding - self.element.height = - math.max(0, self.element._borderBoxHeight - self.element.padding.top - self.element.padding.bottom) - elseif self.element.units.height.unit == "auto" then - -- For auto-sized elements, height is content height (calculated in resize method) - -- Update border-box to include padding - self.element._borderBoxHeight = self.element.height + self.element.padding.top + self.element.padding.bottom - end - -- For pixel units, height stays as-is (may have been manually modified) - - -- Detect overflow after layout calculations - if self.element._detectOverflow then - self.element:_detectOverflow() - end -end - ---- Check if layout can be skipped based on cached state (memoization) ----@return boolean canSkip True if layout hasn't changed and can be skipped -function LayoutEngine:_canSkipLayout() - if not self.element then - return false - end - - -- Performance optimization: Check dirty flags first (fastest check) - -- If element or children are marked dirty, we must recalculate - if self.element._dirty or self.element._childrenDirty then - -- Clear dirty flags after acknowledging them - self.element._dirty = false - self.element._childrenDirty = false - return false - end - - -- If not dirty, check if layout inputs have actually changed (secondary check) - local childrenCount = #self.element.children - local containerWidth = self.element.width - local containerHeight = self.element.height - local containerX = self.element.x - local containerY = self.element.y - - -- Generate simple hash of children dimensions + display state - local childrenHash = "" - for i, child in ipairs(self.element.children) do - if i <= 5 then -- Only hash first 5 children for performance - childrenHash = childrenHash .. child.width .. "x" .. child.height .. "d" .. tostring(child.display) .. "," - end - end - - local cache = self._layoutCache - - -- Check if layout inputs have changed - if - cache.childrenCount == childrenCount - and cache.containerWidth == containerWidth - and cache.containerHeight == containerHeight - and cache.containerX == containerX - and cache.containerY == containerY - and cache.childrenHash == childrenHash - then - return true -- Layout hasn't changed, can skip - end - - -- Update cache with current values - cache.childrenCount = childrenCount - cache.containerWidth = containerWidth - cache.containerHeight = containerHeight - cache.containerX = containerX - cache.containerY = containerY - cache.childrenHash = childrenHash - - return false -- Layout has changed, must recalculate -end - ---- Track layout recalculations and warn about excessive layouts -function LayoutEngine:_trackLayoutRecalculation() - if not LayoutEngine._Performance or not LayoutEngine._Performance.warningsEnabled then - return - end - - -- Get current frame count from Context - local currentFrame = self._Context and self._Context._frameNumber or 0 - - -- Reset counter on new frame - if currentFrame ~= self._lastFrameCount then - self._lastFrameCount = currentFrame - self._layoutCount = 0 - end - - -- Increment layout count - self._layoutCount = self._layoutCount + 1 - - -- Warn if layout is recalculated excessively this frame - if self._layoutCount >= 10 then - local elementId = self.element and self.element.id or "unnamed" - LayoutEngine._Performance:logWarning( - string.format("excessive_layout_%s", elementId), - "LayoutEngine", - string.format("Layout recalculated %d times this frame for element '%s'", self._layoutCount, elementId), - { layoutCount = self._layoutCount, elementId = elementId }, - "This may indicate a layout thrashing issue. Check for circular dependencies or dynamic sizing that triggers re-layout" - ) - end -end - -return LayoutEngine diff --git a/libs/flexlove/modules/MemoryScanner.lua b/libs/flexlove/modules/MemoryScanner.lua deleted file mode 100644 index 07f5ca70..00000000 --- a/libs/flexlove/modules/MemoryScanner.lua +++ /dev/null @@ -1,697 +0,0 @@ ----@class MemoryScanner ----@field _StateManager table ----@field _Context table ----@field _ImageCache table ----@field _ErrorHandler table -local MemoryScanner = {} - ----Initialize MemoryScanner with dependencies ----@param deps {StateManager: table, Context: table, ImageCache: table, ErrorHandler: table} -function MemoryScanner.init(deps) - MemoryScanner._StateManager = deps.StateManager - MemoryScanner._Context = deps.Context - MemoryScanner._ImageCache = deps.ImageCache - MemoryScanner._ErrorHandler = deps.ErrorHandler -end - ----Count items in a table ----@param tbl table ----@return number -local function countTable(tbl) - local count = 0 - for _ in pairs(tbl) do - count = count + 1 - end - return count -end - ----Calculate memory size estimate for a table (recursive) ----@param tbl table ----@param visited table? Tracking table to prevent circular references ----@param depth number? Current recursion depth ----@return number bytes Estimated memory usage in bytes -local function estimateTableSize(tbl, visited, depth) - if type(tbl) ~= "table" then - return 0 - end - - visited = visited or {} - depth = depth or 0 - - -- Limit recursion depth to prevent stack overflow - if depth > 10 then - return 0 - end - - -- Check for circular references - if visited[tbl] then - return 0 - end - visited[tbl] = true - - local size = 40 -- Base table overhead (approximate) - - for k, v in pairs(tbl) do - -- Key size - if type(k) == "string" then - size = size + #k + 24 -- String overhead - elseif type(k) == "number" then - size = size + 8 - else - size = size + 8 -- Reference - end - - -- Value size - if type(v) == "string" then - size = size + #v + 24 - elseif type(v) == "number" then - size = size + 8 - elseif type(v) == "boolean" then - size = size + 4 - elseif type(v) == "table" then - size = size + estimateTableSize(v, visited, depth + 1) - elseif type(v) == "function" then - size = size + 16 -- Function reference - else - size = size + 8 -- Other references - end - end - - return size -end - ----Scan StateManager for memory issues ----@return table report Detailed report of StateManager memory usage -function MemoryScanner.scanStateManager() - local report = { - stateCount = 0, - stateStoreSize = 0, - metadataSize = 0, - callSiteCounterSize = 0, - orphanedStates = {}, - staleStates = {}, - largeStates = {}, - issues = {}, - } - - if not MemoryScanner._StateManager then - table.insert(report.issues, { - severity = "error", - message = "StateManager not initialized", - }) - return report - end - - local internal = MemoryScanner._StateManager._getInternalState() - local stateStore = internal.stateStore - local stateMetadata = internal.stateMetadata - local callSiteCounters = internal.callSiteCounters - local currentFrame = MemoryScanner._StateManager.getFrameNumber() - - -- Count states - report.stateCount = countTable(stateStore) - - -- Estimate sizes - report.stateStoreSize = estimateTableSize(stateStore) - report.metadataSize = estimateTableSize(stateMetadata) - report.callSiteCounterSize = estimateTableSize(callSiteCounters) - - -- Check for orphaned states (metadata without state) - for id, _ in pairs(stateMetadata) do - if not stateStore[id] then - table.insert(report.orphanedStates, id) - end - end - - -- Check for stale states (not accessed in many frames) - local staleThreshold = 120 -- 2 seconds at 60fps - for id, meta in pairs(stateMetadata) do - local framesSinceAccess = currentFrame - meta.lastFrame - if framesSinceAccess > staleThreshold then - table.insert(report.staleStates, { - id = id, - framesSinceAccess = framesSinceAccess, - createdFrame = meta.createdFrame, - accessCount = meta.accessCount, - }) - end - end - - -- Check for large states (may indicate memory bloat) - for id, state in pairs(stateStore) do - local stateSize = estimateTableSize(state) - if stateSize > 1024 then -- More than 1KB - table.insert(report.largeStates, { - id = id, - size = stateSize, - keyCount = countTable(state), - }) - end - end - - -- Check callSiteCounters (should be near 0 after frame cleanup) - local callSiteCount = countTable(callSiteCounters) - if callSiteCount > 100 then - table.insert(report.issues, { - severity = "warning", - message = string.format("callSiteCounters has %d entries (expected near 0)", callSiteCount), - suggestion = "incrementFrame() may not be called properly, or counters aren't being reset", - }) - end - - -- Check for excessive state count - if report.stateCount > 500 then - table.insert(report.issues, { - severity = "warning", - message = string.format("High state count: %d states", report.stateCount), - suggestion = "Consider reducing element count or implementing more aggressive cleanup", - }) - end - - -- Check for orphaned states - if #report.orphanedStates > 0 then - table.insert(report.issues, { - severity = "error", - message = string.format("Found %d orphaned states (metadata without state)", #report.orphanedStates), - suggestion = "This indicates a bug in state management - metadata should be cleaned up with state", - }) - end - - -- Check for stale states - if #report.staleStates > 10 then - table.insert(report.issues, { - severity = "warning", - message = string.format("Found %d stale states (not accessed in 2+ seconds)", #report.staleStates), - suggestion = "Cleanup may not be aggressive enough - consider reducing stateRetentionFrames", - }) - end - - return report -end - ----Scan Context for memory issues ----@return table report Detailed report of Context memory usage -function MemoryScanner.scanContext() - local report = { - topElementCount = 0, - zIndexElementCount = 0, - frameElementCount = 0, - issues = {}, - } - - if not MemoryScanner._Context then - table.insert(report.issues, { - severity = "error", - message = "Context not initialized", - }) - return report - end - - -- Count elements - report.topElementCount = #MemoryScanner._Context.topElements - report.zIndexElementCount = #MemoryScanner._Context._zIndexOrderedElements - report.frameElementCount = #MemoryScanner._Context._currentFrameElements - - -- Check for stale z-index elements (should be cleared each frame) - if MemoryScanner._Context.isImmediateMode() then - -- In immediate mode, _zIndexOrderedElements should be cleared at frame start - -- If it has elements outside of frame rendering, that's a leak - if not MemoryScanner._Context._frameStarted and report.zIndexElementCount > 0 then - table.insert(report.issues, { - severity = "warning", - message = string.format("Z-index array has %d elements outside of frame", report.zIndexElementCount), - suggestion = "clearFrameElements() may not be called properly in beginFrame()", - }) - end - end - - -- Check for excessive element count - if report.topElementCount > 100 then - table.insert(report.issues, { - severity = "info", - message = string.format("High top-level element count: %d", report.topElementCount), - suggestion = "Consider consolidating elements or using fewer top-level containers", - }) - end - - return report -end - ----Scan ImageCache for memory issues ----@return table report Detailed report of ImageCache memory usage -function MemoryScanner.scanImageCache() - local report = { - imageCount = 0, - estimatedMemory = 0, - issues = {}, - } - - if not MemoryScanner._ImageCache then - table.insert(report.issues, { - severity = "error", - message = "ImageCache not initialized", - }) - return report - end - - local stats = MemoryScanner._ImageCache.getStats() - report.imageCount = stats.count - report.estimatedMemory = stats.memoryEstimate - - -- Check for excessive memory usage (>100MB) - if report.estimatedMemory > 100 * 1024 * 1024 then - table.insert(report.issues, { - severity = "warning", - message = string.format("ImageCache using ~%.2f MB", report.estimatedMemory / 1024 / 1024), - suggestion = "Consider implementing cache eviction or clearing unused images", - }) - end - - -- Check for excessive image count - if report.imageCount > 50 then - table.insert(report.issues, { - severity = "info", - message = string.format("ImageCache has %d images", report.imageCount), - suggestion = "Review if all cached images are necessary", - }) - end - - return report -end - ----Check if a circular reference is intentional (parent-child, module, or metatable) ----@param path string The current path where circular ref was detected ----@param originalPath string The original path where the table was first seen ----@return boolean True if this is an intentional circular reference -local function isIntentionalCircularReference(path, originalPath) - -- Pattern 1: child.parent points back to parent - -- Example: "topElements.1.children.1.parent" -> "topElements.1" - if path:match("%.parent$") then - local parentPath = path:match("^(.+)%.children%.[^.]+%.parent$") - if parentPath == originalPath then - return true - end - end - - -- Pattern 2: parent.children[n] points to child, child points back somewhere in parent tree - -- Example: "topElements.1" -> "topElements.1.children.1.parent" - if originalPath:match("%.parent$") then - local childParentPath = originalPath:match("^(.+)%.children%.[^.]+%.parent$") - if childParentPath == path then - return true - end - end - - -- Pattern 3: Check for nested parent-child cycles - -- child.children[n].parent -> child - local segments = {} - for segment in path:gmatch("[^.]+") do - table.insert(segments, segment) - end - - -- Look for .children.N.parent pattern - for i = 1, #segments - 2 do - if segments[i] == "children" and segments[i + 2] == "parent" then - -- Reconstruct path without the .children.N.parent suffix - local reconstructedPath = table.concat(segments, ".", 1, i - 1) - if reconstructedPath == originalPath then - return true - end - end - end - - -- Pattern 4: Metatable __index self-references (modules) - -- Example: "element._renderer._Theme.__index" -> "element._renderer._Theme" - if path:match("%.__index$") then - local basePath = path:match("^(.+)%.__index$") - if basePath == originalPath then - return true - end - end - - -- Pattern 5: Shared module references (elements sharing same module instances) - -- Example: Multiple elements referencing _utils, _Theme, _Blur, etc. - -- These start with _ and are typically modules - local pathModuleName = path:match("%.(_[%w]+)%.") - local originalModuleName = originalPath:match("%.(_[%w]+)%.") - - if pathModuleName and originalModuleName then - -- If both paths reference the same internal module (starting with _), it's intentional - if pathModuleName == originalModuleName then - return true - end - end - - -- Pattern 6: Shared Color/Transform objects between elements - -- These are value objects that can be safely shared - if path:match("Color") and originalPath:match("Color") then - return true - end - if path:match("Transform") and originalPath:match("Transform") then - return true - end - - -- Pattern 7: LayoutEngine holding reference to its element - -- Example: "element._layoutEngine.element" -> "element" - if path:match("%._layoutEngine%.element$") then - local elementPath = path:match("^(.+)%._layoutEngine%.element$") - if elementPath == originalPath then - return true - end - end - - -- Pattern 8: Renderer holding references to element properties - -- Example: "element._renderer.cornerRadius" -> "element.cornerRadius" - if path:match("%._renderer%.") then - local rendererBasePath = path:match("^(.+)%._renderer%.") - local originalBasePath = originalPath:match("^(.+)%.") - if rendererBasePath == originalBasePath then - return true - end - end - - -- Pattern 9: Context reference from layout engine (shared singleton) - -- Example: "element._layoutEngine._Context.topElements" -> "topElements" - if path:match("%._layoutEngine%._Context%.") and originalPath == "topElements" then - return true - end - - return false -end - ----Detect circular references in a table ----@param tbl table Table to check ----@param path string? Current path (for reporting) ----@param visited table? Tracking table ----@return table[] circularRefs Array of circular reference paths ----@return table[] intentionalRefs Array of intentional parent-child refs -local function detectCircularReferences(tbl, path, visited) - if type(tbl) ~= "table" then - return {}, {} - end - - path = path or "root" - visited = visited or {} - local circularRefs = {} - local intentionalRefs = {} - - -- Check if we've seen this table before - if visited[tbl] then - local ref = { - path = path, - originalPath = visited[tbl], - } - - -- Determine if this is an intentional circular reference - if isIntentionalCircularReference(path, visited[tbl]) then - table.insert(intentionalRefs, ref) - else - table.insert(circularRefs, ref) - end - - return circularRefs, intentionalRefs - end - - -- Mark as visited - visited[tbl] = path - - -- Recursively check children - for k, v in pairs(tbl) do - if type(v) == "table" then - local childPath = path .. "." .. tostring(k) - local childRefs, childIntentionalRefs = detectCircularReferences(v, childPath, visited) - for _, ref in ipairs(childRefs) do - table.insert(circularRefs, ref) - end - for _, ref in ipairs(childIntentionalRefs) do - table.insert(intentionalRefs, ref) - end - end - end - - return circularRefs, intentionalRefs -end - ----Scan for circular references in immediate mode ----@return table report Detailed report of circular references -function MemoryScanner.scanCircularReferences() - local report = { - stateStoreCircularRefs = {}, - stateStoreIntentionalRefs = {}, - contextCircularRefs = {}, - contextIntentionalRefs = {}, - issues = {}, - } - - if MemoryScanner._StateManager then - local internal = MemoryScanner._StateManager._getInternalState() - report.stateStoreCircularRefs, report.stateStoreIntentionalRefs = - detectCircularReferences(internal.stateStore, "stateStore") - end - - if MemoryScanner._Context then - report.contextCircularRefs, report.contextIntentionalRefs = - detectCircularReferences(MemoryScanner._Context.topElements, "topElements") - end - - -- Report issues only for cross-module circular references - if #report.stateStoreCircularRefs > 0 then - table.insert(report.issues, { - severity = "info", - message = string.format( - "Found %d cross-module circular references in StateManager", - #report.stateStoreCircularRefs - ), - suggestion = "These are typically architectural dependencies between modules, not memory leaks", - }) - end - - if #report.contextCircularRefs > 0 then - table.insert(report.issues, { - severity = "info", - message = string.format("Found %d cross-module circular references in Context", #report.contextCircularRefs), - suggestion = "These are typically architectural dependencies (e.g., layout engine ↔ renderer), not memory leaks", - }) - end - - return report -end - ----Run comprehensive memory scan ----@return table report Complete memory analysis report -function MemoryScanner.scan() - local startMemory = collectgarbage("count") - - local report = { - timestamp = os.time(), - startMemory = startMemory / 1024, -- MB - stateManager = MemoryScanner.scanStateManager(), - context = MemoryScanner.scanContext(), - imageCache = MemoryScanner.scanImageCache(), - circularRefs = MemoryScanner.scanCircularReferences(), - summary = { - totalIssues = 0, - criticalIssues = 0, - warnings = 0, - info = 0, - }, - } - - -- Count issues by severity - local function countIssues(subReport) - for _, issue in ipairs(subReport.issues or {}) do - report.summary.totalIssues = report.summary.totalIssues + 1 - if issue.severity == "error" then - report.summary.criticalIssues = report.summary.criticalIssues + 1 - elseif issue.severity == "warning" then - report.summary.warnings = report.summary.warnings + 1 - elseif issue.severity == "info" then - report.summary.info = report.summary.info + 1 - end - end - end - - countIssues(report.stateManager) - countIssues(report.context) - countIssues(report.imageCache) - countIssues(report.circularRefs) - - -- Force GC and measure freed memory - local beforeGC = collectgarbage("count") - collectgarbage("collect") - collectgarbage("collect") - local afterGC = collectgarbage("count") - - report.gcAnalysis = { - beforeGC = beforeGC / 1024, -- MB - afterGC = afterGC / 1024, -- MB - freed = (beforeGC - afterGC) / 1024, -- MB - freedPercent = ((beforeGC - afterGC) / beforeGC) * 100, - } - - -- Analyze GC effectiveness - if report.gcAnalysis.freedPercent < 5 then - table.insert(report.stateManager.issues, { - severity = "info", - message = string.format("GC freed only %.1f%% of memory", report.gcAnalysis.freedPercent), - suggestion = "Most memory is still referenced - this is normal if UI is active", - }) - elseif report.gcAnalysis.freedPercent > 30 then - table.insert(report.stateManager.issues, { - severity = "warning", - message = string.format("GC freed %.1f%% of memory", report.gcAnalysis.freedPercent), - suggestion = "Significant memory was unreferenced - may indicate cleanup issues", - }) - end - - return report -end - ----Format report as human-readable string ----@param report table Memory scan report ----@return string formatted Formatted report -function MemoryScanner.formatReport(report) - local lines = {} - - table.insert(lines, "=== FlexLöve Memory Scanner Report ===") - table.insert(lines, string.format("Timestamp: %s", os.date("%Y-%m-%d %H:%M:%S", report.timestamp))) - table.insert(lines, string.format("Memory: %.2f MB", report.startMemory)) - table.insert(lines, "") - - -- Summary - table.insert(lines, "--- Summary ---") - table.insert(lines, string.format("Total Issues: %d", report.summary.totalIssues)) - table.insert(lines, string.format(" Critical: %d", report.summary.criticalIssues)) - table.insert(lines, string.format(" Warnings: %d", report.summary.warnings)) - table.insert(lines, string.format(" Info: %d", report.summary.info)) - table.insert(lines, "") - - -- StateManager - table.insert(lines, "--- StateManager ---") - table.insert(lines, string.format("State Count: %d", report.stateManager.stateCount)) - table.insert(lines, string.format("State Store Size: %.2f KB", report.stateManager.stateStoreSize / 1024)) - table.insert(lines, string.format("Metadata Size: %.2f KB", report.stateManager.metadataSize / 1024)) - table.insert(lines, string.format("CallSite Counters: %.2f KB", report.stateManager.callSiteCounterSize / 1024)) - table.insert(lines, string.format("Orphaned States: %d", #report.stateManager.orphanedStates)) - table.insert(lines, string.format("Stale States: %d", #report.stateManager.staleStates)) - table.insert(lines, string.format("Large States: %d", #report.stateManager.largeStates)) - - if #report.stateManager.issues > 0 then - table.insert(lines, "Issues:") - for _, issue in ipairs(report.stateManager.issues) do - table.insert(lines, string.format(" [%s] %s", string.upper(issue.severity), issue.message)) - if issue.suggestion then - table.insert(lines, string.format(" → %s", issue.suggestion)) - end - end - end - table.insert(lines, "") - - -- Context - table.insert(lines, "--- Context ---") - table.insert(lines, string.format("Top Elements: %d", report.context.topElementCount)) - table.insert(lines, string.format("Z-Index Elements: %d", report.context.zIndexElementCount)) - table.insert(lines, string.format("Frame Elements: %d", report.context.frameElementCount)) - - if #report.context.issues > 0 then - table.insert(lines, "Issues:") - for _, issue in ipairs(report.context.issues) do - table.insert(lines, string.format(" [%s] %s", string.upper(issue.severity), issue.message)) - if issue.suggestion then - table.insert(lines, string.format(" → %s", issue.suggestion)) - end - end - end - table.insert(lines, "") - - -- ImageCache - table.insert(lines, "--- ImageCache ---") - table.insert(lines, string.format("Image Count: %d", report.imageCache.imageCount)) - table.insert(lines, string.format("Estimated Memory: %.2f MB", report.imageCache.estimatedMemory / 1024 / 1024)) - - if #report.imageCache.issues > 0 then - table.insert(lines, "Issues:") - for _, issue in ipairs(report.imageCache.issues) do - table.insert(lines, string.format(" [%s] %s", string.upper(issue.severity), issue.message)) - if issue.suggestion then - table.insert(lines, string.format(" → %s", issue.suggestion)) - end - end - end - table.insert(lines, "") - - -- Circular References - table.insert(lines, "--- Circular References ---") - table.insert(lines, string.format("StateStore (Cross-module refs): %d", #report.circularRefs.stateStoreCircularRefs)) - table.insert( - lines, - string.format( - "StateStore (Intentional - parent-child, modules, metatables): %d", - #report.circularRefs.stateStoreIntentionalRefs - ) - ) - table.insert(lines, string.format("Context (Cross-module refs): %d", #report.circularRefs.contextCircularRefs)) - table.insert( - lines, - string.format( - "Context (Intentional - parent-child, modules, metatables): %d", - #report.circularRefs.contextIntentionalRefs - ) - ) - - if #report.circularRefs.issues > 0 then - table.insert(lines, "Issues:") - for _, issue in ipairs(report.circularRefs.issues) do - table.insert(lines, string.format(" [%s] %s", string.upper(issue.severity), issue.message)) - if issue.suggestion then - table.insert(lines, string.format(" → %s", issue.suggestion)) - end - end - else - table.insert(lines, " ✓ No unexpected circular references detected") - end - table.insert(lines, " Note: Cross-module refs are typically architectural dependencies, not memory leaks") - table.insert(lines, "") - - -- GC Analysis - table.insert(lines, "--- Garbage Collection Analysis ---") - table.insert(lines, string.format("Before GC: %.2f MB", report.gcAnalysis.beforeGC)) - table.insert(lines, string.format("After GC: %.2f MB", report.gcAnalysis.afterGC)) - table.insert(lines, string.format("Freed: %.2f MB (%.1f%%)", report.gcAnalysis.freed, report.gcAnalysis.freedPercent)) - table.insert(lines, "") - - table.insert(lines, "=== End Report ===") - - return table.concat(lines, "\n") -end - ----Save report to file ----@param report table Memory scan report ----@param filename string? Output filename (default: memory_report.txt) -function MemoryScanner.saveReport(report, filename) - filename = filename or "memory_report.txt" - local formatted = MemoryScanner.formatReport(report) - - local file = io.open(filename, "w") - if file then - file:write(formatted) - file:close() - if MemoryScanner._ErrorHandler then - MemoryScanner._ErrorHandler:warn("MemoryScanner", "RES_004", { - resourceType = "report", - path = filename, - status = "saved", - }) - end - else - if MemoryScanner._ErrorHandler then - MemoryScanner._ErrorHandler:warn("MemoryScanner", "RES_004", { - resourceType = "report", - path = filename, - status = "failed to save", - }) - end - end -end - -return MemoryScanner diff --git a/libs/flexlove/modules/ModuleLoader.lua b/libs/flexlove/modules/ModuleLoader.lua deleted file mode 100644 index 1b48daf0..00000000 --- a/libs/flexlove/modules/ModuleLoader.lua +++ /dev/null @@ -1,202 +0,0 @@ ----@class ModuleLoader -local ModuleLoader = {} - --- Module registry to track loaded vs. stub modules -ModuleLoader._registry = {} -ModuleLoader._ErrorHandler = nil - ---- Initialize ModuleLoader with dependencies ----@param deps table -function ModuleLoader.init(deps) - ModuleLoader._ErrorHandler = deps.ErrorHandler -end - ---- Create a null-object stub for a missing optional module ---- Provides safe defaults that won't cause runtime errors ----@param moduleName string ----@return table -local function createNullObject(moduleName) - local stub = { - _isStub = true, - _moduleName = moduleName, - } - - -- Common method stubs that return safe defaults - local metatable = { - __index = function(_, key) - -- Common initialization method - if key == "init" then - return function() - return stub - end - end - - -- Common constructor method - if key == "new" then - return function() - return stub - end - end - - -- Common draw method - if key == "draw" then - return function() end - end - - -- Common update method - if key == "update" then - return function() end - end - - -- Common render method - if key == "render" then - return function() end - end - - -- Common cleanup method - if key == "destroy" then - return function() end - end - - -- Common cleanup method - if key == "cleanup" then - return function() end - end - - -- Common clear method - if key == "clear" then - return function() end - end - - -- Common reset method - if key == "reset" then - return function() end - end - - -- Common get method - if key == "get" then - return function() - return nil - end - end - - -- Common set method - if key == "set" then - return function() end - end - - -- Common load method - if key == "load" then - return function() - return stub - end - end - - -- Common cache-related methods - if key == "cache" or key == "getCache" or key == "clearCache" then - return function() - return {} - end - end - - -- For any unknown method, return a no-op function that accepts any arguments - -- This allows safe method calls on stub objects (e.g., Performance:startFrame()) - return function() - return stub - end - end, - - -- Make function calls safe (in case the stub itself is called) - __call = function() - return stub - end, - } - - setmetatable(stub, metatable) - return stub -end - ---- Safely require a module with graceful fallback for optional modules ---- Returns the module if it exists, or a null-object stub if it's optional and missing ---- Throws an error if a required module is missing ----@param modulePath string Full path to the module (e.g., "modules.Performance") ----@param isOptional boolean If true, returns null-object on failure; if false, throws error ----@return table module The loaded module or a null-object stub -function ModuleLoader.safeRequire(modulePath, isOptional) - -- Check if already loaded - if ModuleLoader._registry[modulePath] then - return ModuleLoader._registry[modulePath] - end - - -- Attempt to load the module - local success, result = pcall(require, modulePath) - - if success then - -- Module loaded successfully - ModuleLoader._registry[modulePath] = result - return result - else - -- Module failed to load - if isOptional then - -- Create null-object stub for optional module - local stub = createNullObject(modulePath) - ModuleLoader._registry[modulePath] = stub - - -- Log warning about missing optional module - if ModuleLoader._ErrorHandler then - ModuleLoader._ErrorHandler:warn("ModuleLoader", "MOD_001", { - modulePath = modulePath, - }) - end - - return stub - else - -- Required module is missing - throw error - error(string.format("Required module '%s' not found: %s", modulePath, tostring(result))) - end - end -end - ---- Check if a module is actually loaded (not a stub) ----@param modulePath string Full path to the module ----@return boolean isLoaded True if module is loaded, false if it's a stub or not loaded -function ModuleLoader.isModuleLoaded(modulePath) - local module = ModuleLoader._registry[modulePath] - if not module then - return false - end - - -- Check if it's a stub - return not module._isStub -end - ---- Get list of all loaded modules ----@return table modules List of module paths that are actually loaded (not stubs) -function ModuleLoader.getLoadedModules() - local loaded = {} - for path, module in pairs(ModuleLoader._registry) do - if not module._isStub then - table.insert(loaded, path) - end - end - return loaded -end - ---- Get list of all stub modules ----@return table stubs List of module paths that are stubs -function ModuleLoader.getStubModules() - local stubs = {} - for path, module in pairs(ModuleLoader._registry) do - if module._isStub then - table.insert(stubs, path) - end - end - return stubs -end - ---- Clear the module registry (useful for testing) -function ModuleLoader._clearRegistry() - ModuleLoader._registry = {} -end - -return ModuleLoader diff --git a/libs/flexlove/modules/NinePatch.lua b/libs/flexlove/modules/NinePatch.lua deleted file mode 100644 index 4adb9696..00000000 --- a/libs/flexlove/modules/NinePatch.lua +++ /dev/null @@ -1,217 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local ImageScaler = require(modulePath .. "ImageScaler") - -local NinePatch = {} - --- ErrorHandler will be injected via init -local ErrorHandler = nil - ---- Initialize NinePatch with dependencies ----@param deps table Dependencies table with ErrorHandler -function NinePatch.init(deps) - if deps and deps.ErrorHandler then - ErrorHandler = deps.ErrorHandler - end - -- Also initialize ImageScaler since it's a dependency - if ImageScaler.init then - ImageScaler.init(deps) - end -end - ---- Draw a 9-patch component using Android-style rendering ---- Corners are scaled by scaleCorners multiplier, edges stretch in one dimension only ----@param component ThemeComponent ----@param atlas love.Image ----@param x number -- X position (top-left corner) ----@param y number -- Y position (top-left corner) ----@param width number -- Total width (border-box) ----@param height number -- Total height (border-box) ----@param opacity number? ----@param elementScaleCorners number? -- Element-level override for scaleCorners (scale multiplier) ----@param elementScalingAlgorithm "nearest"|"bilinear"? -- Element-level override for scalingAlgorithm -function NinePatch.draw(component, atlas, x, y, width, height, opacity, elementScaleCorners, elementScalingAlgorithm) - if not component or not atlas then - return - end - - opacity = opacity or 1 - love.graphics.setColor(1, 1, 1, opacity) - - local regions = component.regions - - -- Extract border dimensions from regions (in pixels) - local left = regions.topLeft.w - local right = regions.topRight.w - local top = regions.topLeft.h - local bottom = regions.bottomLeft.h - local centerW = regions.middleCenter.w - local centerH = regions.middleCenter.h - - -- Calculate content area (space remaining after borders) - local contentWidth = width - left - right - local contentHeight = height - top - bottom - - -- Clamp to prevent negative dimensions - contentWidth = math.max(0, contentWidth) - contentHeight = math.max(0, contentHeight) - - -- Calculate stretch scales for edges and center - local scaleX = contentWidth / centerW - local scaleY = contentHeight / centerH - - -- Create quads for each region - local atlasWidth, atlasHeight = atlas:getDimensions() - - local function makeQuad(region) - return love.graphics.newQuad(region.x, region.y, region.w, region.h, atlasWidth, atlasHeight) - end - - -- Get corner scale multiplier - -- Priority: element-level override > component setting > default (nil = no scaling) - local scaleCorners = elementScaleCorners - if scaleCorners == nil then - scaleCorners = component.scaleCorners - end - - -- Priority: element-level override > component setting > default ("bilinear") - local scalingAlgorithm = elementScalingAlgorithm - if scalingAlgorithm == nil then - scalingAlgorithm = component.scalingAlgorithm or "bilinear" - end - - if scaleCorners and type(scaleCorners) == "number" and scaleCorners > 0 then - -- Initialize cache if needed - if not component._scaledRegionCache then - component._scaledRegionCache = {} - end - - -- Use the numeric scale multiplier directly - local scaleFactor = scaleCorners - - -- Helper to get or create scaled region - local function getScaledRegion(regionName, region, targetWidth, targetHeight) - local cacheKey = string.format("%s_%.2f_%s", regionName, scaleFactor, scalingAlgorithm) - - if component._scaledRegionCache[cacheKey] then - return component._scaledRegionCache[cacheKey] - end - - -- Get ImageData from component (stored during theme loading) - local atlasData = component._loadedAtlasData - if not atlasData then - ErrorHandler.error( - "NinePatch", - "REN_007", - "No ImageData available for atlas. Image must be loaded with safeLoadImage.", - { - componentType = component.type, - } - ) - end - - local scaledData - - if scalingAlgorithm == "nearest" then - scaledData = - ImageScaler.scaleNearest(atlasData, region.x, region.y, region.w, region.h, targetWidth, targetHeight) - else - scaledData = - ImageScaler.scaleBilinear(atlasData, region.x, region.y, region.w, region.h, targetWidth, targetHeight) - end - - -- Convert to image and cache - local scaledImage = love.graphics.newImage(scaledData) - component._scaledRegionCache[cacheKey] = scaledImage - - return scaledImage - end - - -- Calculate scaled dimensions for corners - local scaledLeft = math.floor(left * scaleFactor + 0.5) - local scaledRight = math.floor(right * scaleFactor + 0.5) - local scaledTop = math.floor(top * scaleFactor + 0.5) - local scaledBottom = math.floor(bottom * scaleFactor + 0.5) - - -- CORNERS (scaled using algorithm) - local topLeftScaled = getScaledRegion("topLeft", regions.topLeft, scaledLeft, scaledTop) - local topRightScaled = getScaledRegion("topRight", regions.topRight, scaledRight, scaledTop) - local bottomLeftScaled = getScaledRegion("bottomLeft", regions.bottomLeft, scaledLeft, scaledBottom) - local bottomRightScaled = getScaledRegion("bottomRight", regions.bottomRight, scaledRight, scaledBottom) - - love.graphics.draw(topLeftScaled, x, y) - love.graphics.draw(topRightScaled, x + width - scaledRight, y) - love.graphics.draw(bottomLeftScaled, x, y + height - scaledBottom) - love.graphics.draw(bottomRightScaled, x + width - scaledRight, y + height - scaledBottom) - - -- Update content dimensions to account for scaled borders - local adjustedContentWidth = width - scaledLeft - scaledRight - local adjustedContentHeight = height - scaledTop - scaledBottom - adjustedContentWidth = math.max(0, adjustedContentWidth) - adjustedContentHeight = math.max(0, adjustedContentHeight) - - -- Recalculate stretch scales - local adjustedScaleX = adjustedContentWidth / centerW - local adjustedScaleY = adjustedContentHeight / centerH - - -- TOP/BOTTOM EDGES (stretch horizontally, scale vertically) - if adjustedContentWidth > 0 then - local topCenterScaled = getScaledRegion("topCenter", regions.topCenter, regions.topCenter.w, scaledTop) - local bottomCenterScaled = - getScaledRegion("bottomCenter", regions.bottomCenter, regions.bottomCenter.w, scaledBottom) - - love.graphics.draw(topCenterScaled, x + scaledLeft, y, 0, adjustedScaleX, 1) - love.graphics.draw(bottomCenterScaled, x + scaledLeft, y + height - scaledBottom, 0, adjustedScaleX, 1) - end - - -- LEFT/RIGHT EDGES (stretch vertically, scale horizontally) - if adjustedContentHeight > 0 then - local middleLeftScaled = getScaledRegion("middleLeft", regions.middleLeft, scaledLeft, regions.middleLeft.h) - local middleRightScaled = getScaledRegion("middleRight", regions.middleRight, scaledRight, regions.middleRight.h) - - love.graphics.draw(middleLeftScaled, x, y + scaledTop, 0, 1, adjustedScaleY) - love.graphics.draw(middleRightScaled, x + width - scaledRight, y + scaledTop, 0, 1, adjustedScaleY) - end - - -- CENTER (stretch both dimensions, no scaling) - if adjustedContentWidth > 0 and adjustedContentHeight > 0 then - love.graphics.draw( - atlas, - makeQuad(regions.middleCenter), - x + scaledLeft, - y + scaledTop, - 0, - adjustedScaleX, - adjustedScaleY - ) - end - else - -- Original rendering logic (no scaling) - -- CORNERS (no scaling - 1:1 pixel perfect) - love.graphics.draw(atlas, makeQuad(regions.topLeft), x, y) - love.graphics.draw(atlas, makeQuad(regions.topRight), x + left + contentWidth, y) - love.graphics.draw(atlas, makeQuad(regions.bottomLeft), x, y + top + contentHeight) - love.graphics.draw(atlas, makeQuad(regions.bottomRight), x + left + contentWidth, y + top + contentHeight) - - -- TOP/BOTTOM EDGES (stretch horizontally only) - if contentWidth > 0 then - love.graphics.draw(atlas, makeQuad(regions.topCenter), x + left, y, 0, scaleX, 1) - love.graphics.draw(atlas, makeQuad(regions.bottomCenter), x + left, y + top + contentHeight, 0, scaleX, 1) - end - - -- LEFT/RIGHT EDGES (stretch vertically only) - if contentHeight > 0 then - love.graphics.draw(atlas, makeQuad(regions.middleLeft), x, y + top, 0, 1, scaleY) - love.graphics.draw(atlas, makeQuad(regions.middleRight), x + left + contentWidth, y + top, 0, 1, scaleY) - end - - -- CENTER (stretch both dimensions) - if contentWidth > 0 and contentHeight > 0 then - love.graphics.draw(atlas, makeQuad(regions.middleCenter), x + left, y + top, 0, scaleX, scaleY) - end - end - - -- Reset color - love.graphics.setColor(1, 1, 1, 1) -end - -return NinePatch diff --git a/libs/flexlove/modules/NumberValidation.lua b/libs/flexlove/modules/NumberValidation.lua deleted file mode 100644 index 94124e35..00000000 --- a/libs/flexlove/modules/NumberValidation.lua +++ /dev/null @@ -1,351 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local function req(name) - return require(modulePath .. name) -end - --- All numeric, range, type, and enum validation lives here. --- `clamp` is injected via init() to avoid a cross-import into utils. --- `ErrorHandler` is injected via init() so error reporting routes through --- the shared handler (matching the pre-split behavior of utils.validate*). - -local ErrorHandler = nil -local clamp = nil - ---- Initialize dependencies ----@param deps table Dependencies: { ErrorHandler = table, clamp = function } -local function init(deps) - if type(deps) == "table" then - ErrorHandler = deps.ErrorHandler or ErrorHandler - clamp = deps.clamp or clamp - end -end - --- Numeric validation utilities - ---- Check if a value is NaN (not-a-number) ---- @param value any Value to check ---- @return boolean -local function isNaN(value) - return type(value) == "number" and value ~= value -end - ---- Check if a value is Infinity ---- @param value any Value to check ---- @return boolean -local function isInfinity(value) - return type(value) == "number" and (value == math.huge or value == -math.huge) -end - ---- Validate a numeric value with comprehensive checks ---- @param value any Value to validate ---- @param options table? Validation options ---- @return boolean, string?, number? Returns valid, errorMessage, sanitizedValue -local function validateNumber(value, options) - options = options or {} - - -- Check if value is a number type - if type(value) ~= "number" then - if options.default ~= nil then - return true, nil, options.default - end - return false, string.format("Value must be a number, got %s", type(value)), nil - end - - -- Check for NaN - if isNaN(value) then - if not options.allowNaN then - if options.default ~= nil then - return true, nil, options.default - end - return false, "Value is NaN (not-a-number)", nil - end - end - - -- Check for Infinity - if isInfinity(value) then - if not options.allowInfinity then - if options.default ~= nil then - return true, nil, options.default - end - return false, "Value is Infinity", nil - end - end - - -- Check for integer requirement - if options.integer and math.floor(value) ~= value then - return false, string.format("Value must be an integer, got %s", value), nil - end - - -- Check for positive requirement - if options.positive and value <= 0 then - return false, string.format("Value must be positive, got %s", value), nil - end - - -- Check bounds - if options.min and value < options.min then - return false, string.format("Value %s is below minimum %s", value, options.min), nil - end - - if options.max and value > options.max then - return false, string.format("Value %s is above maximum %s", value, options.max), nil - end - - return true, nil, value -end - ---- Sanitize a numeric value (never errors, always returns valid number) ---- @param value any Value to sanitize ---- @param min number? Minimum value ---- @param max number? Maximum value ---- @param default number? Default value for invalid inputs ---- @return number Sanitized value -local function sanitizeNumber(value, min, max, default) - default = default or 0 - min = min or -math.huge - max = max or math.huge - - -- Convert to number if possible - if type(value) == "string" then - value = tonumber(value) - end - - -- Handle non-numeric - if type(value) ~= "number" then - return default - end - - -- Handle NaN - if isNaN(value) then - return default - end - - -- Handle Infinity - if value == math.huge then - return max - end - if value == -math.huge then - return min - end - - -- Clamp to range - return clamp(value, min, max) -end - ---- Validate and convert to integer ---- @param value any Value to validate ---- @param min number? Minimum value ---- @param max number? Maximum value ---- @return boolean, string?, number? Returns valid, errorMessage, integerValue -local function validateInteger(value, min, max) - local valid, err, sanitized = validateNumber(value, { - min = min, - max = max, - integer = true, - }) - - if not valid then - return false, err, nil - end - - return true, nil, math.floor(sanitized or value) -end - ---- Validate and normalize percentage value ---- @param value any Value to validate (can be "50%", 0.5, or 50) ---- @return boolean, string?, number? Returns valid, errorMessage, normalizedValue (0-1) -local function validatePercentage(value) - -- Handle string percentage - if type(value) == "string" then - local num = value:match("^(%d+%.?%d*)%%$") - if num then - value = tonumber(num) - if value then - value = value / 100 - end - else - value = tonumber(value) - end - end - - if type(value) ~= "number" then - return false, "Percentage must be a number", nil - end - - if isNaN(value) or isInfinity(value) then - return false, "Percentage cannot be NaN or Infinity", nil - end - - -- If value is > 1, assume it's 0-100 range - if value > 1 then - value = value / 100 - end - - -- Clamp to 0-1 - value = clamp(value, 0, 1) - - return true, nil, value -end - ---- Validate opacity value (0-1) ---- @param value any Value to validate ---- @return boolean, string?, number? Returns valid, errorMessage, opacityValue -local function validateOpacity(value) - return validateNumber(value, { min = 0, max = 1, default = 1 }) -end - ---- Validate degree value (0-360) ---- @param value any Value to validate ---- @return boolean, string?, number? Returns valid, errorMessage, degreeValue -local function validateDegrees(value) - local valid, err, sanitized = validateNumber(value) - if not valid then - return false, err, nil - end - - -- Normalize to 0-360 range - local degrees = sanitized or value - degrees = degrees % 360 - if degrees < 0 then - degrees = degrees + 360 - end - - return true, nil, degrees -end - ---- Validate coordinate value (pixel position) ---- @param value any Value to validate ---- @return boolean, string?, number? Returns valid, errorMessage, coordinateValue -local function validateCoordinate(value) - return validateNumber(value, { - allowNaN = false, - allowInfinity = false, - }) -end - ---- Validate dimension value (width/height, must be non-negative) ---- @param value any Value to validate ---- @return boolean, string?, number? Returns valid, errorMessage, dimensionValue -local function validateDimension(value) - return validateNumber(value, { - min = 0, - allowNaN = false, - allowInfinity = false, - }) -end - ---- Validate that a value is in an enum table ----@param value any Value to validate ----@param enumTable table Enum table with valid values ----@param propName string Property name for error messages ----@param moduleName string? Module name for error messages (default: "Element") ----@return boolean True if valid -local function validateEnum(value, enumTable, propName, moduleName) - if value == nil then - return true - end - - for _, validValue in pairs(enumTable) do - if value == validValue then - return true - end - end - - -- Build list of valid options - local validOptions = {} - for _, v in pairs(enumTable) do - table.insert(validOptions, "'" .. v .. "'") - end - table.sort(validOptions) - - if ErrorHandler then - ErrorHandler:error(moduleName or "Element", "VAL_007", { - property = propName, - expected = table.concat(validOptions, ", "), - got = tostring(value), - }) - else - error( - string.format("%s must be one of: %s. Got: '%s'", propName, table.concat(validOptions, ", "), tostring(value)) - ) - end -end - ---- Validate that a numeric value is within a range ----@param value any Value to validate ----@param min number Minimum allowed value ----@param max number Maximum allowed value ----@param propName string Property name for error messages ----@param moduleName string? Module name for error messages (default: "Element") ----@return boolean True if valid -local function validateRange(value, min, max, propName, moduleName) - if value == nil then - return true - end - if type(value) ~= "number" then - if ErrorHandler then - ErrorHandler:error(moduleName or "Element", "VAL_001", { - property = propName, - expected = "number", - got = type(value), - }) - else - error(string.format("%s must be a number, got %s", propName, type(value))) - end - elseif value < min or value > max then - if ErrorHandler then - ErrorHandler:error(moduleName or "Element", "VAL_002", { - property = propName, - min = tostring(min), - max = tostring(max), - value = tostring(value), - }) - else - error( - string.format("%s must be between %s and %s, got %s", propName, tostring(min), tostring(max), tostring(value)) - ) - end - end - return true -end - ---- Validate that a value is of the expected type ----@param value any Value to validate ----@param expectedType string Expected type name ----@param propName string Property name for error messages ----@param moduleName string? Module name for error messages (default: "Element") ----@return boolean True if valid -local function validateType(value, expectedType, propName, moduleName) - if value == nil then - return true - end - local actualType = type(value) - if actualType ~= expectedType then - if ErrorHandler then - ErrorHandler:error(moduleName or "Element", "VAL_001", { - property = propName, - expected = expectedType, - got = actualType, - }) - else - error(string.format("%s must be %s, got %s", propName, expectedType, actualType)) - end - end - return true -end - -return { - init = init, - isNaN = isNaN, - isInfinity = isInfinity, - validateNumber = validateNumber, - sanitizeNumber = sanitizeNumber, - validateInteger = validateInteger, - validatePercentage = validatePercentage, - validateOpacity = validateOpacity, - validateDegrees = validateDegrees, - validateCoordinate = validateCoordinate, - validateDimension = validateDimension, - validateEnum = validateEnum, - validateRange = validateRange, - validateType = validateType, -} diff --git a/libs/flexlove/modules/PathValidator.lua b/libs/flexlove/modules/PathValidator.lua deleted file mode 100644 index b3ba0e81..00000000 --- a/libs/flexlove/modules/PathValidator.lua +++ /dev/null @@ -1,198 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local function req(name) - return require(modulePath .. name) -end - --- Path sanitization, validation, and file-extension helpers. --- Uses love.filesystem when available (optional) for existence checks. - ---- Normalize a file path for consistent cache keys ----@param path string File path to normalize ----@return string Normalized path -local function normalizePath(path) - path = path:match("^%s*(.-)%s*$") - path = path:gsub("\\", "/") - path = path:gsub("/+", "/") - return path -end - ---- Sanitize a file path ---- @param path string Path to sanitize ---- @return string Sanitized path -local function sanitizePath(path) - if path == nil then - return "" - end - path = tostring(path) - - -- Trim whitespace - path = path:match("^%s*(.-)%s*$") or "" - - -- Normalize separators to forward slash - path = path:gsub("\\", "/") - - -- Remove duplicate slashes - path = path:gsub("/+", "/") - - -- Remove trailing slash (except for root) - if #path > 1 and path:sub(-1) == "/" then - path = path:sub(1, -2) - end - - return path -end - ---- Check if a path is safe (no traversal attacks) ---- @param path string Path to check ---- @param baseDir string? Base directory to check against (optional) ---- @return boolean, string? Returns true if safe, or false with reason -local function isPathSafe(path, baseDir) - if path == nil or path == "" then - return false, "Path is empty" - end - - -- Sanitize the path - path = sanitizePath(path) - - -- Check for suspicious patterns - if path:match("%.%.") then - return false, "Path contains '..' (parent directory reference)" - end - - -- Check for null bytes - if path:match("%z") then - return false, "Path contains null bytes" - end - - -- Check for encoded traversal attempts (including double-encoding) - local lowerPath = path:lower() - if - lowerPath:match("%%2e") - or lowerPath:match("%%2f") - or lowerPath:match("%%5c") - or lowerPath:match("%%252e") - or lowerPath:match("%%252f") - or lowerPath:match("%%255c") - then - return false, "Path contains URL-encoded directory separators" - end - - -- If baseDir is provided, ensure path is within it - if baseDir then - baseDir = sanitizePath(baseDir) - - -- For relative paths, prepend baseDir - local fullPath = path - if not path:match("^/") and not path:match("^%a:") then - fullPath = baseDir .. "/" .. path - end - fullPath = sanitizePath(fullPath) - - -- Check if fullPath starts with baseDir - if not fullPath:match("^" .. baseDir:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")) then - return false, "Path is outside allowed directory" - end - end - - return true, nil -end - ---- Validate a file path with comprehensive checks ---- @param path string Path to validate ---- @param options table? Validation options ---- @return boolean, string? Returns true if valid, or false with error message -local function validatePath(path, options) - options = options or {} - - -- Check path is not nil/empty - if path == nil or path == "" then - return false, "Path is empty" - end - - path = tostring(path) - - -- Check maximum length - local maxLength = options.maxLength or 4096 - if #path > maxLength then - return false, string.format("Path exceeds maximum length of %d characters", maxLength) - end - - -- Sanitize path - path = sanitizePath(path) - - -- Check for safety (traversal attacks) - local safe, reason = isPathSafe(path, options.baseDir) - if not safe then - return false, reason - end - - -- Check allowed extensions - if options.allowedExtensions then - local ext = path:match("%.([^%.]+)$") - if not ext then - return false, "Path has no file extension" - end - - ext = ext:lower() - local allowed = false - for _, allowedExt in ipairs(options.allowedExtensions) do - if ext == allowedExt:lower() then - allowed = true - break - end - end - - if not allowed then - return false, string.format("File extension '%s' is not allowed", ext) - end - end - - -- Check if file must exist - if options.mustExist and love and love.filesystem then - local info = love.filesystem.getInfo(path) - if not info then - return false, "File does not exist" - end - end - - return true, nil -end - ---- Get file extension from path ---- @param path string File path ---- @return string? extension File extension (lowercase) or nil -local function getFileExtension(path) - if not path then - return nil - end - local ext = path:match("%.([^%.]+)$") - return ext and ext:lower() or nil -end - ---- Check if path has allowed extension ---- @param path string File path ---- @param allowedExtensions table Array of allowed extensions ---- @return boolean -local function hasAllowedExtension(path, allowedExtensions) - local ext = getFileExtension(path) - if not ext then - return false - end - - for _, allowedExt in ipairs(allowedExtensions) do - if ext == allowedExt:lower() then - return true - end - end - - return false -end - -return { - normalizePath = normalizePath, - sanitizePath = sanitizePath, - isPathSafe = isPathSafe, - validatePath = validatePath, - getFileExtension = getFileExtension, - hasAllowedExtension = hasAllowedExtension, -} diff --git a/libs/flexlove/modules/Performance.lua b/libs/flexlove/modules/Performance.lua deleted file mode 100644 index cf0458f0..00000000 --- a/libs/flexlove/modules/Performance.lua +++ /dev/null @@ -1,560 +0,0 @@ ----@class Performance ----@field enabled boolean ----@field hudEnabled boolean ----@field hudToggleKey string ----@field hudPosition {x: number, y: number} ----@field warningThresholdMs number ----@field criticalThresholdMs number ----@field logToConsole boolean ----@field logWarnings boolean ----@field warningsEnabled boolean ----@field _ErrorHandler table? ----@field _timers table ----@field _metrics table ----@field _lastMetricsCleanup number ----@field _frameMetrics table ----@field _memoryMetrics table ----@field _warnings table ----@field _lastFrameStart number? ----@field _shownWarnings table ----@field _memoryProfiler table -local Performance = {} -Performance.__index = Performance - ----@type Performance|nil -local instance = nil - -local METRICS_CLEANUP_INTERVAL = 30 -local METRICS_RETENTION_TIME = 10 -local MAX_METRICS_COUNT = 500 -local CORE_METRICS = { frame = true, layout = true, render = true } - ----@param config {enabled?: boolean, hudEnabled?: boolean, hudToggleKey?: string, hudPosition?: {x: number, y: number}, warningThresholdMs?: number, criticalThresholdMs?: number, logToConsole?: boolean, logWarnings?: boolean, warningsEnabled?: boolean, memoryProfiling?: boolean}? ----@param deps {ErrorHandler: ErrorHandler} ----@return Performance -function Performance.init(config, deps) - if instance == nil then - local self = setmetatable({}, Performance) - - -- Configuration - self.enabled = config and config.enabled or false - self.hudEnabled = config and config.hudEnabled or false - self.hudToggleKey = config and config.hudToggleKey or "f3" - self.hudPosition = config and config.hudPosition or { x = 10, y = 10 } - self.warningThresholdMs = config and config.warningThresholdMs or 13.0 - self.criticalThresholdMs = config and config.criticalThresholdMs or 16.67 - self.logToConsole = config and config.logToConsole or false - self.logWarnings = config and config.logWarnings or true - self.warningsEnabled = config and config.warningsEnabled or true - - self._timers = {} - self._metrics = {} - self._lastMetricsCleanup = 0 - self._frameMetrics = { - frameCount = 0, - totalTime = 0, - lastFrameTime = 0, - minFrameTime = math.huge, - maxFrameTime = 0, - fps = 0, - lastFpsUpdate = 0, - fpsUpdateInterval = 0.5, - } - self._memoryMetrics = { - current = 0, - peak = 0, - gcCount = 0, - lastGcCheck = 0, - } - self._warnings = {} - self._lastFrameStart = nil - self._shownWarnings = {} - self._memoryProfiler = { - enabled = config and config.memoryProfiling or false, - sampleInterval = 60, - framesSinceLastSample = 0, - samples = {}, - maxSamples = 20, - monitoredTables = {}, - } - self._ErrorHandler = deps and deps.ErrorHandler - instance = self - end - return instance -end - ---- Toggle HUD visibility -function Performance:toggleHUD() - self.hudEnabled = not self.hudEnabled -end - -function Performance:startTimer(name) - if not self.enabled then - return - end - self._timers[name] = love.timer.getTime() -end - -function Performance:stopTimer(name) - if not self.enabled then - return nil - end - - local startTime = self._timers[name] - if not startTime then - -- Silently return nil if timer wasn't started - -- This can happen legitimately when Performance is toggled mid-frame - -- or when layout functions have early returns - return nil - end - - local elapsed = (love.timer.getTime() - startTime) * 1000 - self._timers[name] = nil - - -- Update metrics - if not self._metrics[name] then - self._metrics[name] = { - total = 0, - count = 0, - min = math.huge, - max = 0, - average = 0, - lastUsed = love.timer.getTime(), - } - end - - local m = self._metrics[name] - m.total = m.total + elapsed - m.count = m.count + 1 - m.min = math.min(m.min, elapsed) - m.max = math.max(m.max, elapsed) - m.average = m.total / m.count - m.lastUsed = love.timer.getTime() - - -- Check for warnings - if elapsed > self.criticalThresholdMs then - self:_addWarning(name, elapsed, "critical") - elseif elapsed > self.warningThresholdMs then - self:_addWarning(name, elapsed, "warning") - end - - if self.logToConsole then - -- Use ErrorHandler if available, otherwise fall back to print - if self._ErrorHandler and self._ErrorHandler.warn then - self._ErrorHandler:warn("Performance", "PERF_001", { - metric = name, - elapsed = string.format("%.3fms", elapsed), - }) - else - print(string.format("[Performance] %s: %.3fms", name, elapsed)) - end - end - - return elapsed -end - ---- Update with actual delta time from LÖVE (call from love.update) ----@param dt number Delta time in seconds -function Performance:updateDeltaTime(dt) - if not self.enabled then - return - end - local now = love.timer.getTime() - if now - self._frameMetrics.lastFpsUpdate >= self._frameMetrics.fpsUpdateInterval then - if dt > 0 then - self._frameMetrics.fps = math.floor(1 / dt + 0.5) - end - self._frameMetrics.lastFpsUpdate = now - end -end - ---- Start frame timing (call at beginning of frame) -function Performance:startFrame() - if not self.enabled then - return - end - self._lastFrameStart = love.timer.getTime() - self:_updateMemory() -end - -function Performance:endFrame() - if not self.enabled or not self._lastFrameStart then - return - end - - local now = love.timer.getTime() - local frameTime = (now - self._lastFrameStart) * 1000 - - self._frameMetrics.lastFrameTime = frameTime - self._frameMetrics.totalTime = self._frameMetrics.totalTime + frameTime - self._frameMetrics.frameCount = self._frameMetrics.frameCount + 1 - self._frameMetrics.minFrameTime = math.min(self._frameMetrics.minFrameTime, frameTime) - self._frameMetrics.maxFrameTime = math.max(self._frameMetrics.maxFrameTime, frameTime) - - if frameTime > self.criticalThresholdMs then - self:_addWarning("frame", frameTime, "critical") - end - - self:updateMemoryProfiling() - - -- Periodic metrics cleanup - if now - self._lastMetricsCleanup >= METRICS_CLEANUP_INTERVAL then - local cleanupTime = now - METRICS_RETENTION_TIME - for name, data in pairs(self._metrics) do - if not CORE_METRICS[name] and data.lastUsed and data.lastUsed < cleanupTime then - self._metrics[name] = nil - end - end - self._lastMetricsCleanup = now - end - - -- Enforce max metrics limit - local metricsCount = 0 - for _ in pairs(self._metrics) do - metricsCount = metricsCount + 1 - end - - if metricsCount > MAX_METRICS_COUNT then - local sortedMetrics = {} - for name, data in pairs(self._metrics) do - if not CORE_METRICS[name] then - table.insert(sortedMetrics, { name = name, lastUsed = data.lastUsed or 0 }) - end - end - - table.sort(sortedMetrics, function(a, b) - return a.lastUsed < b.lastUsed - end) - - local toRemove = metricsCount - MAX_METRICS_COUNT - for i = 1, math.min(toRemove, #sortedMetrics) do - self._metrics[sortedMetrics[i].name] = nil - end - end -end - ---- Update memory metrics -function Performance:_updateMemory() - if not self.enabled then - return - end - - local memKb = collectgarbage("count") - self._memoryMetrics.current = memKb - self._memoryMetrics.peak = math.max(self._memoryMetrics.peak, memKb) - - local now = love.timer.getTime() - if now - self._memoryMetrics.lastGcCheck >= 1.0 then - self._memoryMetrics.gcCount = self._memoryMetrics.gcCount + 1 - self._memoryMetrics.lastGcCheck = now - end -end - ---- Add a performance warning (private) ---- @param name string Metric name ---- @param value number Metric value ---- @param level "warning"|"critical" Warning level -function Performance:_addWarning(name, value, level) - if not self.logWarnings then - return - end - - local warning = { - name = name, - value = value, - level = level, - time = love.timer.getTime(), - } - - table.insert(self._warnings, warning) - - if #self._warnings > 100 then - table.remove(self._warnings, 1) - end - - if self.logToConsole or self.warningsEnabled then - local warningKey = name .. "_" .. level - local lastWarningTime = self._shownWarnings[warningKey] or 0 - local now = love.timer.getTime() - - if now - lastWarningTime >= 60 then - if self._ErrorHandler and self._ErrorHandler.warn then - local code = level == "critical" and "PERF_002" or "PERF_001" - - self._ErrorHandler:warn("Performance", code, { - metric = name, - value = string.format("%.2fms", value), - threshold = level == "critical" and self.criticalThresholdMs or self.warningThresholdMs, - }) - end - - self._shownWarnings[warningKey] = now - end - end -end - ---- Render performance HUD ---- @param x number? X position (default: 10) ---- @param y number? Y position (default: 10) -function Performance:renderHUD(x, y) - if not self.hudEnabled then - return - end - - x = x or self.hudPosition.x - y = y or self.hudPosition.y - - self:_updateMemory() - - local fm = self._frameMetrics - local mm = self._memoryMetrics - - love.graphics.setColor(0, 0, 0, 0.8) - love.graphics.rectangle("fill", x, y, 300, 220) - - love.graphics.setColor(1, 1, 1, 1) - local lineHeight = 18 - local currentY = y + 10 - - -- FPS - local fpsColor = { 1, 1, 1 } - if fm.lastFrameTime > self.criticalThresholdMs then - fpsColor = { 1, 0, 0 } - elseif fm.lastFrameTime > self.warningThresholdMs then - fpsColor = { 1, 1, 0 } - end - love.graphics.setColor(fpsColor) - love.graphics.print(string.format("FPS: %d (%.2fms)", fm.fps, fm.lastFrameTime), x + 10, currentY) - currentY = currentY + lineHeight - - love.graphics.setColor(1, 1, 1, 1) - local avgFrame = fm.frameCount > 0 and fm.totalTime / fm.frameCount or 0 - love.graphics.print(string.format("Avg Frame: %.2fms", avgFrame), x + 10, currentY) - currentY = currentY + lineHeight - love.graphics.print(string.format("Min/Max: %.2f/%.2fms", fm.minFrameTime, fm.maxFrameTime), x + 10, currentY) - currentY = currentY + lineHeight - - local currentMb = mm.current / 1024 - local peakMb = mm.peak / 1024 - love.graphics.print(string.format("Memory: %.2f MB (peak: %.2f MB)", currentMb, peakMb), x + 10, currentY) - currentY = currentY + lineHeight - - local metricsCount = 0 - for _ in pairs(self._metrics) do - metricsCount = metricsCount + 1 - end - local metricsColor = metricsCount > MAX_METRICS_COUNT * 0.8 and { 1, 0.5, 0 } or { 1, 1, 1 } - love.graphics.setColor(metricsColor) - love.graphics.print(string.format("Metrics: %d/%d", metricsCount, MAX_METRICS_COUNT), x + 10, currentY) - currentY = currentY + lineHeight + 5 - - -- Top timings - love.graphics.setColor(1, 1, 1, 1) - local sortedMetrics = {} - for name, data in pairs(self._metrics) do - table.insert(sortedMetrics, { name = name, average = data.average }) - end - table.sort(sortedMetrics, function(a, b) - return a.average > b.average - end) - - love.graphics.print("Top Timings:", x + 10, currentY) - currentY = currentY + lineHeight - - for i = 1, math.min(5, #sortedMetrics) do - local m = sortedMetrics[i] - love.graphics.print(string.format(" %s: %.3fms", m.name, m.average), x + 10, currentY) - currentY = currentY + lineHeight - end - - if #self._warnings > 0 then - love.graphics.setColor(1, 0.5, 0, 1) - love.graphics.print(string.format("Warnings: %d", #self._warnings), x + 10, currentY) - end -end - ---- Handle keyboard input for HUD toggle ---- @param key string Key pressed -function Performance:keypressed(key) - if key == self.hudToggleKey then - self:toggleHUD() - end -end - ---- Log a performance warning (only once per warning key) ---- @param warningKey string Unique key for this warning type ---- @param module string Module name (e.g., "LayoutEngine", "Element") ---- @param message string Warning message ---- @param details table? Additional details ---- @param suggestion string? Optimization suggestion -function Performance:logWarning(warningKey, module, message, details, suggestion) - if not self.warningsEnabled then - return - end - - if self._shownWarnings[warningKey] then - return - end - - self._shownWarnings[warningKey] = true - - local count = 0 - for _ in pairs(self._shownWarnings) do - count = count + 1 - end - if count > 1000 then - self._shownWarnings = { [warningKey] = true } - end - - if self._ErrorHandler and self._ErrorHandler.warn then - self._ErrorHandler:warn(module, "PERF_001", details or {}) - end -end - ---- Track a counter metric (increments per frame) ---- @param name string Counter name ---- @param value number? Value to add (default: 1) -function Performance:incrementCounter(name, value) - if not self.enabled then - return - end - - value = value or 1 - - if not self._metrics[name] then - self._metrics[name] = { - total = 0, - count = 0, - min = math.huge, - max = 0, - average = 0, - frameValue = 0, - lastUsed = love.timer.getTime(), - } - end - - local m = self._metrics[name] - m.frameValue = (m.frameValue or 0) + value - m.lastUsed = love.timer.getTime() -end - ---- Reset frame counters (call at end of frame) -function Performance:resetFrameCounters() - if not self.enabled then - return - end - - local now = love.timer.getTime() - local toRemove = {} - - for name, data in pairs(self._metrics) do - if data.frameValue then - if data.frameValue > 0 then - data.total = data.total + data.frameValue - data.count = data.count + 1 - data.min = math.min(data.min, data.frameValue) - data.max = math.max(data.max, data.frameValue) - data.average = data.total / data.count - data.lastUsed = now - end - - data.frameValue = 0 - - if data.count == 0 and not CORE_METRICS[name] then - table.insert(toRemove, name) - end - end - end - - for _, name in ipairs(toRemove) do - self._metrics[name] = nil - end -end - ---- Register a table for memory leak monitoring ---- @param name string Friendly name for the table ---- @param tableRef table Reference to the table to monitor -function Performance:registerTableForMonitoring(name, tableRef) - self._memoryProfiler.monitoredTables[name] = tableRef -end - -function Performance:_sampleMemory() - local sample = { - time = love.timer.getTime(), - memory = collectgarbage("count") / 1024, -- MB - tableSizes = {}, - } - local function getTableSize(tbl) - local count = 0 - for _ in pairs(tbl) do - count = count + 1 - end - return count - end - - for name, tableRef in pairs(self._memoryProfiler.monitoredTables) do - sample.tableSizes[name] = getTableSize(tableRef) - end - - table.insert(self._memoryProfiler.samples, sample) - - -- Keep only maxSamples - if #self._memoryProfiler.samples > self._memoryProfiler.maxSamples then - table.remove(self._memoryProfiler.samples, 1) - end - - -- Check for memory leaks (consistent growth) - if #self._memoryProfiler.samples >= 5 then - for name, _ in pairs(self._memoryProfiler.monitoredTables) do - local sizes = {} - for i = math.max(1, #self._memoryProfiler.samples - 4), #self._memoryProfiler.samples do - table.insert(sizes, self._memoryProfiler.samples[i].tableSizes[name]) - end - - -- Check if table is consistently growing - local growing = true - for i = 2, #sizes do - if sizes[i] <= sizes[i - 1] then - growing = false - break - end - end - - if growing and sizes[#sizes] > sizes[1] * 1.5 then - self:_addWarning("memory_leak", sizes[#sizes], "warning") - - if not self._shownWarnings[name] then - local message = string.format("Table '%s' growing consistently", name) - if self._ErrorHandler and self._ErrorHandler.warn then - self._ErrorHandler:warn("Performance", "MEM_001", { - table = name, - initialSize = sizes[1], - currentSize = sizes[#sizes], - growthPercent = math.floor(((sizes[#sizes] / sizes[1]) - 1) * 100), - }) - end - - self._shownWarnings[name] = true - end - elseif not growing then - self._shownWarnings[name] = nil - end - end - end -end - ---- Update memory profiling (call from endFrame) -function Performance:updateMemoryProfiling() - if not self._memoryProfiler.enabled then - return - end - - self._memoryProfiler.framesSinceLastSample = self._memoryProfiler.framesSinceLastSample + 1 - - if self._memoryProfiler.framesSinceLastSample >= self._memoryProfiler.sampleInterval then - self:_sampleMemory() - self._memoryProfiler.framesSinceLastSample = 0 - end -end - -return Performance diff --git a/libs/flexlove/modules/PropertySchema.lua b/libs/flexlove/modules/PropertySchema.lua deleted file mode 100644 index d2fe5b43..00000000 --- a/libs/flexlove/modules/PropertySchema.lua +++ /dev/null @@ -1,505 +0,0 @@ --- modules/PropertySchema.lua --- --- Declarative source of truth for every Element prop. --- --- Each entry describes one prop that Element.new / Element:setProperty currently --- handles inline. Downstream tasks (03 data-driven prop binding, 05 registry-driven --- setProperty dispatch) read this metadata instead of hardcoding property names. --- --- Design constraints (locked — tasks 03/05 depend on this API): --- * Pure Lua — NO `love` import, NO dependency on utils/Color/Units/ErrorHandler. --- Normalizers/validators are small, dependency-free closures so the module is --- unit-testable standalone. Color/^/unit/enum *defaults* that require those --- modules are left as `nil` here and applied by construction-time special --- handlers in Task 03; only defaults expressible as literals are stored. --- * O(1) lookup — `get(name)` is a single table index into a pre-built registry; --- no per-call construction. --- * Additive — `define(specs)` merges entries by name so build profiles can --- extend/override without rebuilding the whole table. --- --- Metadata shape per prop (all fields present, false/nil when not applicable): --- type string — type tag for tooling ("number"|"string"|"boolean"| --- "table"|"function"|"color"|"any") --- default any|nil — literal default value applied when prop is absent --- normalizer fn|nil — pure fn(value) -> value; transforms input before --- storage (e.g. single-value padding -> 4-side table) --- validator fn|nil — pure fn(value) -> bool; returns false for invalid --- input (Task 03 warns + falls back on false) --- isDimension boolean — true for width/height: setProperty routes these --- through _resolveDimensionProperty (unit-string --- resolution + border-box sync). Other unit-accepting --- props (x/y/gap/padding/etc.) are resolved at --- construction via special handlers, NOT via this flag. --- affectsLayout boolean — true for props in the legacy setProperty --- `layoutProperties` table; setting one invalidates --- layout (matches baseline behavior exactly). --- syncsTheme boolean — true for props whose setProperty path must reach --- ThemeManager/Renderer (disabled/active/themeComponent) --- hasDeferred boolean — true for callbacks that have an `onDeferred` --- boolean companion prop (auto-wired by Task 03) --- storageKey string|nil— when set, the prop is stored on the element under --- this key instead of its own name (prop aliases, e.g. --- isDisabled -> stored as `disabled`) - -local PropertySchema = {} - ----@type table -local registry = {} - --- --------------------------------------------------------------------------- --- Pure normalizers (small + dependency-free; hot-pathed during construction) --- --------------------------------------------------------------------------- - ---- Expand a single value to a 4-side table. Leaves tables unchanged. nil passthrough. ---- Used by padding/margin: `padding = 5` -> `{top=5,right=5,bottom=5,left=5}`. -local function expandSides(value) - if value == nil then - return nil - end - if type(value) == "table" then - return value - end - return { top = value, right = value, bottom = value, left = value } -end - ---- Normalize flex direction aliases to internal enum names. ---- "row" -> "horizontal", "column" -> "vertical", ---- "row-reverse" -> "horizontal-reverse", "column-reverse" -> "vertical-reverse"; ---- everything else passes through. -local function normalizeFlexDirection(value) - if value == "row" then - return "horizontal" - elseif value == "column" then - return "vertical" - elseif value == "row-reverse" then - return "horizontal-reverse" - elseif value == "column-reverse" then - return "vertical-reverse" - end - return value -end - ---- Replicate Element.new's border-shape normalization (pure). ---- * table with sides: true -> 1, number -> value, false/nil -> false; nil if no ---- truthy side remains. ---- * number / other truthy scalar: kept as-is. ---- * nil / false: nil. -local function normalizeBorder(value) - if value == nil or value == false then - return nil - end - if type(value) == "table" then - local function side(v) - if v == true then - return 1 - elseif type(v) == "number" then - return v - else - return false - end - end - local t = side(value.top) - local r = side(value.right) - local b = side(value.bottom) - local l = side(value.left) - if not (t or r or b or l) then - return nil - end - return { top = t, right = r, bottom = b, left = l } - end - return value -end - ---- Replicate Element.new's cornerRadius-shape normalization (pure). ---- * number: 0 -> nil, else the number. ---- * table: nil if all four sides are zero/absent, else fill zeros for absent sides. ---- * nil -> nil. -local function normalizeCornerRadius(value) - if value == nil then - return nil - end - if type(value) == "number" then - if value == 0 then - return nil - end - return value - end - if type(value) == "table" then - -- Mirrors Element.new: `or` truthiness (0 is truthy in Lua). Only an all- - -- nil/false table collapses to nil; any present side — including 0 — yields - -- the 4-side table with zero-filled absent sides. - local hasAny = value.topLeft or value.topRight or value.bottomLeft or value.bottomRight - if not hasAny then - return nil - end - return { - topLeft = value.topLeft or 0, - topRight = value.topRight or 0, - bottomLeft = value.bottomLeft or 0, - bottomRight = value.bottomRight or 0, - } - end - return value -end - --- --------------------------------------------------------------------------- --- Pure validators (dependency-free; return boolean) --- --------------------------------------------------------------------------- - ---- Range validator factory: returns fn(v) -> bool. nil is treated as valid ---- (absence handling is the default mechanism's job). -local function rangeValidator(min, max) - return function(v) - if v == nil then - return true - end - return type(v) == "number" and v >= min and v <= max - end -end - ---- Enum validator factory: returns fn(v) -> bool for membership in `set` (set may ---- be an array or a map of value->truthy). -local function enumValidator(set) - local lookup = {} - if type(set) == "table" then - for k, v in pairs(set) do - if type(k) == "number" then - lookup[v] = true - else - lookup[k] = true - end - end - end - return function(v) - if v == nil then - return true - end - return lookup[v] == true - end -end - ---- Boolean validator: nil is valid (absence); otherwise must be a boolean. -local function booleanValidator(v) - return v == nil or type(v) == "boolean" -end - --- --------------------------------------------------------------------------- --- Registry construction --- --------------------------------------------------------------------------- - ---- Build a fully-populated metadata entry, filling omitted fields with defaults. -local function entry(spec) - return { - type = spec.type or "any", - default = spec.default, - normalizer = spec.normalizer, - validator = spec.validator, - isDimension = spec.isDimension == true, - affectsLayout = spec.affectsLayout == true, - syncsTheme = spec.syncsTheme == true, - hasDeferred = spec.hasDeferred == true, - storageKey = spec.storageKey, - } -end - ---- Merge prop specs into the registry (additive; later entries override earlier). ----@param specs table map of prop-name -> spec ----@return table registry the live registry table (for chaining/inspection) -function PropertySchema.define(specs) - for name, spec in pairs(specs) do - registry[name] = entry(spec) - end - return registry -end - ---- O(1) metadata lookup. ----@param name string prop name ----@return table|nil metadata nil for unknown props (no error) -function PropertySchema.get(name) - return registry[name] -end - ---- Return the live registry (for inspection / coverage assertions only — not for ---- per-call construction). ----@return table -function PropertySchema.all() - return registry -end - ---- True if a prop is registered. ----@param name string ----@return boolean -function PropertySchema.has(name) - return registry[name] ~= nil -end - ---- True if setting this prop invalidates layout (legacy `layoutProperties` set). ---- O(1) registry lookup — no per-call table construction. Unknown props return false, ---- matching the legacy `layoutProperties[name]` nil-lookup behavior exactly. ----@param name string prop name ----@return boolean -function PropertySchema.affectsLayout(name) - local meta = registry[name] - return meta ~= nil and meta.affectsLayout == true -end - ---- True for dimension props (width/height) that `setProperty` routes through ---- `_resolveDimensionProperty` (unit-string resolution + border-box sync). ---- O(1) registry lookup — no per-call table construction. Unknown props return false, ---- matching the legacy `dimensionProperties[name]` nil-lookup behavior exactly. ----@param name string prop name ----@return boolean -function PropertySchema.isDimension(name) - local meta = registry[name] - return meta ~= nil and meta.isDimension == true -end - ---- True for props whose setProperty path must reach ThemeManager/Renderer ---- (disabled/active/themeComponent). O(1) registry lookup — no per-call table ---- construction. Unknown props return false, matching a legacy nil-lookup exactly. ----@param name string prop name ----@return boolean -function PropertySchema.syncsTheme(name) - local meta = registry[name] - return meta ~= nil and meta.syncsTheme == true -end - --- --------------------------------------------------------------------------- --- Default schema (covers every prop handled in Element.new lines 259-1909 and --- Element:setProperty lines 4291-4417 of the Task-01 baseline). --- --------------------------------------------------------------------------- -local function defineDefaults() - PropertySchema.define({ - -- ------------------------------------------------------------------ identity - id = { type = "string" }, - userdata = { type = "any" }, - parent = { type = "table", affectsLayout = true }, - children = { type = "table" }, - - -- ------------------------------------------------------------------ callbacks - onEvent = { type = "function", hasDeferred = true }, - onFocus = { type = "function", hasDeferred = true }, - onBlur = { type = "function", hasDeferred = true }, - onTextInput = { type = "function", hasDeferred = true }, - onTextChange = { type = "function", hasDeferred = true }, - onEnter = { type = "function", hasDeferred = true }, - onCreate = { type = "function", hasDeferred = true }, - onTouchEvent = { type = "function", hasDeferred = true }, - onGesture = { type = "function", hasDeferred = true }, - onImageLoad = { type = "function", hasDeferred = true }, - onImageError = { type = "function", hasDeferred = true }, - - -- Deferred companion flags (stored directly; no further Deferred companion) - onEventDeferred = { type = "boolean", default = false }, - onFocusDeferred = { type = "boolean", default = false }, - onBlurDeferred = { type = "boolean", default = false }, - onTextInputDeferred = { type = "boolean", default = false }, - onTextChangeDeferred = { type = "boolean", default = false }, - onEnterDeferred = { type = "boolean", default = false }, - onCreateDeferred = { type = "boolean", default = false }, - onTouchEventDeferred = { type = "boolean", default = false }, - onGestureDeferred = { type = "boolean", default = false }, - onImageLoadDeferred = { type = "boolean", default = false }, - onImageErrorDeferred = { type = "boolean", default = false }, - - -- focus / touch behavior - dropFocusOnSelection = { type = "boolean" }, - customDraw = { type = "function" }, - touchEnabled = { type = "boolean", default = true }, - multiTouchEnabled = { type = "boolean", default = false }, - - -- ------------------------------------------------------------------ theme - theme = { type = "table" }, - themeComponent = { type = "string", syncsTheme = true }, - disabled = { type = "boolean", default = false, syncsTheme = true }, - isDisabled = { - type = "boolean", - default = false, - syncsTheme = true, - storageKey = "disabled", - }, - active = { type = "boolean", default = false, syncsTheme = true }, - disableHighlight = { type = "boolean" }, - themeStateLock = { type = "boolean" }, - themeComponentDisabledStates = { type = "table" }, - scaleCorners = { type = "boolean" }, - scalingAlgorithm = { type = "string" }, - contentAutoSizingMultiplier = { type = "table" }, - contentBlur = { type = "table" }, - backdropBlur = { type = "table" }, - - -- ------------------------------------------------------------------ text editing - editable = { type = "boolean", default = false }, - multiline = { type = "boolean", default = false }, - passwordMode = { type = "boolean", default = false }, - textWrap = { type = "string" }, -- default computed from multiline - maxLines = { type = "number" }, - maxLength = { type = "number" }, - placeholder = { type = "string" }, - inputType = { type = "string", default = "text" }, - textOverflow = { type = "string", default = "clip" }, - scrollable = { type = "boolean" }, -- default = multiline - autoGrow = { type = "boolean" }, -- default = multiline - selectOnFocus = { type = "boolean", default = false }, - cursorColor = { type = "color" }, - selectionColor = { type = "color" }, - cursorBlinkRate = { type = "number", default = 0.5 }, - text = { type = "string" }, - textAlign = { - type = "string", - default = "start", - validator = enumValidator({ "start", "center", "end", "justify" }), - }, - -- textAlignVertical is a derived storage field split out from textAlign - -- (bindVisualState resolves table/compound-string input into H + V). Its - -- validator is exposed for bindVisualState to validate the V component; the - -- prop itself stays in SPECIAL_PROPS because compound parsing needs - -- ErrorHandler warnings (schema is pure-Lua, cannot warn). - textAlignVertical = { - type = "string", - default = "start", - validator = enumValidator({ "start", "center", "end" }), - }, - textColor = { type = "color" }, - fontFamily = { type = "string" }, - textSize = { type = "any" }, -- number | preset string; resolved by special handler - minTextSize = { type = "number" }, - maxTextSize = { type = "number" }, - autoScaleText = { type = "boolean", default = true }, - - -- ------------------------------------------------------------------ dimensions / box model - width = { type = "any", isDimension = true, affectsLayout = true }, - height = { type = "any", isDimension = true, affectsLayout = true }, - x = { type = "any", affectsLayout = false }, - y = { type = "any", affectsLayout = false }, - minWidth = { type = "any" }, - maxWidth = { type = "any" }, - minHeight = { type = "any" }, - maxHeight = { type = "any" }, - gap = { type = "any", affectsLayout = true }, - padding = { - type = "any", - affectsLayout = true, - normalizer = expandSides, - }, - margin = { - type = "any", - affectsLayout = true, - normalizer = expandSides, - }, - flexDirection = { - type = "string", - default = "horizontal", - affectsLayout = true, - normalizer = normalizeFlexDirection, - }, - flexWrap = { type = "string", default = "nowrap", affectsLayout = true }, - justifyContent = { type = "string", default = "flex-start", affectsLayout = true }, - alignItems = { type = "string", default = "stretch", affectsLayout = true }, - alignContent = { type = "string", default = "stretch", affectsLayout = true }, - positioning = { type = "string", default = "relative", affectsLayout = true }, - gridRows = { type = "number", affectsLayout = true }, - gridColumns = { type = "number", affectsLayout = true }, - top = { type = "any", affectsLayout = true }, - right = { type = "any", affectsLayout = true }, - bottom = { type = "any", affectsLayout = true }, - left = { type = "any", affectsLayout = true }, - columnGap = { type = "any" }, - rowGap = { type = "any" }, - flex = { type = "any" }, -- shorthand: expands to flexGrow/flexShrink/flexBasis - flexGrow = { type = "number", default = 0, validator = rangeValidator(0, math.huge) }, - flexShrink = { type = "number", default = 1, validator = rangeValidator(0, math.huge) }, - flexBasis = { type = "any", default = "auto" }, - alignSelf = { type = "string", default = "auto" }, - justifySelf = { type = "string" }, - z = { type = "number", default = 0 }, - tabIndex = { type = "number" }, - - -- ------------------------------------------------------------------ border / background / visual - border = { type = "any", normalizer = normalizeBorder }, - borderColor = { type = "color" }, -- default Color.new(0,0,0,1) via special handler - backgroundColor = { type = "color" }, -- default transparent via special handler - opacity = { - type = "number", - default = 1, - validator = rangeValidator(0, 1), - }, - visibility = { type = "string", default = "visible" }, - display = { - type = "boolean", - default = true, - validator = booleanValidator, - }, - transform = { type = "table" }, - cornerRadius = { type = "any", normalizer = normalizeCornerRadius }, - - -- ------------------------------------------------------------------ image - imagePath = { type = "string" }, - image = { type = "table" }, - objectFit = { - type = "string", - default = "fill", - validator = enumValidator({ "fill", "contain", "cover", "scale-down", "none" }), - }, - objectPosition = { type = "string", default = "center center" }, - imageOpacity = { - type = "number", - default = 1, - validator = rangeValidator(0, 1), - }, - imageRepeat = { - type = "string", - default = "no-repeat", - validator = enumValidator({ - "no-repeat", - "repeat", - "repeat-x", - "repeat-y", - "space", - "round", - }), - }, - imageTint = { type = "color" }, - - -- ------------------------------------------------------------------ scroll / scrollbar - overflow = { type = "string" }, - overflowX = { type = "string" }, - overflowY = { type = "string" }, - scrollbarWidth = { type = "number" }, - scrollbarColor = { type = "color" }, - scrollbarTrackColor = { type = "color" }, - scrollbarRadius = { type = "number" }, - scrollbarPadding = { type = "number" }, - scrollSpeed = { type = "number" }, - invertScroll = { type = "boolean" }, - smoothScrollEnabled = { type = "boolean" }, - scrollBarStyle = { type = "string" }, - scrollbarKnobOffset = { type = "number" }, - hideScrollbars = { type = "boolean" }, - scrollbarPlacement = { type = "string" }, - scrollbarBalance = { type = "number" }, - _scrollX = { type = "number", storageKey = "_scrollX" }, - _scrollY = { type = "number", storageKey = "_scrollY" }, - - -- ------------------------------------------------------------------ select - selectParent = { type = "table" }, - selectOption = { type = "table" }, - - -- ------------------------------------------------------------------ transition - transition = { type = "table", default = {} }, - }) -end - ---- (Re)populate the default schema. Idempotent: safe to call from Element.init ---- for build profiles that re-require the module. Returns the live registry. ----@return table registry -function PropertySchema.populate() - defineDefaults() - return registry -end - --- Auto-populate on require so the registry is ready without an explicit init call --- (pure module, no external deps — safe at load time). -PropertySchema.populate() - -return PropertySchema diff --git a/libs/flexlove/modules/Renderer.lua b/libs/flexlove/modules/Renderer.lua deleted file mode 100644 index 8265793e..00000000 --- a/libs/flexlove/modules/Renderer.lua +++ /dev/null @@ -1,1230 +0,0 @@ -local UTF8 = require((...):match("(.-)[^%.]+$") .. "UTF8") - ----@class Renderer ----@field backgroundColor Color ----@field borderColor Color ----@field opacity number ----@field border {top:boolean, right:boolean, bottom:boolean, left:boolean} ----@field cornerRadius {topLeft:number, topRight:number, bottomLeft:number, bottomRight:number} ----@field theme string? ----@field themeComponent string? ----@field _themeState string ----@field imagePath string? ----@field image love.Image? ----@field _loadedImage love.Image? ----@field objectFit string ----@field objectPosition string ----@field imageOpacity number ----@field contentBlur {intensity:number, quality:number}? ----@field backdropBlur {intensity:number, quality:number}? ----@field _blurInstance table? ----@field _element Element? ----@field _Color Color ----@field _RoundedRect table ----@field _NinePatch table ----@field _ImageRenderer table ----@field _ImageCache table ----@field _Theme table ----@field _Transform Transform ----@field _Blur Blur ----@field _utils table ----@field _FONT_CACHE table ----@field _TextAlign table ----@field _ErrorHandler ErrorHandler ----@field _Performance Performance? Performance module dependency -local Renderer = {} -Renderer.__index = Renderer - ---- Initialize module with shared dependencies ----@param deps table Dependencies {ErrorHandler, Performance} -function Renderer.init(deps) - Renderer._ErrorHandler = deps.ErrorHandler - Renderer._Performance = deps.Performance -end - ---- Create a new Renderer instance ----@param config table Configuration table with rendering properties ----@param deps table Dependencies {Color, RoundedRect, NinePatch, ImageRenderer, ImageCache, Theme, Blur, Transform, utils} -function Renderer.new(config, deps) - local Color = deps.Color - local ImageCache = deps.ImageCache - - local self = setmetatable({}, Renderer) - - -- Store dependencies for instance methods - self._Color = Color - self._RoundedRect = deps.RoundedRect - self._NinePatch = deps.NinePatch - self._ImageRenderer = deps.ImageRenderer - self._ImageCache = ImageCache - self._Theme = deps.Theme - self._Blur = deps.Blur - self._Transform = deps.Transform - self._utils = deps.utils - self._FONT_CACHE = deps.utils.FONT_CACHE - self._TextAlign = deps.utils.enums.TextAlign - self._TextAlignVertical = deps.utils.enums.TextAlignVertical - - -- Visual properties - self.backgroundColor = config.backgroundColor or Color.new(0, 0, 0, 0) - self.borderColor = config.borderColor or Color.new(0, 0, 0, 1) - self.opacity = config.opacity or 1 - - -- NOTE: border is intentionally NOT cached here. Renderer:draw resolves it from - -- element.border (source of truth) so retained-mode bare writes and - -- setProperty("border", ...) both take effect immediately. - - -- Corner radius - self.cornerRadius = config.cornerRadius - or { - topLeft = 0, - topRight = 0, - bottomLeft = 0, - bottomRight = 0, - } - - -- Theme properties - self.theme = config.theme - self.themeComponent = config.themeComponent - self._themeState = "normal" - - -- Image properties - self.imagePath = config.imagePath - self.image = config.image - self._loadedImage = nil - self.objectFit = config.objectFit or "fill" - self.objectPosition = config.objectPosition or "center center" - self.imageOpacity = config.imageOpacity or 1 - self.imageRepeat = config.imageRepeat or "no-repeat" - self.imageTint = config.imageTint - - -- Blur effects - self.contentBlur = config.contentBlur - self.backdropBlur = config.backdropBlur - self._blurInstance = nil - - -- Load image if path provided - if self.imagePath and not self.image then - local loadedImage = ImageCache.load(self.imagePath) - if loadedImage then - self._loadedImage = loadedImage - else - self._loadedImage = nil - end - elseif self.image then - self._loadedImage = self.image - else - self._loadedImage = nil - end - - return self -end - ---- Get or create blur instance for this element ----@return table|nil Blur instance or nil -function Renderer:getBlurInstance() - -- Determine quality from blur settings - local quality = "medium" - if self.contentBlur and self.contentBlur.quality then - quality = self.contentBlur.quality - elseif self.backdropBlur and self.backdropBlur.quality then - quality = self.backdropBlur.quality - end - - -- Map string quality to numeric quality (1-10) - local numericQuality = 5 -- default medium - if type(quality) == "string" then - if quality == "low" then - numericQuality = 3 - elseif quality == "medium" then - numericQuality = 5 - elseif quality == "high" then - numericQuality = 8 - end - elseif type(quality) == "number" then - numericQuality = quality - end - - -- Create or reuse blur instance - if not self._blurInstance or self._blurInstance.quality ~= numericQuality then - self._blurInstance = self._Blur.new({ quality = numericQuality }) - end - - return self._blurInstance -end - ---- Set theme state (normal, hover, pressed, disabled, active) ----@param state string The theme state -function Renderer:setThemeState(state) - self._themeState = state -end - ---- Execute a single core draw command (background, image, theme, borders). ---- Commands are plain tables: { type = "background"|"image"|"theme"|"borders", ... } ----@param cmd table Command table ----@param ctx table Resolved draw context -function Renderer:_executeDrawCommand(cmd, ctx) - if cmd.type == "background" then - local c = self._Color.new(cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a * ctx.opacity) - love.graphics.setColor(c:toRGBA()) - self._RoundedRect.draw("fill", ctx.x, ctx.y, ctx.borderBoxWidth, ctx.borderBoxHeight, ctx.cornerRadius) - elseif cmd.type == "image" then - -- Image value props (imageOpacity/imageRepeat/imageTint/objectFit/ - -- objectPosition) and imagePath are read from the element as the single - -- source of truth, so retained-mode bare writes (`element.imageOpacity = 0.5`), - -- the setImage* setters, and setProperty(...) are all immediately - -- consistent. The renderer's own config is a fallback for standalone - -- Renderer usage with a sparse element (mirrors `element.onEvent or - -- self.onEvent`); the integrated path always supplies an element whose - -- _applyProps-bound values take precedence. _loadedImage intentionally - -- remains on the renderer (the resolved love.Image from the Imageable load - -- pipeline). See TestRetainedPropertyConsistency. - local el = self._element - local imageOpacity = (el and el.imageOpacity) or self.imageOpacity - local imageRepeat = (el and el.imageRepeat) or self.imageRepeat - local imageTint = (el and el.imageTint) or self.imageTint - local objectFit = (el and el.objectFit) or self.objectFit - local objectPosition = (el and el.objectPosition) or self.objectPosition - local imagePath = (el and el.imagePath) or self.imagePath - if not self._loadedImage then - return - end - local img = self._loadedImage - local imageX = ctx.x + ctx.paddingLeft - local imageY = ctx.y + ctx.paddingTop - local finalOpacity = ctx.opacity * imageOpacity - local hasCornerRadius = false - if ctx.cornerRadius then - if type(ctx.cornerRadius) == "number" then - hasCornerRadius = ctx.cornerRadius > 0 - else - hasCornerRadius = ctx.cornerRadius.topLeft > 0 - or ctx.cornerRadius.topRight > 0 - or ctx.cornerRadius.bottomLeft > 0 - or ctx.cornerRadius.bottomRight > 0 - end - end - if hasCornerRadius then - local success, err = pcall(function() - love.graphics.stencil(function() - self._RoundedRect.draw("fill", ctx.x, ctx.y, ctx.borderBoxWidth, ctx.borderBoxHeight, ctx.cornerRadius) - end, "replace", 1) - love.graphics.setStencilTest("greater", 0) - end) - if not success then - if err and err:match("stencil") then - local cr = ctx.cornerRadius - local crStr = type(cr) == "number" and tostring(cr) - or string.format("TL:%d TR:%d BL:%d BR:%d", cr.topLeft, cr.topRight, cr.bottomLeft, cr.bottomRight) - Renderer._ErrorHandler:warn( - "Renderer", - "IMG_001", - { imagePath = imagePath or "unknown", cornerRadius = crStr, error = tostring(err) } - ) - hasCornerRadius = false - else - error(err, 2) - end - end - end - if imageRepeat and imageRepeat ~= "no-repeat" then - self._ImageRenderer.drawTiled( - img, - imageX, - imageY, - ctx.contentWidth, - ctx.contentHeight, - imageRepeat, - finalOpacity, - imageTint - ) - else - self._ImageRenderer.draw( - img, - imageX, - imageY, - ctx.contentWidth, - ctx.contentHeight, - objectFit, - objectPosition, - finalOpacity, - imageTint - ) - end - if hasCornerRadius then - love.graphics.setStencilTest() - end - elseif cmd.type == "theme" then - if not cmd.themeComponent then - return - end - local themeToUse = nil - if self.theme then - themeToUse = self._Theme.get(self.theme) - if not themeToUse then - pcall(function() - self._Theme.load(self.theme) - end) - themeToUse = self._Theme.get(self.theme) - end - else - themeToUse = self._Theme.getActive() - end - if not themeToUse then - return - end - local component = themeToUse.components[cmd.themeComponent] - if not component then - return - end - local state = self._themeState - if state and component.states and component.states[state] then - component = component.states[state] - end - local atlasToUse = component._loadedAtlas or themeToUse.atlas - if atlasToUse and component.regions then - local r = component.regions - if - r.topLeft - and r.topCenter - and r.topRight - and r.middleLeft - and r.middleCenter - and r.middleRight - and r.bottomLeft - and r.bottomCenter - and r.bottomRight - then - self._NinePatch.draw( - component, - atlasToUse, - ctx.x, - ctx.y, - ctx.borderBoxWidth, - ctx.borderBoxHeight, - ctx.opacity, - cmd.scaleCorners, - cmd.scalingAlgorithm - ) - end - end - elseif cmd.type == "borders" then - local border = cmd.border - if not border then - return - end - local bc = cmd.borderColor - local borderColorWithOpacity = self._Color.new(bc.r, bc.g, bc.b, bc.a * ctx.opacity) - love.graphics.setColor(borderColorWithOpacity:toRGBA()) - local bw, bh = ctx.borderBoxWidth, ctx.borderBoxHeight - if type(border) == "number" then - love.graphics.setLineWidth(border) - self._RoundedRect.draw("line", ctx.x, ctx.y, bw, bh, ctx.cornerRadius) - love.graphics.setLineWidth(1) - else - local allBorders = border.top and border.bottom and border.left and border.right - local uniformWidth = allBorders - and type(border.top) == "number" - and border.top == border.right - and border.top == border.bottom - and border.top == border.left - if uniformWidth then - love.graphics.setLineWidth(border.top) - self._RoundedRect.draw("line", ctx.x, ctx.y, bw, bh, ctx.cornerRadius) - love.graphics.setLineWidth(1) - else - if border.top then - love.graphics.setLineWidth(type(border.top) == "number" and border.top or 1) - love.graphics.line(ctx.x, ctx.y, ctx.x + bw, ctx.y) - end - if border.bottom then - love.graphics.setLineWidth(type(border.bottom) == "number" and border.bottom or 1) - love.graphics.line(ctx.x, ctx.y + bh, ctx.x + bw, ctx.y + bh) - end - if border.left then - love.graphics.setLineWidth(type(border.left) == "number" and border.left or 1) - love.graphics.line(ctx.x, ctx.y, ctx.x, ctx.y + bh) - end - if border.right then - love.graphics.setLineWidth(type(border.right) == "number" and border.right or 1) - love.graphics.line(ctx.x + bw, ctx.y, ctx.x + bw, ctx.y + bh) - end - love.graphics.setLineWidth(1) - end - end - end -end - ---- Build the render command buffer: resolve draw properties once from the ---- element (source of truth) and return a flat command list + draw context. ----@param element table Element instance ----@param backdropCanvas table|nil ----@return table cmds, table ctx Command list and resolved context -function Renderer:_buildCommands(element, backdropCanvas) - local opacity = element.opacity ~= nil and element.opacity or 1 - local backgroundColor = element.backgroundColor or self._Color.new(0, 0, 0, 0) - local borderColor = element.borderColor or self._Color.new(0, 0, 0, 1) - local cornerRadius = element.cornerRadius ~= nil and element.cornerRadius or nil - local themeComponent = element.themeComponent - local border = element.border - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local borderBoxHeight = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom) - - -- Handle opacity during animation - local drawBackgroundColor = backgroundColor - if element.animation then - local anim = element.animation:interpolate() - if anim.opacity then - drawBackgroundColor = self._Color.new(backgroundColor.r, backgroundColor.g, backgroundColor.b, anim.opacity) - end - end - - -- Build resolved context (shared by all commands — eliminates per-method params) - local ctx = { - x = element.x, - y = element.y, - opacity = opacity, - cornerRadius = cornerRadius, - borderBoxWidth = borderBoxWidth, - borderBoxHeight = borderBoxHeight, - paddingLeft = element.padding.left, - paddingTop = element.padding.top, - contentWidth = element.width, - contentHeight = element.height, - backdropCanvas = backdropCanvas, - } - - -- Build command list (conditional — only emit layers that have data) - local cmds = {} - local n = 0 - - -- LAYER 0.5: backdrop blur (handled separately, not a command — needs canvas access) - if self.backdropBlur and self.backdropBlur.radius > 0 then - n = n + 1 - cmds[n] = { type = "backdropBlur", radius = self.backdropBlur.radius } -- executed before background - end - - -- LAYER 1: background - n = n + 1 - cmds[n] = { type = "background", color = drawBackgroundColor } - - -- LAYER 1.5: image (always emit; _executeCommand early-exits if no image) - n = n + 1 - cmds[n] = { type = "image" } - - -- LAYER 2: theme 9-patch - n = n + 1 - cmds[n] = { - type = "theme", - themeComponent = themeComponent, - scaleCorners = element.scaleCorners, - scalingAlgorithm = element.scalingAlgorithm, - } - - -- LAYER 3: borders - n = n + 1 - cmds[n] = { type = "borders", borderColor = borderColor, border = border } - - -- LAYER 4: text (cursor, selection, placeholder, password masking) - n = n + 1 - cmds[n] = { type = "text" } - - -- LAYER 4.5: custom draw callback (if provided) - if element.customDraw then - n = n + 1 - cmds[n] = { type = "customDraw" } - end - - -- NOTE: pressed-state overlay (former Layer 5) is now owned by the Clickable - -- behavior's onDraw, dispatched from Element:draw. The renderer no longer - -- branches on element.onEvent for press feedback. - - return cmds, ctx -end - ---- Execute a special render command (backdropBlur, customDraw, pressedState). ---- These interact with love.graphics state in non-uniform ways and are handled separately ---- from the core draw commands (background/image/theme/borders). ----@param cmd table Command table ----@param ctx table Resolved draw context -function Renderer:_executeSpecialCommand(cmd, ctx) - if cmd.type == "backdropBlur" then - if ctx.backdropCanvas then - local blurInstance = self:getBlurInstance() - if blurInstance then - local eid = self._element and self._element.id and self._element.id ~= "" and self._element.id or nil - blurInstance:applyBackdropCached( - cmd.radius, - ctx.x, - ctx.y, - ctx.borderBoxWidth, - ctx.borderBoxHeight, - ctx.backdropCanvas, - eid - ) - end - end - elseif cmd.type == "text" then - self:drawText(self._element) - elseif cmd.type == "customDraw" then - love.graphics.push() - love.graphics.setColor(1, 1, 1, 1) - self._element.customDraw(self._element) - love.graphics.pop() - end -end - ---- Main draw method - renders all visual layers via command buffer. ----@param element Element The parent Element instance ----@param backdropCanvas table|nil Backdrop canvas for backdrop blur -function Renderer:draw(element, backdropCanvas) - self._element = element -- cache for customDraw/pressedState - - if not element then - Renderer._ErrorHandler:warn("Renderer", "SYS_002", { method = "draw" }) - return - end - - -- Start performance timing - local elementId - if Renderer._Performance and Renderer._Performance.enabled then - elementId = element.id or "unnamed" - Renderer._Performance:startTimer("render_" .. elementId) - Renderer._Performance:incrementCounter("draw_calls", 1) - end - - -- Early exit if element is invisible (optimization) - if element.opacity ~= nil and element.opacity <= 0 then - if Renderer._Performance and Renderer._Performance.enabled and elementId then - Renderer._Performance:stopTimer("render_" .. elementId) - end - return - end - - -- Build command buffer + resolve draw context once - local cmds, ctx = self:_buildCommands(element, backdropCanvas) - - -- Apply transform if exists - local hasTransform = element.transform and self._Transform and not self._Transform.isIdentity(element.transform) - if hasTransform then - self._Transform.apply(element.transform, element.x, element.y, element.width, element.height) - end - - -- Execute all commands in order - for _, cmd in ipairs(cmds) do - -- Draw commands (background, image, theme, borders) use the core executor; - -- special commands (backdropBlur, customDraw, pressedState) are handled in _executeCommand. - local typ = cmd.type - if typ == "background" or typ == "image" or typ == "theme" or typ == "borders" then - self:_executeDrawCommand(cmd, ctx) - else - self:_executeSpecialCommand(cmd, ctx) - end - end - - -- Unapply transform if it was applied - if hasTransform then - self._Transform.unapply() - end - - -- Stop performance timing - if Renderer._Performance and Renderer._Performance.enabled and elementId then - Renderer._Performance:stopTimer("render_" .. elementId) - end -end - ---- Get font for element (resolves from theme or fontFamily) ----@param element table Reference to the parent Element instance ----@return love.Font -function Renderer:getFont(element) - return self._utils.getFont(element.textSize, element.fontFamily, element.themeComponent, element._themeManager) -end - ---- Wrap a line of text based on element's textWrap mode ----@param element table Reference to the parent Element instance ----@param line string The line of text to wrap ----@param maxWidth number Maximum width for wrapping ----@return table Array of {text, startIdx, endIdx} -function Renderer:wrapLine(element, line, maxWidth) - -- UTF-8 support - local utf8 = UTF8 - - if not element.editable then - return { { text = line, startIdx = 0, endIdx = utf8.len(line) } } - end - - local font = self:getFont(element) - local wrappedParts = {} - local currentLine = "" - local startIdx = 0 - - -- Helper function to extract a UTF-8 character by character index - local function getUtf8Char(str, charIndex) - local byteStart = utf8.offset(str, charIndex) - if not byteStart then - return "" - end - local byteEnd = utf8.offset(str, charIndex + 1) - if byteEnd then - return str:sub(byteStart, byteEnd - 1) - else - return str:sub(byteStart) - end - end - - if element.textWrap == "word" then - -- Tokenize into words and whitespace, preserving exact spacing - local tokens = {} - local pos = 1 - local lineLen = utf8.len(line) - - while pos <= lineLen do - -- Check if current position is whitespace - local char = getUtf8Char(line, pos) - if char:match("%s") then - -- Collect whitespace sequence - local wsStart = pos - while pos <= lineLen and getUtf8Char(line, pos):match("%s") do - pos = pos + 1 - end - table.insert(tokens, { - type = "space", - text = line:sub(utf8.offset(line, wsStart), utf8.offset(line, pos) and utf8.offset(line, pos) - 1 or #line), - startPos = wsStart - 1, - length = pos - wsStart, - }) - else - -- Collect word (non-whitespace sequence) - local wordStart = pos - while pos <= lineLen and not getUtf8Char(line, pos):match("%s") do - pos = pos + 1 - end - table.insert(tokens, { - type = "word", - text = line:sub(utf8.offset(line, wordStart), utf8.offset(line, pos) and utf8.offset(line, pos) - 1 or #line), - startPos = wordStart - 1, - length = pos - wordStart, - }) - end - end - - -- Process tokens and wrap - local charPos = 0 -- Track our position in the original line - for _, token in ipairs(tokens) do - if token.type == "word" then - local testLine = currentLine .. token.text - local width = font:getWidth(testLine) - - if width > maxWidth and currentLine ~= "" then - -- Current line is full, wrap before this word - local currentLineLen = utf8.len(currentLine) - table.insert(wrappedParts, { - text = currentLine, - startIdx = startIdx, - endIdx = startIdx + currentLineLen, - }) - startIdx = charPos - currentLine = token.text - charPos = charPos + token.length - - -- Check if the word itself is too long - if so, break it with character wrapping - if font:getWidth(token.text) > maxWidth then - local wordLen = utf8.len(token.text) - local charLine = "" - local charStartIdx = startIdx - - for j = 1, wordLen do - local char = getUtf8Char(token.text, j) - local testCharLine = charLine .. char - local charWidth = font:getWidth(testCharLine) - - if charWidth > maxWidth and charLine ~= "" then - table.insert(wrappedParts, { - text = charLine, - startIdx = charStartIdx, - endIdx = charStartIdx + utf8.len(charLine), - }) - charStartIdx = charStartIdx + utf8.len(charLine) - charLine = char - else - charLine = testCharLine - end - end - - currentLine = charLine - startIdx = charStartIdx - end - elseif width > maxWidth and currentLine == "" then - -- Word is too long to fit on a line by itself - use character wrapping - local wordLen = utf8.len(token.text) - local charLine = "" - local charStartIdx = startIdx - - for j = 1, wordLen do - local char = getUtf8Char(token.text, j) - local testCharLine = charLine .. char - local charWidth = font:getWidth(testCharLine) - - if charWidth > maxWidth and charLine ~= "" then - table.insert(wrappedParts, { - text = charLine, - startIdx = charStartIdx, - endIdx = charStartIdx + utf8.len(charLine), - }) - charStartIdx = charStartIdx + utf8.len(charLine) - charLine = char - else - charLine = testCharLine - end - end - - currentLine = charLine - startIdx = charStartIdx - charPos = charPos + token.length - else - currentLine = testLine - charPos = charPos + token.length - end - else - -- It's whitespace - add to current line - currentLine = currentLine .. token.text - charPos = charPos + token.length - end - end - else - -- Character wrapping - local lineLength = utf8.len(line) - for i = 1, lineLength do - local char = getUtf8Char(line, i) - local testLine = currentLine .. char - local width = font:getWidth(testLine) - - if width > maxWidth and currentLine ~= "" then - table.insert(wrappedParts, { - text = currentLine, - startIdx = startIdx, - endIdx = startIdx + utf8.len(currentLine), - }) - currentLine = char - startIdx = i - 1 - else - currentLine = testLine - end - end - end - - -- Add remaining text - if currentLine ~= "" then - table.insert(wrappedParts, { - text = currentLine, - startIdx = startIdx, - endIdx = startIdx + utf8.len(currentLine), - }) - end - - -- Ensure at least one part - if #wrappedParts == 0 then - table.insert(wrappedParts, { - text = "", - startIdx = 0, - endIdx = 0, - }) - end - - return wrappedParts -end - ---- Draw text content (includes text, cursor, selection, placeholder, password masking) ----@param element table Reference to the parent Element instance -function Renderer:drawText(element) - -- Update text layout if dirty (for multiline auto-grow) - if element._textEditor then - element._textEditor:_updateTextIfDirty(element) - element._textEditor:updateAutoGrowHeight(element) - end - - -- For editable elements, use TextEditor buffer; for non-editable, use text - local displayText = element._textEditor and element._textEditor:getText() or element.text - local isPlaceholder = false - - -- Show placeholder if editable and empty - if element.editable and (not displayText or displayText == "") and element.placeholder then - displayText = element.placeholder - isPlaceholder = true - end - - -- Apply password masking if enabled - if element.passwordMode and displayText and displayText ~= "" and not isPlaceholder then - local maskedText = string.rep("•", UTF8.len(displayText)) - displayText = maskedText - end - - if displayText and displayText ~= "" then - local textColor = isPlaceholder - and self._Color.new( - element.textColor.r * 0.5, - element.textColor.g * 0.5, - element.textColor.b * 0.5, - element.textColor.a * 0.5 - ) - or element.textColor - local textColorOpacity = element.opacity ~= nil and element.opacity or (self.opacity or 1) - local textColorWithOpacity = self._Color.new(textColor.r, textColor.g, textColor.b, textColor.a * textColorOpacity) - love.graphics.setColor(textColorWithOpacity:toRGBA()) - - local origFont = love.graphics.getFont() - if element.textSize then - -- Use cached font instead of creating new one every frame - local font = - self._utils.getFont(element.textSize, element.fontFamily, element.themeComponent, element._themeManager) - love.graphics.setFont(font) - end - local font = love.graphics.getFont() - local textWidth = font:getWidth(displayText) - local textHeight = font:getHeight() - local tx, ty - - -- Text is drawn in the content box (inside padding) - -- For 9-patch components, use contentPadding if available - local textPaddingLeft = element.padding.left - local textPaddingTop = element.padding.top - local textAreaWidth = element.width - local textAreaHeight = element.height - - -- Check if we should use 9-patch contentPadding for text positioning - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local borderBoxHeight = element._borderBoxHeight - or (element.height + element.padding.top + element.padding.bottom) - - textPaddingLeft = scaledContentPadding.left - textPaddingTop = scaledContentPadding.top - textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right - textAreaHeight = borderBoxHeight - scaledContentPadding.top - scaledContentPadding.bottom - end - - local contentX = element.x + textPaddingLeft - local contentY = element.y + textPaddingTop - - -- Resolve horizontal and vertical alignment (new format with backward compatibility) - local hAlign = element.textAlignHorizontal or element.textAlign or self._TextAlign.START - local vAlign = element.textAlignVertical or self._TextAlignVertical.START - - -- Check if text wrapping is enabled - if element.textWrap and (element.textWrap == "word" or element.textWrap == "char" or element.textWrap == true) then - -- Use printf for wrapped text (horizontal alignment only) - local align = "left" - if hAlign == self._TextAlign.CENTER then - align = "center" - elseif hAlign == self._TextAlign.END then - align = "right" - elseif hAlign == self._TextAlign.JUSTIFY then - align = "justify" - end - - tx = contentX - ty = contentY - - -- Use printf with the available width for wrapping - love.graphics.printf(displayText, tx, ty, textAreaWidth, align) - else - -- Use regular print for non-wrapped text - -- Horizontal alignment - if hAlign == self._TextAlign.START then - tx = contentX - elseif hAlign == self._TextAlign.CENTER then - tx = contentX + (textAreaWidth - textWidth) / 2 - elseif hAlign == self._TextAlign.END then - tx = contentX + textAreaWidth - textWidth - 10 - else -- JUSTIFY or unknown - tx = contentX - end - - -- Vertical alignment - if vAlign == self._TextAlignVertical.START then - ty = contentY - elseif vAlign == self._TextAlignVertical.CENTER then - ty = contentY + (textAreaHeight - textHeight) / 2 - elseif vAlign == self._TextAlignVertical.END then - ty = contentY + textAreaHeight - textHeight - else - ty = contentY - end - - -- Apply scroll offset for editable single-line inputs - if element.editable and not element.multiline and element._textScrollX then - tx = tx - element._textScrollX - end - - -- Use scissor to clip text to content area for editable inputs - if element.editable and not element.multiline then - love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight) - end - - love.graphics.print(displayText, tx, ty) - - -- Reset scissor - if element.editable and not element.multiline then - love.graphics.setScissor() - end - end - - -- Draw cursor for focused editable elements (even if text is empty) - if element._textEditor and element._textEditor:isFocused() and element._textEditor._cursorVisible then - local cursorColor = element.cursorColor or element.textColor - local elemOpacity = element.opacity ~= nil and element.opacity or (self.opacity or 1) - local cursorWithOpacity = - self._Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * elemOpacity) - love.graphics.setColor(cursorWithOpacity:toRGBA()) - - -- Calculate cursor position using TextEditor method - local cursorRelX, cursorRelY = element._textEditor:_getCursorScreenPosition(element) - local cursorX = contentX + cursorRelX - local cursorY = contentY + cursorRelY - local cursorHeight = textHeight - - -- Apply scroll offset for single-line inputs - if not element.multiline and element._textEditor._textScrollX then - cursorX = cursorX - element._textEditor._textScrollX - end - - -- Apply scissor for single-line editable inputs - if not element.multiline then - love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight) - end - - -- Draw cursor line - love.graphics.rectangle("fill", cursorX, cursorY, 2, cursorHeight) - - -- Reset scissor - if not element.multiline then - love.graphics.setScissor() - end - end - - -- Draw selection highlight for editable elements - if element._textEditor and element._textEditor:isFocused() and element._textEditor:hasSelection() then - -- For editable elements, check TextEditor buffer instead of element.text - local textBuffer = element._textEditor:getText() - if textBuffer and textBuffer ~= "" then - local selStart, selEnd = element._textEditor:getSelection() - local selectionColor = element.selectionColor or self._Color.new(0.3, 0.5, 0.8, 0.5) - local elemOpacity = element.opacity ~= nil and element.opacity or (self.opacity or 1) - local selectionWithOpacity = - self._Color.new(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * elemOpacity) - - -- Get selection rectangles from TextEditor - local selectionRects = element._textEditor:_getSelectionRects(element, selStart, selEnd) - - -- Apply scissor for single-line editable inputs - if not element.multiline then - love.graphics.setScissor(contentX, contentY, textAreaWidth, textAreaHeight) - end - - -- Draw selection background rectangles - love.graphics.setColor(selectionWithOpacity:toRGBA()) - for _, rect in ipairs(selectionRects) do - local rectX = contentX + rect.x - local rectY = contentY + rect.y - if not element.multiline and element._textEditor._textScrollX then - rectX = rectX - element._textEditor._textScrollX - end - love.graphics.rectangle("fill", rectX, rectY, rect.width, rect.height) - end - - -- Reset scissor - if not element.multiline then - love.graphics.setScissor() - end - end - end - - if element.textSize then - love.graphics.setFont(origFont) - end - end - - -- Draw cursor for focused editable elements even when empty - if - element._textEditor - and element._textEditor:isFocused() - and element._textEditor._cursorVisible - and (not displayText or displayText == "") - then - -- Set up font for cursor rendering - local origFont = love.graphics.getFont() - if element.textSize then - local font = - self._utils.getFont(element.textSize, element.fontFamily, element.themeComponent, element._themeManager) - love.graphics.setFont(font) - end - - local font = love.graphics.getFont() - local textHeight = font:getHeight() - - -- Calculate text area position - local textPaddingLeft = element.padding.left - local textPaddingTop = element.padding.top - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - textPaddingLeft = scaledContentPadding.left - textPaddingTop = scaledContentPadding.top - end - - local contentX = element.x + textPaddingLeft - local contentY = element.y + textPaddingTop - - -- Draw cursor - local cursorColor = element.cursorColor or element.textColor - local elemOpacity = element.opacity ~= nil and element.opacity or (self.opacity or 1) - local cursorWithOpacity = self._Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * elemOpacity) - love.graphics.setColor(cursorWithOpacity:toRGBA()) - love.graphics.rectangle("fill", contentX, contentY, 2, textHeight) - - if element.textSize then - love.graphics.setFont(origFont) - end - end -end - ---- Draw scrollbars (both vertical and horizontal) ----@param element table Reference to the parent Element instance ----@param x number X position ----@param y number Y position ----@param w number Width ----@param h number Height ----@param dims table Scrollbar dimensions from _calculateScrollbarDimensions -function Renderer:drawScrollbars(element, x, y, w, h, dims) - -- Try to get themed scrollbar component - local scrollbarComponent = nil - if element.scrollBarStyle or self._Theme.hasActive() then - scrollbarComponent = self._Theme.getScrollbar(element.scrollBarStyle) - end - - -- Vertical scrollbar - if dims.vertical.visible and not element.hideScrollbars.vertical then - -- Position scrollbar within content area (x, y is border-box origin) - local contentX = x + element.padding.left - local contentY = y + element.padding.top - local trackX = contentX + w - element.scrollbarWidth - element.scrollbarPadding - local trackY = contentY + element.scrollbarPadding - - -- Check if we should use themed rendering - if scrollbarComponent then - -- Themed scrollbar rendering using NinePatch - local frameComponent = scrollbarComponent.frame or scrollbarComponent - local barComponent = scrollbarComponent.bar or scrollbarComponent - - -- Calculate knob offset (element overrides theme) - local knobOffsetX = 0 - local knobOffsetY = 0 - - -- Use element offset if provided, otherwise use theme offset - if element.scrollbarKnobOffset then - knobOffsetX = element.scrollbarKnobOffset.x or 0 - knobOffsetY = element.scrollbarKnobOffset.vertical or 0 - elseif barComponent and barComponent.knobOffset then - local themeOffset = self._utils.normalizeOffsetTable(barComponent.knobOffset, 0) - knobOffsetX = themeOffset.x - knobOffsetY = themeOffset.vertical - end - - -- Extract contentPadding top inset from frame for knob sizing. - -- Vertical scrollbar only consumes framePaddingTop; other insets are unused. - local framePaddingTop = 0 - if frameComponent and frameComponent._ninePatchData and frameComponent._ninePatchData.contentPadding then - framePaddingTop = frameComponent._ninePatchData.contentPadding.top or 0 - end - - -- Draw track (frame) if component exists - if frameComponent and frameComponent._loadedAtlas and frameComponent.regions then - self._NinePatch.draw( - frameComponent, - frameComponent._loadedAtlas, - trackX, - trackY, - element.scrollbarWidth, - dims.vertical.trackHeight - ) - end - - -- Draw thumb (bar) if component exists - if barComponent and barComponent._loadedAtlas and barComponent.regions then - -- Adjust knob dimensions to account for frame's contentPadding - -- Vertical scrollbar: width affected by left+right, height affected by top+bottom - local knobWidth = element.scrollbarWidth - local knobHeight = dims.vertical.thumbHeight - framePaddingTop / 2 - self._NinePatch.draw( - barComponent, - barComponent._loadedAtlas, - trackX + knobOffsetX, - trackY + dims.vertical.thumbY + knobOffsetY, - knobWidth, - knobHeight - ) - end - else - -- Fallback to color-based rendering - -- Determine thumb color based on state (independent for vertical) - local thumbColor = element.scrollbarColor - if element._scrollbarDragging and element._hoveredScrollbar == "vertical" then - -- Active state: brighter - local r, g, b, a = self._utils.brightenColor(thumbColor.r, thumbColor.g, thumbColor.b, thumbColor.a, 1.4) - thumbColor = self._Color.new(r, g, b, a) - elseif element._scrollbarHoveredVertical then - -- Hover state: slightly brighter - local r, g, b, a = self._utils.brightenColor(thumbColor.r, thumbColor.g, thumbColor.b, thumbColor.a, 1.2) - thumbColor = self._Color.new(r, g, b, a) - end - - -- Draw track - love.graphics.setColor(element.scrollbarTrackColor:toRGBA()) - love.graphics.rectangle( - "fill", - trackX, - trackY, - element.scrollbarWidth, - dims.vertical.trackHeight, - element.scrollbarRadius - ) - - -- Draw thumb with state-based color - love.graphics.setColor(thumbColor:toRGBA()) - love.graphics.rectangle( - "fill", - trackX, - trackY + dims.vertical.thumbY, - element.scrollbarWidth, - dims.vertical.thumbHeight, - element.scrollbarRadius - ) - end - end - - -- Horizontal scrollbar - if dims.horizontal.visible and not element.hideScrollbars.horizontal then - -- Position scrollbar within content area (x, y is border-box origin) - local contentX = x + element.padding.left - local contentY = y + element.padding.top - local trackX = contentX + element.scrollbarPadding - local trackY = contentY + h - element.scrollbarWidth - element.scrollbarPadding - - -- Check if we should use themed rendering - if scrollbarComponent then - -- Themed scrollbar rendering using NinePatch - local frameComponent = scrollbarComponent.frame or scrollbarComponent - local barComponent = scrollbarComponent.bar or scrollbarComponent - - -- Calculate knob offset (element overrides theme) - local knobOffsetX = 0 - local knobOffsetY = 0 - - -- Use element offset if provided, otherwise use theme offset - if element.scrollbarKnobOffset then - knobOffsetX = element.scrollbarKnobOffset.horizontal or 0 - knobOffsetY = element.scrollbarKnobOffset.y or 0 - elseif barComponent and barComponent.knobOffset then - local themeOffset = self._utils.normalizeOffsetTable(barComponent.knobOffset, 0) - knobOffsetX = themeOffset.horizontal - knobOffsetY = themeOffset.y - end - - -- Extract contentPadding from frame for knob sizing (horizontal: right inset unused). - local framePaddingLeft = 0 - local framePaddingTop = 0 - local framePaddingBottom = 0 - if frameComponent and frameComponent._ninePatchData and frameComponent._ninePatchData.contentPadding then - framePaddingLeft = frameComponent._ninePatchData.contentPadding.left or 0 - framePaddingTop = frameComponent._ninePatchData.contentPadding.top or 0 - framePaddingBottom = frameComponent._ninePatchData.contentPadding.bottom or 0 - end - - -- Draw track (frame) if component exists - if frameComponent and frameComponent._loadedAtlas and frameComponent.regions then - self._NinePatch.draw( - frameComponent, - frameComponent._loadedAtlas, - trackX, - trackY, - dims.horizontal.trackWidth, - element.scrollbarWidth - ) - end - - -- Draw thumb (bar) if component exists - if barComponent and barComponent._loadedAtlas and barComponent.regions then - -- Adjust knob dimensions to account for frame's contentPadding - -- Horizontal scrollbar: width affected by left+right, height affected by top+bottom - local knobWidth = dims.horizontal.thumbWidth - framePaddingLeft / 2 - local knobHeight = element.scrollbarWidth - framePaddingTop - framePaddingBottom - self._NinePatch.draw( - barComponent, - barComponent._loadedAtlas, - trackX + dims.horizontal.thumbX + knobOffsetX, - trackY + knobOffsetY, - knobWidth, - knobHeight - ) - end - else - -- Fallback to color-based rendering - -- Determine thumb color based on state (independent for horizontal) - local thumbColor = element.scrollbarColor - if element._scrollbarDragging and element._hoveredScrollbar == "horizontal" then - -- Active state: brighter - local r, g, b, a = self._utils.brightenColor(thumbColor.r, thumbColor.g, thumbColor.b, thumbColor.a, 1.4) - thumbColor = self._Color.new(r, g, b, a) - elseif element._scrollbarHoveredHorizontal then - -- Hover state: slightly brighter - local r, g, b, a = self._utils.brightenColor(thumbColor.r, thumbColor.g, thumbColor.b, thumbColor.a, 1.2) - thumbColor = self._Color.new(r, g, b, a) - end - - -- Draw track - love.graphics.setColor(element.scrollbarTrackColor:toRGBA()) - love.graphics.rectangle( - "fill", - trackX, - trackY, - dims.horizontal.trackWidth, - element.scrollbarWidth, - element.scrollbarRadius - ) - - -- Draw thumb with state-based color - love.graphics.setColor(thumbColor:toRGBA()) - love.graphics.rectangle( - "fill", - trackX + dims.horizontal.thumbX, - trackY, - dims.horizontal.thumbWidth, - element.scrollbarWidth, - element.scrollbarRadius - ) - end - end - - -- Reset color - love.graphics.setColor(1, 1, 1, 1) -end - ---- Draw visual feedback when element is pressed ----@param x number X position ----@param y number Y position ----@param borderBoxWidth number Border box width ----@param borderBoxHeight number Border box height ----@param opacity number Element opacity ----@param cornerRadius number|table Corner radius -function Renderer:drawPressedState(x, y, borderBoxWidth, borderBoxHeight, opacity, cornerRadius) - love.graphics.setColor(0.5, 0.5, 0.5, 0.3 * (opacity or 1)) - self._RoundedRect.draw("fill", x, y, borderBoxWidth, borderBoxHeight, cornerRadius) -end - ---- Cleanup renderer resources -function Renderer:destroy() - self._loadedImage = nil - self._blurInstance = nil -end - -return Renderer diff --git a/libs/flexlove/modules/RoundedRect.lua b/libs/flexlove/modules/RoundedRect.lua deleted file mode 100644 index 8db7222d..00000000 --- a/libs/flexlove/modules/RoundedRect.lua +++ /dev/null @@ -1,124 +0,0 @@ -local RoundedRect = {} - ---- Generate points for a rounded rectangle ----@param x number ----@param y number ----@param width number ----@param height number ----@param cornerRadius {topLeft:number, topRight:number, bottomLeft:number, bottomRight:number}|number ----@param segments number? -- Number of segments per corner arc (default: 10) ----@return table -- Array of vertices for love.graphics.polygon -function RoundedRect.getPoints(x, y, width, height, cornerRadius, segments) - segments = segments or 10 - local points = {} - - -- Helper to add arc points - local function addArc(cx, cy, radius, startAngle, endAngle) - if radius <= 0 then - table.insert(points, cx) - table.insert(points, cy) - return - end - - for i = 0, segments do - local angle = startAngle + (endAngle - startAngle) * (i / segments) - table.insert(points, cx + math.cos(angle) * radius) - table.insert(points, cy + math.sin(angle) * radius) - end - end - - -- Handle uniform corner radius (number) - if type(cornerRadius) == "number" then - cornerRadius = { - topLeft = cornerRadius, - topRight = cornerRadius, - bottomLeft = cornerRadius, - bottomRight = cornerRadius, - } - end - - local r1 = math.min(cornerRadius.topLeft, width / 2, height / 2) - local r2 = math.min(cornerRadius.topRight, width / 2, height / 2) - local r3 = math.min(cornerRadius.bottomRight, width / 2, height / 2) - local r4 = math.min(cornerRadius.bottomLeft, width / 2, height / 2) - - -- Top-right corner - addArc(x + width - r2, y + r2, r2, -math.pi / 2, 0) - - -- Bottom-right corner - addArc(x + width - r3, y + height - r3, r3, 0, math.pi / 2) - - -- Bottom-left corner - addArc(x + r4, y + height - r4, r4, math.pi / 2, math.pi) - - -- Top-left corner - addArc(x + r1, y + r1, r1, math.pi, math.pi * 1.5) - - return points -end - ---- Draw a filled rounded rectangle ----@param mode string -- "fill" or "line" ----@param x number ----@param y number ----@param width number ----@param height number ----@param cornerRadius {topLeft:number, topRight:number, bottomLeft:number, bottomRight:number}|number|nil -function RoundedRect.draw(mode, x, y, width, height, cornerRadius) - -- OPTIMIZATION: Handle nil cornerRadius (no rounding) - if not cornerRadius then - love.graphics.rectangle(mode, x, y, width, height) - return - end - - -- Handle uniform corner radius (number) - if type(cornerRadius) == "number" then - if cornerRadius <= 0 then - love.graphics.rectangle(mode, x, y, width, height) - return - end - -- Convert to table format for processing - cornerRadius = { - topLeft = cornerRadius, - topRight = cornerRadius, - bottomLeft = cornerRadius, - bottomRight = cornerRadius, - } - end - - -- Check if any corners are rounded - local hasRoundedCorners = cornerRadius.topLeft > 0 - or cornerRadius.topRight > 0 - or cornerRadius.bottomLeft > 0 - or cornerRadius.bottomRight > 0 - - if not hasRoundedCorners then - -- No rounded corners, use regular rectangle - love.graphics.rectangle(mode, x, y, width, height) - return - end - - local points = RoundedRect.getPoints(x, y, width, height, cornerRadius) - - if mode == "fill" then - love.graphics.polygon("fill", points) - else - -- For line mode, draw the outline - love.graphics.polygon("line", points) - end -end - ---- Create a stencil function for rounded rectangle clipping ----@param x number ----@param y number ----@param width number ----@param height number ----@param cornerRadius {topLeft:number, topRight:number, bottomLeft:number, bottomRight:number}|number|nil ----@return function -function RoundedRect.stencilFunction(x, y, width, height, cornerRadius) - return function() - RoundedRect.draw("fill", x, y, width, height, cornerRadius) - end -end - -return RoundedRect diff --git a/libs/flexlove/modules/ScrollManager.lua b/libs/flexlove/modules/ScrollManager.lua deleted file mode 100644 index a23d4d19..00000000 --- a/libs/flexlove/modules/ScrollManager.lua +++ /dev/null @@ -1,1479 +0,0 @@ ----@class ScrollManager ----@field overflow string -- "visible"|"hidden"|"auto"|"scroll" ----@field overflowX string? -- X-axis specific overflow (overrides overflow) ----@field overflowY string? -- Y-axis specific overflow (overrides overflow) ----@field scrollbarWidth number -- Width/height of scrollbar track ----@field scrollbarColor Color -- Scrollbar thumb color ----@field scrollbarTrackColor Color -- Scrollbar track background color ----@field scrollbarRadius number -- Border radius for scrollbars ----@field scrollbarPadding number -- Padding around scrollbar ----@field scrollSpeed number -- Scroll speed for wheel events (pixels per wheel unit) ----@field invertScroll boolean -- Invert mouse wheel scroll direction (default: false) ----@field scrollBarStyle string? -- Scrollbar style name from theme (selects from theme.scrollbars) ----@field scrollbarKnobOffset table -- {x: number, y: number, horizontal: number, vertical: number} -- Offset for scrollbar knob/handle position ----@field hideScrollbars table -- {vertical: boolean, horizontal: boolean} ----@field scrollbarPlacement string -- "reserve-space"|"overlay" -- Whether scrollbar reserves space or overlays content (default: "reserve-space") ----@field scrollbarBalance boolean -- When true, reserve space on both sides of content for visual balance (default: false) ----@field touchScrollEnabled boolean -- Enable touch scrolling ----@field momentumScrollEnabled boolean -- Enable momentum scrolling ----@field bounceEnabled boolean -- Enable bounce effects at boundaries ----@field scrollFriction number -- Friction coefficient for momentum (0.95-0.98) ----@field bounceStiffness number -- Bounce spring constant (0.1-0.3) ----@field maxOverscroll number -- Maximum overscroll distance (pixels) ----@field _overflowX boolean -- True if content overflows horizontally ----@field _overflowY boolean -- True if content overflows vertically ----@field _contentWidth number -- Total content width (including overflow) ----@field _contentHeight number -- Total content height (including overflow) ----@field _scrollX number -- Current horizontal scroll position ----@field _scrollY number -- Current vertical scroll position ----@field _targetScrollX number? -- Target scroll X for smooth scrolling ----@field _targetScrollY number? -- Target scroll Y for smooth scrolling ----@field _smoothScrollSpeed number -- Speed of smooth scroll interpolation (0-1, higher = faster) ----@field _maxScrollX number -- Maximum horizontal scroll (contentWidth - containerWidth) ----@field _maxScrollY number -- Maximum vertical scroll (contentHeight - containerHeight) ----@field _scrollbarHoveredVertical boolean -- True if mouse is over vertical scrollbar ----@field _scrollbarHoveredHorizontal boolean -- True if mouse is over horizontal scrollbar ----@field _scrollbarDragging boolean -- True if currently dragging a scrollbar ----@field _hoveredScrollbar string? -- "vertical" or "horizontal" when dragging ----@field _scrollbarDragOffset number -- DEPRECATED: Offset from thumb top when drag started (kept for compatibility) ----@field _dragStartMouseX number -- Mouse X position when drag started ----@field _dragStartMouseY number -- Mouse Y position when drag started ----@field _dragStartScrollX number -- Scroll X position when drag started ----@field _dragStartScrollY number -- Scroll Y position when drag started ----@field _scrollbarPressHandled boolean -- Track if scrollbar press was handled this frame ----@field _touchScrolling boolean -- True if currently touch scrolling ----@field _scrollVelocityX number -- Current horizontal scroll velocity (px/s) ----@field _scrollVelocityY number -- Current vertical scroll velocity (px/s) ----@field _momentumScrolling boolean -- True if momentum scrolling is active ----@field _lastTouchTime number -- Timestamp of last touch move ----@field _lastTouchX number -- Last touch X position ----@field _lastTouchY number -- Last touch Y position ----@field _Color table ----@field _utils table ----@field _ErrorHandler table? ErrorHandler module dependency -local ScrollManager = {} -ScrollManager.__index = ScrollManager - ---- Initialize module with shared dependencies ----@param deps table Dependencies {ErrorHandler} -function ScrollManager.init(deps) - if type(deps) == "table" then - ScrollManager._ErrorHandler = deps.ErrorHandler or ScrollManager._ErrorHandler - ScrollManager._Context = deps.Context or ScrollManager._Context - ScrollManager._StateManager = deps.StateManager or ScrollManager._StateManager - end -end - ---- Create a new ScrollManager instance ----@param config table Configuration options ----@param deps table Dependencies {Color: Color module, utils: utils module} ----@return ScrollManager -function ScrollManager.new(config, deps) - local Color = deps.Color - local self = setmetatable({}, ScrollManager) - - -- Store dependencies for instance methods - self._Color = Color - self._utils = deps.utils - - -- Configuration - self.overflow = config.overflow or "hidden" - self.overflowX = config.overflowX - self.overflowY = config.overflowY - - -- Scrollbar appearance - self.scrollbarWidth = config.scrollbarWidth or 12 - self.scrollbarColor = config.scrollbarColor or Color.new(0.5, 0.5, 0.5, 0.8) - self.scrollbarTrackColor = config.scrollbarTrackColor or Color.new(0.2, 0.2, 0.2, 0.5) - self.scrollbarRadius = config.scrollbarRadius or 6 - self.scrollbarPadding = config.scrollbarPadding or 2 - self.scrollSpeed = config.scrollSpeed or 20 - self.invertScroll = config.invertScroll or false - self.scrollBarStyle = config.scrollBarStyle -- Theme scrollbar style name (nil = use default) - - -- scrollbarKnobOffset can be number or table {x, y} or {horizontal, vertical} - -- Only normalize if actually provided (nil means use theme default) - if config.scrollbarKnobOffset ~= nil then - self.scrollbarKnobOffset = self._utils.normalizeOffsetTable(config.scrollbarKnobOffset, 0) - else - self.scrollbarKnobOffset = nil - end - - -- hideScrollbars can be boolean or table {vertical: boolean, horizontal: boolean} - self.hideScrollbars = self._utils.normalizeBooleanTable(config.hideScrollbars, false) - - -- Scrollbar placement: "reserve-space" (default) or "overlay" - self.scrollbarPlacement = config.scrollbarPlacement or "reserve-space" - - -- Scrollbar balance: when true, reserve space on both sides for visual balance - self.scrollbarBalance = config.scrollbarBalance or false - - -- Touch scrolling configuration - self.touchScrollEnabled = config.touchScrollEnabled ~= false -- Default true - self.momentumScrollEnabled = config.momentumScrollEnabled ~= false -- Default true - self.bounceEnabled = config.bounceEnabled ~= false -- Default true - self.scrollFriction = config.scrollFriction or 0.95 -- Exponential decay per frame - self.bounceStiffness = config.bounceStiffness or 0.2 -- Spring constant - self.maxOverscroll = config.maxOverscroll or 100 -- pixels - - -- Internal overflow state - self._overflowX = false - self._overflowY = false - self._contentWidth = 0 - self._contentHeight = 0 - - -- Scroll state (can be restored from config in immediate mode) - self._scrollX = config._scrollX or 0 - self._scrollY = config._scrollY or 0 - self._targetScrollX = nil - self._targetScrollY = nil - self._smoothScrollSpeed = 0.25 -- Interpolation speed (0-1, higher = faster) - self.smoothScrollEnabled = config.smoothScrollEnabled or false -- Enable smooth wheel scrolling - self._maxScrollX = 0 - self._maxScrollY = 0 - - -- Scrollbar interaction state - self._scrollbarHoveredVertical = false - self._scrollbarHoveredHorizontal = false - self._scrollbarDragging = false - self._hoveredScrollbar = nil -- "vertical" or "horizontal" - self._scrollbarDragOffset = 0 -- DEPRECATED: kept for backward compatibility - self._dragStartMouseX = 0 -- Mouse X position when drag started - self._dragStartMouseY = 0 -- Mouse Y position when drag started - self._dragStartScrollX = 0 -- Scroll X position when drag started - self._dragStartScrollY = 0 -- Scroll Y position when drag started - self._scrollbarPressHandled = false - - -- Touch scrolling state - self._touchScrolling = false - self._scrollVelocityX = 0 - self._scrollVelocityY = 0 - self._momentumScrolling = false - self._lastTouchTime = 0 - self._lastTouchX = 0 - self._lastTouchY = 0 - - return self -end - ---- Get the space reserved for scrollbars (width and height reduction) ---- This is called BEFORE layout to reduce available space for children ----@param element Element The parent Element instance ----@return number reservedWidth, number reservedHeight -function ScrollManager:getReservedSpace() - if self.scrollbarPlacement ~= "reserve-space" then - return 0, 0 - end - - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - - local reservedWidth = 0 - local reservedHeight = 0 - - -- Reserve space for vertical scrollbar if overflow mode requires it - if (overflowY == "scroll" or overflowY == "auto") and not self.hideScrollbars.vertical then - local scrollbarSpace = self.scrollbarWidth + (self.scrollbarPadding * 2) - reservedWidth = self.scrollbarBalance and (scrollbarSpace * 2) or scrollbarSpace - end - - -- Reserve space for horizontal scrollbar if overflow mode requires it - if (overflowX == "scroll" or overflowX == "auto") and not self.hideScrollbars.horizontal then - local scrollbarSpace = self.scrollbarWidth + (self.scrollbarPadding * 2) - reservedHeight = self.scrollbarBalance and (scrollbarSpace * 2) or scrollbarSpace - end - - return reservedWidth, reservedHeight -end - ---- Detect if content overflows container bounds ----@param element Element The parent Element instance -function ScrollManager:detectOverflow(element) - -- Reset overflow state - self._overflowX = false - self._overflowY = false - self._contentWidth = element.width - self._contentHeight = element.height - - -- Skip detection if overflow is visible (no clipping needed) - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - if overflowX == "visible" and overflowY == "visible" then - return - end - - -- Calculate content bounds based on children - if #element.children == 0 then - return -- No children, no overflow - end - - local maxX, maxY = 0, 0 - - -- Content area starts after padding - local contentX = element.x + element.padding.left - local contentY = element.y + element.padding.top - - for _, child in ipairs(element.children) do - -- Skip absolutely positioned children (they don't contribute to overflow) - if not child._explicitlyAbsolute then - -- Calculate child's margin box bounds relative to content area - local childMarginRight = child.x - contentX + child:getBorderBoxWidth() + child.margin.right - local childMarginBottom = child.y - contentY + child:getBorderBoxHeight() + child.margin.bottom - - -- Track the maximum extents (we ignore negative space from margins) - maxX = math.max(maxX, childMarginRight) - maxY = math.max(maxY, childMarginBottom) - end - end - - -- Calculate content dimensions - self._contentWidth = maxX - self._contentHeight = maxY - - -- Detect overflow (compare against content area, not total element size). - -- element.width/height semantics depend on unit type: - -- px units → border-box size (padding NOT yet subtracted) - -- %, vh, vw → content size (padding already subtracted by LayoutEngine) - -- auto → content size - -- Using getBorderBoxWidth/Height() normalises both cases: border-box - padding = content. - local containerWidth = element:getBorderBoxWidth() - element.padding.left - element.padding.right - local containerHeight = element:getBorderBoxHeight() - element.padding.top - element.padding.bottom - - -- If scrollbarPlacement is "reserve-space", we need to subtract the reserved space - -- because the layout already accounted for it, but element.width/height are still full size - if self.scrollbarPlacement == "reserve-space" then - local reservedWidth, reservedHeight = self:getReservedSpace() - containerWidth = containerWidth - reservedWidth - containerHeight = containerHeight - reservedHeight - end - - self._overflowX = self._contentWidth > containerWidth - self._overflowY = self._contentHeight > containerHeight - - -- Calculate maximum scroll bounds - self._maxScrollX = math.max(0, self._contentWidth - containerWidth) - self._maxScrollY = math.max(0, self._contentHeight - containerHeight) - - -- Clamp current scroll position to new bounds - self._scrollX = self._utils.clamp(self._scrollX, 0, self._maxScrollX) - self._scrollY = self._utils.clamp(self._scrollY, 0, self._maxScrollY) -end - ---- Set scroll position with bounds clamping ----@param x number? -- X scroll position (nil to keep current) ----@param y number? -- Y scroll position (nil to keep current) -function ScrollManager:setScroll(x, y) - if x ~= nil then - self._scrollX = self._utils.clamp(x, 0, self._maxScrollX) - end - if y ~= nil then - self._scrollY = self._utils.clamp(y, 0, self._maxScrollY) - end -end - ---- Get current scroll position ----@return number scrollX, number scrollY -function ScrollManager:getScroll() - return self._scrollX, self._scrollY -end - ---- Scroll by delta amount ----@param dx number? -- X delta (nil for no change) ----@param dy number? -- Y delta (nil for no change) -function ScrollManager:scrollBy(dx, dy) - if dx then - self._scrollX = self._utils.clamp(self._scrollX + dx, 0, self._maxScrollX) - end - if dy then - self._scrollY = self._utils.clamp(self._scrollY + dy, 0, self._maxScrollY) - end -end - ---- Get maximum scroll bounds ----@return number maxScrollX, number maxScrollY -function ScrollManager:getMaxScroll() - return self._maxScrollX, self._maxScrollY -end - ---- Get scroll percentage (0-1) ----@return number percentX, number percentY -function ScrollManager:getScrollPercentage() - local percentX = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0 - local percentY = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0 - return percentX, percentY -end - ---- Check if element has overflow ----@return boolean hasOverflowX, boolean hasOverflowY -function ScrollManager:hasOverflow() - return self._overflowX, self._overflowY -end - ---- Get content dimensions (including overflow) ----@return number contentWidth, number contentHeight -function ScrollManager:getContentSize() - return self._contentWidth, self._contentHeight -end - ---- Calculate scrollbar dimensions and positions ----@param element Element The parent Element instance ----@return table -- {vertical: {visible, trackHeight, thumbHeight, thumbY}, horizontal: {visible, trackWidth, thumbWidth, thumbX}} -function ScrollManager:calculateScrollbarDimensions(element) - local result = { - vertical = { visible = false, trackHeight = 0, thumbHeight = 0, thumbY = 0 }, - horizontal = { visible = false, trackWidth = 0, thumbWidth = 0, thumbX = 0 }, - } - - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - - -- Vertical scrollbar - -- Note: overflow="scroll" always shows scrollbar; overflow="auto" only when content overflows - if overflowY == "scroll" then - -- Always show scrollbar for "scroll" mode - result.vertical.visible = true - result.vertical.trackHeight = element.height - (self.scrollbarPadding * 2) - - if self._overflowY then - -- Content overflows, calculate proper thumb size - local contentRatio = element.height / math.max(self._contentHeight, element.height) - result.vertical.thumbHeight = math.max(20, result.vertical.trackHeight * contentRatio) - - -- Calculate thumb position based on scroll ratio - local scrollRatio = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0 - local maxThumbY = result.vertical.trackHeight - result.vertical.thumbHeight - result.vertical.thumbY = maxThumbY * scrollRatio - else - -- No overflow, thumb fills entire track - result.vertical.thumbHeight = result.vertical.trackHeight - result.vertical.thumbY = 0 - end - elseif self._overflowY and overflowY == "auto" then - -- Only show scrollbar when content actually overflows - result.vertical.visible = true - result.vertical.trackHeight = element.height - (self.scrollbarPadding * 2) - - -- Calculate thumb height based on content ratio - local contentRatio = element.height / math.max(self._contentHeight, element.height) - result.vertical.thumbHeight = math.max(20, result.vertical.trackHeight * contentRatio) - - -- Calculate thumb position based on scroll ratio - local scrollRatio = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0 - local maxThumbY = result.vertical.trackHeight - result.vertical.thumbHeight - result.vertical.thumbY = maxThumbY * scrollRatio - end - - -- Horizontal scrollbar - -- Note: overflow="scroll" always shows scrollbar; overflow="auto" only when content overflows - if overflowX == "scroll" then - -- Always show scrollbar for "scroll" mode - result.horizontal.visible = true - result.horizontal.trackWidth = element.width - (self.scrollbarPadding * 2) - - if self._overflowX then - -- Content overflows, calculate proper thumb size - local contentRatio = element.width / math.max(self._contentWidth, element.width) - result.horizontal.thumbWidth = math.max(20, result.horizontal.trackWidth * contentRatio) - - -- Calculate thumb position based on scroll ratio - local scrollRatio = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0 - local maxThumbX = result.horizontal.trackWidth - result.horizontal.thumbWidth - result.horizontal.thumbX = maxThumbX * scrollRatio - else - -- No overflow, thumb fills entire track - result.horizontal.thumbWidth = result.horizontal.trackWidth - result.horizontal.thumbX = 0 - end - elseif self._overflowX and overflowX == "auto" then - -- Only show scrollbar when content actually overflows - result.horizontal.visible = true - result.horizontal.trackWidth = element.width - (self.scrollbarPadding * 2) - - -- Calculate thumb width based on content ratio - local contentRatio = element.width / math.max(self._contentWidth, element.width) - result.horizontal.thumbWidth = math.max(20, result.horizontal.trackWidth * contentRatio) - - -- Calculate thumb position based on scroll ratio - local scrollRatio = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0 - local maxThumbX = result.horizontal.trackWidth - result.horizontal.thumbWidth - result.horizontal.thumbX = maxThumbX * scrollRatio - end - - return result -end - ---- Get scrollbar at mouse position ----@param element Element The parent Element instance ----@param mouseX number ----@param mouseY number ----@return table|nil -- {component: "vertical"|"horizontal", region: "thumb"|"track"} -function ScrollManager:getScrollbarAtPosition(element, mouseX, mouseY) - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - - if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then - return nil - end - - local dims = self:calculateScrollbarDimensions(element) - local x, y = element.x, element.y - local w, h = element.width, element.height - - -- Check vertical scrollbar (only if not hidden) - if dims.vertical.visible and not self.hideScrollbars.vertical then - -- Position scrollbar within content area (x, y is border-box origin) - local contentX = x + element.padding.left - local contentY = y + element.padding.top - local trackX = contentX + w - self.scrollbarWidth - self.scrollbarPadding - local trackY = contentY + self.scrollbarPadding - local trackW = self.scrollbarWidth - local trackH = dims.vertical.trackHeight - - if mouseX >= trackX and mouseX <= trackX + trackW and mouseY >= trackY and mouseY <= trackY + trackH then - -- Check if over thumb - local thumbY = trackY + dims.vertical.thumbY - local thumbH = dims.vertical.thumbHeight - if mouseY >= thumbY and mouseY <= thumbY + thumbH then - return { component = "vertical", region = "thumb" } - else - return { component = "vertical", region = "track" } - end - end - end - - -- Check horizontal scrollbar (only if not hidden) - if dims.horizontal.visible and not self.hideScrollbars.horizontal then - -- Position scrollbar within content area (x, y is border-box origin) - local contentX = x + element.padding.left - local contentY = y + element.padding.top - local trackX = contentX + self.scrollbarPadding - local trackY = contentY + h - self.scrollbarWidth - self.scrollbarPadding - local trackW = dims.horizontal.trackWidth - local trackH = self.scrollbarWidth - - if mouseX >= trackX and mouseX <= trackX + trackW and mouseY >= trackY and mouseY <= trackY + trackH then - -- Check if over thumb - local thumbX = trackX + dims.horizontal.thumbX - local thumbW = dims.horizontal.thumbWidth - if mouseX >= thumbX and mouseX <= thumbX + thumbW then - return { component = "horizontal", region = "thumb" } - else - return { component = "horizontal", region = "track" } - end - end - end - - return nil -end - ---- Handle scrollbar mouse press ----@param element Element The parent Element instance ----@param mouseX number ----@param mouseY number ----@param button number ----@return boolean -- True if event was consumed -function ScrollManager:handleMousePress(element, mouseX, mouseY, button) - if button ~= 1 then - return false - end -- Only left click - - local scrollbar = self:getScrollbarAtPosition(element, mouseX, mouseY) - if not scrollbar then - return false - end - - if scrollbar.region == "thumb" then - -- Start dragging thumb - store start positions for relative movement tracking - self._scrollbarDragging = true - self._hoveredScrollbar = scrollbar.component - - -- Store drag start positions for relative movement calculation - self._dragStartMouseX = mouseX - self._dragStartMouseY = mouseY - self._dragStartScrollX = self._scrollX - self._dragStartScrollY = self._scrollY - - return true -- Event consumed - elseif scrollbar.region == "track" then - self:_scrollToTrackPosition(element, mouseX, mouseY, scrollbar.component) - return true - end - - return false -end - ---- Handle scrollbar drag ----@param element Element The parent Element instance ----@param mouseX number ----@param mouseY number ----@return boolean -- True if event was consumed -function ScrollManager:handleMouseMove(element, mouseX, mouseY) - if not self._scrollbarDragging then - return false - end - - local dims = self:calculateScrollbarDimensions(element) - - if self._hoveredScrollbar == "vertical" then - local trackH = dims.vertical.trackHeight - local thumbH = dims.vertical.thumbHeight - - -- Calculate relative mouse movement from drag start - local mouseDeltaY = mouseY - self._dragStartMouseY - - -- Convert mouse delta to scroll delta - -- scrollDelta / maxScroll = thumbDelta / (trackHeight - thumbHeight) - local scrollableTrackHeight = trackH - thumbH - local scrollDelta = scrollableTrackHeight > 0 and (mouseDeltaY / scrollableTrackHeight) * self._maxScrollY or 0 - - local newScrollY = self._dragStartScrollY + scrollDelta - newScrollY = self._utils.clamp(newScrollY, 0, self._maxScrollY) - - self:setScroll(nil, newScrollY) - return true - elseif self._hoveredScrollbar == "horizontal" then - local trackW = dims.horizontal.trackWidth - local thumbW = dims.horizontal.thumbWidth - - -- Calculate relative mouse movement from drag start - local mouseDeltaX = mouseX - self._dragStartMouseX - - -- Convert mouse delta to scroll delta - local scrollableTrackWidth = trackW - thumbW - local scrollDelta = scrollableTrackWidth > 0 and (mouseDeltaX / scrollableTrackWidth) * self._maxScrollX or 0 - - -- Apply delta to starting scroll position - local newScrollX = self._dragStartScrollX + scrollDelta - newScrollX = self._utils.clamp(newScrollX, 0, self._maxScrollX) - - self:setScroll(newScrollX, nil) - return true - end - - return false -end - ---- Handle scrollbar release ----@param button number ----@return boolean -- True if event was consumed -function ScrollManager:handleMouseRelease(button) - if button ~= 1 then - return false - end - - if self._scrollbarDragging then - self._scrollbarDragging = false - return true - end - - return false -end - ---- Scroll to track click position (internal helper) ----@param element Element The parent Element instance ----@param mouseX number ----@param mouseY number ----@param component string -- "vertical" or "horizontal" -function ScrollManager:_scrollToTrackPosition(element, mouseX, mouseY, component) - local dims = self:calculateScrollbarDimensions(element) - - if component == "vertical" then - local contentY = element.y + element.padding.top - local trackY = contentY + self.scrollbarPadding - local trackH = dims.vertical.trackHeight - local thumbH = dims.vertical.thumbHeight - - -- Calculate target thumb position (centered on click) - local targetThumbY = mouseY - trackY - (thumbH / 2) - targetThumbY = self._utils.clamp(targetThumbY, 0, trackH - thumbH) - - -- Convert to scroll position - local scrollRatio = (trackH - thumbH) > 0 and (targetThumbY / (trackH - thumbH)) or 0 - local newScrollY = scrollRatio * self._maxScrollY - - self:setScroll(nil, newScrollY) - elseif component == "horizontal" then - local contentX = element.x + element.padding.left - local trackX = contentX + self.scrollbarPadding - local trackW = dims.horizontal.trackWidth - local thumbW = dims.horizontal.thumbWidth - - -- Calculate target thumb position (centered on click) - local targetThumbX = mouseX - trackX - (thumbW / 2) - targetThumbX = self._utils.clamp(targetThumbX, 0, trackW - thumbW) - - -- Convert to scroll position - local scrollRatio = (trackW - thumbW) > 0 and (targetThumbX / (trackW - thumbW)) or 0 - local newScrollX = scrollRatio * self._maxScrollX - - self:setScroll(newScrollX, nil) - end -end - ---- Handle mouse wheel scrolling ----@param x number -- Horizontal scroll amount ----@param y number -- Vertical scroll amount ----@return boolean -- True if scroll was handled -function ScrollManager:handleWheel(x, y) - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - - if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then - return false - end - - -- In immediate mode, overflow might not be calculated yet, so allow scrolling based on maxScroll values - -- If _overflowY is nil/false but _maxScrollY > 0, we should still allow scrolling (from restored state) - local hasVerticalOverflow = (self._overflowY and self._maxScrollY > 0) or (self._maxScrollY and self._maxScrollY > 0) - local hasHorizontalOverflow = (self._overflowX and self._maxScrollX > 0) - or (self._maxScrollX and self._maxScrollX > 0) - - local scrolled = false - - -- Vertical scrolling - if y ~= 0 and (overflowY == "scroll" or overflowY == "auto") and hasVerticalOverflow then - local delta = -y * self.scrollSpeed -- Negative because wheel up = scroll up - if self.invertScroll then - delta = -delta -- Invert scroll direction if enabled - end - if self.smoothScrollEnabled then - -- Set target for smooth scrolling instead of instant jump - self._targetScrollY = self._utils.clamp((self._targetScrollY or self._scrollY) + delta, 0, self._maxScrollY) - else - -- Instant scrolling (default behavior) - local newScrollY = self._scrollY + delta - self:setScroll(nil, newScrollY) - end - scrolled = true - end - - -- Horizontal scrolling - if x ~= 0 and (overflowX == "scroll" or overflowX == "auto") and hasHorizontalOverflow then - local delta = -x * self.scrollSpeed - if self.invertScroll then - delta = -delta -- Invert scroll direction if enabled - end - if self.smoothScrollEnabled then - -- Set target for smooth scrolling instead of instant jump - self._targetScrollX = self._utils.clamp((self._targetScrollX or self._scrollX) + delta, 0, self._maxScrollX) - else - -- Instant scrolling (default behavior) - local newScrollX = self._scrollX + delta - self:setScroll(newScrollX, nil) - end - scrolled = true - end - - return scrolled -end - ---- Update scrollbar hover state based on mouse position ----@param element Element The parent Element instance ----@param mouseX number ----@param mouseY number -function ScrollManager:updateHoverState(element, mouseX, mouseY) - local scrollbar = self:getScrollbarAtPosition(element, mouseX, mouseY) - - if scrollbar then - if scrollbar.component == "vertical" then - self._scrollbarHoveredVertical = true - self._scrollbarHoveredHorizontal = false - elseif scrollbar.component == "horizontal" then - self._scrollbarHoveredVertical = false - self._scrollbarHoveredHorizontal = true - end - else - self._scrollbarHoveredVertical = false - self._scrollbarHoveredHorizontal = false - end -end - ---- Reset scrollbar press handled flag (call at start of frame) -function ScrollManager:resetScrollbarPressFlag() - self._scrollbarPressHandled = false -end - ---- Check if scrollbar press was handled this frame ----@return boolean -function ScrollManager:wasScrollbarPressHandled() - return self._scrollbarPressHandled -end - ---- Set scrollbar press handled flag -function ScrollManager:setScrollbarPressHandled() - self._scrollbarPressHandled = true -end - ---- Get state for immediate mode persistence ----@return table State data -function ScrollManager:getState() - return { - _scrollX = self._scrollX or 0, - _scrollY = self._scrollY or 0, - _targetScrollX = self._targetScrollX, - _targetScrollY = self._targetScrollY, - _scrollbarDragging = self._scrollbarDragging or false, - _hoveredScrollbar = self._hoveredScrollbar, - _scrollbarDragOffset = self._scrollbarDragOffset or 0, -- Deprecated but kept for compatibility - _dragStartMouseX = self._dragStartMouseX or 0, - _dragStartMouseY = self._dragStartMouseY or 0, - _dragStartScrollX = self._dragStartScrollX or 0, - _dragStartScrollY = self._dragStartScrollY or 0, - _scrollbarHoveredVertical = self._scrollbarHoveredVertical or false, - _scrollbarHoveredHorizontal = self._scrollbarHoveredHorizontal or false, - scrollBarStyle = self.scrollBarStyle, - scrollbarKnobOffset = self.scrollbarKnobOffset, - scrollbarPlacement = self.scrollbarPlacement, - scrollbarBalance = self.scrollbarBalance, - _overflowX = self._overflowX, - _overflowY = self._overflowY, - _contentWidth = self._contentWidth, - _contentHeight = self._contentHeight, - -- Touch fling state: without these, immediate-mode recreation zeroes the - -- release velocity on the next frame and momentum scrolling never runs. - _touchScrolling = self._touchScrolling or false, - _momentumScrolling = self._momentumScrolling or false, - _scrollVelocityX = self._scrollVelocityX or 0, - _scrollVelocityY = self._scrollVelocityY or 0, - _lastTouchTime = self._lastTouchTime or 0, - _lastTouchX = self._lastTouchX or 0, - _lastTouchY = self._lastTouchY or 0, - } -end - ---- Set state from immediate mode persistence ----@param state table State data -function ScrollManager:setState(state) - if not state then - return - end - - -- Support both old (scrollX) and new (_scrollX) field names for backward compatibility - if state._scrollX ~= nil then - self._scrollX = state._scrollX - elseif state.scrollX ~= nil then - self._scrollX = state.scrollX - end - - if state._scrollY ~= nil then - self._scrollY = state._scrollY - elseif state.scrollY ~= nil then - self._scrollY = state.scrollY - end - - if state._scrollbarDragging ~= nil then - self._scrollbarDragging = state._scrollbarDragging - elseif state.scrollbarDragging ~= nil then - self._scrollbarDragging = state.scrollbarDragging - end - - if state._hoveredScrollbar ~= nil then - self._hoveredScrollbar = state._hoveredScrollbar - elseif state.hoveredScrollbar ~= nil then - self._hoveredScrollbar = state.hoveredScrollbar - end - - if state._scrollbarDragOffset ~= nil then - self._scrollbarDragOffset = state._scrollbarDragOffset - elseif state.scrollbarDragOffset ~= nil then - self._scrollbarDragOffset = state.scrollbarDragOffset - end - - -- Restore drag start positions for relative movement tracking - if state._dragStartMouseX ~= nil then - self._dragStartMouseX = state._dragStartMouseX - end - - if state._dragStartMouseY ~= nil then - self._dragStartMouseY = state._dragStartMouseY - end - - if state._dragStartScrollX ~= nil then - self._dragStartScrollX = state._dragStartScrollX - end - - if state._dragStartScrollY ~= nil then - self._dragStartScrollY = state._dragStartScrollY - end - - if state._scrollbarHoveredVertical ~= nil then - self._scrollbarHoveredVertical = state._scrollbarHoveredVertical - end - - if state._scrollbarHoveredHorizontal ~= nil then - self._scrollbarHoveredHorizontal = state._scrollbarHoveredHorizontal - end - - if state.scrollBarStyle ~= nil then - self.scrollBarStyle = state.scrollBarStyle - end - - if state.scrollbarKnobOffset ~= nil then - self.scrollbarKnobOffset = self._utils.normalizeOffsetTable(state.scrollbarKnobOffset, 0) - end - - if state.scrollbarPlacement ~= nil then - self.scrollbarPlacement = state.scrollbarPlacement - end - - if state.scrollbarBalance ~= nil then - self.scrollbarBalance = state.scrollbarBalance - end - - if state._overflowX ~= nil then - self._overflowX = state._overflowX - end - - if state._overflowY ~= nil then - self._overflowY = state._overflowY - end - - if state._contentWidth ~= nil then - self._contentWidth = state._contentWidth - end - - if state._contentHeight ~= nil then - self._contentHeight = state._contentHeight - end - - if state._targetScrollX ~= nil then - self._targetScrollX = state._targetScrollX - end - - if state._targetScrollY ~= nil then - self._targetScrollY = state._targetScrollY - end - - -- Touch fling state (see getState): restore so momentum survives - -- immediate-mode element recreation between frames. - if state._touchScrolling ~= nil then - self._touchScrolling = state._touchScrolling - end - if state._momentumScrolling ~= nil then - self._momentumScrolling = state._momentumScrolling - end - if state._scrollVelocityX ~= nil then - self._scrollVelocityX = state._scrollVelocityX - end - if state._scrollVelocityY ~= nil then - self._scrollVelocityY = state._scrollVelocityY - end - if state._lastTouchTime ~= nil then - self._lastTouchTime = state._lastTouchTime - end - if state._lastTouchX ~= nil then - self._lastTouchX = state._lastTouchX - end - if state._lastTouchY ~= nil then - self._lastTouchY = state._lastTouchY - end -end - ---- Handle touch press for scrolling ----@param touchX number ----@param touchY number ----@return boolean -- True if touch scroll started -function ScrollManager:handleTouchPress(touchX, touchY) - if not self.touchScrollEnabled then - return false - end - - local overflowX = self.overflowX or self.overflow - local overflowY = self.overflowY or self.overflow - - if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then - return false - end - - -- Stop momentum scrolling if active - if self._momentumScrolling then - self._momentumScrolling = false - self._scrollVelocityX = 0 - self._scrollVelocityY = 0 - end - - -- Start touch scrolling - self._touchScrolling = true - self._lastTouchX = touchX - self._lastTouchY = touchY - self._lastTouchTime = love.timer.getTime() - - return true -end - ---- Handle touch move for scrolling ----@param touchX number ----@param touchY number ----@return boolean -- True if touch scroll was handled -function ScrollManager:handleTouchMove(touchX, touchY) - if not self._touchScrolling then - return false - end - - local currentTime = love.timer.getTime() - local dt = currentTime - self._lastTouchTime - - if dt <= 0 then - return false - end - - -- Calculate delta and velocity - local dx = touchX - self._lastTouchX - local dy = touchY - self._lastTouchY - - -- Invert deltas (touch moves opposite to scroll) - dx = -dx - dy = -dy - - -- Calculate velocity (pixels per second) - self._scrollVelocityX = dx / dt - self._scrollVelocityY = dy / dt - - -- Apply scroll with bounce if enabled - if self.bounceEnabled then - -- Allow overscroll - local newScrollX = self._scrollX + dx - local newScrollY = self._scrollY + dy - - -- Clamp to max overscroll limits - local minScrollX = -self.maxOverscroll - local maxScrollX = self._maxScrollX + self.maxOverscroll - local minScrollY = -self.maxOverscroll - local maxScrollY = self._maxScrollY + self.maxOverscroll - - newScrollX = self._utils.clamp(newScrollX, minScrollX, maxScrollX) - newScrollY = self._utils.clamp(newScrollY, minScrollY, maxScrollY) - - self._scrollX = newScrollX - self._scrollY = newScrollY - else - -- Normal clamped scrolling - self:scrollBy(dx, dy) - end - - -- Update last touch state - self._lastTouchX = touchX - self._lastTouchY = touchY - self._lastTouchTime = currentTime - - return true -end - ---- Handle touch release for scrolling ----@return boolean -- True if touch scroll was active -function ScrollManager:handleTouchRelease() - if not self._touchScrolling then - return false - end - - self._touchScrolling = false - - -- Start momentum scrolling if enabled and velocity is significant - if self.momentumScrollEnabled then - local velocityThreshold = 50 -- pixels per second - local totalVelocity = math.sqrt(self._scrollVelocityX ^ 2 + self._scrollVelocityY ^ 2) - - if totalVelocity > velocityThreshold then - self._momentumScrolling = true - else - self._scrollVelocityX = 0 - self._scrollVelocityY = 0 - end - else - self._scrollVelocityX = 0 - self._scrollVelocityY = 0 - end - - return true -end - ---- Update momentum scrolling (call every frame with dt) ----@param dt number Delta time in seconds -function ScrollManager:update(dt) - -- Smooth scroll interpolation - if self._targetScrollX or self._targetScrollY then - if self._targetScrollY then - local diff = self._targetScrollY - self._scrollY - if math.abs(diff) > 0.5 then - self._scrollY = self._scrollY + diff * self._smoothScrollSpeed - else - self._scrollY = self._targetScrollY - self._targetScrollY = nil - end - end - - if self._targetScrollX then - local diff = self._targetScrollX - self._scrollX - if math.abs(diff) > 0.5 then - self._scrollX = self._scrollX + diff * self._smoothScrollSpeed - else - self._scrollX = self._targetScrollX - self._targetScrollX = nil - end - end - end - - if not self._momentumScrolling then - -- Handle bounce back if overscrolled - if self.bounceEnabled then - self:_updateBounce(dt) - end - return - end - - -- Apply velocity to scroll position - local dx = self._scrollVelocityX * dt - local dy = self._scrollVelocityY * dt - - if self.bounceEnabled then - -- Allow overscroll during momentum - self._scrollX = self._scrollX + dx - self._scrollY = self._scrollY + dy - else - self:scrollBy(dx, dy) - end - - -- Apply friction (exponential decay) - self._scrollVelocityX = self._scrollVelocityX * self.scrollFriction - self._scrollVelocityY = self._scrollVelocityY * self.scrollFriction - - -- Stop momentum when velocity is very low - local totalVelocity = math.sqrt(self._scrollVelocityX ^ 2 + self._scrollVelocityY ^ 2) - if totalVelocity < 1 then - self._momentumScrolling = false - self._scrollVelocityX = 0 - self._scrollVelocityY = 0 - end - - -- Handle bounce back if overscrolled - if self.bounceEnabled then - self:_updateBounce(dt) - end -end - ---- Update bounce effect when overscrolled (internal) ----@param dt number Delta time in seconds -function ScrollManager:_updateBounce() - local bounced = false - - -- Bounce back horizontal overscroll - if self._scrollX < 0 then - local springForce = -self._scrollX * self.bounceStiffness - self._scrollX = self._scrollX + springForce - if math.abs(self._scrollX) < 0.5 then - self._scrollX = 0 - end - bounced = true - elseif self._scrollX > self._maxScrollX then - local overflow = self._scrollX - self._maxScrollX - local springForce = -overflow * self.bounceStiffness - self._scrollX = self._scrollX + springForce - if math.abs(overflow) < 0.5 then - self._scrollX = self._maxScrollX - end - bounced = true - end - - -- Bounce back vertical overscroll - if self._scrollY < 0 then - local springForce = -self._scrollY * self.bounceStiffness - self._scrollY = self._scrollY + springForce - if math.abs(self._scrollY) < 0.5 then - self._scrollY = 0 - end - bounced = true - elseif self._scrollY > self._maxScrollY then - local overflow = self._scrollY - self._maxScrollY - local springForce = -overflow * self.bounceStiffness - self._scrollY = self._scrollY + springForce - if math.abs(overflow) < 0.5 then - self._scrollY = self._maxScrollY - end - bounced = true - end - - -- Stop momentum if bouncing - if bounced and self._momentumScrolling then - -- Reduce velocity during bounce - self._scrollVelocityX = self._scrollVelocityX * 0.9 - self._scrollVelocityY = self._scrollVelocityY * 0.9 - end -end - ---- Check if currently touch scrolling ----@return boolean -function ScrollManager:isTouchScrolling() - return self._touchScrolling -end - ---- Check if currently momentum scrolling ----@return boolean -function ScrollManager:isMomentumScrolling() - return self._momentumScrolling -end - -------------------------------------------------------------------------------- --- Element-facing delegates --- --- These wrappers bind the Element class's scroll API directly onto the --- ScrollManager instance methods. Each takes the Element as its first argument --- (the role `self` played when these methods lived on Element), performs the --- nil-safety guard, invokes the owning ScrollManager instance, and syncs state --- back onto the element for backward-compatible readers (Renderer, FlexLove, --- Context hit-testing read these fields from Element). --- --- Element binds them via direct assignment in Element.init, e.g. --- Element.scrollToTop = Element._ScrollManager.scrollToTop --- so Element retains only 1-line delegates and owns no scroll logic. -------------------------------------------------------------------------------- - -local _EMPTY_SCROLLBAR_DIMS = { - vertical = { visible = false, trackHeight = 0, thumbHeight = 0, thumbY = 0 }, - horizontal = { visible = false, trackWidth = 0, thumbWidth = 0, thumbX = 0 }, -} - ---- Sync internal scroll state onto the element for backward-compatible readers. ----@param element table Element instance whose _scrollManager holds the state -function ScrollManager.syncToElement(element) - local sm = element._scrollManager - if not sm then - return - end - element._overflowX = sm._overflowX - element._overflowY = sm._overflowY - element._contentWidth = sm._contentWidth - element._contentHeight = sm._contentHeight - element._scrollX = sm._scrollX - element._scrollY = sm._scrollY - element._maxScrollX = sm._maxScrollX - element._maxScrollY = sm._maxScrollY - element._scrollbarHoveredVertical = sm._scrollbarHoveredVertical - element._scrollbarHoveredHorizontal = sm._scrollbarHoveredHorizontal - element._scrollbarDragging = sm._scrollbarDragging - element._hoveredScrollbar = sm._hoveredScrollbar - element._scrollbarDragOffset = sm._scrollbarDragOffset -end - ---- Backward-compatible alias retained by Element internals (update hover/drag). -ScrollManager.syncScrollManagerState = ScrollManager.syncToElement - ---- Detect overflow and sync state onto element. ----@param element table Element instance -function ScrollManager._detectOverflow(element) - local sm = element._scrollManager - if not sm then - return - end - sm:detectOverflow(element) - ScrollManager.syncToElement(element) -end - ---- Set scroll position (element-facing). Nil args keep the current axis. ----@param element table Element instance ----@param x number? X scroll position ----@param y number? Y scroll position -function ScrollManager.setScrollPosition(element, x, y) - local sm = element._scrollManager - if not sm then - return - end - sm:setScroll(x, y) - ScrollManager.syncToElement(element) -end - ---- Calculate scrollbar dimensions (element-facing). ----@param element table Element instance ----@return table dims {vertical, horizontal} -function ScrollManager._calculateScrollbarDimensions(element) - local sm = element._scrollManager - if not sm then - return _EMPTY_SCROLLBAR_DIMS - end - return sm:calculateScrollbarDimensions(element) -end - ---- Get scrollbar at mouse position (element-facing). ----@param element table Element instance ----@param mouseX number ----@param mouseY number ----@return table|nil {component, region} -function ScrollManager._getScrollbarAtPosition(element, mouseX, mouseY) - local sm = element._scrollManager - if not sm then - return nil - end - return sm:getScrollbarAtPosition(element, mouseX, mouseY) -end - ---- Handle scrollbar mouse press (element-facing). ----@param element table Element instance ----@param mouseX number ----@param mouseY number ----@param button number ----@return boolean consumed -function ScrollManager._handleScrollbarPress(element, mouseX, mouseY, button) - local sm = element._scrollManager - if not sm then - return false - end - local consumed = sm:handleMousePress(element, mouseX, mouseY, button) - ScrollManager.syncToElement(element) - return consumed -end - ---- Handle scrollbar drag (element-facing). ----@param element table Element instance ----@param mouseX number ----@param mouseY number ----@return boolean consumed -function ScrollManager._handleScrollbarDrag(element, mouseX, mouseY) - local sm = element._scrollManager - if not sm then - return false - end - local consumed = sm:handleMouseMove(element, mouseX, mouseY) - ScrollManager.syncToElement(element) - return consumed -end - ---- Handle scrollbar release (element-facing). ----@param element table Element instance ----@param button number ----@return boolean consumed -function ScrollManager._handleScrollbarRelease(element, button) - local sm = element._scrollManager - if not sm then - return false - end - local consumed = sm:handleMouseRelease(button) - ScrollManager.syncToElement(element) - return consumed -end - ---- Handle mouse wheel scrolling (element-facing). ----@param element table Element instance ----@param x number Horizontal scroll amount ----@param y number Vertical scroll amount ----@return boolean consumed -function ScrollManager._handleWheelScroll(element, x, y) - local sm = element._scrollManager - if not sm then - return false - end - local consumed = sm:handleWheel(x, y) - ScrollManager.syncToElement(element) - return consumed -end - ---- Get current scroll position (element-facing). ----@param element table Element instance ----@return number scrollX, number scrollY -function ScrollManager.getScrollPosition(element) - local sm = element._scrollManager - if not sm then - return 0, 0 - end - return sm:getScroll() -end - --- The following getters share names with ScrollManager *instance* methods, --- so the element-facing wrappers use distinct `element`-prefixed names to --- avoid shadowing the instance API (tests call sm:getMaxScroll() etc.). - ---- Get maximum scroll bounds (element-facing). ----@param element table Element instance ----@return number maxScrollX, number maxScrollY -function ScrollManager.elementGetMaxScroll(element) - local sm = element._scrollManager - if not sm then - return 0, 0 - end - return sm:getMaxScroll() -end - ---- Get scroll percentage 0-1 (element-facing). ----@param element table Element instance ----@return number percentX, number percentY -function ScrollManager.elementGetScrollPercentage(element) - local sm = element._scrollManager - if not sm then - return 0, 0 - end - return sm:getScrollPercentage() -end - ---- Check if element has overflow (element-facing). ----@param element table Element instance ----@return boolean hasOverflowX, boolean hasOverflowY -function ScrollManager.elementHasOverflow(element) - local sm = element._scrollManager - if not sm then - return false, false - end - return sm:hasOverflow() -end - ---- Get content dimensions (element-facing). ----@param element table Element instance ----@return number contentWidth, number contentHeight -function ScrollManager.elementGetContentSize(element) - local sm = element._scrollManager - if not sm then - return 0, 0 - end - return sm:getContentSize() -end - ---- Scroll by relative delta (element-facing). --- In immediate mode, per-axis deltas whose scroll bound is still 0 are deferred --- until layout calculates the bound (delegates to Element:_deferMethod). ----@param element table Element instance ----@param dx number? X delta ----@param dy number? Y delta -function ScrollManager.elementScrollBy(element, dx, dy) - local sm = element._scrollManager - if not sm then - return - end - local maxScrollX, maxScrollY = sm:getMaxScroll() - if dx ~= nil and maxScrollX == 0 then - element:_deferMethod("scrollBy", dx, nil) - dx = nil - end - if dy ~= nil and maxScrollY == 0 then - element:_deferMethod("scrollBy", nil, dy) - dy = nil - end - if dx ~= nil or dy ~= nil then - sm:scrollBy(dx, dy) - ScrollManager.syncToElement(element) - end -end - ---- Jump to the top of scrollable content. ----@param element table Element instance -function ScrollManager.scrollToTop(element) - element:setScrollPosition(nil, 0) -end - ---- Jump to the bottom of scrollable content. --- Defers until layout has calculated the vertical scroll bound. ----@param element table Element instance -function ScrollManager.scrollToBottom(element) - local sm = element._scrollManager - if not sm then - return - end - local _, maxScrollY = sm:getMaxScroll() - if maxScrollY > 0 then - element:setScrollPosition(nil, maxScrollY) - else - element:_deferMethod("scrollToBottom") - end -end - ---- Jump to the leftmost position of scrollable content. ----@param element table Element instance -function ScrollManager.scrollToLeft(element) - element:setScrollPosition(0, nil) -end - ---- Jump to the rightmost position of scrollable content. --- Defers until layout has calculated the horizontal scroll bound. ----@param element table Element instance -function ScrollManager.scrollToRight(element) - local sm = element._scrollManager - if not sm then - return - end - local maxScrollX, _ = sm:getMaxScroll() - if maxScrollX > 0 then - element:setScrollPosition(maxScrollX, nil) - else - element:_deferMethod("scrollToRight") - end -end - ---- Restore scrollbar state from StateManager in immediate mode. ----@param element table Element instance -function ScrollManager.restoreImmediateState(element) - -- Mode-aware guard: only immediate-mode frames keep state in StateManager; - -- in retained mode the element (and its ScrollManager) persist between - -- frames, so there is nothing to restore. Routed through StateManager so no - -- raw mode check lives here (behavior-mode-unification task 11). - if not element._stateId or not ScrollManager._StateManager.isImmediateMode() then - return - end - local state = ScrollManager._StateManager.getState(element._stateId) - if not state or not state.scrollManager then - return - end - local sm_state = state.scrollManager - element._scrollbarHoveredVertical = sm_state._scrollbarHoveredVertical or false - element._scrollbarHoveredHorizontal = sm_state._scrollbarHoveredHorizontal or false - element._scrollbarDragging = sm_state._scrollbarDragging or false - element._hoveredScrollbar = sm_state._hoveredScrollbar - element._scrollbarDragOffset = sm_state._scrollbarDragOffset or 0 - - local sm = element._scrollManager - if sm then - sm._scrollbarHoveredVertical = element._scrollbarHoveredVertical - sm._scrollbarHoveredHorizontal = element._scrollbarHoveredHorizontal - sm._scrollbarDragging = element._scrollbarDragging - sm._hoveredScrollbar = element._hoveredScrollbar - sm._scrollbarDragOffset = element._scrollbarDragOffset - sm._dragStartMouseX = sm_state._dragStartMouseX or 0 - sm._dragStartMouseY = sm_state._dragStartMouseY or 0 - sm._dragStartScrollX = sm_state._dragStartScrollX or 0 - sm._dragStartScrollY = sm_state._dragStartScrollY or 0 - end -end - ---- Update hover, drag, and press interaction for scrollbars during Element:update. ----@param element table Element instance ----@param mx number Mouse X ----@param my number Mouse Y -function ScrollManager.updateInteraction(element, mx, my) - local sm = element._scrollManager - if sm then - sm:updateHoverState(element, mx, my) - ScrollManager.syncToElement(element) - end - - if element._scrollbarDragging and love.mouse.isDown(1) then - ScrollManager._handleScrollbarDrag(element, mx, my) - elseif element._scrollbarDragging then - if sm then - sm:handleMouseRelease(1) - ScrollManager.syncToElement(element) - end - if element._stateId and ScrollManager._StateManager.isImmediateMode() then - ScrollManager._StateManager.updateState(element._stateId, { - scrollbarDragging = false, - }) - end - end - - -- Handle scrollbar press for elements with scrollable overflow - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - local hasScrollableOverflow = ( - overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" - ) - - if hasScrollableOverflow and not element._scrollbarDragging then - if love.mouse.isDown(1) and not element._scrollbarPressHandled then - local scrollbarPressed = ScrollManager._handleScrollbarPress(element, mx, my, 1) - if scrollbarPressed then - element._scrollbarPressHandled = true - end - elseif not love.mouse.isDown(1) then - element._scrollbarPressHandled = false - end - end -end - -return ScrollManager diff --git a/libs/flexlove/modules/Select.lua b/libs/flexlove/modules/Select.lua deleted file mode 100644 index 21de8ecd..00000000 --- a/libs/flexlove/modules/Select.lua +++ /dev/null @@ -1,719 +0,0 @@ ----@class Select -local Select = {} - ----Initialize Select module with required dependencies ----@param deps table -function Select.init(deps) - Select._ErrorHandler = deps.ErrorHandler - Select._Context = deps.Context - Select._StateManager = deps.StateManager - Select._utils = deps.utils - Select._Element = deps.Element -end - ----Initialize selectParent state on an element ----@param element Element ----@param selectParentConfig table -function Select.initSelectParent(element, selectParentConfig) - element._selectState = { - value = selectParentConfig.value, - open = selectParentConfig.open or false, - placeholder = selectParentConfig.placeholder, - selectFrame = nil, - selectAnchor = nil, - onChange = selectParentConfig.onChange, - options = {}, - optionLookup = {}, - expectedFrameParent = nil, - frameAdopted = false, - } - - -- Restore select state from StateManager. Mode-aware via - -- Context.isImmediateMode (behavior-mode-unification task 11). - if Select._Context.isImmediateMode() and element._stateId and element._stateId ~= "" then - local state = Select._StateManager.getState(element._stateId) - if state and state._selectOpen ~= nil then - element._selectState.open = state._selectOpen - end - if state and state._selectValue ~= nil then - element._selectState.value = state._selectValue - if element.selectParent then - element.selectParent.value = state._selectValue - end - end - if state and state._selectSelectedLabel ~= nil then - element._selectState.selectedLabel = state._selectSelectedLabel - end - end -end - ----Initialize selectOption on an element ----@param element Element ----@param selectOptionConfig table -function Select.initSelectOption(element, selectOptionConfig) - element.selectOption = { - value = selectOptionConfig.value, - label = selectOptionConfig.label or element.text, - disabled = selectOptionConfig.disabled or false, - } -end - ----@param selectParent Element -function Select.rebuildOptionLookup(selectParent) - if not selectParent or not selectParent._selectState then - return - end - - selectParent._selectState.optionLookup = {} - for _, optionElement in ipairs(selectParent._selectState.options) do - if optionElement and optionElement.selectOption then - selectParent._selectState.optionLookup[optionElement.selectOption.value] = optionElement - end - end -end - ----@param selectParent Element -function Select.syncOptionStates(selectParent) - if not selectParent or not selectParent._selectState then - return - end - - local selectedOption = nil - local selectedLabel = selectParent._selectState.selectedLabel - - for _, optionElement in ipairs(selectParent._selectState.options) do - local isSelected = optionElement.selectOption - and optionElement.selectOption.value == selectParent._selectState.value - optionElement._selectSelected = isSelected - optionElement.ariaChecked = isSelected - - if isSelected then - selectedOption = optionElement - selectedLabel = optionElement.selectOption.label or optionElement.text - end - end - - selectParent._selectState.selectedOption = selectedOption - selectParent._selectState.selectedLabel = selectedLabel -end - ----@param element Element -function Select.resetOptions(element) - if not element._selectState then - return - end - - element._selectState.options = {} - element._selectState.optionLookup = {} - element._selectState.selectedOption = nil -end - ----@param frame any ----@return boolean -function Select.isValidSelectFrame(frame) - local Element = Select._Element - return type(frame) == "table" and getmetatable(frame) == Element -end - ----@param element Element ----@param code string ----@param details table? -function Select.warnSelectFrame(element, code, details) - Select._ErrorHandler:warn("Element", code, details or { element = element.id }) -end - ----@param element Element ----@param frame Element -function Select.trackManagedFrame(element, frame) - element._selectState.selectFrame = frame - local expectedParent = element._selectState.selectAnchor or element - element._selectState.expectedFrameParent = expectedParent - element._selectState.frameAdopted = frame.parent == expectedParent - if frame._managedSelectBaseOpacity == nil then - frame._managedSelectBaseOpacity = frame.opacity - end - if frame._managedSelectBaseVisibility == nil then - frame._managedSelectBaseVisibility = frame.visibility or "visible" - end - if frame._managedSelectBaseDisabled == nil then - frame._managedSelectBaseDisabled = frame.disabled or false - end - frame._managedSelectOwner = element - frame._managedSelectFrame = true -end - ----@param element Element ----@return Element -function Select.getOrCreateManagedAnchor(element) - if element._selectState.selectAnchor then - return element._selectState.selectAnchor - end - - local Element = Select._Element - local anchor = Element.new({ - id = string.format("%s__select_anchor", element.id or "select"), - parent = element, - positioning = Select._utils.enums.Positioning.ABSOLUTE, - left = 0, - top = element:getBorderBoxHeight(), - width = element:getBorderBoxWidth(), - opacity = 1, - visibility = "hidden", - disabled = true, - }) - - anchor._managedSelectAnchor = true - anchor._managedSelectOwner = element - element._selectState.selectAnchor = anchor - return anchor -end - ----@param element Element ----@param frame Element -function Select.applyManagedFrameLayout(element, frame) - local anchor = Select.getOrCreateManagedAnchor(element) - local triggerBorderBoxWidth = element:getBorderBoxWidth() - anchor.left = 0 - anchor.top = element:getBorderBoxHeight() - anchor.width = triggerBorderBoxWidth - anchor.units.left = { value = 0, unit = "px" } - anchor.units.top = { value = element:getBorderBoxHeight(), unit = "px" } - anchor.units.width = { value = triggerBorderBoxWidth, unit = "px" } - frame._managedSelectMinimumBorderBoxWidth = triggerBorderBoxWidth - - frame.positioning = frame.positioning or Select._utils.enums.Positioning.RELATIVE - frame._explicitlyAbsolute = false - frame.left = nil - frame.top = nil - frame.right = nil - frame.bottom = nil - - if frame.parent ~= anchor then - frame:setParent(anchor) - end - - if frame.autosizing and frame.autosizing.width then - local contentWidth = frame:calculateAutoWidth() - frame._borderBoxWidth = contentWidth + frame.padding.left + frame.padding.right - frame.width = contentWidth - end - - if frame.parent == anchor then - anchor.width = math.max(triggerBorderBoxWidth, frame:getBorderBoxWidth()) - anchor.units.width = { value = anchor.width, unit = "px" } - end - - element._selectState.expectedFrameParent = anchor - element._selectState.frameAdopted = frame.parent == anchor -end - ----@param element Element ----@param frame Element -function Select.adoptSelectFrame(element, frame) - if not element._selectState then - return - end - - if not Select.isValidSelectFrame(frame) then - Select.warnSelectFrame(element, "ELEM_007", { - element = element.id, - property = "selectParent.selectFrame", - got = type(frame), - }) - return - end - - if frame == element then - Select.warnSelectFrame(element, "ELEM_007", { - element = element.id, - property = "selectParent.selectFrame", - reason = "select cannot use itself as its managed frame", - }) - return - end - - local anchor = Select.getOrCreateManagedAnchor(element) - - if frame.parent and frame.parent ~= element and frame.parent ~= anchor then - Select.warnSelectFrame(element, "ELEM_008", { - element = element.id, - frame = frame.id, - parent = frame.parent.id, - }) - end - - Select.trackManagedFrame(element, frame) - Select.applyManagedFrameLayout(element, frame) - Select.syncManagedFrameVisibility(element) - - -- Layout is deferred to endFrame in immediate mode. shouldLayout() - -- encapsulates the mode check (behavior-mode-unification task 11). - if Select._StateManager.shouldLayout() then - anchor:layoutChildren() - element:layoutChildren() - end - - local pendingOptions = {} - for _, child in ipairs(element.children) do - if child ~= frame and child.selectOption then - table.insert(pendingOptions, child) - end - end - - for _, option in ipairs(pendingOptions) do - Select.attachOptionToManagedFrame(option) - end -end - ----@param element Element -function Select.ensureFrameState(element) - if not element._selectState or not element._selectState.selectFrame then - return - end - - local frame = element._selectState.selectFrame - local anchor = element._selectState.selectAnchor - if anchor then - local triggerBorderBoxWidth = element:getBorderBoxWidth() - anchor.left = 0 - anchor.top = element:getBorderBoxHeight() - anchor.width = triggerBorderBoxWidth - anchor.units.left = { value = 0, unit = "px" } - anchor.units.top = { value = element:getBorderBoxHeight(), unit = "px" } - anchor.units.width = { value = triggerBorderBoxWidth, unit = "px" } - frame._managedSelectMinimumBorderBoxWidth = triggerBorderBoxWidth - if frame.autosizing and frame.autosizing.width then - local contentWidth = frame:calculateAutoWidth() - frame._borderBoxWidth = contentWidth + frame.padding.left + frame.padding.right - frame.width = contentWidth - end - if frame.parent == anchor then - anchor.width = math.max(triggerBorderBoxWidth, frame:getBorderBoxWidth()) - anchor.units.width = { value = anchor.width, unit = "px" } - end - if frame.parent == anchor then - anchor:layoutChildren() - end - elseif frame.parent == element then - Select.applyManagedFrameLayout(element, frame) - end - - local expectedParent = anchor or element._selectState.expectedFrameParent - if frame.parent ~= expectedParent then - Select.warnSelectFrame(element, "ELEM_009", { - element = element.id, - frame = frame.id, - expectedParent = expectedParent and expectedParent.id or nil, - actualParent = frame.parent and frame.parent.id or nil, - }) - element._selectState.expectedFrameParent = frame.parent - element._selectState.frameAdopted = frame.parent == expectedParent - end -end - ----@param element Element -function Select.syncManagedFrameVisibility(element) - if not element._selectState or not element._selectState.selectFrame then - return - end - - local frame = element._selectState.selectFrame - local anchor = element._selectState.selectAnchor - local isOpen = element._selectState.open == true - frame.visibility = isOpen and (frame._managedSelectBaseVisibility or "visible") or "hidden" - frame.opacity = frame._managedSelectBaseOpacity or 1 - if isOpen then - frame.disabled = frame._managedSelectBaseDisabled == true - else - frame.disabled = true - end - if anchor then - anchor.visibility = isOpen and "visible" or "hidden" - anchor.opacity = 1 - anchor.disabled = not isOpen - end -end - ----@param element Element ----@return Element? -function Select.findOwningSelectParent(element) - if element._selectParentHint and element._selectParentHint._selectState then - return element._selectParentHint - end - - local current = element.parent - while current do - if current._selectState then - return current - end - current = current.parent - end - - return nil -end - ----@param element Element -function Select.registerWithSelectParent(element) - if not element.selectOption then - return - end - - local selectParent = Select.findOwningSelectParent(element) - if not selectParent then - return - end - - element._selectParentElement = selectParent - - for _, optionElement in ipairs(selectParent._selectState.options) do - if optionElement == element then - return - end - end - - table.insert(selectParent._selectState.options, element) - Select.rebuildOptionLookup(selectParent) - Select.syncOptionStates(selectParent) -end - ----@param element Element -function Select.attachOptionToManagedFrame(element) - if not element.selectOption then - return - end - - local selectParent = Select.findOwningSelectParent(element) - if not selectParent or not selectParent._selectState or not selectParent._selectState.selectFrame then - return - end - - local selectFrame = selectParent._selectState.selectFrame - if element.parent ~= selectFrame then - element._selectParentHint = selectParent - - if - element._originalPositioning == Select._utils.enums.Positioning.ABSOLUTE - and element._managedSelectOptionUsesFrameLayout == nil - then - element._managedSelectOptionUsesFrameLayout = true - element.positioning = Select._utils.enums.Positioning.RELATIVE - element._originalPositioning = nil - element._explicitlyAbsolute = false - element.left = nil - element.top = nil - element.right = nil - element.bottom = nil - end - - element:setParent(selectFrame) - -- Ensure frame geometry eagerly only in retained mode; deferred to the - -- per-frame update in immediate mode (behavior-mode-unification task 11). - if Select._StateManager.shouldLayout() then - Select.ensureFrameState(selectParent) - end - end -end - ----@param element Element -function Select.unregisterFromSelectParent(element) - if not element.selectOption or not element._selectParentElement or not element._selectParentElement._selectState then - element._selectParentElement = nil - return - end - - local selectParent = element._selectParentElement - for index, optionElement in ipairs(selectParent._selectState.options) do - if optionElement == element then - table.remove(selectParent._selectState.options, index) - break - end - end - - Select.rebuildOptionLookup(selectParent) - Select.syncOptionStates(selectParent) - element._selectParentElement = nil -end - ----@param element Element -function Select.saveStateToStateManager(element) - if not element._selectState then - return - end - if element._stateId and Select._Context.isImmediateMode() and element._stateId ~= "" then - Select._StateManager.updateState(element._stateId, { - _selectOpen = element._selectState.open, - _selectValue = element._selectState.value, - _selectSelectedLabel = element._selectState.selectedLabel, - }) - end -end - ----@param element Element -function Select.openSelect(element) - if not element._selectState then - return - end - - Select.ensureFrameState(element) - element._selectState.open = true - element.ariaExpanded = true - if element.selectParent then - element.selectParent.open = true - end - Select.syncManagedFrameVisibility(element) - Select.saveStateToStateManager(element) -end - ----@param element Element -function Select.closeSelect(element) - if not element._selectState then - return - end - - Select.ensureFrameState(element) - element._selectState.open = false - element.ariaExpanded = false - if element.selectParent then - element.selectParent.open = false - end - Select.syncManagedFrameVisibility(element) - Select.saveStateToStateManager(element) -end - ----@param element Element -function Select.toggleSelect(element) - if not element._selectState then - return - end - - if element.disabled then - return - end - - if element._selectState.open then - Select.closeSelect(element) - else - Select.openSelect(element) - end - - if element.onEvent then - element.onEvent(element, { type = "selecttoggle", open = element._selectState.open }) - end -end - ----@param element Element ----@return boolean -function Select.isSelectOpen(element) - return element._selectState ~= nil and element._selectState.open == true -end - ----@param element Element ----@return any -function Select.getSelectValue(element) - if not element._selectState then - return nil - end - return element._selectState.value -end - ----@param element Element ----@return string? -function Select.getSelectLabel(element) - if not element._selectState then - return nil - end - - local selectedOption = element._selectState.selectedOption - or element._selectState.optionLookup[element._selectState.value] - if selectedOption and selectedOption.selectOption then - return selectedOption.selectOption.label or selectedOption.text - end - - return element._selectState.selectedLabel or element._selectState.placeholder -end - ----@param element Element ----@return boolean -function Select.isSelectedOption(element) - if not element.selectOption or not element._selectParentElement or not element._selectParentElement._selectState then - return false - end - return element._selectParentElement._selectState.value == element.selectOption.value -end - ----@param element Element ----@param value any ----@param optionElement Element? -function Select.setSelectValue(element, value, optionElement) - if not element._selectState then - return - end - - if element.disabled then - return - end - - local didChange = element._selectState.value ~= value - element._selectState.value = value - if element.selectParent then - element.selectParent.value = value - end - - if optionElement and optionElement.selectOption then - element._selectState.selectedLabel = optionElement.selectOption.label or optionElement.text - end - - Select.syncOptionStates(element) - Select.closeSelect(element) - Select.saveStateToStateManager(element) - - if element.onEvent then - element.onEvent(element, { type = "selectchange", value = value, option = optionElement }) - end - - if didChange and element._selectState.onChange then - element._selectState.onChange(element, value, optionElement and optionElement.selectOption or nil) - end -end - ----@param element Element -function Select.handleRelease(element) - if element.disabled then - return - end - - if element.selectOption then - local selectParent = element._selectParentElement or Select.findOwningSelectParent(element) - if not selectParent then - return - end - - if element.selectOption.disabled then - Select.closeSelect(selectParent) - return - end - - Select.setSelectValue(selectParent, element.selectOption.value, element) - return - end - - if element._selectState then - Select.toggleSelect(element) - end -end - ----Save select state for state persistence (called from Element:saveState) ----@param element Element ----@return table? -function Select.saveState(element) - if not element._selectState then - return nil - end - return { - value = element._selectState.value, - open = element._selectState.open, - selectedLabel = element._selectState.selectedLabel, - } -end - ----Restore select state (called from Element:restoreState) ----@param element Element ----@param state table -function Select.restoreState(element, state) - if not element._selectState or not state then - return - end - element._selectState.value = state.value - element._selectState.open = state.open or false - element._selectState.selectedLabel = state.selectedLabel - if element.selectParent then - element.selectParent.value = state.value - element.selectParent.open = state.open or false - end - element.ariaExpanded = element._selectState.open - Select.syncOptionStates(element) -end - ----Clean up select-related resources (called from Element:destroy) ----@param element Element -function Select.cleanupDestroy(element) - if element._selectState then - local frame = element._selectState.selectFrame - local anchor = element._selectState.selectAnchor - if frame then - frame._managedSelectOwner = nil - frame._managedSelectFrame = nil - frame._managedSelectBaseOpacity = nil - frame._managedSelectBaseVisibility = nil - frame._managedSelectBaseDisabled = nil - end - if anchor then - anchor._managedSelectOwner = nil - anchor._managedSelectAnchor = nil - end - element._selectState = nil - end - if element._managedSelectFrame and element._managedSelectOwner then - if element._managedSelectOwner._selectState then - element._managedSelectOwner._selectState.selectFrame = nil - element._managedSelectOwner._selectState.expectedFrameParent = nil - element._managedSelectOwner._selectState.frameAdopted = false - end - element._managedSelectOwner = nil - element._managedSelectFrame = nil - element._managedSelectBaseOpacity = nil - element._managedSelectBaseVisibility = nil - element._managedSelectBaseDisabled = nil - end - if element._managedSelectAnchor and element._managedSelectOwner then - if element._managedSelectOwner._selectState then - element._managedSelectOwner._selectState.selectAnchor = nil - end - element._managedSelectOwner = nil - element._managedSelectAnchor = nil - end - if element.selectParent then - element.selectParent.onChange = nil - end -end - ---- Called when a select parent removes a child: clears frame/anchor refs if the removed child was the ---- select-managed frame or anchor. Keeps select state-mutation logic owned by the Select module. ----@param element Element The select parent whose child was removed. ----@param child Element The removed child. -function Select.handleChildRemoved(element, child) - if not element._selectState then - return - end - if element._selectState.selectFrame == child then - element._selectState.selectFrame = nil - element._selectState.expectedFrameParent = nil - element._selectState.frameAdopted = false - end - if element._selectState.selectAnchor == child then - element._selectState.selectAnchor = nil - end -end - ---- Layout-path hook: adjust an auto-width child's border-box width for a managed-select frame. ---- Invoked from LayoutEngine (via the Element delegate) during vertical-flex auto-width calculation. ----@param element Element The managed-select frame (the dropdown container). ----@param child Element The flex child being measured. ----@param childBorderBoxWidth number Current computed border-box width of `child`. ----@return number Possibly-adjusted border-box width. -function Select.adjustAutoWidthChild(element, child, childBorderBoxWidth) - if - element._managedSelectFrame - and element.autosizing - and element.autosizing.width - and child.units - and child.units.width - and child.units.width.unit == "%" - then - local intrinsicBorderBoxWidth = child:calculateAutoWidth() + child.padding.left + child.padding.right - return math.max(childBorderBoxWidth, intrinsicBorderBoxWidth) - end - return childBorderBoxWidth -end - -return Select diff --git a/libs/flexlove/modules/StateManager.lua b/libs/flexlove/modules/StateManager.lua deleted file mode 100644 index 3a7b2af7..00000000 --- a/libs/flexlove/modules/StateManager.lua +++ /dev/null @@ -1,790 +0,0 @@ ----@class StateManager -local StateManager = {} - --- ErrorHandler will be injected via init -local ErrorHandler - --- State storage: ID -> state table -local stateStore = {} - --- Frame tracking metadata: ID -> {lastFrame, createdFrame, accessCount} -local stateMetadata = {} - --- Frame counter -local frameNumber = 0 - --- Counter to track multiple elements created at the same source location (e.g., in loops) -local callSiteCounters = {} - --- Stateful element mapping: stateId -> element instance --- Used in retained mode for cache-through: StateManager resolves id -> element -> field -local statefulElements = {} - --- Dirty state tracking for flushFrame: set of {id, key} pairs modified this frame -local dirtyState = {} - --- Immediate mode flag -local _immediateMode = false - --- Configuration -local config = { - stateRetentionFrames = 2, -- Keep unused state for 2 frames - maxStateEntries = 1000, -- Maximum state entries before forced GC -} - --- Default state values (sparse storage - don't store these) -local stateDefaults = { - -- Interaction states - hover = false, - pressed = false, - focused = false, - disabled = false, - active = false, - - -- Scrollbar states - scrollbarHoveredVertical = false, - scrollbarHoveredHorizontal = false, - scrollbarDragging = false, - hoveredScrollbar = nil, - scrollbarDragOffset = 0, - dragStartMouseX = 0, - dragStartMouseY = 0, - dragStartScrollX = 0, - dragStartScrollY = 0, - - -- Scroll position - scrollX = 0, - scrollY = 0, - _scrollX = 0, - _scrollY = 0, - - -- Click tracking - _clickCount = 0, - _lastClickTime = nil, - _lastClickButton = nil, - - -- Internal states - _hovered = nil, - _focused = nil, - _cursorPosition = nil, - _selectionStart = nil, - _selectionEnd = nil, - _textBuffer = "", - _cursorBlinkTimer = 0, - _cursorVisible = true, - _cursorBlinkPaused = false, - _cursorBlinkPauseTimer = 0, -} - ---- Check if a value equals the default for a key ----@param key string State key ----@param value any Value to check ----@return boolean isDefault True if value equals default -local function isDefaultValue(key, value) - local defaultVal = stateDefaults[key] - - -- If no default defined, check for common defaults - if defaultVal == nil then - -- Empty tables are default - if type(value) == "table" and next(value) == nil then - return true - end - -- nil values are default - if value == nil then - return true - end - -- Otherwise, not a default value - return false - end - - -- Compare values - if type(value) == "table" then - -- Empty tables are considered default - if next(value) == nil then - return true - end - -- For other tables, compare contents (shallow) - if type(defaultVal) ~= "table" then - return false - end - for k, v in pairs(value) do - if defaultVal[k] ~= v then - return false - end - end - return true - else - return value == defaultVal - end -end - --- ==================== --- ID Generation --- ==================== - ---- Generate a hash from a table of properties ----@param props table ----@param visited table|nil Tracking table to prevent circular references ----@param depth number|nil Current recursion depth ----@return string -local function hashProps(props, visited, depth) - if not props then - return "" - end - - -- Initialize visited table on first call - visited = visited or {} - depth = depth or 0 - - -- Limit recursion depth to prevent deep nesting issues - if depth > 3 then - return "[deep]" - end - - -- Check if we've already visited this table (circular reference) - if visited[props] then - return "[circular]" - end - - -- Mark this table as visited - visited[props] = true - - local parts = {} - local keys = {} - - -- Properties to skip (they cause issues or aren't relevant for ID generation) - local skipKeys = { - onEvent = true, - parent = true, - children = true, - onFocus = true, - onBlur = true, - onTextInput = true, - onTextChange = true, - onEnter = true, - userdata = true, - -- Dynamic input/state properties that should not affect ID stability - text = true, -- Text content changes as user types - placeholder = true, -- Placeholder text is presentational - editable = true, -- Editable state can be toggled dynamically - selectOnFocus = true, -- Input behavior flag - autoGrow = true, -- Auto-grow behavior flag - passwordMode = true, -- Password mode can be toggled - } - - -- Collect and sort keys for consistent ordering - for k in pairs(props) do - if not skipKeys[k] then - table.insert(keys, k) - end - end - table.sort(keys) - - -- Build hash string from sorted key-value pairs - for _, k in ipairs(keys) do - local v = props[k] - local vtype = type(v) - - if vtype == "string" or vtype == "number" or vtype == "boolean" then - table.insert(parts, k .. "=" .. tostring(v)) - elseif vtype == "table" then - table.insert(parts, k .. "={" .. hashProps(v, visited, depth + 1) .. "}") - end - end - - return table.concat(parts, ";") -end - ---- Generate a unique ID from call site and properties ----@param props table|nil Optional properties to include in ID generation ----@param parent table|nil Optional parent element for tree-based ID generation ----@return string -function StateManager.generateID(props, parent) - -- Get call stack information - local info = debug.getinfo(3, "Sl") -- Level 3: caller of Element.new -> caller of generateID - - if not info then - -- Fallback to random ID if debug info unavailable - return "auto_" .. tostring(math.random(1000000, 9999999)) - end - - local source = info.source or "unknown" - local line = info.currentline or 0 - - -- Create base location key from source file and line number - local filename = source:match("([^/\\]+)$") or source -- Get filename - filename = filename:gsub("%.lua$", "") -- Remove .lua extension - local locationKey = filename .. "_L" .. line - - -- If we have a parent, use tree-based ID generation for stability - if parent and parent.id and parent.id ~= "" then - -- For child elements, use call-site (file + line) like top-level elements - -- This ensures the same call site always generates the same ID, even when - -- retained children persist in parent.children array - local baseID = parent.id .. "_" .. locationKey - - -- Count how many children have been created at THIS call site - local callSiteKey = parent.id .. "_" .. locationKey - callSiteCounters[callSiteKey] = (callSiteCounters[callSiteKey] or 0) + 1 - local instanceNum = callSiteCounters[callSiteKey] - - if instanceNum > 1 then - baseID = baseID .. "_" .. instanceNum - end - - -- Add property hash if provided (for additional differentiation) - if props then - local propHash = hashProps(props) - if propHash ~= "" then - -- Use first 8 chars of a simple hash - local hash = 0 - for i = 1, #propHash do - hash = (hash * 31 + string.byte(propHash, i)) % 1000000 - end - baseID = baseID .. "_" .. hash - end - end - - return baseID - end - - -- No parent (top-level element): use call-site counter approach - -- Track how many elements have been created at this location - callSiteCounters[locationKey] = (callSiteCounters[locationKey] or 0) + 1 - local instanceNum = callSiteCounters[locationKey] - - local baseID = locationKey - - -- Add instance number if multiple elements created at same location (e.g., in loops) - if instanceNum > 1 then - baseID = baseID .. "_" .. instanceNum - end - - -- Add property hash if provided (for additional differentiation) - if props then - local propHash = hashProps(props) - if propHash ~= "" then - -- Use first 8 chars of a simple hash - local hash = 0 - for i = 1, #propHash do - hash = (hash * 31 + string.byte(propHash, i)) % 1000000 - end - baseID = baseID .. "_" .. hash - end - end - - return baseID -end - --- ==================== --- State Management --- ==================== - ---- Initialize StateManager with dependencies ----@param deps table Dependencies: { ErrorHandler = ErrorHandler } -function StateManager.init(deps) - if type(deps) == "table" then - ErrorHandler = deps.ErrorHandler - end -end - ---- Get state for an element ID, creating if it doesn't exist ----@param id string Element ID ----@param defaultState table|nil Default state if creating new ----@return table state State table for the element -function StateManager.getState(id, defaultState) - if not id then - ErrorHandler:error("StateManager", "SYS_001", { - parameter = "id", - value = "nil", - }) - end - - -- Create state if it doesn't exist - if not stateStore[id] then - -- Start with empty state (sparse storage) - stateStore[id] = defaultState or {} - - -- Create metadata - stateMetadata[id] = { - lastFrame = frameNumber, - createdFrame = frameNumber, - accessCount = 0, - } - else - -- Update metadata - local meta = stateMetadata[id] - meta.lastFrame = frameNumber - meta.accessCount = meta.accessCount + 1 - end - - return stateStore[id] -end - ---- Set state for an element ID (replaces entire state) ----@param id string Element ID ----@param state table State to store -function StateManager.setState(id, state) - if not id then - ErrorHandler:error("StateManager", "SYS_001", { - parameter = "id", - value = "nil", - }) - end - - -- Create sparse state (remove default values) - local sparseState = {} - for key, value in pairs(state) do - if not isDefaultValue(key, value) then - sparseState[key] = value - end - end - - stateStore[id] = sparseState - - -- Update or create metadata - if not stateMetadata[id] then - stateMetadata[id] = { - lastFrame = frameNumber, - createdFrame = frameNumber, - accessCount = 1, - } - else - stateMetadata[id].lastFrame = frameNumber - end -end - ---- Update state for an element ID (merges with existing state) ----@param id string Element ID ----@param newState table New state values to merge -function StateManager.updateState(id, newState) - local state = StateManager.getState(id) - - -- Merge new state into existing state (with diffing optimization) - local changed = false - for key, value in pairs(newState) do - if state[key] ~= value then - state[key] = value - changed = true - end - end - - -- Only update metadata if something actually changed - if changed then - stateMetadata[id].lastFrame = frameNumber - end -end - ---- Update state only if values have changed (optimized for immediate mode) ----@param id string Element ID ----@param newState table New state values to merge ----@return boolean changed True if any values changed -function StateManager.updateStateIfChanged(id, newState) - local state = StateManager.getState(id) - local changed = false - - for key, value in pairs(newState) do - -- Skip if value hasn't changed (optimization) - if state[key] ~= value then - state[key] = value - changed = true - end - end - - if changed then - stateMetadata[id].lastFrame = frameNumber - end - - return changed -end - ---- Clear state for a specific element ID ----@param id string Element ID -function StateManager.clearState(id) - stateStore[id] = nil - stateMetadata[id] = nil -end - ---- Mark state as used this frame (updates last accessed frame) ----@param id string Element ID -function StateManager.markStateUsed(id) - if stateMetadata[id] then - stateMetadata[id].lastFrame = frameNumber - end -end - --- ==================== --- Frame Management --- ==================== - ---- Increment frame counter (called at frame start) -function StateManager.incrementFrame() - frameNumber = frameNumber + 1 - -- Reset call site counters for new frame - callSiteCounters = {} -end - ---- Get current frame number ----@return number -function StateManager.getFrameNumber() - return frameNumber -end - --- ==================== --- Granular State Access (Unified API for both modes) --- ==================== - ---- Get a single state value by key for a given element ID. ---- Works identically in both modes — the caller does not need to know the mode. ---- ---- Immediate mode: reads from persistent state store. ---- Retained mode: resolves through registered element field (cache-through). ---- ----@param id string Element state ID ----@param key string State key ----@return any value The stored value, or nil if not found -function StateManager.getStateValue(id, key) - if not id or not key then - ErrorHandler:error("StateManager", "SYS_001", { - parameter = "id and key", - value = "missing", - }) - end - - -- Update metadata for access tracking - if stateMetadata[id] then - stateMetadata[id].lastFrame = frameNumber - stateMetadata[id].accessCount = stateMetadata[id].accessCount + 1 - end - - if _immediateMode then - -- Immediate mode: read from persistent state store - local state = stateStore[id] - if state then - return state[key] - end - return nil - else - -- Retained mode: resolve through element field - local element = statefulElements[id] - if element then - return element[key] - end - return nil - end -end - ---- Set a single state value by key for a given element ID. ---- Works identically in both modes — the caller does not need to know the mode. ---- ---- Immediate mode: marks dirty for flushFrame() persistence. ---- Retained mode: writes directly to element field (cache-through). ---- ----@param id string Element state ID ----@param key string State key ----@param value any Value to store -function StateManager.setStateValue(id, key, value) - if not id or not key then - ErrorHandler:error("StateManager", "SYS_001", { - parameter = "id and key", - value = "missing", - }) - end - - -- Update metadata - if not stateMetadata[id] then - stateMetadata[id] = { - lastFrame = frameNumber, - createdFrame = frameNumber, - accessCount = 1, - } - else - stateMetadata[id].lastFrame = frameNumber - end - - if _immediateMode then - -- Immediate mode: mark dirty for flushFrame persistence - local state = StateManager.getState(id) - state[key] = value - dirtyState[id] = dirtyState[id] or {} - dirtyState[id][key] = true - else - -- Retained mode: write directly to element field - local element = statefulElements[id] - if element then - element[key] = value - end - end -end - --- ==================== --- Stateful Element Registration (Retained Mode Cache-Through) --- ==================== - ---- Register an element instance for retained-mode cache-through. ---- After registration, getStateValue/setStateValue will resolve through the element's fields. ---- ---- Called by Element in _construct phase. ---- ----@param id string State ID (typically element.id) ----@param element table Element instance to link -function StateManager.registerStateful(id, element) - if not id or not element then - return - end - statefulElements[id] = element -end - ---- Unregister an element instance. ---- After unregistration, retained-mode access will fall back to nil. ---- ---- Called by Element in _cleanup phase. ---- ----@param id string State ID to unregister -function StateManager.unregisterStateful(id) - if id then - statefulElements[id] = nil - end -end - --- ==================== --- Frame Flush (Immediate Mode Dirty State Persistence) --- ==================== - ---- Flush dirty state to persistent store at end of frame. ---- Called automatically at frame end in immediate mode. ---- Behaviors call setStateValue during update without knowing the mode. ---- ---- In retained mode, this is a no-op (state is written directly to elements). -function StateManager.flushFrame() - if not _immediateMode then - return - end - - -- All dirty writes were already applied to stateStore during setStateValue - -- This method exists for future extensions (e.g., batching, analytics) - -- Reset dirty tracking for next frame - dirtyState = {} -end - --- ==================== --- Mode Configuration --- ==================== - ---- Configure immediate mode state. ---- Called by Context when immediate mode is enabled/disabled. ---- ----@param enabled boolean Whether immediate mode is active -function StateManager.setImmediateMode(enabled) - _immediateMode = enabled -end - ---- Check if immediate mode is active. ----@return boolean -function StateManager.isImmediateMode() - return _immediateMode -end - ---- Whether at-construction layout / eager initialization should run now. ---- Returns true in retained mode (layout eagerly), false in immediate mode ---- (layout is deferred to `FlexLove.endFrame` / FlexLove so it runs once all ---- elements for the frame have been created). This replaces the scattered ---- `if not _immediateMode then layoutChildren()` mode checks with a single ---- mode-aware query (behavior-mode-unification task 11). ----@return boolean -function StateManager.shouldLayout() - return not _immediateMode -end - --- ==================== --- Cleanup & Maintenance --- ==================== - ---- Clean up stale states (not accessed recently) ----@return number count Number of states cleaned up -function StateManager.cleanup() - local cleanedCount = 0 - local retentionFrames = config.stateRetentionFrames - - for id, meta in pairs(stateMetadata) do - local framesSinceAccess = frameNumber - meta.lastFrame - - if framesSinceAccess > retentionFrames then - stateStore[id] = nil - stateMetadata[id] = nil - cleanedCount = cleanedCount + 1 - end - end - - -- Clean up empty states (sparse storage optimization) - for id, state in pairs(stateStore) do - if next(state) == nil then - stateStore[id] = nil - stateMetadata[id] = nil - cleanedCount = cleanedCount + 1 - end - end - - return cleanedCount -end - ---- Force cleanup if state count exceeds maximum ----@return number count Number of states cleaned up -function StateManager.forceCleanupIfNeeded() - local stateCount = StateManager.getStateCount() - - if stateCount > config.maxStateEntries then - -- Clean up states not accessed in last 10 frames (aggressive) - local cleanedCount = 0 - - for id, meta in pairs(stateMetadata) do - local framesSinceAccess = frameNumber - meta.lastFrame - - if framesSinceAccess > 10 then - stateStore[id] = nil - stateMetadata[id] = nil - cleanedCount = cleanedCount + 1 - end - end - - return cleanedCount - end - - return 0 -end - ---- Get total number of stored states ----@return number -function StateManager.getStateCount() - local count = 0 - for _ in pairs(stateStore) do - count = count + 1 - end - return count -end - ---- Clear all states -function StateManager.clearAllStates() - stateStore = {} - stateMetadata = {} -end - ---- Configure state management ----@param newConfig {stateRetentionFrames?: number, maxStateEntries?: number} -function StateManager.configure(newConfig) - if newConfig.stateRetentionFrames then - config.stateRetentionFrames = newConfig.stateRetentionFrames - end - if newConfig.maxStateEntries then - config.maxStateEntries = newConfig.maxStateEntries - end -end - ---- Get state statistics for debugging ----@return table stats State usage statistics -function StateManager.getStats() - local stateCount = StateManager.getStateCount() - local oldest = nil - local newest = nil - - for _, meta in pairs(stateMetadata) do - if not oldest or meta.createdFrame < oldest then - oldest = meta.createdFrame - end - if not newest or meta.createdFrame > newest then - newest = meta.createdFrame - end - end - - -- Count callSiteCounters - local callSiteCount = 0 - for _ in pairs(callSiteCounters) do - callSiteCount = callSiteCount + 1 - end - - -- Warn if callSiteCounters is unexpectedly large - if callSiteCount > 1000 then - if ErrorHandler then - ErrorHandler.warn("StateManager", "STATE_001", { - count = callSiteCount, - expected = "near 0", - frameNumber = frameNumber, - }) - end - end - - return { - stateCount = stateCount, - frameNumber = frameNumber, - oldestState = oldest, - newestState = newest, - callSiteCounterCount = callSiteCount, - } -end - ---- Get internal state (for debugging/profiling only) ----@return table internal {stateStore, stateMetadata, callSiteCounters} -function StateManager._getInternalState() - return { - stateStore = stateStore, - stateMetadata = stateMetadata, - callSiteCounters = callSiteCounters, - } -end - ---- Reset the entire state system (for testing) -function StateManager.reset() - stateStore = {} - stateMetadata = {} - frameNumber = 0 - callSiteCounters = {} - statefulElements = {} - dirtyState = {} - _immediateMode = false -end - --- ==================== --- Convenience Functions (for backward compatibility) --- ==================== - ---- Check if an element is currently hovered ----@param id string Element ID ----@return boolean -function StateManager.isHovered(id) - local state = StateManager.getState(id) - return state.hover or false -end - ---- Check if an element is currently pressed ----@param id string Element ID ----@return boolean -function StateManager.isPressed(id) - local state = StateManager.getState(id) - return state.pressed or false -end - ---- Check if an element is currently focused ----@param id string Element ID ----@return boolean -function StateManager.isFocused(id) - local state = StateManager.getState(id) - return state.focused or false -end - ---- Check if an element is disabled ----@param id string Element ID ----@return boolean -function StateManager.isDisabled(id) - local state = StateManager.getState(id) - return state.disabled or false -end - ---- Check if an element is active (e.g., input focused) ----@param id string Element ID ----@return boolean -function StateManager.isActive(id) - local state = StateManager.getState(id) - return state.active or false -end - -return StateManager diff --git a/libs/flexlove/modules/TextEditor.lua b/libs/flexlove/modules/TextEditor.lua deleted file mode 100644 index 35af6354..00000000 --- a/libs/flexlove/modules/TextEditor.lua +++ /dev/null @@ -1,1783 +0,0 @@ -local UTF8 = require((...):match("(.-)[^%.]+$") .. "UTF8") -local utf8 = UTF8 - ----@class TextEditor ----@field editable boolean ----@field multiline boolean ----@field passwordMode boolean ----@field textWrap boolean|"word"|"char" ----@field maxLines number? ----@field maxLength number? ----@field placeholder string? ----@field inputType "text"|"number"|"email"|"url" ----@field textOverflow "clip"|"ellipsis"|"scroll" ----@field scrollable boolean ----@field autoGrow boolean ----@field selectOnFocus boolean ----@field sanitize boolean ----@field allowNewlines boolean ----@field allowTabs boolean ----@field customSanitizer function? ----@field cursorColor Color? ----@field selectionColor Color? ----@field cursorBlinkRate number ----@field _textBuffer string ----@field _lines table? ----@field _wrappedLines table? ----@field _textDirty boolean ----@field _cursorPosition number ----@field _cursorLine number ----@field _cursorColumn number ----@field _cursorBlinkTimer number ----@field _cursorVisible boolean ----@field _cursorBlinkPaused boolean ----@field _cursorBlinkPauseTimer number ----@field _selectionStart number? ----@field _selectionEnd number? ----@field _selectionAnchor number? ----@field _focused boolean ----@field _textScrollX number ----@field onFocus fun(element:Element)? ----@field onBlur fun(element:Element)? ----@field onTextInput fun(element:Element, text:string)? ----@field onTextChange fun(element:Element, text:string)? ----@field onEnter fun(element:Element)? ----@field onSanitize fun(element:Element, original:string, sanitized:string)? ----@field _Context table ----@field _StateManager table ----@field _Color table ----@field _FONT_CACHE table ----@field _getModifiers function ----@field _utils table ----@field _textDragOccurred boolean? -local TextEditor = {} -TextEditor.__index = TextEditor - ----@class TextEditorConfig ----@field editable boolean -- Whether text is editable ----@field multiline boolean -- Whether multi-line is supported ----@field passwordMode boolean -- Whether to mask text ----@field textWrap boolean|"word"|"char" -- Text wrapping mode ----@field maxLines number? -- Maximum number of lines ----@field maxLength number? -- Maximum text length in characters ----@field placeholder string? -- Placeholder text when empty ----@field inputType "text"|"number"|"email"|"url" -- Input validation type ----@field textOverflow "clip"|"ellipsis"|"scroll" -- Text overflow behavior ----@field scrollable boolean -- Whether text is scrollable ----@field autoGrow boolean -- Whether element auto-grows with text ----@field selectOnFocus boolean -- Whether to select all text on focus ----@field sanitize boolean? -- Whether to sanitize text input (default: true) ----@field allowNewlines boolean? -- Whether to allow newline characters (default: true in multiline) ----@field allowTabs boolean? -- Whether to allow tab characters (default: true) ----@field customSanitizer function? -- Custom sanitization function ----@field cursorColor Color? -- Cursor color ----@field selectionColor Color? -- Selection background color ----@field cursorBlinkRate number -- Cursor blink rate in seconds - ----Create a new TextEditor instance ----@param config TextEditorConfig ----@param deps table Dependencies {Context, StateManager, Color, utils} ----@return table TextEditor instance -function TextEditor.new(config, deps) - local self = setmetatable({}, TextEditor) - - -- Store dependencies - self._Context = deps.Context - self._StateManager = deps.StateManager - self._Color = deps.Color - self._FONT_CACHE = deps.utils.FONT_CACHE - self._getModifiers = deps.utils.getModifiers - self._utils = deps.utils - - -- Store configuration - self.editable = config.editable or false - self.multiline = config.multiline or false - self.passwordMode = config.passwordMode or false - self.textWrap = config.textWrap - self.maxLines = config.maxLines - self.maxLength = config.maxLength - self.placeholder = config.placeholder - self.inputType = config.inputType or "text" - self.textOverflow = config.textOverflow or "clip" - self.scrollable = config.scrollable - self.autoGrow = config.autoGrow - self.selectOnFocus = config.selectOnFocus or false - self.cursorColor = config.cursorColor - self.selectionColor = config.selectionColor - self.cursorBlinkRate = config.cursorBlinkRate or 0.5 - - -- Sanitization configuration - self.sanitize = config.sanitize ~= false -- Default to true - -- If allowNewlines is explicitly set, use that value; otherwise follow multiline setting - if config.allowNewlines ~= nil then - self.allowNewlines = config.allowNewlines - else - self.allowNewlines = self.multiline - end - self.allowTabs = config.allowTabs ~= false -- Default to true - self.customSanitizer = config.customSanitizer - - -- Initialize text buffer state (with sanitization) - local initialText = config.text or "" - self._textBuffer = self:_sanitizeText(initialText) - self._lines = nil - self._wrappedLines = nil - self._textDirty = true - - -- Initialize cursor state - self._cursorPosition = 0 - self._cursorLine = 1 - self._cursorColumn = 0 - self._cursorBlinkTimer = 0 - self._cursorVisible = true - self._cursorBlinkPaused = false - self._cursorBlinkPauseTimer = 0 - - -- Initialize selection state - self._selectionStart = nil - self._selectionEnd = nil - self._selectionAnchor = nil - - -- Initialize focus state - self._focused = false - - -- Initialize scroll state - self._textScrollX = 0 - - -- Store callbacks - self.onFocus = config.onFocus - self.onBlur = config.onBlur - self.onTextInput = config.onTextInput - self.onTextChange = config.onTextChange - self.onEnter = config.onEnter - self.onSanitize = config.onSanitize - - return self -end - ----Internal: Sanitize text input ----@param text string -- Text to sanitize ----@return string -- Sanitized text -function TextEditor:_sanitizeText(text) - if not self.sanitize then - return text - end - - -- Use custom sanitizer if provided - if self.customSanitizer then - return self.customSanitizer(text) or text - end - - local options = { - maxLength = self.maxLength, - allowNewlines = self.allowNewlines, - allowTabs = self.allowTabs, - trimWhitespace = false, -- Preserve whitespace in text editors - } - - local sanitized = self._utils.sanitizeText(text, options) - - return sanitized -end - ----Restore state from StateManager (for immediate mode) ----@param element table The parent Element instance -function TextEditor:restoreState(element) - -- Restore state from StateManager. Mode-aware via Context.isImmediateMode: - -- in retained mode the TextEditor persists between frames so nothing to - -- restore (behavior-mode-unification task 11). - if element._stateId and self._Context.isImmediateMode() then - local state = self._StateManager.getState(element._stateId) - if state then - if state._focused then - self._focused = true - self._Context.setFocused(element) - end - if state._textBuffer and state._textBuffer ~= "" then - self._textBuffer = state._textBuffer - end - if state._cursorPosition then - self._cursorPosition = state._cursorPosition - end - if state._selectionStart then - self._selectionStart = state._selectionStart - end - if state._selectionEnd then - self._selectionEnd = state._selectionEnd - end - if state._cursorBlinkTimer then - self._cursorBlinkTimer = state._cursorBlinkTimer - end - if state._cursorVisible ~= nil then - self._cursorVisible = state._cursorVisible - end - if state._cursorBlinkPaused ~= nil then - self._cursorBlinkPaused = state._cursorBlinkPaused - end - if state._cursorBlinkPauseTimer then - self._cursorBlinkPauseTimer = state._cursorBlinkPauseTimer - end - end - end -end - --- ==================== --- Text Buffer Management --- ==================== - ----Get current text buffer ----@return string -function TextEditor:getText() - return self._textBuffer or "" -end - ----Set text buffer and mark dirty ----@param element Element? The parent element (for state saving) ----@param text string ----@param skipSanitization boolean? -- Skip sanitization (for trusted input) -function TextEditor:setText(element, text, skipSanitization) - text = text or "" - - -- Sanitize text unless explicitly skipped - if not skipSanitization then - local originalText = text - text = self:_sanitizeText(text) - - -- Trigger onSanitize callback if text was sanitized - if text ~= originalText and self.onSanitize and element then - self.onSanitize(element, originalText, text) - end - end - - self._textBuffer = text - self:_markTextDirty() - self:_updateTextIfDirty(element) - self:_validateCursorPosition() - self:_saveState(element) -end - ----Insert text at position ----@param element Element The parent element (for state saving) ----@param text string -- Text to insert ----@param position number? -- Position to insert at (default: cursor position) ----@param skipSanitization boolean? -- Skip sanitization (for internal use) -function TextEditor:insertText(element, text, position, skipSanitization) - position = position or self._cursorPosition - local buffer = self._textBuffer or "" - - -- Sanitize text unless explicitly skipped - if not skipSanitization then - text = self:_sanitizeText(text) - end - - -- Check if text is empty after sanitization - if not text or text == "" then - return - end - - -- Check maxLength constraint before inserting - if self.maxLength then - local currentLength = utf8.len(buffer) or 0 - local textLength = utf8.len(text) or 0 - local newLength = currentLength + textLength - - if newLength > self.maxLength then - -- Truncate text to fit - local remaining = self.maxLength - currentLength - if remaining <= 0 then - return - end - -- Truncate to remaining characters - local truncated = "" - local count = 0 - for _, code in utf8.codes(text) do - if count >= remaining then - break - end - truncated = truncated .. utf8.char(code) - count = count + 1 - end - text = truncated - end - end - - -- Convert character position to byte offset - local byteOffset = utf8.offset(buffer, position + 1) or (#buffer + 1) - - -- Insert text - local before = buffer:sub(1, byteOffset - 1) - local after = buffer:sub(byteOffset) - self._textBuffer = before .. text .. after - - self._cursorPosition = position + utf8.len(text) - - self:_markTextDirty() - self:_updateTextIfDirty(element) - self:_validateCursorPosition() - self:_resetCursorBlink(element, true) - self:_saveState(element) -end - ----Delete text in range ----@param element Element The parent element (for state saving) ----@param startPos number -- Start position (inclusive) ----@param endPos number -- End position (inclusive) -function TextEditor:deleteText(element, startPos, endPos) - local buffer = self._textBuffer or "" - - -- Ensure valid range - local textLength = utf8.len(buffer) - startPos = math.max(0, math.min(startPos, textLength)) - endPos = math.max(0, math.min(endPos, textLength)) - - if startPos > endPos then - startPos, endPos = endPos, startPos - end - - -- Convert character positions to byte offsets - local startByte = utf8.offset(buffer, startPos + 1) or 1 - local endByte = utf8.offset(buffer, endPos + 1) or (#buffer + 1) - - -- Delete text - local before = buffer:sub(1, startByte - 1) - local after = buffer:sub(endByte) - self._textBuffer = before .. after - - self:_markTextDirty() - self:_updateTextIfDirty(element) - self:_resetCursorBlink(element, true) - self:_saveState(element) -end - ----Replace text in range ----@param element Element The parent element (for state saving) ----@param startPos number -- Start position (inclusive) ----@param endPos number -- End position (inclusive) ----@param newText string -- Replacement text -function TextEditor:replaceText(element, startPos, endPos, newText) - self:deleteText(element, startPos, endPos) - self:insertText(element, newText, startPos) -end - ----Mark text as dirty (needs recalculation) -function TextEditor:_markTextDirty() - self._textDirty = true -end - ----Update text if dirty (recalculate lines and wrapping) ----@param element Element? The parent element (for wrapping calculations) -function TextEditor:_updateTextIfDirty(element) - if not self._textDirty then - return - end - - self:_splitLines() - self:_calculateWrapping(element) - self:_validateCursorPosition() - self._textDirty = false -end - --- ==================== --- Line Splitting and Wrapping --- ==================== - ----Split text into lines (for multi-line text) -function TextEditor:_splitLines() - if not self.multiline then - self._lines = { self._textBuffer or "" } - return - end - - self._lines = {} - local text = self._textBuffer or "" - - -- Split on newlines - for line in (text .. "\n"):gmatch("([^\n]*)\n") do - table.insert(self._lines, line) - end - - -- Ensure at least one line - if #self._lines == 0 then - self._lines = { "" } - end -end - ----Calculate text wrapping ----@param element Element? The parent element -function TextEditor:_calculateWrapping(element) - if not self.textWrap or not element then - self._wrappedLines = nil - return - end - - self._wrappedLines = {} - local availableWidth = element.width - element.padding.left - element.padding.right - - for lineNum, line in ipairs(self._lines or {}) do - if line == "" then - table.insert(self._wrappedLines, { - text = "", - startIdx = 0, - endIdx = 0, - lineNum = lineNum, - }) - else - local wrappedParts = self:_wrapLine(element, line, availableWidth) - for _, part in ipairs(wrappedParts) do - part.lineNum = lineNum - table.insert(self._wrappedLines, part) - end - end - end -end - ----Wrap a single line of text ----@param element Element The parent element ----@param line string -- Line to wrap ----@param maxWidth number -- Maximum width in pixels ----@return table -- Array of wrapped line parts -function TextEditor:_wrapLine(element, line, maxWidth) - if not element then - return { { text = line, startIdx = 0, endIdx = utf8.len(line) } } - end - - -- Delegate to Renderer - return element._renderer:wrapLine(element, line, maxWidth) -end - --- ==================== --- Cursor Management --- ==================== - ----Set cursor position ----@param element Element? The parent element (for scroll updates) ----@param position number -- Character index (0-based) -function TextEditor:setCursorPosition(element, position) - self._cursorPosition = position - self:_validateCursorPosition() - self:_resetCursorBlink(element) -end - ----Get cursor position ----@return number -- Character index (0-based) -function TextEditor:getCursorPosition() - return self._cursorPosition -end - ----Move cursor by delta characters ----@param element Element? The parent element (for scroll updates) ----@param delta number -- Number of characters to move (positive or negative) -function TextEditor:moveCursorBy(element, delta) - self._cursorPosition = self._cursorPosition + delta - self:_validateCursorPosition() - self:_resetCursorBlink(element) -end - ----Move cursor to start of text ----@param element Element? The parent element (for scroll updates) -function TextEditor:moveCursorToStart(element) - self._cursorPosition = 0 - self:_resetCursorBlink(element) -end - ----Move cursor to end of text ----@param element Element? The parent element (for scroll updates) -function TextEditor:moveCursorToEnd(element) - local textLength = utf8.len(self._textBuffer or "") - self._cursorPosition = textLength - self:_resetCursorBlink(element) -end - ----Move cursor to start of current line ----@param element Element? The parent element (for scroll updates) -function TextEditor:moveCursorToLineStart(element) - -- For now, just move to start (will be enhanced for multi-line) - self:moveCursorToStart(element) -end - ----Move cursor to end of current line ----@param element Element? The parent element (for scroll updates) -function TextEditor:moveCursorToLineEnd(element) - -- For now, just move to end (will be enhanced for multi-line) - self:moveCursorToEnd(element) -end - ----Move cursor to start of previous word -function TextEditor:moveCursorToPreviousWord() - if not self._textBuffer then - return - end - - local text = self._textBuffer - local pos = self._cursorPosition - - if pos <= 0 then - return - end - - -- Helper function to get character at position - local function getCharAt(p) - if p < 0 or p >= utf8.len(text) then - return nil - end - local offset1 = utf8.offset(text, p + 1) - local offset2 = utf8.offset(text, p + 2) - if not offset1 then - return nil - end - if not offset2 then - return text:sub(offset1) - end - return text:sub(offset1, offset2 - 1) - end - - -- Skip any whitespace/punctuation before current position - while pos > 0 do - local char = getCharAt(pos - 1) - if char and char:match("[%w]") then - break - end - pos = pos - 1 - end - - -- Move to start of current word - while pos > 0 do - local char = getCharAt(pos - 1) - if not char or not char:match("[%w]") then - break - end - pos = pos - 1 - end - - self._cursorPosition = pos - self:_validateCursorPosition() -end - ----Move cursor to start of next word -function TextEditor:moveCursorToNextWord() - if not self._textBuffer then - return - end - - local text = self._textBuffer - local textLength = utf8.len(text) or 0 - local pos = self._cursorPosition - - if pos >= textLength then - return - end - - -- Helper function to get character at position - local function getCharAt(p) - if p < 0 or p >= textLength then - return nil - end - local offset1 = utf8.offset(text, p + 1) - local offset2 = utf8.offset(text, p + 2) - if not offset1 then - return nil - end - if not offset2 then - return text:sub(offset1) - end - return text:sub(offset1, offset2 - 1) - end - - -- Skip current word - while pos < textLength do - local char = getCharAt(pos) - if not char or not char:match("[%w]") then - break - end - pos = pos + 1 - end - - -- Skip any whitespace/punctuation - while pos < textLength do - local char = getCharAt(pos) - if char and char:match("[%w]") then - break - end - pos = pos + 1 - end - - self._cursorPosition = pos - self:_validateCursorPosition() -end - ----Validate cursor position (ensure it's within text bounds) -function TextEditor:_validateCursorPosition() - local textLength = utf8.len(self._textBuffer or "") or 0 - local cursorPos = tonumber(self._cursorPosition) or 0 - self._cursorPosition = math.max(0, math.min(cursorPos, textLength)) -end - ----Reset cursor blink (show cursor immediately) ----@param element Element? The parent element (for scroll updates) ----@param pauseBlink boolean|nil -- Whether to pause blinking (for typing) -function TextEditor:_resetCursorBlink(element, pauseBlink) - self._cursorBlinkTimer = 0 - self._cursorVisible = true - - if pauseBlink then - self._cursorBlinkPaused = true - self._cursorBlinkPauseTimer = 0 - end - - self:_updateTextScroll(element) -end - ----Update text scroll offset to keep cursor visible ----@param element Element? The parent element -function TextEditor:_updateTextScroll(element) - if not element or self.multiline then - return - end - - local font = self:_getFont(element) - if not font then - return - end - - -- Calculate cursor X position in text coordinates - local cursorText = "" - if self._textBuffer and self._textBuffer ~= "" and self._cursorPosition > 0 then - local byteOffset = utf8.offset(self._textBuffer, self._cursorPosition + 1) - if byteOffset then - cursorText = self._textBuffer:sub(1, byteOffset - 1) - end - end - local cursorX = font:getWidth(cursorText) - - -- Get available text area width - local textAreaWidth = element.width - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right - end - - -- Add some padding on the right for the cursor - local cursorPadding = 4 - local visibleWidth = textAreaWidth - cursorPadding - - -- Adjust scroll to keep cursor visible - if cursorX - self._textScrollX < 0 then - self._textScrollX = cursorX - elseif cursorX - self._textScrollX > visibleWidth then - self._textScrollX = cursorX - visibleWidth - end - - -- Ensure we don't scroll past the beginning - self._textScrollX = math.max(0, self._textScrollX) -end - ----Get cursor screen position for rendering (handles multiline text) ----@param element Element? The parent element ----@return number, number -- Cursor X and Y position relative to content area -function TextEditor:_getCursorScreenPosition(element) - local font = self:_getFont(element) - if not font then - return 0, 0 - end - - local text = self._textBuffer or "" - local cursorPos = self._cursorPosition or 0 - - -- Apply password masking for cursor position calculation - local textForMeasurement = text - if self.passwordMode and text ~= "" then - textForMeasurement = string.rep("•", utf8.len(text)) - end - - -- For single-line text, calculate simple X position - if not self.multiline then - local cursorText = "" - if textForMeasurement ~= "" and cursorPos > 0 then - local byteOffset = utf8.offset(textForMeasurement, cursorPos + 1) - if byteOffset then - cursorText = textForMeasurement:sub(1, byteOffset - 1) - end - end - return font:getWidth(cursorText), 0 - end - - -- For multiline text, we need to find which wrapped line the cursor is on - self:_updateTextIfDirty(element) - - if not element then - return 0, 0 - end - - -- Get text area width for wrapping - local textAreaWidth = element.width - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right - end - - -- Split text by actual newlines first - local lines = {} - for line in (text .. "\n"):gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - if #lines == 0 then - lines = { "" } - end - - -- Track character position as we iterate through lines - local charCount = 0 - local cursorX = 0 - local cursorY = 0 - local lineHeight = font:getHeight() - - for lineNum, line in ipairs(lines) do - local lineLength = utf8.len(line) or 0 - - -- Check if cursor is on this line - if cursorPos <= charCount + lineLength then - local posInLine = cursorPos - charCount - - -- If text wrapping is enabled, find which wrapped segment - if self.textWrap and textAreaWidth > 0 then - local wrappedSegments = self:_wrapLine(element, line, textAreaWidth) - - for segmentIdx, segment in ipairs(wrappedSegments) do - if posInLine >= segment.startIdx and posInLine <= segment.endIdx then - local posInSegment = posInLine - segment.startIdx - local segmentText = "" - if posInSegment > 0 and segment.text ~= "" then - local endByte = utf8.offset(segment.text, posInSegment + 1) - if endByte then - segmentText = segment.text:sub(1, endByte - 1) - else - segmentText = segment.text - end - end - cursorX = font:getWidth(segmentText) - cursorY = (lineNum - 1) * lineHeight + (segmentIdx - 1) * lineHeight - - return cursorX, cursorY - end - end - else - -- No wrapping, simple calculation - local lineText = "" - if posInLine > 0 then - local endByte = utf8.offset(line, posInLine + 1) - if endByte then - lineText = line:sub(1, endByte - 1) - else - lineText = line - end - end - cursorX = font:getWidth(lineText) - cursorY = (lineNum - 1) * lineHeight - return cursorX, cursorY - end - end - - charCount = charCount + lineLength + 1 - end - - -- Cursor is at the very end - return 0, #lines * lineHeight -end - --- ==================== --- Selection Management --- ==================== - ----Set selection range ----@param element Element? The parent element (for scroll updates) ----@param startPos number -- Start position (inclusive) ----@param endPos number -- End position (inclusive) -function TextEditor:setSelection(element, startPos, endPos) - local textLength = utf8.len(self._textBuffer or "") - self._selectionStart = math.max(0, math.min(startPos, textLength)) - self._selectionEnd = math.max(0, math.min(endPos, textLength)) - - -- Ensure start <= end - if self._selectionStart > self._selectionEnd then - self._selectionStart, self._selectionEnd = self._selectionEnd, self._selectionStart - end - - self:_resetCursorBlink(element) -end - ----Get selection range ----@return number?, number? -- Start and end positions, or nil if no selection -function TextEditor:getSelection() - if not self:hasSelection() then - return nil, nil - end - return self._selectionStart, self._selectionEnd -end - ----Check if there is an active selection ----@return boolean -function TextEditor:hasSelection() - return self._selectionStart ~= nil and self._selectionEnd ~= nil and self._selectionStart ~= self._selectionEnd -end - ----Clear selection -function TextEditor:clearSelection() - self._selectionStart = nil - self._selectionEnd = nil - self._selectionAnchor = nil -end - ----Select all text ----@param element Element? The parent element (for scroll updates) -function TextEditor:selectAll(element) - local textLength = utf8.len(self._textBuffer or "") - self._selectionStart = 0 - self._selectionEnd = textLength - self:_resetCursorBlink(element) -end - ----Get selected text ----@return string? -- Selected text or nil if no selection -function TextEditor:getSelectedText() - if not self:hasSelection() then - return nil - end - - local startPos, endPos = self:getSelection() - if not startPos or not endPos then - return nil - end - - -- Convert character indices to byte offsets - local text = self._textBuffer or "" - local startByte = utf8.offset(text, startPos + 1) - local endByte = utf8.offset(text, endPos + 1) - - if not startByte then - return "" - end - - if endByte then - endByte = endByte - 1 - end - - return string.sub(text, startByte, endByte) -end - ----Delete selected text ----@param element Element The parent element (for state saving) ----@return boolean -- True if text was deleted -function TextEditor:deleteSelection(element) - if not self:hasSelection() then - return false - end - - local startPos, endPos = self:getSelection() - if not startPos or not endPos then - return false - end - - self:deleteText(element, startPos, endPos) - self:clearSelection() - self._cursorPosition = startPos - self:_validateCursorPosition() - self:_saveState(element) - - -- Sync display text and auto-grow height on the owning element - if element then - element.text = self:getText() - self:updateAutoGrowHeight(element) - end - - return true -end - ----Get selection rectangles for rendering ----@param element Element The parent element ----@param selStart number -- Selection start position ----@param selEnd number -- Selection end position ----@return table -- Array of rectangles {x, y, width, height} -function TextEditor:_getSelectionRects(element, selStart, selEnd) - local font = self:_getFont(element) - if not font or not element then - return {} - end - - local text = self._textBuffer or "" - local rects = {} - - -- Apply password masking - local textForMeasurement = text - if self.passwordMode and text ~= "" then - textForMeasurement = string.rep("•", utf8.len(text)) - end - - -- For single-line text, calculate simple rectangle - if not self.multiline then - local startByte = utf8.offset(textForMeasurement, selStart + 1) - local endByte = utf8.offset(textForMeasurement, selEnd + 1) - - if startByte and endByte then - local beforeSelection = textForMeasurement:sub(1, startByte - 1) - local selectedText = textForMeasurement:sub(startByte, endByte - 1) - local selX = font:getWidth(beforeSelection) - local selWidth = font:getWidth(selectedText) - local selY = 0 - local selHeight = font:getHeight() - - table.insert(rects, { x = selX, y = selY, width = selWidth, height = selHeight }) - end - - return rects - end - - -- For multiline text, handle line wrapping - self:_updateTextIfDirty(element) - - -- Get text area width for wrapping - local textAreaWidth = element.width - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right - end - - -- Split text by actual newlines - local lines = {} - for line in (text .. "\n"):gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - if #lines == 0 then - lines = { "" } - end - - local lineHeight = font:getHeight() - local charCount = 0 - local visualLineNum = 0 - - for lineNum, line in ipairs(lines) do - local lineLength = utf8.len(line) or 0 - local lineStartChar = charCount - local lineEndChar = charCount + lineLength - - if selEnd > lineStartChar and selStart <= lineEndChar then - local selStartInLine = math.max(0, selStart - charCount) - local selEndInLine = math.min(lineLength, selEnd - charCount) - - if self.textWrap and textAreaWidth > 0 then - local wrappedSegments = self:_wrapLine(element, line, textAreaWidth) - - for segmentIdx, segment in ipairs(wrappedSegments) do - if selEndInLine > segment.startIdx and selStartInLine <= segment.endIdx then - local segSelStart = math.max(segment.startIdx, selStartInLine) - local segSelEnd = math.min(segment.endIdx, selEndInLine) - - local beforeText = "" - local selectedText = "" - - if segSelStart > segment.startIdx then - local startByte = utf8.offset(segment.text, segSelStart - segment.startIdx + 1) - if startByte then - beforeText = segment.text:sub(1, startByte - 1) - end - end - - local selStartByte = utf8.offset(segment.text, segSelStart - segment.startIdx + 1) - local selEndByte = utf8.offset(segment.text, segSelEnd - segment.startIdx + 1) - if selStartByte and selEndByte then - selectedText = segment.text:sub(selStartByte, selEndByte - 1) - end - - local selX = font:getWidth(beforeText) - local selWidth = font:getWidth(selectedText) - local selY = visualLineNum * lineHeight - local selHeight = lineHeight - - table.insert(rects, { x = selX, y = selY, width = selWidth, height = selHeight }) - end - - visualLineNum = visualLineNum + 1 - end - else - -- No wrapping - local beforeText = "" - local selectedText = "" - - if selStartInLine > 0 then - local startByte = utf8.offset(line, selStartInLine + 1) - if startByte then - beforeText = line:sub(1, startByte - 1) - end - end - - local selStartByte = utf8.offset(line, selStartInLine + 1) - local selEndByte = utf8.offset(line, selEndInLine + 1) - if selStartByte and selEndByte then - selectedText = line:sub(selStartByte, selEndByte - 1) - end - - local selX = font:getWidth(beforeText) - local selWidth = font:getWidth(selectedText) - local selY = visualLineNum * lineHeight - local selHeight = lineHeight - - table.insert(rects, { x = selX, y = selY, width = selWidth, height = selHeight }) - visualLineNum = visualLineNum + 1 - end - else - -- Selection doesn't intersect, but count visual lines - if self.textWrap and textAreaWidth > 0 then - local wrappedSegments = self:_wrapLine(element, line, textAreaWidth) - visualLineNum = visualLineNum + #wrappedSegments - else - visualLineNum = visualLineNum + 1 - end - end - - charCount = charCount + lineLength + 1 - end - - return rects -end - --- ==================== --- Focus Management --- ==================== - ----Focus this element for keyboard input ----@param element Element The parent element -function TextEditor:focus(element) - if not element then - return - end - - -- Use centralized Context focus management - self._Context.setFocused(element) - self._focused = true - - self:_resetCursorBlink(element) - - if self.selectOnFocus then - self:selectAll(element) - else - self:moveCursorToEnd(element) - end - - if self.onFocus then - self.onFocus(element) - end - - self:_saveState(element) -end - ----Remove focus from this element ----@param element Element The parent element -function TextEditor:blur(element) - if not element then - return - end - - self._focused = false - - -- Clear focused element in Context if this element is currently focused - -- Use direct assignment to avoid circular call back to blur() - if self._Context.getFocused() == element then - self._Context._focusedElement = nil - end - - if self.onBlur then - self.onBlur(element) - end - - self:_saveState(element) -end - ----Check if this element is focused ----@return boolean -function TextEditor:isFocused() - return self._focused == true -end - --- ==================== --- Input Handling --- ==================== - ----Handle text input (character insertion) ----@param element Element The parent element ----@param text string -function TextEditor:handleTextInput(element, text) - if not self._focused then - return - end - - -- Trigger onTextInput callback if defined - if self.onTextInput then - local result = self.onTextInput(element, text) - if result == false then - return - end - end - - local oldText = self._textBuffer - - -- Delete selection if exists - if self:hasSelection() then - self:deleteSelection(element) - end - - -- Insert text at cursor position - self:insertText(element, text) - -- Trigger onTextChange callback - if self.onTextChange and self._textBuffer ~= oldText then - self.onTextChange(element, self._textBuffer, oldText) - end - - self:_saveState(element) -end - ----Handle key press (special keys) ----@param element Element The parent element ----@param key string -- Key name ----@param scancode string -- Scancode ----@param isrepeat boolean -- Whether this is a key repeat -function TextEditor:handleKeyPress(element, key, scancode, isrepeat) - if not self._focused then - return - end - - local modifiers = self._getModifiers() - local ctrl = modifiers.ctrl or modifiers.super - - -- Handle cursor movement with selection - if key == "left" or key == "right" or key == "home" or key == "end" or key == "up" or key == "down" then - if modifiers.shift and not self._selectionAnchor then - self._selectionAnchor = self._cursorPosition - end - - if key == "left" then - if modifiers.super then - self:moveCursorToStart(element) - if not modifiers.shift then - self:clearSelection() - end - elseif modifiers.alt then - self:moveCursorToPreviousWord() - elseif self:hasSelection() and not modifiers.shift then - local startPos, _ = self:getSelection() - self._cursorPosition = startPos - self:clearSelection() - else - self:moveCursorBy(element, -1) - end - elseif key == "right" then - if modifiers.super then - self:moveCursorToEnd(element) - if not modifiers.shift then - self:clearSelection() - end - elseif modifiers.alt then - self:moveCursorToNextWord() - elseif self:hasSelection() and not modifiers.shift then - local _, endPos = self:getSelection() - self._cursorPosition = endPos - self:clearSelection() - else - self:moveCursorBy(element, 1) - end - elseif key == "home" then - if not self.multiline then - self:moveCursorToStart(element) - else - self:moveCursorToLineStart(element) - end - if not modifiers.shift then - self:clearSelection() - end - elseif key == "end" then - if not self.multiline then - self:moveCursorToEnd(element) - else - self:moveCursorToLineEnd(element) - end - if not modifiers.shift then - self:clearSelection() - end - elseif key == "up" or key == "down" then - if not modifiers.shift then - self:clearSelection() - end - end - - -- Update selection if Shift is pressed - if modifiers.shift and self._selectionAnchor then - self:setSelection(element, self._selectionAnchor, self._cursorPosition) - elseif not modifiers.shift then - self._selectionAnchor = nil - end - - self:_resetCursorBlink(element) - - -- Handle backspace and delete - elseif key == "backspace" then - local oldText = self._textBuffer - if self:hasSelection() then - self:deleteSelection(element) - elseif ctrl then - if self._cursorPosition > 0 then - self:deleteText(element, 0, self._cursorPosition) - self._cursorPosition = 0 - self:_validateCursorPosition() - end - elseif self._cursorPosition > 0 then - local deleteStart = self._cursorPosition - 1 - local deleteEnd = self._cursorPosition - self._cursorPosition = deleteStart - self:deleteText(element, deleteStart, deleteEnd) - self:_validateCursorPosition() - end - - if self.onTextChange and self._textBuffer ~= oldText then - self.onTextChange(element, self._textBuffer, oldText) - end - self:_resetCursorBlink(element, true) - elseif key == "delete" then - local oldText = self._textBuffer - if self:hasSelection() then - self:deleteSelection(element) - else - local textLength = utf8.len(self._textBuffer or "") - if self._cursorPosition < textLength then - self:deleteText(element, self._cursorPosition, self._cursorPosition + 1) - end - end - - if self.onTextChange and self._textBuffer ~= oldText then - self.onTextChange(element, self._textBuffer, oldText) - end - self:_resetCursorBlink(element, true) - - -- Handle return/enter - elseif key == "return" or key == "kpenter" then - if self.multiline then - local oldText = self._textBuffer - if self:hasSelection() then - self:deleteSelection(element) - end - self:insertText(element, "\n") - - if self.onTextChange and self._textBuffer ~= oldText then - self.onTextChange(element, self._textBuffer, oldText) - end - else - if self.onEnter then - self.onEnter(element) - end - end - self:_resetCursorBlink(element, true) - - -- Handle Ctrl/Cmd+A (select all) - elseif ctrl and key == "a" then - self:selectAll(element) - self:_resetCursorBlink(element) - - -- Handle Ctrl/Cmd+C (copy) - elseif ctrl and key == "c" then - if self:hasSelection() then - local selectedText = self:getSelectedText() - if selectedText then - love.system.setClipboardText(selectedText) - end - end - self:_resetCursorBlink(element) - - -- Handle Ctrl/Cmd+X (cut) - elseif ctrl and key == "x" then - if self:hasSelection() then - local selectedText = self:getSelectedText() - if selectedText then - love.system.setClipboardText(selectedText) - - local oldText = self._textBuffer - self:deleteSelection(element) - - if self.onTextChange and self._textBuffer ~= oldText then - self.onTextChange(element, self._textBuffer, oldText) - end - end - end - self:_resetCursorBlink(element, true) - - -- Handle Ctrl/Cmd+V (paste) - elseif ctrl and key == "v" then - local clipboardText = love.system.getClipboardText() - if clipboardText and clipboardText ~= "" then - local oldText = self._textBuffer - - if self:hasSelection() then - self:deleteSelection(element) - end - - self:insertText(element, clipboardText) - - if self.onTextChange and self._textBuffer ~= oldText then - self.onTextChange(element, self._textBuffer, oldText) - end - end - self:_resetCursorBlink(element, true) - - -- Handle Escape - elseif key == "escape" then - if self:hasSelection() then - self:clearSelection() - else - self:blur(element) - end - self:_resetCursorBlink(element) - end - - self:_saveState(element) -end - --- ==================== --- Mouse Input --- ==================== - ----Convert mouse coordinates to cursor position in text ----@param element Element The parent element ----@param mouseX number -- Mouse X coordinate (absolute) ----@param mouseY number -- Mouse Y coordinate (absolute) ----@return number -- Cursor position (character index) -function TextEditor:mouseToTextPosition(element, mouseX, mouseY) - if not element or not self._textBuffer then - return 0 - end - - local font = self:_getFont(element) - if not font then - return 0 - end - - -- Get content area bounds - local contentX = (element._absoluteX or element.x) + element.padding.left - local contentY = (element._absoluteY or element.y) + element.padding.top - - -- Calculate relative position - local relativeX = mouseX - contentX - local relativeY = mouseY - contentY - - local text = self._textBuffer - local textLength = utf8.len(text) or 0 - - -- Single-line handling - if not self.multiline then - if self._textScrollX then - relativeX = relativeX + self._textScrollX - end - - local closestPos = 0 - local closestDist = math.huge - - for i = 0, textLength do - local offset = utf8.offset(text, i + 1) - local beforeText = offset and text:sub(1, offset - 1) or text - local textWidth = font:getWidth(beforeText) - local dist = math.abs(relativeX - textWidth) - - if dist < closestDist then - closestDist = dist - closestPos = i - end - end - - return closestPos - end - - -- Multiline handling - self:_updateTextIfDirty(element) - - -- Split text into lines - local lines = {} - for line in (text .. "\n"):gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - if #lines == 0 then - lines = { "" } - end - - local lineHeight = font:getHeight() - - -- Get text area width - local textAreaWidth = element.width - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right - end - - -- Determine which line was clicked - local clickedLineNum = math.floor(relativeY / lineHeight) + 1 - clickedLineNum = math.max(1, math.min(clickedLineNum, #lines)) - - -- Calculate character offset for lines before clicked line - local charOffset = 0 - for i = 1, clickedLineNum - 1 do - local lineLen = utf8.len(lines[i]) or 0 - charOffset = charOffset + lineLen + 1 - end - - local clickedLine = lines[clickedLineNum] - local lineLen = utf8.len(clickedLine) or 0 - - -- Handle wrapped segments - if self.textWrap and textAreaWidth > 0 then - local wrappedSegments = self:_wrapLine(element, clickedLine, textAreaWidth) - local lineYOffset = (clickedLineNum - 1) * lineHeight - local segmentNum = math.floor((relativeY - lineYOffset) / lineHeight) + 1 - segmentNum = math.max(1, math.min(segmentNum, #wrappedSegments)) - - local segment = wrappedSegments[segmentNum] - local segmentText = segment.text - local segmentLen = utf8.len(segmentText) or 0 - local closestPos = segment.startIdx - local closestDist = math.huge - - for i = 0, segmentLen do - local offset = utf8.offset(segmentText, i + 1) - local beforeText = offset and segmentText:sub(1, offset - 1) or segmentText - local textWidth = font:getWidth(beforeText) - local dist = math.abs(relativeX - textWidth) - - if dist < closestDist then - closestDist = dist - closestPos = segment.startIdx + i - end - end - - return charOffset + closestPos - end - - -- No wrapping - local closestPos = 0 - local closestDist = math.huge - - for i = 0, lineLen do - local offset = utf8.offset(clickedLine, i + 1) - local beforeText = offset and clickedLine:sub(1, offset - 1) or clickedLine - local textWidth = font:getWidth(beforeText) - local dist = math.abs(relativeX - textWidth) - - if dist < closestDist then - closestDist = dist - closestPos = i - end - end - - return charOffset + closestPos -end - ----Handle mouse click on text ----@param element Element The parent element ----@param mouseX number ----@param mouseY number ----@param clickCount number -- 1=single, 2=double, 3=triple -function TextEditor:handleTextClick(element, mouseX, mouseY, clickCount) - if not self._focused then - return - end - - if clickCount == 1 then - local pos = self:mouseToTextPosition(element, mouseX, mouseY) - self:setCursorPosition(element, pos) - self:clearSelection() - self._mouseDownPosition = pos - elseif clickCount == 2 then - self:_selectWordAtPosition(element, self:mouseToTextPosition(element, mouseX, mouseY)) - elseif clickCount >= 3 then - self:selectAll(element) - end - - self:_resetCursorBlink(element) -end - ----Handle mouse drag for text selection ----@param element Element The parent element ----@param mouseX number ----@param mouseY number -function TextEditor:handleTextDrag(element, mouseX, mouseY) - if not self._focused or not element._mouseDownPosition then - return - end - - local currentPos = self:mouseToTextPosition(element, mouseX, mouseY) - - if currentPos ~= element._mouseDownPosition then - self:setSelection(element, element._mouseDownPosition, currentPos) - self._cursorPosition = currentPos - self._textDragOccurred = true - else - self:clearSelection() - end - - self:_resetCursorBlink(element) -end - ----Select word at given position ----@param element Element? The parent element (for scroll updates) ----@param position number -function TextEditor:_selectWordAtPosition(element, position) - if not self._textBuffer then - return - end - - local text = self._textBuffer - local textLength = utf8.len(text) or 0 - - if textLength == 0 then - return - end - - -- Helper to get character at position - local function getCharAt(p) - if p < 0 or p >= textLength then - return nil - end - local offset1 = utf8.offset(text, p + 1) - local offset2 = utf8.offset(text, p + 2) - if not offset1 then - return nil - end - if not offset2 then - return text:sub(offset1) - end - return text:sub(offset1, offset2 - 1) - end - - -- Find word boundaries - local startPos = position - local endPos = position - - -- Expand left to start of word - while startPos > 0 do - local char = getCharAt(startPos - 1) - if not char or not char:match("[%w]") then - break - end - startPos = startPos - 1 - end - - -- Expand right to end of word - while endPos < textLength do - local char = getCharAt(endPos) - if not char or not char:match("[%w]") then - break - end - endPos = endPos + 1 - end - - self:setSelection(element, startPos, endPos) - self._cursorPosition = endPos -end - --- ==================== --- Update and Rendering --- ==================== - ----Update cursor blink animation ----@param element Element The parent element ----@param dt number -- Delta time -function TextEditor:update(element, dt) - if not self._focused then - return - end - - -- Update cursor blink - if self._cursorBlinkPaused then - self._cursorBlinkPauseTimer = (self._cursorBlinkPauseTimer or 0) + dt - if self._cursorBlinkPauseTimer >= 0.5 then - self._cursorBlinkPaused = false - self._cursorBlinkPauseTimer = 0 - end - else - self._cursorBlinkTimer = self._cursorBlinkTimer + dt - if self._cursorBlinkTimer >= self.cursorBlinkRate then - self._cursorBlinkTimer = 0 - self._cursorVisible = not self._cursorVisible - end - end - - -- Save state for immediate mode (cursor blink timer changes need to persist) - self:_saveState(element) -end - ----Update element height based on text content (for autoGrow) ----@param element Element The parent element -function TextEditor:updateAutoGrowHeight(element) - if not self.multiline or not self.autoGrow or not element then - return - end - - local font = self:_getFont(element) - if not font then - return - end - - local text = self._textBuffer or "" - local lineHeight = font:getHeight() - - -- Get text area width - local textAreaWidth = element.width - local scaledContentPadding = element:getScaledContentPadding() - if scaledContentPadding then - local borderBoxWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - textAreaWidth = borderBoxWidth - scaledContentPadding.left - scaledContentPadding.right - end - - -- Split text by newlines - local lines = {} - for line in (text .. "\n"):gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - if #lines == 0 then - lines = { "" } - end - - -- Count total wrapped lines - local totalWrappedLines = 0 - if self.textWrap and textAreaWidth > 0 then - for _, line in ipairs(lines) do - if line == "" then - totalWrappedLines = totalWrappedLines + 1 - else - local wrappedSegments = self:_wrapLine(element, line, textAreaWidth) - totalWrappedLines = totalWrappedLines + #wrappedSegments - end - end - else - totalWrappedLines = #lines - end - - totalWrappedLines = math.max(1, totalWrappedLines) - local newContentHeight = totalWrappedLines * lineHeight - - if element.height ~= newContentHeight then - element.height = newContentHeight - element._borderBoxHeight = element.height + element.padding.top + element.padding.bottom - if element.parent and not element._explicitlyAbsolute then - element.parent:layoutChildren() - end - end -end - --- ==================== --- Helper Methods --- ==================== - ----Get font for text rendering ----@param element Element? The parent element ----@return love.Font? -function TextEditor:_getFont(element) - if not element then - return nil - end - - -- Delegate to Renderer - return element._renderer:getFont(element) -end - ---- Get current state for persistence ----@return table state TextEditor state snapshot -function TextEditor:getState() - return { - _cursorPosition = self._cursorPosition, - _selectionStart = self._selectionStart, - _selectionEnd = self._selectionEnd, - _textBuffer = self._textBuffer, - _cursorBlinkTimer = self._cursorBlinkTimer, - _cursorVisible = self._cursorVisible, - _cursorBlinkPaused = self._cursorBlinkPaused, - _cursorBlinkPauseTimer = self._cursorBlinkPauseTimer, - _focused = self._focused, - } -end - ---- Restore state from persistence ----@param state table State to restore ----@param element Element? The parent element (needed for focus restoration) -function TextEditor:setState(state, element) - if not state then - return - end - - if state._cursorPosition ~= nil then - self._cursorPosition = state._cursorPosition - end - - if state._selectionStart ~= nil then - self._selectionStart = state._selectionStart - end - - if state._selectionEnd ~= nil then - self._selectionEnd = state._selectionEnd - end - - if state._textBuffer ~= nil then - self._textBuffer = state._textBuffer - end - - if state._cursorBlinkTimer ~= nil then - self._cursorBlinkTimer = state._cursorBlinkTimer - end - - if state._cursorVisible ~= nil then - self._cursorVisible = state._cursorVisible - end - - if state._cursorBlinkPaused ~= nil then - self._cursorBlinkPaused = state._cursorBlinkPaused - end - - if state._cursorBlinkPauseTimer ~= nil then - self._cursorBlinkPauseTimer = state._cursorBlinkPauseTimer - end - - if state._focused ~= nil then - self._focused = state._focused - -- Restore focused element in Context if this element was focused - if self._focused and element then - self._Context.setFocused(element) - end - end -end - ----Save state to StateManager (for immediate mode) ----@param element Element? The parent element -function TextEditor:_saveState(element) - -- Mode-aware guard: in retained mode the TextEditor persists, so state only - -- needs persisting to StateManager in immediate mode. Routed through - -- Context.isImmediateMode (behavior-mode-unification task 11). - if not element or not element._stateId or not self._Context.isImmediateMode() then - return - end - - -- Get current state (may have other sub-modules like eventHandler, scrollManager) - local currentState = self._StateManager.getState(element._stateId) or {} - - -- Update only the textEditor sub-table to match the nested structure - -- used by element:saveState() at endFrame - currentState.textEditor = { - _focused = self._focused, - _textBuffer = self._textBuffer, - _cursorPosition = self._cursorPosition, - _selectionStart = self._selectionStart, - _selectionEnd = self._selectionEnd, - _cursorBlinkTimer = self._cursorBlinkTimer, - _cursorVisible = self._cursorVisible, - _cursorBlinkPaused = self._cursorBlinkPaused, - _cursorBlinkPauseTimer = self._cursorBlinkPauseTimer, - } - - self._StateManager.updateState(element._stateId, currentState) -end - -return TextEditor diff --git a/libs/flexlove/modules/TextSanitizer.lua b/libs/flexlove/modules/TextSanitizer.lua deleted file mode 100644 index e7e57d12..00000000 --- a/libs/flexlove/modules/TextSanitizer.lua +++ /dev/null @@ -1,183 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local function req(name) - return require(modulePath .. name) -end - --- Text sanitization, escaping, and input validation utilities. - --- ErrorHandler is injected via init() for truncation warnings. -local ErrorHandler = nil - ---- Initialize dependencies ----@param deps table Dependencies: { ErrorHandler = ErrorHandler } -local function init(deps) - if type(deps) == "table" then - ErrorHandler = deps.ErrorHandler - end -end - ---- Sanitize text to prevent security vulnerabilities ---- @param text string? Text to sanitize ---- @param options table? Sanitization options ---- @return string Sanitized text -local function sanitizeText(text, options) - local utf8 = require("utf8") - -- Handle nil or non-string inputs - if text == nil then - return "" - end - if type(text) ~= "string" then - text = tostring(text) - end - - -- Default options - options = options or {} - local maxLength = options.maxLength or 10000 - local allowNewlines = options.allowNewlines ~= false -- default true - local allowTabs = options.allowTabs ~= false -- default true - local stripControls = options.stripControls ~= false -- default true - local trimWhitespace = options.trimWhitespace ~= false -- default true - - -- Remove null bytes (critical security risk) - text = text:gsub("%z", "") - - -- Strip control characters except allowed ones - if stripControls then - local pattern = "[\1-\31\127]" -- All control characters - if allowNewlines and allowTabs then - pattern = "[\1-\8\11\12\14-\31\127]" -- Exclude \t (9), \n (10), \r (13) - elseif allowNewlines then - pattern = "[\1-\9\11\12\14-\31\127]" -- Exclude \n (10), \r (13) - elseif allowTabs then - pattern = "[\1-\8\10\12-\31\127]" -- Exclude \t (9) - end - text = text:gsub(pattern, "") - end - - -- Trim leading/trailing whitespace - if trimWhitespace then - text = text:match("^%s*(.-)%s*$") or "" - end - - -- Limit string length (use UTF-8 character count, not byte count) - local charCount = utf8.len(text) - if charCount and charCount > maxLength then - if ErrorHandler then - ErrorHandler:warn("utils", "UTIL_001", { - original = charCount, - truncated = maxLength, - }) - end - -- Truncate to maxLength UTF-8 characters - local bytePos = utf8.offset(text, maxLength + 1) - if bytePos then - text = text:sub(1, bytePos - 1) - end - if ErrorHandler then - ErrorHandler:warn("utils", string.format("Text truncated from %d to %d characters", charCount, maxLength)) - end - end - - return text -end - ---- Validate text input against rules ---- @param text string Text to validate ---- @param rules table Validation rules ---- @return boolean, string? Returns true if valid, or false with error message -local function validateTextInput(text, rules) - rules = rules or {} - - -- Check minimum length - if rules.minLength and #text < rules.minLength then - return false, string.format("Text must be at least %d characters", rules.minLength) - end - - -- Check maximum length - if rules.maxLength and #text > rules.maxLength then - return false, string.format("Text must be at most %d characters", rules.maxLength) - end - - -- Check pattern match - if rules.pattern and not text:match(rules.pattern) then - return false, rules.patternError or "Text does not match required pattern" - end - - -- Check character whitelist - if rules.allowedChars then - local pattern = "[^" .. rules.allowedChars .. "]" - if text:match(pattern) then - return false, "Text contains invalid characters" - end - end - - -- Check character blacklist - if rules.forbiddenChars then - local pattern = "[" .. rules.forbiddenChars .. "]" - if text:match(pattern) then - return false, "Text contains forbidden characters" - end - end - - return true, nil -end - ---- Validate text against range/length rules (alias of validateTextInput) ---- @param text string Text to validate ---- @param rules table Validation rules (minLength, maxLength, pattern, etc.) ---- @return boolean, string? Returns true if valid, or false with error message -local function validateTextRange(text, rules) - return validateTextInput(text, rules) -end - ---- Escape HTML special characters ---- @param text string Text to escape ---- @return string Escaped text -local function escapeHtml(text) - if text == nil then - return "" - end - text = tostring(text) - text = text:gsub("&", "&") - text = text:gsub("<", "<") - text = text:gsub(">", ">") - text = text:gsub('"', """) - text = text:gsub("'", "'") - return text -end - ---- Escape Lua pattern special characters ---- @param text string Text to escape ---- @return string Escaped text -local function escapeLuaPattern(text) - if text == nil then - return "" - end - text = tostring(text) - -- Escape all Lua pattern special characters - text = text:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") - return text -end - ---- Strip all non-printable characters from text ---- @param text string Text to clean ---- @return string Cleaned text -local function stripNonPrintable(text) - if text == nil then - return "" - end - text = tostring(text) - -- Keep printable ASCII (32-126), newline (10), tab (9), and carriage return (13) - text = text:gsub("[^\9\10\13\32-\126]", "") - return text -end - -return { - init = init, - sanitizeText = sanitizeText, - validateTextInput = validateTextInput, - validateTextRange = validateTextRange, - escapeHtml = escapeHtml, - escapeLuaPattern = escapeLuaPattern, - stripNonPrintable = stripNonPrintable, -} diff --git a/libs/flexlove/modules/Theme.lua b/libs/flexlove/modules/Theme.lua deleted file mode 100644 index f6e95bfe..00000000 --- a/libs/flexlove/modules/Theme.lua +++ /dev/null @@ -1,1655 +0,0 @@ ---- Auto-detect the base path where FlexLove is located ----@return string modulePath, string filesystemPath -local function getFlexLoveBasePath() - -- Get debug info to find where this file is loaded from - local info = debug.getinfo(1, "S") - if info and info.source then - local source = info.source - -- Remove leading @ if present - if source:sub(1, 1) == "@" then - source = source:sub(2) - end - - -- Extract the directory path (remove Theme.lua and modules/) - local filesystemPath = source:match("(.*/)") - if filesystemPath then - -- Store the original filesystem path for loading assets - local fsPath = filesystemPath - -- Remove leading ./ if present - fsPath = fsPath:gsub("^%./", "") - -- Remove trailing / - fsPath = fsPath:gsub("/$", "") - -- Remove the flexlove subdirectory to get back to base - fsPath = fsPath:gsub("/modules$", "") - - -- Convert filesystem path to Lua module path - local modulePath = fsPath:gsub("/", ".") - - return modulePath, fsPath - end - end - - -- Fallback: try a common path - return "libs", "libs" -end - --- Store the base paths when module loads -local FLEXLOVE_BASE_PATH, FLEXLOVE_FILESYSTEM_PATH = getFlexLoveBasePath() - ---- Validate theme definition structure ----@param definition ThemeDefinition ----@return boolean, string? -- Returns true if valid, or false with error message -local function validateThemeDefinition(definition) - if not definition then - return false, "Theme definition is nil" - end - - if type(definition) ~= "table" then - return false, "Theme definition must be a table" - end - - if not definition.name or type(definition.name) ~= "string" then - return false, "Theme must have a 'name' field (string)" - end - - if definition.components and type(definition.components) ~= "table" then - return false, "Theme 'components' must be a table" - end - - if definition.colors and type(definition.colors) ~= "table" then - return false, "Theme 'colors' must be a table" - end - - if definition.fonts and type(definition.fonts) ~= "table" then - return false, "Theme 'fonts' must be a table" - end - - if definition.scrollbars and type(definition.scrollbars) ~= "table" then - return false, "Theme 'scrollbars' must be a table" - end - - return true, nil -end - ---- Load image data from a file path ----@param imagePath string ----@return love.ImageData -local function loadImageData(imagePath) - if not imagePath then - error("Image path cannot be nil") - end - - local success, result = pcall(function() - return love.image.newImageData(imagePath) - end) - - if not success then - error("Failed to load image data from '" .. imagePath .. "': " .. tostring(result)) - end - - return result -end - ---- Extract all pixels from a specific row ----@param imageData love.ImageData ----@param rowIndex number 0-based row index ----@return table Array of {r, g, b, a} values (0-255 range) -local function getRow(imageData, rowIndex) - if not imageData then - error("ImageData cannot be nil") - end - - local width = imageData:getWidth() - local height = imageData:getHeight() - - if rowIndex < 0 or rowIndex >= height then - error(string.format("Row index %d out of bounds (height: %d)", rowIndex, height)) - end - - local pixels = {} - for x = 0, width - 1 do - local r, g, b, a = imageData:getPixel(x, rowIndex) - table.insert(pixels, { - r = math.floor(r * 255 + 0.5), - g = math.floor(g * 255 + 0.5), - b = math.floor(b * 255 + 0.5), - a = math.floor(a * 255 + 0.5), - }) - end - - return pixels -end - ---- Extract all pixels from a specific column ----@param imageData love.ImageData ----@param colIndex number 0-based column index ----@return table Array of {r, g, b, a} values (0-255 range) -local function getColumn(imageData, colIndex) - if not imageData then - error("ImageData cannot be nil") - end - - local width = imageData:getWidth() - local height = imageData:getHeight() - - if colIndex < 0 or colIndex >= width then - error(string.format("Column index %d out of bounds (width: %d)", colIndex, width)) - end - - local pixels = {} - for y = 0, height - 1 do - local r, g, b, a = imageData:getPixel(colIndex, y) - table.insert(pixels, { - r = math.floor(r * 255 + 0.5), - g = math.floor(g * 255 + 0.5), - b = math.floor(b * 255 + 0.5), - a = math.floor(a * 255 + 0.5), - }) - end - - return pixels -end - ---- Check if a pixel is black with full alpha (9-patch marker) ----@param r number Red (0-255) ----@param g number Green (0-255) ----@param b number Blue (0-255) ----@param a number Alpha (0-255) ----@return boolean -local function isBlackPixel(r, g, b, a) - return r == 0 and g == 0 and b == 0 and a == 255 -end - ---- Find all continuous runs of black pixels in a pixel array ----@param pixels table Array of {r, g, b, a} pixel values ----@return table Array of {start, end} pairs (1-based indices, inclusive) -local function findBlackPixelRuns(pixels) - local runs = {} - local inRun = false - local runStart = nil - - for i = 1, #pixels do - local pixel = pixels[i] - local isBlack = isBlackPixel(pixel.r, pixel.g, pixel.b, pixel.a) - - if isBlack and not inRun then - -- Start of a new run - inRun = true - runStart = i - elseif not isBlack and inRun then - -- End of current run - table.insert(runs, { start = runStart, ["end"] = i - 1 }) - inRun = false - runStart = nil - end - end - - -- Handle case where run extends to end of array - if inRun then - table.insert(runs, { start = runStart, ["end"] = #pixels }) - end - - return runs -end - ---- Parse a 9-patch PNG image to extract stretch regions and content padding ----@param imagePath string Path to the 9-patch image file ----@return table|nil, string|nil Returns {insets, stretchX, stretchY} or nil, error message -local function parseNinePatch(imagePath) - if not imagePath then - return nil, "Image path cannot be nil" - end - - local success, imageData = pcall(function() - return loadImageData(imagePath) - end) - - if not success then - return nil, "Failed to load image data: " .. tostring(imageData) - end - - local width = imageData:getWidth() - local height = imageData:getHeight() - - -- Validate minimum size (must be at least 3x3 with 1px border) - if width < 3 or height < 3 then - return nil, string.format("Invalid 9-patch dimensions: %dx%d (minimum 3x3)", width, height) - end - - -- Extract border pixels (0-based indexing, but we convert to 1-based for processing) - local topBorder = getRow(imageData, 0) - local leftBorder = getColumn(imageData, 0) - local bottomBorder = getRow(imageData, height - 1) - local rightBorder = getColumn(imageData, width - 1) - - -- Remove corner pixels from borders (they're not part of the stretch/content markers) - -- Top and bottom borders: remove first and last pixel - local topStretchPixels = {} - local bottomContentPixels = {} - for i = 2, #topBorder - 1 do - table.insert(topStretchPixels, topBorder[i]) - end - for i = 2, #bottomBorder - 1 do - table.insert(bottomContentPixels, bottomBorder[i]) - end - - -- Left and right borders: remove first and last pixel - local leftStretchPixels = {} - local rightContentPixels = {} - for i = 2, #leftBorder - 1 do - table.insert(leftStretchPixels, leftBorder[i]) - end - for i = 2, #rightBorder - 1 do - table.insert(rightContentPixels, rightBorder[i]) - end - - -- Find stretch regions (top and left borders) - local stretchX = findBlackPixelRuns(topStretchPixels) - local stretchY = findBlackPixelRuns(leftStretchPixels) - - -- Find content padding regions (bottom and right borders) - local contentX = findBlackPixelRuns(bottomContentPixels) - local contentY = findBlackPixelRuns(rightContentPixels) - - -- Validate that we have at least one stretch region - if #stretchX == 0 or #stretchY == 0 then - return nil, "No stretch regions found (top or left border has no black pixels)" - end - - -- Calculate stretch insets from stretch regions (top/left guides) - -- Use the first stretch region's start and last stretch region's end - local firstStretchX = stretchX[1] - local lastStretchX = stretchX[#stretchX] - local firstStretchY = stretchY[1] - local lastStretchY = stretchY[#stretchY] - - -- Stretch insets define the 9-patch regions - local stretchLeft = firstStretchX.start - local stretchRight = #topStretchPixels - lastStretchX["end"] - local stretchTop = firstStretchY.start - local stretchBottom = #leftStretchPixels - lastStretchY["end"] - - -- Calculate content padding from content guides (bottom/right guides) - -- If content padding is defined, use it; otherwise use stretch regions - local contentLeft, contentRight, contentTop, contentBottom - - if #contentX > 0 then - contentLeft = contentX[1].start - contentRight = #topStretchPixels - contentX[#contentX]["end"] - else - contentLeft = stretchLeft - contentRight = stretchRight - end - - if #contentY > 0 then - contentTop = contentY[1].start - contentBottom = #leftStretchPixels - contentY[#contentY]["end"] - else - contentTop = stretchTop - contentBottom = stretchBottom - end - - return { - insets = { - left = stretchLeft, - top = stretchTop, - right = stretchRight, - bottom = stretchBottom, - }, - contentPadding = { - left = contentLeft, - top = contentTop, - right = contentRight, - bottom = contentBottom, - }, - stretchX = stretchX, - stretchY = stretchY, - } -end - ----@class Theme -local Theme = {} -Theme.__index = Theme - ---- Initialize module with shared dependencies ----@param deps table Dependencies {ErrorHandler, Color, utils} -function Theme.init(deps) - if type(deps) == "table" then - Theme._ErrorHandler = deps.ErrorHandler - Theme._Color = deps.Color - Theme._utils = deps.utils - end -end - --- Global theme registry -local themes = {} -local activeTheme = nil - ---- Create reusable design systems with consistent styling, 9-patch assets, and component states ---- Use this to build professional-looking UIs with minimal per-element configuration ----@param definition ThemeDefinition Theme definition table ----@return Theme theme The new theme instance -function Theme.new(definition) - -- Validate input type first - if type(definition) ~= "table" then - Theme._ErrorHandler:warn("Theme", "THM_001", { - error = "Theme definition must be a table, got " .. type(definition), - }) - return Theme.new({ name = "fallback", components = {}, colors = {}, fonts = {} }) - end - - -- Validate theme definition - local valid, err = validateThemeDefinition(definition) - if not valid then - Theme._ErrorHandler:warn("Theme", "THM_001", { - error = tostring(err), - }) - return Theme.new({ name = "fallback", components = {}, colors = {}, fonts = {} }) - end - - local self = setmetatable({}, Theme) - self.name = definition.name - - -- Load global atlas if it's a string path - if definition.atlas then - if type(definition.atlas) == "string" then - local resolvedPath = Theme._utils.resolveImagePath(definition.atlas) - local image, imageData, loaderr = Theme._utils.safeLoadImage(resolvedPath) - if image then - self.atlas = image - self.atlasData = imageData - else - Theme._ErrorHandler:warn("Theme", "RES_001", { - theme = definition.name, - path = resolvedPath, - error = loaderr, - }) - end - else - self.atlas = definition.atlas - end - end - - self.components = definition.components or {} - self.scrollbars = definition.scrollbars or {} - self.colors = definition.colors or {} - self.fonts = definition.fonts or {} - self.contentAutoSizingMultiplier = definition.contentAutoSizingMultiplier or nil - - -- Helper function to strip 1-pixel guide border from 9-patch ImageData - ---@param sourceImageData love.ImageData - ---@return love.ImageData -- New ImageData without guide border - local function stripNinePatchBorder(sourceImageData) - local srcWidth = sourceImageData:getWidth() - local srcHeight = sourceImageData:getHeight() - - -- Content dimensions (excluding 1px border on all sides) - local contentWidth = srcWidth - 2 - local contentHeight = srcHeight - 2 - - if contentWidth <= 0 or contentHeight <= 0 then - Theme._ErrorHandler:warn("Theme", "RES_002", { - width = srcWidth, - height = srcHeight, - reason = "Image must be larger than 2x2 pixels to have content after stripping 1px border", - }) - return nil - end - - -- Create new ImageData for content only - local strippedImageData = love.image.newImageData(contentWidth, contentHeight) - - -- Copy pixels from source (1,1) to (width-2, height-2) - for y = 0, contentHeight - 1 do - for x = 0, contentWidth - 1 do - local r, g, b, a = sourceImageData:getPixel(x + 1, y + 1) - strippedImageData:setPixel(x, y, r, g, b, a) - end - end - - return strippedImageData - end - - -- Helper function to load atlas with 9-patch support - local function loadAtlasWithNinePatch(comp, atlasPath, errorContext) - ---@diagnostic disable-next-line - local resolvedPath = Theme._utils.resolveImagePath(atlasPath) - ---@diagnostic disable-next-line - local is9Patch = not comp.insets and atlasPath:match("%.9%.png$") - - if is9Patch then - local parseResult, parseErr = parseNinePatch(resolvedPath) - if parseResult then - comp.insets = parseResult.insets - comp._ninePatchData = parseResult - else - Theme._ErrorHandler:warn("Theme", "RES_003", { - context = errorContext, - path = resolvedPath, - error = tostring(parseErr), - }) - end - end - - local image, imageData, loaderr = Theme._utils.safeLoadImage(resolvedPath) - if image then - -- Strip guide border for 9-patch images - if is9Patch and imageData then - local strippedImageData = stripNinePatchBorder(imageData) - local strippedImage = love.graphics.newImage(strippedImageData) - comp._loadedAtlas = strippedImage - comp._loadedAtlasData = strippedImageData - else - comp._loadedAtlas = image - comp._loadedAtlasData = imageData - end - else - Theme._ErrorHandler:warn("Theme", "RES_001", { - context = errorContext, - path = resolvedPath, - error = tostring(loaderr), - }) - end - end - - -- Helper function to create regions from insets - local function createRegionsFromInsets(comp, fallbackAtlas) - local atlasImage = comp._loadedAtlas or fallbackAtlas - if not atlasImage or type(atlasImage) == "string" then - return - end - - local imgWidth, imgHeight = atlasImage:getDimensions() - local left = comp.insets.left or 0 - local top = comp.insets.top or 0 - local right = comp.insets.right or 0 - local bottom = comp.insets.bottom or 0 - - -- No offsets needed - guide border has been stripped for 9-patch images - local centerWidth = imgWidth - left - right - local centerHeight = imgHeight - top - bottom - - comp.regions = { - topLeft = { x = 0, y = 0, w = left, h = top }, - topCenter = { x = left, y = 0, w = centerWidth, h = top }, - topRight = { x = left + centerWidth, y = 0, w = right, h = top }, - middleLeft = { x = 0, y = top, w = left, h = centerHeight }, - middleCenter = { x = left, y = top, w = centerWidth, h = centerHeight }, - middleRight = { x = left + centerWidth, y = top, w = right, h = centerHeight }, - bottomLeft = { x = 0, y = top + centerHeight, w = left, h = bottom }, - bottomCenter = { x = left, y = top + centerHeight, w = centerWidth, h = bottom }, - bottomRight = { x = left + centerWidth, y = top + centerHeight, w = right, h = bottom }, - } - end - - -- Load component-specific atlases and process 9-patch definitions - for componentName, component in pairs(self.components) do - if component.atlas then - if type(component.atlas) == "string" then - loadAtlasWithNinePatch(component, component.atlas, "for component '" .. componentName .. "'") - else - -- Direct Image object (no ImageData available - scaleCorners won't work) - component._loadedAtlas = component.atlas - end - end - - if component.insets then - createRegionsFromInsets(component, self.atlas) - end - - if component.states then - for stateName, stateComponent in pairs(component.states) do - if stateComponent.atlas then - if type(stateComponent.atlas) == "string" then - loadAtlasWithNinePatch(stateComponent, stateComponent.atlas, "for state '" .. stateName .. "'") - else - -- Direct Image object (no ImageData available - scaleCorners won't work) - stateComponent._loadedAtlas = stateComponent.atlas - end - end - - if stateComponent.insets then - createRegionsFromInsets(stateComponent, component._loadedAtlas or self.atlas) - end - end - end - end - - -- Load scrollbar-specific atlases and process 9-patch definitions - -- Scrollbars can have 'bar' and 'frame' subcomponents - for scrollbarName, scrollbarDef in pairs(self.scrollbars) do - -- Handle scrollbar definitions with bar/frame subcomponents - if scrollbarDef.bar or scrollbarDef.frame then - -- Process 'bar' subcomponent - if scrollbarDef.bar then - if type(scrollbarDef.bar) == "string" then - -- Convert string path to ThemeComponent structure - local barComponent = { atlas = scrollbarDef.bar } - -- Copy knobOffset from parent scrollbarDef if it exists - if scrollbarDef.knobOffset then - barComponent.knobOffset = scrollbarDef.knobOffset - end - loadAtlasWithNinePatch(barComponent, scrollbarDef.bar, "for scrollbar '" .. scrollbarName .. ".bar'") - if barComponent.insets then - createRegionsFromInsets(barComponent, barComponent._loadedAtlas or self.atlas) - end - scrollbarDef.bar = barComponent - elseif type(scrollbarDef.bar) == "table" then - -- Already a ThemeComponent structure, process it - -- Copy knobOffset from parent if bar component doesn't have one - if scrollbarDef.knobOffset and not scrollbarDef.bar.knobOffset then - scrollbarDef.bar.knobOffset = scrollbarDef.knobOffset - end - if scrollbarDef.bar.atlas and type(scrollbarDef.bar.atlas) == "string" then - loadAtlasWithNinePatch( - scrollbarDef.bar, - scrollbarDef.bar.atlas, - "for scrollbar '" .. scrollbarName .. ".bar'" - ) - end - if scrollbarDef.bar.insets then - createRegionsFromInsets(scrollbarDef.bar, scrollbarDef.bar._loadedAtlas or self.atlas) - end - end - end - - -- Process 'frame' subcomponent - if scrollbarDef.frame then - if type(scrollbarDef.frame) == "string" then - -- Convert string path to ThemeComponent structure - local frameComponent = { atlas = scrollbarDef.frame } - loadAtlasWithNinePatch(frameComponent, scrollbarDef.frame, "for scrollbar '" .. scrollbarName .. ".frame'") - if frameComponent.insets then - createRegionsFromInsets(frameComponent, frameComponent._loadedAtlas or self.atlas) - end - scrollbarDef.frame = frameComponent - elseif type(scrollbarDef.frame) == "table" then - -- Already a ThemeComponent structure, process it - if scrollbarDef.frame.atlas and type(scrollbarDef.frame.atlas) == "string" then - loadAtlasWithNinePatch( - scrollbarDef.frame, - scrollbarDef.frame.atlas, - "for scrollbar '" .. scrollbarName .. ".frame'" - ) - end - if scrollbarDef.frame.insets then - createRegionsFromInsets(scrollbarDef.frame, scrollbarDef.frame._loadedAtlas or self.atlas) - end - end - end - else - -- Treat as a single ThemeComponent (no bar/frame split) - if scrollbarDef.atlas then - if type(scrollbarDef.atlas) == "string" then - loadAtlasWithNinePatch(scrollbarDef, scrollbarDef.atlas, "for scrollbar '" .. scrollbarName .. "'") - else - scrollbarDef._loadedAtlas = scrollbarDef.atlas - end - end - - if scrollbarDef.insets then - createRegionsFromInsets(scrollbarDef, self.atlas) - end - - if scrollbarDef.states then - for stateName, stateComponent in pairs(scrollbarDef.states) do - if stateComponent.atlas then - if type(stateComponent.atlas) == "string" then - loadAtlasWithNinePatch( - stateComponent, - stateComponent.atlas, - "for scrollbar '" .. scrollbarName .. "' state '" .. stateName .. "'" - ) - else - stateComponent._loadedAtlas = stateComponent.atlas - end - end - - if stateComponent.insets then - createRegionsFromInsets(stateComponent, scrollbarDef._loadedAtlas or self.atlas) - end - end - end - end - end - - return self -end - ---- Import a theme definition from a file to enable hot-reloading and modular design systems ---- Use this to load bundled or user-created themes dynamically ----@param path string Path to theme definition file (e.g., "space" or "mytheme") ----@return Theme? theme The loaded theme, or nil on error -function Theme.load(path) - local definition - local themePath = FLEXLOVE_BASE_PATH .. ".themes." .. path - - local success, result = pcall(function() - return require(themePath) - end) - if success then - definition = result - else - success, result = pcall(function() - return require(path) - end) - if success then - definition = result - else - Theme._ErrorHandler:warn("Theme", "RES_004", { - theme = path, - tried = themePath, - error = tostring(result), - fallback = "nil (no theme loaded)", - }) - return nil - end - end - - local theme = Theme.new(definition) - themes[theme.name] = theme - themes[path] = theme - - return theme -end - ---- Switch the global theme to instantly restyle all themed UI elements ---- Use this to implement light/dark mode toggles or user-selectable skins ----@param themeOrName Theme|string Theme instance or theme name to activate -function Theme.setActive(themeOrName) - if type(themeOrName) == "string" then - -- Try to load if not already loaded - if not themes[themeOrName] then - Theme.load(themeOrName) - end - activeTheme = themes[themeOrName] - else - activeTheme = themeOrName - end - - if not activeTheme then - Theme._ErrorHandler:warn("Theme", "THM_002", { - theme = tostring(themeOrName), - reason = "Theme not found or not loaded", - fallback = "current theme unchanged", - }) - -- Keep current activeTheme unchanged (fallback behavior) - end -end - ---- Access the current theme to query colors, fonts, or create theme-aware components ---- Use this to build UI that adapts to the active design system ----@return Theme? theme The active theme, or nil if none is active -function Theme.getActive() - return activeTheme -end - ---- Retrieve pre-configured visual styles for UI components to maintain consistency ---- Use this to apply theme definitions to custom elements ----@param componentName string Name of the component (e.g., "button", "panel") ----@param state string? Optional state (e.g., "hover", "pressed", "disabled") ----@return ThemeComponent? component Returns component or nil if not found -function Theme.getComponent(componentName, state) - if not activeTheme then - return nil - end - - local component = activeTheme.components[componentName] - if not component then - return nil - end - - -- Check for state-specific override - if state and component.states and component.states[state] then - return component.states[state] - end - - return component -end - ---- Get the first (default) scrollbar from the active theme ---- Returns the first scrollbar component in insertion order ----@return ThemeComponent? scrollbar Returns first scrollbar component or nil if no scrollbars defined -function Theme.getDefaultScrollbar() - if not activeTheme or not activeTheme.scrollbars then - return nil - end - - local _, scrollbar = next(activeTheme.scrollbars) - return scrollbar -end - ---- Retrieve themed scrollbar components for consistent scrollbar styling ---- Use this to apply theme-based scrollbar appearance to scrollable elements ----@param scrollbarName string? Name of the scrollbar style (e.g., "v1", "v2"). If nil, returns default (first) scrollbar ----@param state string? Optional state name (e.g., "hover", "pressed") - currently unused for scrollbars ----@return ThemeComponent? scrollbar Returns scrollbar component or nil if not found -function Theme.getScrollbar(scrollbarName, state) - if not activeTheme or not activeTheme.scrollbars then - return nil - end - - -- If no scrollbarName specified, return default (first) scrollbar - if not scrollbarName then - return Theme.getDefaultScrollbar() - end - - local scrollbar = activeTheme.scrollbars[scrollbarName] - if not scrollbar then - return nil - end - - -- Check for state-specific override (if scrollbar supports states in the future) - if state and scrollbar.states and scrollbar.states[state] then - return scrollbar.states[state] - end - - return scrollbar -end - ---- Access theme-defined fonts for consistent typography across your UI ---- Use this to load fonts specified in your theme definition ----@param fontName string Name of the font family (e.g., "default", "heading") ----@return string? fontPath Returns font path or nil if not found -function Theme.getFont(fontName) - if not activeTheme then - return nil - end - - return activeTheme.fonts and activeTheme.fonts[fontName] -end - ---- Retrieve semantic colors from the theme palette for consistent brand identity ---- Use this instead of hardcoding colors to support themeing and color scheme switches ----@param colorName string Name of the color (e.g., "primary", "secondary") ----@return Color? color Returns Color instance or nil if not found -function Theme.getColor(colorName) - if not activeTheme then - return nil - end - - return activeTheme.colors and activeTheme.colors[colorName] -end - ---- Check if a theme is currently active ----@return boolean active Returns true if a theme is active -function Theme.hasActive() - return activeTheme ~= nil -end - ---- Get all registered theme names ----@return string[] themeNames Array of theme names -function Theme.getRegisteredThemes() - local themeNames = {} - for name, _ in pairs(themes) do - table.insert(themeNames, name) - end - return themeNames -end - ---- Get all available color names from the active theme ----@return string[]? colorNames Array of color names, or nil if no theme active -function Theme.getColorNames() - if not activeTheme or not activeTheme.colors then - return nil - end - - local colorNames = {} - for name, _ in pairs(activeTheme.colors) do - table.insert(colorNames, name) - end - return colorNames -end - ---- Get all colors from the active theme ----@return table? colors Table of all colors, or nil if no theme active -function Theme.getAllColors() - if not activeTheme then - return nil - end - - return activeTheme.colors -end - ---- Safely get theme colors with guaranteed fallbacks to prevent missing color errors ---- Use this when you need a color value no matter what ----@param colorName string Name of the color to retrieve ----@param fallback Color? Fallback color if not found (default: white) ----@return Color color The color or fallback (guaranteed non-nil) -function Theme.getColorOrDefault(colorName, fallback) - local color = Theme.getColor(colorName) - if color then - return color - end - - return fallback or Theme._Color.new(1, 1, 1, 1) -end - ---- Get a theme by name ----@param themeName string Name of the theme ----@return Theme? theme Returns theme or nil if not found -function Theme.get(themeName) - return themes[themeName] -end - --------------------------------------------------------------------------------- --- ThemeManager: Instance-level theme state management --------------------------------------------------------------------------------- - ----@class ThemeManager -local ThemeManager = {} -ThemeManager.__index = ThemeManager - ----Create a new ThemeManager instance ----@param config table Configuration options {theme: string?, themeComponent: string?, disabled: boolean?, active: boolean?, disableHighlight: boolean?, themeStateLock: boolean|string?, themeComponentDisabledStates: string[]?, scaleCorners: number?, scalingAlgorithm: string?} ----@return ThemeManager manager The new ThemeManager instance -function ThemeManager.new(config) - local self = setmetatable({}, ThemeManager) - - self.theme = config.theme - self.themeComponent = config.themeComponent - self.disabled = config.disabled or false - self.active = config.active or false - self.disableHighlight = config.disableHighlight - self.themeStateLock = config.themeStateLock or false - self.scaleCorners = config.scaleCorners - self.scalingAlgorithm = config.scalingAlgorithm - - -- Normalize themeComponentDisabledStates to a lookup set for O(1) checks - self.themeComponentDisabledStates = {} - if config.themeComponentDisabledStates then - for _, state in ipairs(config.themeComponentDisabledStates) do - if type(state) == "string" then - self.themeComponentDisabledStates[state] = true - end - end - end - - -- Set initial state based on themeStateLock - if self.themeStateLock == true or self.themeStateLock == "default" then - self._themeState = "normal" - elseif type(self.themeStateLock) == "string" then - self._themeState = self.themeStateLock - else - self._themeState = "normal" - end - - return self -end - ----Update the theme state based on element interaction state ----@param isHovered boolean Whether element is hovered ----@param isPressed boolean Whether element is pressed ----@param isFocused boolean Whether element is focused (keyboard focus) ----@param isDisabled boolean Whether element is disabled ----@return string state The new theme state ("normal", "hover", "pressed", "active", "disabled") -function ThemeManager:updateState(isHovered, isPressed, isFocused, isDisabled) - -- If themeStateLock is set (and not false), use the locked state - if self.themeStateLock ~= false and self.themeStateLock ~= nil then - local lockedState - - if self.themeStateLock == true or self.themeStateLock == "default" then - -- true or "default" means lock to "normal" (base state) - lockedState = "normal" - elseif type(self.themeStateLock) == "string" then - -- String means lock to specific state - lockedState = self.themeStateLock - - -- Validate the locked state exists in the theme component (will be done during initialization) - -- For now, just use the string value - else - -- Invalid themeStateLock value, fall back to normal behavior - lockedState = nil - end - - if lockedState then - self._themeState = lockedState - return lockedState - end - end - - -- Normal behavior: calculate state based on interaction - -- Keyboard focus reuses the hover state so themes only need one visual variant. - -- Priority: disabled > active > pressed > hover/focus > normal - -- If a state is in themeComponentDisabledStates, fall through to the next lower-priority state. - local candidates = { - { state = "disabled", condition = isDisabled or self.disabled }, - { state = "active", condition = self.active }, - { state = "pressed", condition = isPressed }, - { state = "hover", condition = isHovered or isFocused }, - } - - local newState = "normal" - for _, candidate in ipairs(candidates) do - if candidate.condition and not self.themeComponentDisabledStates[candidate.state] then - newState = candidate.state - break - end - end - - self._themeState = newState - return newState -end - ----Get the current theme state ----@return string state The current theme state -function ThemeManager:getState() - return self._themeState -end - ----Set the theme state explicitly ----@param state string The theme state to set ("normal", "hover", "pressed", "active", "disabled") -function ThemeManager:setState(state) - if type(state) ~= "string" then - return - end - self._themeState = state -end - ----Check if a theme component is set ----@return boolean hasComponent True if a theme component is set -function ThemeManager:hasThemeComponent() - return self.themeComponent ~= nil -end - ----Get the theme (either instance-specific or active theme) ----@return Theme? theme The theme instance, or nil if not found -function ThemeManager:getTheme() - if self.theme then - return Theme.get(self.theme) - end - return Theme.getActive() -end - ----Get the base theme component ----@return ThemeComponent? component The theme component, or nil if not found -function ThemeManager:getComponent() - if not self.themeComponent then - return nil - end - - local themeToUse = self:getTheme() - if not themeToUse or not themeToUse.components or type(themeToUse.components) ~= "table" then - return nil - end - - if not themeToUse.components[self.themeComponent] then - return nil - end - - return themeToUse.components[self.themeComponent] -end - ----Get the theme component for the current state ----@return ThemeComponent? component The state-specific component, or base component, or nil -function ThemeManager:getStateComponent() - local component = self:getComponent() - if not component then - return nil - end - - local state = self._themeState - if - state - and state ~= "normal" - and component.states - and type(component.states) == "table" - and component.states[state] - then - return component.states[state] - end - - return component -end - ----Get a scrollbar component from the theme ----@param scrollbarName string? The scrollbar style name (e.g., "v1", "v2"). If nil, returns default (first) scrollbar ----@return ThemeComponent? scrollbar The scrollbar component, or nil if not found -function ThemeManager:getScrollbarComponent(scrollbarName) - local themeToUse = self:getTheme() - if not themeToUse or not themeToUse.scrollbars or type(themeToUse.scrollbars) ~= "table" then - return nil - end - - if not scrollbarName then - local _, scrollbar = next(themeToUse.scrollbars) - return scrollbar - end - - return themeToUse.scrollbars[scrollbarName] -end - ----Get a style property from the current state component ----@param property string The property name ----@return any? value The property value, or nil if not found -function ThemeManager:getStyle(property) - if type(property) ~= "string" then - return nil - end - - local stateComponent = self:getStateComponent() - if not stateComponent or type(stateComponent) ~= "table" then - return nil - end - - return stateComponent[property] -end - ----Get scaled content padding based on border box dimensions ----@param borderBoxWidth number The border box width ----@param borderBoxHeight number The border box height ----@return table? padding Table with {left, top, right, bottom}, or nil if no contentPadding -function ThemeManager:_getScaledContentPaddingForComponent(component, borderBoxWidth, borderBoxHeight) - if not component or not component._ninePatchData or not component._ninePatchData.contentPadding then - return nil - end - - local contentPadding = component._ninePatchData.contentPadding - local themeToUse = self:getTheme() - local atlasImage = component._loadedAtlas or (themeToUse and themeToUse.atlas) - - if atlasImage and type(atlasImage) ~= "string" then - local originalWidth, originalHeight = atlasImage:getDimensions() - - local insets = component.insets - if insets and type(insets) == "table" then - local cornerScale = self.scaleCorners - if cornerScale == nil then - cornerScale = component.scaleCorners - end - if type(cornerScale) ~= "number" or cornerScale <= 0 then - cornerScale = 1 - end - - local function mapDistanceFromStart(sourceDistance, sourceSize, targetSize, sourceStartInset, sourceEndInset) - local sourceStart = sourceStartInset or 0 - local sourceEnd = sourceEndInset or 0 - - local sourceCenter = math.max(0, sourceSize - sourceStart - sourceEnd) - local targetStart = sourceStart * cornerScale - local targetEnd = sourceEnd * cornerScale - local targetCenter = math.max(0, targetSize - targetStart - targetEnd) - - if sourceDistance <= sourceStart then - return sourceDistance * cornerScale - end - - if sourceDistance >= (sourceSize - sourceEnd) then - local distanceFromEnd = sourceSize - sourceDistance - return targetSize - (distanceFromEnd * cornerScale) - end - - if sourceCenter <= 0 then - return targetStart - end - - local t = (sourceDistance - sourceStart) / sourceCenter - return targetStart + (t * targetCenter) - end - - local left = mapDistanceFromStart(contentPadding.left, originalWidth, borderBoxWidth, insets.left, insets.right) - local rightBoundary = mapDistanceFromStart( - originalWidth - contentPadding.right, - originalWidth, - borderBoxWidth, - insets.left, - insets.right - ) - local right = borderBoxWidth - rightBoundary - - local top = mapDistanceFromStart(contentPadding.top, originalHeight, borderBoxHeight, insets.top, insets.bottom) - local bottomBoundary = mapDistanceFromStart( - originalHeight - contentPadding.bottom, - originalHeight, - borderBoxHeight, - insets.top, - insets.bottom - ) - local bottom = borderBoxHeight - bottomBoundary - - return { - left = math.max(0, left), - top = math.max(0, top), - right = math.max(0, right), - bottom = math.max(0, bottom), - } - end - - local scaleX = borderBoxWidth / originalWidth - local scaleY = borderBoxHeight / originalHeight - return { - left = contentPadding.left * scaleX, - top = contentPadding.top * scaleY, - right = contentPadding.right * scaleX, - bottom = contentPadding.bottom * scaleY, - } - end - - return nil -end - ----Get scaled content padding for a specific theme state ----@param state string The theme state to resolve (e.g. "normal", "pressed") ----@param borderBoxWidth number The border box width ----@param borderBoxHeight number The border box height ----@return table? padding Table with {left, top, right, bottom}, or nil if no contentPadding -function ThemeManager:_getScaledContentPaddingForState(state, borderBoxWidth, borderBoxHeight) - if not self.themeComponent then - return nil - end - - local themeToUse = self:getTheme() - if not themeToUse or not themeToUse.components[self.themeComponent] then - return nil - end - - local component = themeToUse.components[self.themeComponent] - - local stateToUse = state or "normal" - if stateToUse ~= "normal" and component.states and component.states[stateToUse] then - component = component.states[stateToUse] - end - - return self:_getScaledContentPaddingForComponent(component, borderBoxWidth, borderBoxHeight) -end - ----@param state string The theme state to resolve (e.g. "normal", "pressed") ----@param borderBoxWidth number The border box width ----@param borderBoxHeight number The border box height ----@return table? padding Table with {left, top, right, bottom}, or nil if no contentPadding -function ThemeManager:getScaledContentPaddingForState(state, borderBoxWidth, borderBoxHeight) - Theme._ErrorHandler:warnDeprecated("Theme", "getScaledContentPaddingForState", "getScaledContentPadding") - return self:getScaledContentPadding(borderBoxWidth, borderBoxHeight) -end - ----Get scaled content padding based on current theme state and border box dimensions ----@param borderBoxWidth number The border box width ----@param borderBoxHeight number The border box height ----@return table? padding Table with {left, top, right, bottom}, or nil if no contentPadding -function ThemeManager:getScaledContentPadding(borderBoxWidth, borderBoxHeight) - local state = self._themeState or "normal" - return self:_getScaledContentPaddingForState(state, borderBoxWidth, borderBoxHeight) -end - ----Get content auto-sizing multiplier from theme or component ----@return table? multiplier Table with {width: number?, height: number?}, or nil if not defined -function ThemeManager:getContentAutoSizingMultiplier() - if not self.themeComponent then - return nil - end - - local themeToUse = self:getTheme() - if not themeToUse then - return nil - end - - if self.themeComponent and themeToUse.components and type(themeToUse.components) == "table" then - local component = themeToUse.components[self.themeComponent] - if component and component.contentAutoSizingMultiplier then - return component.contentAutoSizingMultiplier - elseif themeToUse.contentAutoSizingMultiplier then - return themeToUse.contentAutoSizingMultiplier - end - end - - if themeToUse.contentAutoSizingMultiplier then - return themeToUse.contentAutoSizingMultiplier - end - - return nil -end - ----Get the default font family path from the theme ----@return string? fontPath The default font path, or nil if not defined -function ThemeManager:getDefaultFontFamily() - local themeToUse = self:getTheme() - if themeToUse and themeToUse.fonts and type(themeToUse.fonts) == "table" and themeToUse.fonts["default"] then - return themeToUse.fonts["default"] - end - return nil -end - ----Set the theme and component for this ThemeManager ----@param themeName string? The theme name to use (nil to use active theme) ----@param componentName string? The component name to use -function ThemeManager:setTheme(themeName, componentName) - self.theme = themeName - self.themeComponent = componentName -end - ----Validate themeStateLock and warn if invalid ----@return boolean isValid True if themeStateLock is valid or false/nil -function ThemeManager:validateThemeStateLock() - -- false or nil is always valid (no lock) - if not self.themeStateLock or self.themeStateLock == false then - return true - end - - -- true is always valid (lock to normal) - if self.themeStateLock == true then - return true - end - - -- String value needs validation - if type(self.themeStateLock) == "string" then - -- "default" is always valid (lock to normal/base state) - if self.themeStateLock == "default" then - return true - end - - local component = self:getComponent() - - -- If no component, warn that themeStateLock has no effect - if not component then - if self.themeComponent then - Theme._ErrorHandler:warn("Theme", "THM_007", { - themeComponent = self.themeComponent, - reason = "themeStateLock has no effect without a valid theme component", - }) - end - self.themeStateLock = false - return false - end - - -- Check if component has any states at all - if not component.states or type(component.states) ~= "table" or next(component.states) == nil then - Theme._ErrorHandler:warn("Theme", "THM_008", { - themeComponent = self.themeComponent, - reason = "Theme component has no state variants, themeStateLock has no effect", - }) - self.themeStateLock = false - return false - end - - -- Check if the specified state exists - if not component.states[self.themeStateLock] then - -- Warn and fall back to false (no lock) - Theme._ErrorHandler:warn("Theme", "THM_009", { - themeComponent = self.themeComponent, - requestedState = self.themeStateLock, - availableStates = table.concat(self:_getAvailableStates(component), ", "), - fallback = "themeStateLock disabled (using dynamic state)", - }) - self.themeStateLock = false - return false - end - - return true - end - - -- Invalid type for themeStateLock - Theme._ErrorHandler:warn("Theme", "THM_010", { - themeStateLockType = type(self.themeStateLock), - reason = "themeStateLock must be boolean or string", - fallback = "themeStateLock disabled", - }) - self.themeStateLock = false - return false -end - ----Get available state names for a component ----@param component ThemeComponent The component to check ----@return table stateNames Array of state names -function ThemeManager:_getAvailableStates(component) - local states = {} - if component and component.states and type(component.states) == "table" then - for stateName, _ in pairs(component.states) do - table.insert(states, stateName) - end - end - return states -end - -Theme.Manager = ThemeManager - ---- Check theme definitions for correctness before use to catch configuration errors early ---- Use this during development to verify custom themes are properly structured ----@param theme table? The theme to validate ----@param options table? Optional validation options {strict: boolean} ----@return boolean valid, table errors List of validation errors -function Theme.validateTheme(theme, options) - local errors = {} - options = options or {} - - -- Basic structure validation - if theme == nil then - table.insert(errors, "Theme is nil") - return false, errors - end - - if type(theme) ~= "table" then - table.insert(errors, "Theme must be a table") - return false, errors - end - - -- Name validation (only required field) - if not theme.name then - table.insert(errors, "Theme must have a 'name' field") - elseif type(theme.name) ~= "string" then - table.insert(errors, "Theme 'name' must be a string") - elseif theme.name == "" then - table.insert(errors, "Theme 'name' cannot be empty") - end - - -- Colors validation (optional, but if present must be valid) - if theme.colors ~= nil then - if type(theme.colors) ~= "table" then - table.insert(errors, "Theme 'colors' must be a table") - else - for colorName, colorValue in pairs(theme.colors) do - if type(colorName) ~= "string" then - table.insert(errors, "Color name must be a string, got " .. type(colorName)) - else - -- Accept Color objects, hex strings, or named colors - local colorType = type(colorValue) - if colorType == "table" then - -- Assume it's a Color object if it has r,g,b fields - if not (colorValue.r and colorValue.g and colorValue.b) then - table.insert(errors, "Color '" .. colorName .. "' is not a valid Color object") - end - elseif colorType == "string" then - -- Validate color string - local isValid, err = Theme._Color.validateColor(colorValue) - if not isValid then - table.insert(errors, "Color '" .. colorName .. "': " .. err) - end - else - table.insert(errors, "Color '" .. colorName .. "' must be a Color object or string") - end - end - end - end - end - - -- Fonts validation (optional) - if theme.fonts ~= nil then - if type(theme.fonts) ~= "table" then - table.insert(errors, "Theme 'fonts' must be a table") - else - for fontName, fontPath in pairs(theme.fonts) do - if type(fontName) ~= "string" then - table.insert(errors, "Font name must be a string, got " .. type(fontName)) - elseif type(fontPath) ~= "string" then - table.insert(errors, "Font '" .. fontName .. "' path must be a string") - end - end - end - end - - -- Components validation (optional) - if theme.components ~= nil then - if type(theme.components) ~= "table" then - table.insert(errors, "Theme 'components' must be a table") - else - for componentName, component in pairs(theme.components) do - if type(component) == "table" then - -- Validate atlas if present - if component.atlas ~= nil and type(component.atlas) ~= "string" then - table.insert(errors, "Component '" .. componentName .. "' atlas must be a string") - end - - -- Validate insets if present - if component.insets ~= nil then - if type(component.insets) ~= "table" then - table.insert(errors, "Component '" .. componentName .. "' insets must be a table") - else - -- If insets are provided, all 4 sides must be present - for _, side in ipairs({ "left", "top", "right", "bottom" }) do - if component.insets[side] == nil then - table.insert(errors, "Component '" .. componentName .. "' insets must have '" .. side .. "' field") - elseif type(component.insets[side]) ~= "number" then - table.insert(errors, "Component '" .. componentName .. "' insets." .. side .. " must be a number") - elseif component.insets[side] < 0 then - table.insert(errors, "Component '" .. componentName .. "' insets." .. side .. " must be non-negative") - end - end - end - end - - -- Validate states if present - if component.states ~= nil then - if type(component.states) ~= "table" then - table.insert(errors, "Component '" .. componentName .. "' states must be a table") - else - for stateName, stateComponent in pairs(component.states) do - if type(stateComponent) ~= "table" then - table.insert( - errors, - "Component '" .. componentName .. "' state '" .. stateName .. "' must be a table" - ) - end - end - end - end - - -- Validate scaleCorners if present - if component.scaleCorners ~= nil then - if type(component.scaleCorners) ~= "number" then - table.insert(errors, "Component '" .. componentName .. "' scaleCorners must be a number") - elseif component.scaleCorners <= 0 then - table.insert(errors, "Component '" .. componentName .. "' scaleCorners must be positive") - end - end - - -- Validate scalingAlgorithm if present - if component.scalingAlgorithm ~= nil then - if type(component.scalingAlgorithm) ~= "string" then - table.insert(errors, "Component '" .. componentName .. "' scalingAlgorithm must be a string") - elseif component.scalingAlgorithm ~= "nearest" and component.scalingAlgorithm ~= "bilinear" then - table.insert( - errors, - "Component '" .. componentName .. "' scalingAlgorithm must be 'nearest' or 'bilinear'" - ) - end - end - end - end - end - end - - -- Scrollbars validation (optional) - if theme.scrollbars ~= nil then - if type(theme.scrollbars) ~= "table" then - table.insert(errors, "Theme 'scrollbars' must be a table") - else - for scrollbarName, scrollbarDef in pairs(theme.scrollbars) do - if type(scrollbarDef) == "table" then - -- Check if it has bar/frame subcomponents - if scrollbarDef.bar or scrollbarDef.frame then - -- Validate bar subcomponent - if scrollbarDef.bar ~= nil then - if type(scrollbarDef.bar) ~= "string" and type(scrollbarDef.bar) ~= "table" then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' bar must be a string or table") - end - end - -- Validate frame subcomponent - if scrollbarDef.frame ~= nil then - if type(scrollbarDef.frame) ~= "string" and type(scrollbarDef.frame) ~= "table" then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' frame must be a string or table") - end - end - else - -- Validate as a single ThemeComponent - -- Validate atlas if present - if scrollbarDef.atlas ~= nil and type(scrollbarDef.atlas) ~= "string" then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' atlas must be a string") - end - - -- Validate insets if present - if scrollbarDef.insets ~= nil then - if type(scrollbarDef.insets) ~= "table" then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' insets must be a table") - else - for _, side in ipairs({ "left", "top", "right", "bottom" }) do - if scrollbarDef.insets[side] == nil then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' insets must have '" .. side .. "' field") - elseif type(scrollbarDef.insets[side]) ~= "number" then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' insets." .. side .. " must be a number") - elseif scrollbarDef.insets[side] < 0 then - table.insert( - errors, - "Scrollbar '" .. scrollbarName .. "' insets." .. side .. " must be non-negative" - ) - end - end - end - end - - -- Validate states if present - if scrollbarDef.states ~= nil then - if type(scrollbarDef.states) ~= "table" then - table.insert(errors, "Scrollbar '" .. scrollbarName .. "' states must be a table") - else - for stateName, stateComponent in pairs(scrollbarDef.states) do - if type(stateComponent) ~= "table" then - table.insert( - errors, - "Scrollbar '" .. scrollbarName .. "' state '" .. stateName .. "' must be a table" - ) - end - end - end - end - end - end - end - end - end - - -- contentAutoSizingMultiplier validation (optional) - if theme.contentAutoSizingMultiplier ~= nil then - if type(theme.contentAutoSizingMultiplier) ~= "table" then - table.insert(errors, "Theme 'contentAutoSizingMultiplier' must be a table") - else - if theme.contentAutoSizingMultiplier.width ~= nil then - if type(theme.contentAutoSizingMultiplier.width) ~= "number" then - table.insert(errors, "contentAutoSizingMultiplier.width must be a number") - elseif theme.contentAutoSizingMultiplier.width <= 0 then - table.insert(errors, "contentAutoSizingMultiplier.width must be positive") - end - end - if theme.contentAutoSizingMultiplier.height ~= nil then - if type(theme.contentAutoSizingMultiplier.height) ~= "number" then - table.insert(errors, "contentAutoSizingMultiplier.height must be a number") - elseif theme.contentAutoSizingMultiplier.height <= 0 then - table.insert(errors, "contentAutoSizingMultiplier.height must be positive") - end - end - end - end - - -- Global atlas validation (optional) - if theme.atlas ~= nil then - if type(theme.atlas) ~= "string" then - table.insert(errors, "Theme 'atlas' must be a string") - end - end - - -- Strict mode: warn about unknown fields - if options.strict then - local knownFields = { - name = true, - atlas = true, - components = true, - scrollbars = true, - colors = true, - fonts = true, - contentAutoSizingMultiplier = true, - } - for field in pairs(theme) do - if not knownFields[field] then - table.insert(errors, "Unknown field '" .. field .. "' in theme") - end - end - end - - return #errors == 0, errors -end - ---- Clean up malformed theme data to make it usable without crashing ---- Use this to robustly handle user-created or external themes ----@param theme table? The theme to sanitize ----@return table sanitized The sanitized theme -function Theme.sanitizeTheme(theme) - local sanitized = {} - - -- Handle nil theme - if theme == nil then - return { name = "Invalid Theme" } - end - - -- Handle non-table theme - if type(theme) ~= "table" then - return { name = "Invalid Theme" } - end - - -- Sanitize name - if type(theme.name) == "string" and theme.name ~= "" then - sanitized.name = theme.name - else - sanitized.name = "Unnamed Theme" - end - - -- Sanitize colors - if type(theme.colors) == "table" then - sanitized.colors = {} - for colorName, colorValue in pairs(theme.colors) do - if type(colorName) == "string" then - local colorType = type(colorValue) - if colorType == "table" and colorValue.r and colorValue.g and colorValue.b then - -- Valid Color object - sanitized.colors[colorName] = colorValue - elseif colorType == "string" then - -- Try to validate color string - local isValid = Theme._Color.validateColor(colorValue) - if isValid then - sanitized.colors[colorName] = colorValue - else - -- Provide fallback color - sanitized.colors[colorName] = Theme._Color.new(0, 0, 0, 1) - end - end - end - end - end - - -- Sanitize fonts - if type(theme.fonts) == "table" then - sanitized.fonts = {} - for fontName, fontPath in pairs(theme.fonts) do - if type(fontName) == "string" and type(fontPath) == "string" then - sanitized.fonts[fontName] = fontPath - end - end - end - - -- Sanitize components (preserve as-is, they're complex) - if type(theme.components) == "table" then - sanitized.components = theme.components - end - - -- Sanitize scrollbars (preserve as-is, they're complex like components) - if type(theme.scrollbars) == "table" then - sanitized.scrollbars = theme.scrollbars - end - - -- Sanitize contentAutoSizingMultiplier - if type(theme.contentAutoSizingMultiplier) == "table" then - sanitized.contentAutoSizingMultiplier = {} - if type(theme.contentAutoSizingMultiplier.width) == "number" and theme.contentAutoSizingMultiplier.width > 0 then - sanitized.contentAutoSizingMultiplier.width = theme.contentAutoSizingMultiplier.width - end - if type(theme.contentAutoSizingMultiplier.height) == "number" and theme.contentAutoSizingMultiplier.height > 0 then - sanitized.contentAutoSizingMultiplier.height = theme.contentAutoSizingMultiplier.height - end - end - - -- Sanitize atlas - if type(theme.atlas) == "string" then - sanitized.atlas = theme.atlas - end - - return sanitized -end - -return Theme diff --git a/libs/flexlove/modules/UTF8.lua b/libs/flexlove/modules/UTF8.lua deleted file mode 100644 index ba2adc95..00000000 --- a/libs/flexlove/modules/UTF8.lua +++ /dev/null @@ -1,44 +0,0 @@ ----@class UTF8 ----Compatibility layer for UTF-8 support across Lua versions ----Handles utf8 (Lua 5.3+), lua-utf8 (LuaRocks), and basic fallbacks - -local UTF8 = {} - --- Try to load UTF-8 library in order of preference: --- 1. Built-in utf8 (Lua 5.3+, LÖVE2D) --- 2. lua-utf8 from LuaRocks (Lua 5.1, 5.2) --- 3. Error if neither available -local function loadUTF8() - -- Try built-in utf8 first (Lua 5.3+ and LÖVE2D) - if utf8 and type(utf8) == "table" and utf8.len then - return utf8 - end - - -- Try lua-utf8 from LuaRocks - local ok, luautf8 = pcall(require, "lua-utf8") - if ok then - return luautf8 - end - - -- Try standard utf8 module name as fallback - ok, luautf8 = pcall(require, "utf8") - if ok then - return luautf8 - end - - -- No UTF-8 library available - error("No UTF-8 library available. Please install 'luautf8' via LuaRocks: luarocks install luautf8") -end - --- Load the UTF-8 implementation -local utf8lib = loadUTF8() - --- Export all utf8 functions -UTF8.char = utf8lib.char -UTF8.charpattern = utf8lib.charpattern -UTF8.codes = utf8lib.codes -UTF8.codepoint = utf8lib.codepoint -UTF8.len = utf8lib.len -UTF8.offset = utf8lib.offset - -return UTF8 diff --git a/libs/flexlove/modules/Units.lua b/libs/flexlove/modules/Units.lua deleted file mode 100644 index aa25707f..00000000 --- a/libs/flexlove/modules/Units.lua +++ /dev/null @@ -1,335 +0,0 @@ ---- Utility module for parsing and resolving CSS-like units (px, %, vw, vh) ---- Provides unit parsing, validation, and conversion to pixel values ----@class Units ----@field _Context table? Context module dependency ----@field _ErrorHandler table? ErrorHandler module dependency ----@field _Calc table? Calc module dependency -local Units = {} - ---- Initialize Units module with dependencies ----@param deps table Dependencies: { Context = table?, ErrorHandler = table?, Calc = table? } -function Units.init(deps) - Units._Context = deps.Context - Units._ErrorHandler = deps.ErrorHandler - Units._Calc = deps.Calc -end - ---- Parse a unit value into numeric value and unit type ---- Supports: px (pixels), % (percentage), vw/vh (viewport), and calc() expressions ----@param value string|number|table The value to parse (e.g., "50px", "10%", "2vw", 100, or calc object) ----@return number|table numericValue The numeric portion of the value or calc object ----@return string unitType The unit type ("px", "%", "vw", "vh", "calc") -function Units.parse(value) - -- Check if value is a calc expression - if Units._Calc and Units._Calc.isCalc(value) then - return value, "calc" - end - - if type(value) == "number" then - return value, "px" - end - - if type(value) ~= "string" and type(value) ~= "table" then - Units._ErrorHandler:warn("Units", "VAL_001", { - property = "unit value", - expected = "string, number, or calc object", - got = type(value), - }) - return 0, "px" - end - - -- Check for unit-only input (e.g., "px", "%", "vw" without a number) - local validUnits = { px = true, ["%"] = true, vw = true, vh = true } - if validUnits[value] then - Units._ErrorHandler:warn("Units", "VAL_005", { - input = value, - expected = "number + unit (e.g., '50" .. value .. "')", - }) - return 0, "px" - end - - -- Check for invalid format (space between number and unit) - if value:match("%d%s+%a") then - Units._ErrorHandler:warn("Units", "VAL_005", { - input = value, - issue = "contains space between number and unit", - }) - return 0, "px" - end - - -- Match number followed by optional unit - local numStr, unit = value:match("^([%-]?[%d%.]+)(.*)$") - if not numStr then - Units._ErrorHandler:warn("Units", "VAL_005", { - input = value, - }) - return 0, "px" - end - - local num = tonumber(numStr) - if not num then - Units._ErrorHandler:warn("Units", "VAL_005", { - input = value, - issue = "numeric value cannot be parsed", - }) - return 0, "px" - end - - -- Default to pixels if no unit specified - if unit == "" then - unit = "px" - end - - -- validUnits is already defined at the top of the function - if not validUnits[unit] then - Units._ErrorHandler:warn("Units", "VAL_005", { - input = value, - unit = unit, - validUnits = "px, %, vw, vh", - }) - return num, "px" - end - - return num, unit -end - ---- Convert relative units to absolute pixel values ---- Resolves %, vw, vh units based on viewport and parent dimensions, and evaluates calc() expressions ----@param value number|table Numeric value to convert or calc object ----@param unit string Unit type ("px", "%", "vw", "vh", "calc") ----@param viewportWidth number Current viewport width in pixels ----@param viewportHeight number Current viewport height in pixels ----@param parentSize number? Required for percentage units (parent dimension in pixels) ----@return number resolvedValue Resolved pixel value -function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize) - if unit == "calc" then - -- Resolve calc expression - if Units._Calc then - return Units._Calc.resolve(value, viewportWidth, viewportHeight, parentSize) - else - Units._ErrorHandler:warn("Units", "VAL_006", { - unit = "calc", - issue = "Calc module not available", - }) - return 0 - end - elseif unit == "px" then - return value - elseif unit == "%" then - if not parentSize then - Units._ErrorHandler:warn("Units", "LAY_003", { - unit = "%", - issue = "parent dimension not available", - }) - return 0 - end - return (value / 100) * parentSize - elseif unit == "vw" then - return (value / 100) * viewportWidth - elseif unit == "vh" then - return (value / 100) * viewportHeight - else - Units._ErrorHandler:warn("Units", "VAL_005", { - unit = unit, - validUnits = "px, %, vw, vh, calc", - }) - return 0 - end -end - ---- Get current viewport dimensions ---- Uses cached viewport during resize operations, otherwise queries LÖVE graphics ----@return number width Viewport width in pixels ----@return number height Viewport height in pixels -function Units.getViewport() - -- Return cached viewport if available (only during resize operations) - if Units._Context._cachedViewport and Units._Context._cachedViewport.width > 0 then - return Units._Context._cachedViewport.width, Units._Context._cachedViewport.height - end - - if love.graphics and love.graphics.getDimensions then - return love.graphics.getDimensions() - else - local w, h = love.window.getMode() - return w, h - end -end - ---- Apply base scale factor to a value based on axis ---- Used for responsive scaling of UI elements ----@param value number The value to scale ----@param axis "x"|"y" The axis to scale on ----@param scaleFactors {x:number, y:number} Scale factors for each axis ----@return number scaledValue The scaled value -function Units.applyBaseScale(value, axis, scaleFactors) - if axis == "x" then - return value * scaleFactors.x - else - return value * scaleFactors.y - end -end - ---- Resolve spacing properties (margin, padding) to pixel values ---- Supports individual sides (top, right, bottom, left) and shortcuts (vertical, horizontal) ----@param spacingProps table? Spacing properties with top/right/bottom/left/vertical/horizontal ----@param parentWidth number Parent element width in pixels ----@param parentHeight number Parent element height in pixels ----@return table resolvedSpacing Table with top, right, bottom, left in pixels -function Units.resolveSpacing(spacingProps, parentWidth, parentHeight) - if not spacingProps then - return { top = 0, right = 0, bottom = 0, left = 0 } - end - - local viewportWidth, viewportHeight = Units.getViewport() - local result = {} - - local vertical = spacingProps.vertical - local horizontal = spacingProps.horizontal - - if vertical then - if type(vertical) == "string" or (Units._Calc and Units._Calc.isCalc(vertical)) then - local value, unit = Units.parse(vertical) - vertical = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight) - end - end - - if horizontal then - if type(horizontal) == "string" or (Units._Calc and Units._Calc.isCalc(horizontal)) then - local value, unit = Units.parse(horizontal) - horizontal = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth) - end - end - - for _, side in ipairs({ "top", "right", "bottom", "left" }) do - local value = spacingProps[side] - if value then - if type(value) == "string" or (Units._Calc and Units._Calc.isCalc(value)) then - local numValue, unit = Units.parse(value) - local parentSize = (side == "top" or side == "bottom") and parentHeight or parentWidth - result[side] = Units.resolve(numValue, unit, viewportWidth, viewportHeight, parentSize) - else - result[side] = value - end - else - if side == "top" or side == "bottom" then - result[side] = vertical or 0 - else - result[side] = horizontal or 0 - end - end - end - - return result -end - ---- Validate a unit string format ---- Checks if the string can be successfully parsed as a valid unit or calc expression ----@param unitStr string|table The unit string to validate (e.g., "50px", "10%") or calc object ----@return boolean isValid True if the unit string is valid, false otherwise -function Units.isValid(unitStr) - -- Check if it's a calc expression - if Units._Calc and Units._Calc.isCalc(unitStr) then - return true - end - - if type(unitStr) ~= "string" then - return false - end - - -- Check for invalid format (space between number and unit) - if unitStr:match("%d%s+%a") then - return false - end - - -- Match number followed by optional unit - local numStr, unit = unitStr:match("^([%-]?[%d%.]+)(.*)$") - if not numStr then - return false - end - - -- Check if numeric part is valid - local num = tonumber(numStr) - if not num then - return false - end - - -- Default to pixels if no unit specified - if unit == "" then - unit = "px" - end - - -- Check if unit is valid - local validUnits = { px = true, ["%"] = true, vw = true, vh = true } - return validUnits[unit] == true -end - ---- Parse CSS flex shorthand into flexGrow, flexShrink, flexBasis ---- Supports: number, "auto", "none", "grow shrink basis" ----@param flexValue number|string The flex shorthand value ----@return number flexGrow ----@return number flexShrink ----@return string|number flexBasis -function Units.parseFlexShorthand(flexValue) - -- Single number: flex-grow - if type(flexValue) == "number" then - return flexValue, 1, 0 - end - - -- String values - if type(flexValue) == "string" then - -- "auto" = 1 1 auto - if flexValue == "auto" then - return 1, 1, "auto" - end - - -- "none" = 0 0 auto - if flexValue == "none" then - return 0, 0, "auto" - end - - -- Parse "grow shrink basis" format - local parts = {} - for part in flexValue:gmatch("%S+") do - table.insert(parts, part) - end - - local grow = 0 - local shrink = 1 - local basis = "auto" - - if #parts == 1 then - -- Single value: could be grow (number) or basis (with unit) - local num = tonumber(parts[1]) - if num then - grow = num - basis = 0 - else - basis = parts[1] - end - elseif #parts == 2 then - -- Two values: grow shrink (both numbers) or grow basis - local num1 = tonumber(parts[1]) - local num2 = tonumber(parts[2]) - if num1 and num2 then - grow = num1 - shrink = num2 - basis = 0 - elseif num1 then - grow = num1 - basis = parts[2] - end - elseif #parts >= 3 then - -- Three values: grow shrink basis - grow = tonumber(parts[1]) or 0 - shrink = tonumber(parts[2]) or 1 - basis = parts[3] - end - - return grow, shrink, basis - end - - -- Default fallback - return 0, 1, "auto" -end - -return Units diff --git a/libs/flexlove/modules/ZIndex.lua b/libs/flexlove/modules/ZIndex.lua deleted file mode 100644 index e10bdf56..00000000 --- a/libs/flexlove/modules/ZIndex.lua +++ /dev/null @@ -1,35 +0,0 @@ ----@class ZIndex -local ZIndex = {} - --- The effective z-index formula used for sorting is: --- rootZ * ROOT_WEIGHT + depth * DEPTH_WEIGHT + ownZ --- where rootZ is the z-index of the top-level ancestor, depth is the --- nesting level, and ownZ is the element's own z property. --- --- Constraints enforced by these weights: --- |ownZ| <= MAX_Z (must fit within DEPTH_WEIGHT digits) --- DEPTH_WEIGHT has enough room for depths well beyond any practical tree --- ROOT_WEIGHT has enough room for the rootZ without exceeding double-precision ---- ----@type integer -ZIndex.MIN_Z = -999 ----@type integer -ZIndex.MAX_Z = 999 ----@type integer -ZIndex.ROOT_WEIGHT = 10000000000 ----@type integer -ZIndex.DEPTH_WEIGHT = 1000 - ---- Clamp a z-index value to the valid range ----@param value number ----@return integer -function ZIndex.clamp(value) - if value < ZIndex.MIN_Z then - return ZIndex.MIN_Z - elseif value > ZIndex.MAX_Z then - return ZIndex.MAX_Z - end - return value -end - -return ZIndex diff --git a/libs/flexlove/modules/behaviors/Animated.lua b/libs/flexlove/modules/behaviors/Animated.lua deleted file mode 100644 index 16592025..00000000 --- a/libs/flexlove/modules/behaviors/Animated.lua +++ /dev/null @@ -1,245 +0,0 @@ --- modules/behaviors/Animated.lua --- --- Concrete behavior: animation update, interpolation application, chaining --- resolution, and transition wiring. --- --- Task 06 of the behavior-mode-unification refactor. Moves the entire --- animation-update block out of Element:update (lines ~2761-2800) into --- `Animated.onUpdate(element, dt)`, and the `_ColorModule`/`_TransformModule` --- init-time wiring into `Animated.onAttach(element)`. --- --- This behavior is UNIQUE among the behavior set because it can attach --- AFTER element creation. Animation is opt-in: a plain Element created without --- `transitions` and without an `animation` field never attaches Animated. --- The moment something creates an animation on the element — either directly --- (`element.animation = Animation.new(...)`, `element:fadeIn(...)`) or via a --- transition firing in `setProperty` — `Animated.ensureAttached(element)` --- attaches this behavior on demand so subsequent `Element:update` frames --- dispatch to `Animated.onUpdate`. --- --- Attachment rule (shouldAttach): true when `props.transitions` is set OR an --- `element.animation` already exists at runtime. The runtime arm covers the --- late-attach case (animateTo / fadeIn / direct animation assignment). --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (`element.animation`). --- * The behavior instance itself is stateless and shared across elements. --- * Element-class-level dependencies (Element._Animation, Element._Color, --- Element._Transform) are resolved from the owning element's metatable, --- exactly like Clickable does — keeping the behavior stateless without --- expanding the 6-hook signature. --- --- saveState/restoreState are no-ops: animations are ephemeral (an in-flight --- animation is not part of immediate-mode persisted state — the next frame --- re-evaluates transitions / re-applies animations fresh). Persisted scalar --- props (`opacity`, `x`, ...) survive via Element.saveState's `_props` block, --- not via the animation. - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance. --- Element instances are created via `setmetatable({}, Element)` in _construct, --- so their metatable IS the Element class — giving us Element._Animation, --- Element._Color, Element._Transform, etc. without threading deps through the --- behavior hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ---------------------------------------------------------------------------- --- ensureAnimationModuleWiring — set Element._Animation._ColorModule / --- _TransformModule. Idempotent; called from both onAttach and onUpdate so it --- works even when an animation was assigned by a caller that bypassed --- onAttach (direct `element.animation = Animation.new(...)`). --- ---------------------------------------------------------------------------- - -local function ensureAnimationModuleWiring(element) - local Element = ElementClass(element) - local Animation = Element._Animation - if not Animation then - return - end - -- Ensure animation has Color module reference for color interpolation - if not Animation._ColorModule and Element._Color then - Animation._ColorModule = Element._Color - end - -- Ensure animation has Transform module reference for transform interpolation - if not Animation._TransformModule and Element._Transform then - Animation._TransformModule = Element._Transform - end -end - --- ---------------------------------------------------------------------------- --- shouldAttach (class-level predicate, no element required) --- ---------------------------------------------------------------------------- - --- True when the element declares transitions up front OR already has an --- animation attached. The `animation` arm is consulted by ensureAttached at --- runtime (after creation); the `transitions` arm lets Animated auto-attach --- during Element.new for elements that pre-declare transitions. -local function shouldAttach(props) - if not props then - return false - end - if props.transitions ~= nil then - return true - end - -- Late-attach case: an animation was assigned after creation. When ensure - -- Attached passes the element instance as `props`, this arm catches it. - if type(props) == "table" and props.animation ~= nil then - return true - end - return false -end - --- ---------------------------------------------------------------------------- --- ensureAttached — dynamic late-attach entry point --- ---------------------------------------------------------------------------- - --- Idempotently attach the Animated behavior to an element that just gained an --- animation (via animateTo / fadeIn / direct assignment / a firing transition --- in setProperty). Called from Element.setProperty when a transition fires and --- from the transition helper methods on Element. Safe to call when already --- attached (no-op / returns false). --- --- `animatedBehavior` is the shared behavior instance resolved lazily by --- Element (see Element._resolveAnimatedBehavior). The behavior is looked up --- from the registry once and cached on the class. --- --- Returns true if the behavior was attached this call, false otherwise. -local function ensureAttached(element, animatedBehavior) - if not element or not animatedBehavior then - return false - end - -- Already attached? Avoid duplicate entries within one element lifetime - -- (a behavior may legitimately be re-added across immediate-mode frames - -- since Element is recreated each frame, but within one lifetime at most - -- once). - local behaviors = element.behaviors - if behaviors then - for i = 1, #behaviors do - if behaviors[i] == animatedBehavior then - return false - end - end - end - table.insert(element.behaviors, animatedBehavior) - animatedBehavior.onAttach(element) - return true -end - --- ---------------------------------------------------------------------------- --- onAttach — initialize Animation module references (formerly the --- Element._Animation._ColorModule / _TransformModule wiring in Element:update --- lines ~2772-2778). --- ---------------------------------------------------------------------------- - -local function onAttach(element) - ensureAnimationModuleWiring(element) -end - --- ---------------------------------------------------------------------------- --- onUpdate — the animation update + interpolation + chain-resolution block --- (formerly Element:update lines ~2761-2800). --- ---------------------------------------------------------------------------- - -local function onUpdate(element, dt) - local animation = element.animation - if not animation then - return - end - - -- (Re)ensure module wiring is present in case the Animation instance was - -- created by a caller that bypassed onAttach (e.g. direct - -- `element.animation = Animation.new(...)`). Cheap idempotent writes. - ensureAnimationModuleWiring(element) - - local finished = animation:update(dt, element) - if finished then - -- Animation:update() already called onComplete callback. - -- Check for chained animation. - if animation._next then - element.animation = animation._next - elseif animation._nextFactory and type(animation._nextFactory) == "function" then - local success, nextAnim = pcall(animation._nextFactory, element) - if success and nextAnim then - element.animation = nextAnim - else - element.animation = nil - end - else - element.animation = nil - end - else - -- Apply animation interpolation during update. - animation:applyInterpolation(element) - end -end - --- ---------------------------------------------------------------------------- --- saveState / restoreState — no-ops (animations are ephemeral). --- ---------------------------------------------------------------------------- - --- Animations are not persisted across immediate-mode frames — they are --- re-derived each frame from transitions / direct calls. The element's scalar --- props (opacity, x, ...) are persisted by Element.saveState's _props block, --- so a completed animation's final visual state still survives recreation. --- While an animation is mid-flight in immediate mode, the element is recreated --- and the animation is NOT carried over (intentional — animating in immediate --- mode requires setting up the animation each frame). -local function saveState() - return nil -end - -local function restoreState() - return nil -end - --- ---------------------------------------------------------------------------- --- Build the (stateless, shared) behavior instance. --- ---------------------------------------------------------------------------- - --- onDetach/onDraw omitted: they default to no-ops (the behavior allocates no --- behavior-local state and animations have no draw pass). Animation state lives --- on the element (`element.animation`); nothing to tear down on detach. --- --- We build the immutable behavior via Behavior.new (for validation + freeze + --- isBehavior parity with Clickable), then expose the late-attach helper on a --- thin module table since the frozen instance cannot accept new keys. The --- module table passes the behavior to the registry while making --- `Animated.ensureAttached` callable from Element.setProperty / the transition --- helpers — exactly as the task spec requires. -local behavior = Behavior.new({ - onAttach = onAttach, - onUpdate = onUpdate, - saveState = saveState, - restoreState = restoreState, - shouldAttach = shouldAttach, -}) - --- Thin module table: exposes the behavior instance (for the registry) plus the --- late-attach helper (for Element.setProperty). All hooks delegate to the --- frozen behavior instance so dispatch sites get the validated, frozen --- implementation. shouldAttach is also exposed at module level (mirrors --- Clickable.shouldAttach) for tests/callers without an element. -local Animated = { - behavior = behavior, - ensureAttached = ensureAttached, - shouldAttach = shouldAttach, - onAttach = onAttach, - onUpdate = onUpdate, -} - --- Metatable so the module table itself satisfies the duck-typed registry --- contract (iterating `Element._behaviorRegistry` calls `behavior.shouldAttach` --- and `behavior.onAttach` / `behavior.onUpdate` directly). Falls through to the --- frozen behavior instance for every hook. -setmetatable(Animated, { - __index = behavior, - __tostring = function() - return "Animated" - end, -}) - -return Animated diff --git a/libs/flexlove/modules/behaviors/Clickable.lua b/libs/flexlove/modules/behaviors/Clickable.lua deleted file mode 100644 index 811ae11e..00000000 --- a/libs/flexlove/modules/behaviors/Clickable.lua +++ /dev/null @@ -1,344 +0,0 @@ --- modules/behaviors/Clickable.lua --- --- Concrete behavior: mouse/touch event handling, pressed-state tracking, --- hit-testing, and theme-state sync. --- --- This is the largest behavior in the behavior-mode-unification refactor --- (~200 LOC moved out of Element:update / _initSubSystems / saveState). --- Task 02 extracts the entire `if self.onEvent or self.themeComponent or --- self.editable or self._selectState or self.selectOption then ... end` block --- from Element:update (hit-testing, mouse/touch event processing, immediate- --- mode state save, theme-state update) plus EventHandler creation (formerly the --- first half of Element:_initSubSystems) plus pressed-state drawing (formerly a --- render layer in Renderer) plus EventHandler save/restore. --- --- Attachment rule (shouldAttach): the same predicate that previously guarded --- mouse-event processing in Element:update. An element owns the EventHandler / --- gets press feedback exactly when it is interactive: when it declares an --- `onEvent` callback, a `themeComponent`, is `editable`, or participates in a --- Select group (selectParent / selectOption). A plain passive element never --- attaches Clickable and therefore never allocates an EventHandler. --- --- Element retains only the `self._eventHandler` field; Clickable owns it on --- attach. All other Element paths that touched the EventHandler (handleTouchEvent, --- handleGesture, getTouches) already nil-guard `self._eventHandler`, so they keep --- working unchanged for non-clickable elements. --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (self._eventHandler etc.). --- * The behavior instance itself is stateless and shared across elements. --- * Element-class-level dependencies (EventHandler factory, StateManager, --- Context) are resolved from the owning element's metatable (the Element --- class set by Element:_construct). This keeps the behavior stateless while --- avoiding a dependency-injection parameter that would violate the locked --- 6-hook signature `(element, ...)`. - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance. --- Element instances are created via `setmetatable({}, Element)` in _construct, --- so their metatable IS the Element class — giving us Element._EventHandler, --- Element._eventHandlerDeps, Element._StateManager, Element._Context, etc. --- without threading deps through the behavior hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ---------------------------------------------------------------------------- --- shouldAttach (class-level predicate, no element required) --- ---------------------------------------------------------------------------- - --- Mirrors the cases that previously caused Element to allocate + use an --- EventHandler. MUST cover every element that touches the EventHandler at --- runtime: click (onEvent), theme press-feedback (themeComponent), text mouse --- interaction (editable), Select groups (selectParent / selectOption), touch --- callbacks (onTouchEvent), and gesture callbacks (onGesture). selectParent / --- selectOption are the props that produce _selectState during _initSubSystems; --- checking the props (rather than the runtime _selectState) lets shouldAttach --- run before the Select subsystem is initialized. -local function shouldAttach(props) - props = props or {} - return props.onEvent ~= nil - or props.themeComponent ~= nil - or props.editable == true - or props.onTouchEvent ~= nil - or props.onGesture ~= nil - or props.selectOption ~= nil - or props.selectParent ~= nil -end - --- ---------------------------------------------------------------------------- --- onAttach — create the EventHandler (formerly Element:_initSubSystems --- lines ~640-690) and restore immediate-mode EventHandler state. --- ---------------------------------------------------------------------------- - -local function onAttach(element) - local Element = ElementClass(element) - - local eventHandlerConfig = { - -- element.onEvent is source of truth; not cached on handler - onEventDeferred = element.onEventDeferred, - -- element.onTouchEvent is source of truth; not cached on handler - onTouchEventDeferred = element.onTouchEventDeferred, - -- element.onGesture is source of truth; not cached on handler - onGestureDeferred = element.onGestureDeferred, - touchEnabled = element.touchEnabled, - multiTouchEnabled = element.multiTouchEnabled, - } - - -- In immediate mode, restore EventHandler state from StateManager so pressed - -- / hovered / click-count survive the per-frame element recreation cycle. - -- Mode-aware via Context.isImmediateMode (behavior-mode-unification task 11): - -- in retained mode the eventHandler persists, so nothing to restore. - if Element._Context.isImmediateMode() and element._stateId and element._stateId ~= "" then - local state = Element._StateManager.getState(element._stateId) - if state then - -- Restore EventHandler state from StateManager (sparse storage — provide defaults) - eventHandlerConfig._pressed = state._pressed or {} - eventHandlerConfig._lastClickTime = state._lastClickTime - eventHandlerConfig._lastClickButton = state._lastClickButton - eventHandlerConfig._clickCount = state._clickCount or 0 - eventHandlerConfig._dragStartX = state._dragStartX or {} - eventHandlerConfig._dragStartY = state._dragStartY or {} - eventHandlerConfig._lastMouseX = state._lastMouseX or {} - eventHandlerConfig._lastMouseY = state._lastMouseY or {} - eventHandlerConfig._hovered = state._hovered - end - end - - element._eventHandler = Element._EventHandler.new(eventHandlerConfig, Element._eventHandlerDeps) -end - -local function onDetach(element) - -- Clear focus callbacks read by KeyboardNavigation / TextEditor:focus so the - -- element's closure references can be collected in immediate mode (formerly - -- part of Element:_cleanup). The EventHandler instance itself is INTENTIONALLY - -- kept: Element:_cleanup preserves element structure for inspection (the - -- stale-element refs are released when the element is GC'd). onEvent, - -- onTouchEvent, onGesture are also left intact — the Renderer/EventHandler - -- read those directly from the element (not the cache), so clearing them - -- would break retained mode. - element.onFocus = nil - element.onBlur = nil -end - --- ---------------------------------------------------------------------------- --- onUpdate — the mouse hit-testing + event-processing + theme-state + --- immediate-mode save block (formerly Element:update lines ~2813-2960). --- ---------------------------------------------------------------------------- - -local function onUpdate(element, dt) - local Element = ElementClass(element) - local eventHandler = element._eventHandler - if not eventHandler then - return - end - - local mx, my = love.mouse.getPosition() - - -- Clickable area is the border box (x, y already includes padding) - -- BORDER-BOX MODEL: Use stored border-box dimensions for hit detection - local bx = element.x - local by = element.y - local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom) - - -- Account for scroll offsets from parent containers - -- Walk up the parent chain and accumulate scroll offsets. This stays in - -- Clickable because it's an interaction concern (hit-testing), not layout. - local scrollOffsetX = 0 - local scrollOffsetY = 0 - local current = element.parent - while current do - local overflowX = current.overflowX or current.overflow - local overflowY = current.overflowY or current.overflow - local hasScrollableOverflow = ( - overflowX == "scroll" - or overflowX == "auto" - or overflowY == "scroll" - or overflowY == "auto" - or overflowX == "hidden" - or overflowY == "hidden" - ) - if hasScrollableOverflow then - scrollOffsetX = scrollOffsetX + (current._scrollX or 0) - scrollOffsetY = scrollOffsetY + (current._scrollY or 0) - end - current = current.parent - end - - -- Adjust mouse position by accumulated scroll offset for hit testing - local adjustedMx = mx + scrollOffsetX - local adjustedMy = my + scrollOffsetY - local isHovering = adjustedMx >= bx and adjustedMx <= bx + bw and adjustedMy >= by and adjustedMy <= by + bh - - -- Check if this is the topmost interactive element at the mouse position - -- (z-index ordering). This prevents blocked/occluded elements from - -- receiving interactions or visual feedback. A single mode-agnostic lookup - -- via `Context.findInteractiveAtPosition` (unified-event-routing task 05) - -- replaces the previous immediate/retained-mode split that used - -- `getTopElementAt` in immediate mode and `_activeEventElement` in retained - -- mode. `findInteractiveAtPosition` routes every hit test through - -- `pointHitsElement` (the single canonical `display == false` guard) and - -- resolves occlusion by z-index in both modes, so the active element is the - -- same one that would receive a hit under the cursor. - local topElement = Element._Context.findInteractiveAtPosition(mx, my) - local isActiveElement = (topElement == element or topElement == nil) - - -- Reset scrollbar press flag at start of each frame - eventHandler:resetScrollbarPressFlag() - - -- Process mouse events through EventHandler FIRST - -- This ensures pressed states are updated before theme state is calculated - eventHandler:processMouseEvents(element, mx, my, isHovering, isActiveElement) - - -- In immediate mode, save EventHandler state to StateManager after - -- processing events so it survives the per-frame recreation. - if element._stateId and Element._Context.isImmediateMode() and element._stateId ~= "" then - local eventHandlerState = eventHandler:getState() - Element._StateManager.updateState(element._stateId, { - _pressed = eventHandlerState._pressed, - _lastClickTime = eventHandlerState._lastClickTime, - _lastClickButton = eventHandlerState._lastClickButton, - _clickCount = eventHandlerState._clickCount, - _dragStartX = eventHandlerState._dragStartX, - _dragStartY = eventHandlerState._dragStartY, - _lastMouseX = eventHandlerState._lastMouseX, - _lastMouseY = eventHandlerState._lastMouseY, - _hovered = eventHandlerState._hovered, - }) - end - - -- Update theme state based on interaction. themeComponent state update - -- lives in Clickable because it is driven by hover/press state; the actual - -- theme RENDERING is the Themed behavior (task 07). - if element.themeComponent then - -- Check if any button is pressed via EventHandler - local anyPressed = eventHandler:isAnyButtonPressed() - - -- Update theme state via ThemeManager - local isFocused = Element._Context.getFocused() == element - local newThemeState = - element._themeManager:updateState(isHovering and isActiveElement, anyPressed, isFocused, element.disabled) - - if element._stateId and Element._Context.isImmediateMode() then - local hover = newThemeState == "hover" - local pressed = newThemeState == "pressed" - local focused = isFocused - - Element._StateManager.updateState(element._stateId, { - hover = hover, - pressed = pressed, - focused = focused, - disabled = element.disabled, - active = element.active, - }) - end - - if element._renderer then - element._renderer:setThemeState(newThemeState) - end - end - - -- Process touch events through EventHandler - eventHandler:processTouchEvents(element) -end - --- ---------------------------------------------------------------------------- --- onDraw — pressed-state visual feedback (formerly Renderer Layer 5). --- ---------------------------------------------------------------------------- - --- Draws the grey pressed overlay when any mouse button is currently pressed on --- the element. Delegates the actual pixels to Renderer:drawPressedState (which --- owns the RoundedRect + opacity math) but drives the DECISION + transform --- context here, so the renderer no longer needs the `if element.onEvent ...` --- behavioral branch. Honors disableHighlight (themes handle their own visual --- feedback) exactly as the old render layer did. -local function onDraw(element) - if element.disableHighlight then - return - end - local eventHandler = element._eventHandler - if not eventHandler then - return - end - - local anyPressed = false - local pressedState = eventHandler:getState()._pressed or {} - for _, pressed in pairs(pressedState) do - if pressed then - anyPressed = true - break - end - end - if not anyPressed then - return - end - - local renderer = element._renderer - if not renderer then - return - end - - local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right) - local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom) - - -- Apply the element transform around the overlay, mirroring how the - -- Renderer wrapped its whole command buffer (pressed state was a render - -- layer subject to the same transform). - local Element = ElementClass(element) - local Transform = Element._Transform - local hasTransform = element.transform ~= nil and Transform ~= nil and not Transform.isIdentity(element.transform) - if hasTransform then - Transform.apply(element.transform, element.x, element.y, element.width, element.height) - end - - renderer:drawPressedState(element.x, element.y, bw, bh, element.opacity, element.cornerRadius) - - if hasTransform then - Transform.unapply() - end -end - --- ---------------------------------------------------------------------------- --- saveState / restoreState — EventHandler state (formerly the eventHandler --- branches of Element:saveState / Element:restoreState). --- ---------------------------------------------------------------------------- - -local function saveState(element) - if element._eventHandler then - return { eventHandler = element._eventHandler:getState() } - end - return nil -end - -local function restoreState(element, state) - if not state then - return nil - end - if element._eventHandler and state.eventHandler then - element._eventHandler:setState(state.eventHandler) - end - return nil -end - --- ---------------------------------------------------------------------------- --- Build the (stateless, shared, immutable) behavior instance. --- ---------------------------------------------------------------------------- - -local Clickable = Behavior.new({ - onAttach = onAttach, - onDetach = onDetach, - onUpdate = onUpdate, - onDraw = onDraw, - saveState = saveState, - restoreState = restoreState, - shouldAttach = shouldAttach, -}) - --- Expose the predicate at module level so callers/tests can reference it --- directly without an element instance (mirrors Behavior.shouldAttach). -Clickable.shouldAttach = shouldAttach - -return Clickable diff --git a/libs/flexlove/modules/behaviors/Imageable.lua b/libs/flexlove/modules/behaviors/Imageable.lua deleted file mode 100644 index b448a47b..00000000 --- a/libs/flexlove/modules/behaviors/Imageable.lua +++ /dev/null @@ -1,282 +0,0 @@ --- modules/behaviors/Imageable.lua --- --- Concrete behavior: image loading + image rendering config. --- --- Imageable owns the image side of the Renderer: it runs the deferred image- --- load pipeline (cache check → defer → load → fire onImageLoad/onImageError --- callbacks), populates the resolved `_loadedImage` cache on both the element --- and the shared renderer, and persists that cache across immediate-mode --- recreation. It is the behavior-mode-unification replacement for the image- --- loading half of Element:_initImageAndRenderer and the deferred --- Element:_loadImage method (behavior-mode-unification task 07). --- --- Image value props (imagePath/image/objectFit/objectPosition/imageOpacity/ --- imageRepeat/imageTint) are bound on the ELEMENT by Element:_applyProps and read --- from the element at draw time (Renderer._executeDrawCommand image branch) — --- Imageable does NOT mirror them onto the renderer, so bare writes and --- setProperty(...) are immediately consistent. Only the resolved _loadedImage --- cache (the love.Image produced by the load pipeline) is renderer-mirrored, --- because Renderer:draw reads `self._loadedImage`. --- --- Runtime reload: setProperty("imagePath", ...) / setProperty("image", ...) and --- the bare-write-equivalent setImage* flows route through element._reloadImage --- (installed below) which re-runs the load pipeline. See --- TestRetainedPropertyConsistency (image props) and TestImageableIntegration. --- --- Attachment rule (shouldAttach): an element owns image concern exactly when it --- declares an `imagePath` (load-from-path) or a direct `image` (already-loaded --- love.Image). Mirrors the old `if self.imagePath / if self.image` init branches. --- --- Pairing with Themed: Themed.onAttach creates the Renderer with theme/blur --- config; Imageable.onAttach enriches the SAME renderer instance with image --- config + kicks off loading. They share `element._renderer`. In the registry --- Imageable runs after Themed, so the renderer already exists; the create-or- --- reuse guard below covers the defensive case where Imageable attaches first. --- --- onDraw: the image LAYER is rendered by the integrated `Renderer:draw` call --- (owned by the Themed behavior) which executes the renderer's `image` draw --- command using the config Imageable.onAttach wired. Imageable.onDraw is --- therefore a no-op for the draw call itself — there is no separate --- `_renderer:_drawImage` entry point; pixel emission lives in the integrated --- Renderer:draw command buffer. Splitting it out would require Renderer surgery --- with no behavioral gain (Renderer:draw already conditionally skips the image --- layer when no image is loaded). --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (`element._loadedImage`, --- `element._renderer._loadedImage`). The behavior instance is stateless. --- * saveState/restoreState persist `_loadedImage` across immediate-mode frames --- so the image renders even if the ImageCache is cleared between frames and --- so the renderer's loaded-image cache survives element recreation. - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Lua 5.4 removed the global `unpack`; mirror Element's alias. -local unpack = table.unpack or unpack - --- Resolve the Element class from an element instance (mirrors Clickable/Themed). -local function ElementClass(element) - return getmetatable(element) -end - --- ---------------------------------------------------------------------------- --- shouldAttach (class-level predicate, no element required) --- ---------------------------------------------------------------------------- - -local function shouldAttach(props) - props = props or {} - return props.imagePath ~= nil or props.image ~= nil -end - --- ---------------------------------------------------------------------------- --- Image callback helper (moved from Element._fireImageCallback). --- Fires a user-supplied image callback (onImageLoad/onImageError) under pcall, --- honoring the onXDeferred flag when `honorDeferred` is true, and emits a single --- EVT_002 warn on failure. The direct-`image` sync init path passes --- honorDeferred=false to preserve immediate firing (image is already loaded). --- ---------------------------------------------------------------------------- - -local function fireImageCallback(element, callbackField, honorDeferred, ...) - local cb = element[callbackField] - if type(cb) ~= "function" then - return - end - local Element = ElementClass(element) - local argc = select("#", ...) - local args = { ... } - local function invoke() - local ok, err = pcall(cb, element, unpack(args, 1, argc)) - if not ok then - Element._ErrorHandler:warn("Element", "EVT_002", { - callback = callbackField, - error = tostring(err), - }) - end - end - if honorDeferred and element[callbackField .. "Deferred"] then - Element._Context.deferCallback(invoke) - else - invoke() - end -end - --- ---------------------------------------------------------------------------- --- Deferred image loader (replaces Element:_loadImage). --- --- Invoked by Element's deferred-method dispatcher via the instance closure that --- onAttach installs on `element._loadImage`. Loads the image from cache or disk --- (I/O), updates BOTH the element and renderer `_loadedImage` caches so the --- image draws after an async load, and fires the load/error callback (deferred, --- honoring onImageLoadDeferred / onImageErrorDeferred). --- ---------------------------------------------------------------------------- - -local function loadImage(element) - if not element.imagePath or element.image then - return - end - local Element = ElementClass(element) - local loadedImage, err = Element._ImageCache.load(element.imagePath) - if loadedImage then - element._loadedImage = loadedImage - if element._renderer then - element._renderer._loadedImage = loadedImage - end - fireImageCallback(element, "onImageLoad", true, loadedImage) - else - fireImageCallback(element, "onImageError", true, err or "Unknown error") - end -end - --- ---------------------------------------------------------------------------- --- reloadImage — recompute the loaded-image cache from the current image/imagePath. --- --- This is the single entry point for (re)loading after either initial attach or --- a runtime property change (see Element._specialSetHandlers.imagePath/image, --- which call element:_reloadImage()). Precedence matches onAttach: a direct --- `image` wins over `imagePath`; `nil` for both clears the cache. --- --- * direct image → set _loadedImage immediately, fire onImageLoad SYNC (the --- image is already loaded; honorDeferred=false preserves the --- original synchronous init contract). --- * imagePath → cache CHECK only (no I/O) so a cached image can draw this --- frame, then defer the loader (_loadImage) for the actual --- I/O + deferred callbacks. load bails if `image` is later set. --- * neither → clear _loadedImage on both element + renderer. --- --- Image value props (objectFit/imageOpacity/imageRepeat/imageTint/objectPosition) --- and imagePath/image themselves live on the ELEMENT as source of truth; the --- renderer reads them at draw time, so reloadImage does NOT mirror them onto the --- renderer — only the resolved _loadedImage cache is pushed. --- ---------------------------------------------------------------------------- - -local function reloadImage(element) - local Element = ElementClass(element) - local renderer = element._renderer - if element.image then - element._loadedImage = element.image - if renderer then - renderer._loadedImage = element.image - end - fireImageCallback(element, "onImageLoad", false, element.image) - elseif element.imagePath then - -- Cache check (no I/O). Populate both caches immediately if cached so the - -- image can draw this frame without waiting for the deferred load. - local cached = Element._ImageCache.get(element.imagePath) - element._loadedImage = cached - if renderer then - renderer._loadedImage = cached - end - -- Kick off the deferred I/O load + callbacks (idempotent: loadImage bails - -- if image is set or imagePath is nil by the time it runs). - if element._loadImage then - element:_deferMethod("_loadImage") - end - else - element._loadedImage = nil - if renderer then - renderer._loadedImage = nil - end - end -end - --- ---------------------------------------------------------------------------- --- onAttach — enrich the shared renderer with image config + kick off loading --- (formerly the image block of Element:_initImageAndRenderer). --- ---------------------------------------------------------------------------- - -local function onAttach(element) - local Element = ElementClass(element) - - -- Ensure the renderer exists (Thamed normally creates it; this create-or-reuse - -- guard is defensive for the Imageable-attaches-first ordering). - if not element._renderer then - element._renderer = Element._Renderer.new({ - theme = element.theme, - scaleCorners = element.scaleCorners, - scalingAlgorithm = element.scalingAlgorithm, - contentBlur = element.contentBlur, - backdropBlur = element.backdropBlur, - }, Element._rendererDeps) - end - - -- Install the (re)load hooks as instance methods so Element's - -- deferred-method dispatcher / setProperty special handlers can trigger a - -- reload without Element needing a behavior reference. This keeps Element - -- decoupled from the Imageable behavior (mirrors the stateless-behavior + - -- element-owned-state contract). Image value props and imagePath/image live - -- on the element as source of truth (read at draw time); only the resolved - -- _loadedImage cache is mirrored onto the renderer by reloadImage. - element._loadImage = function(el) - loadImage(el) - end - element._reloadImage = function(el) - reloadImage(el) - end - - -- Initial load: compute _loadedImage + defer the I/O load. - reloadImage(element) -end - --- ---------------------------------------------------------------------------- --- onDraw — no-op (see file header: the image layer is rendered by the integrated --- Renderer:draw call owned by the Themed behavior, using the config wired here). --- ---------------------------------------------------------------------------- - --- ---------------------------------------------------------------------------- --- saveState / restoreState — `_loadedImage` cache (for immediate-mode). --- ---------------------------------------------------------------------------- - -local function saveState(element) - if element._loadedImage ~= nil then - return { _loadedImage = element._loadedImage } - end - return nil -end - -local function restoreState(element, state) - if not state or state._loadedImage == nil then - return nil - end - local loadedImage = state._loadedImage - element._loadedImage = loadedImage - if element._renderer then - element._renderer._loadedImage = loadedImage - end - return nil -end - --- ---------------------------------------------------------------------------- --- onDetach — release image-load callback closures so the element can be GC'd --- cleanly in immediate mode (formerly part of Element:_cleanup). The cached --- `_loadedImage` is reproduced on the next attach via the Imageable saveState --- -> restoreState cycle, so dropping the live references is always safe. --- ---------------------------------------------------------------------------- - -local function onDetach(element) - element.onImageLoad = nil - element.onImageError = nil -end - --- ---------------------------------------------------------------------------- --- Build the (stateless, shared, immutable) behavior instance. --- ---------------------------------------------------------------------------- - -local Imageable = Behavior.new({ - onAttach = onAttach, - onDetach = onDetach, - onUpdate = function() end, - onDraw = function() end, - saveState = saveState, - restoreState = restoreState, - shouldAttach = shouldAttach, -}) - --- Expose the predicate at module level so callers/tests can reference it --- directly without an element instance (mirrors Behavior.shouldAttach / --- Clickable.shouldAttach). `loadImage` is NOT exposed on the (frozen) behavior --- instance; it is captured as a module-local upvalue by the onAttach closure that --- installs `element._loadImage`. -Imageable.shouldAttach = shouldAttach - -return Imageable diff --git a/libs/flexlove/modules/behaviors/Persistable.lua b/libs/flexlove/modules/behaviors/Persistable.lua deleted file mode 100644 index 6bcdd0de..00000000 --- a/libs/flexlove/modules/behaviors/Persistable.lua +++ /dev/null @@ -1,132 +0,0 @@ --- modules/behaviors/Persistable.lua --- --- Concrete behavior: generic public-property persistence across the immediate- --- mode recreation cycle (behavior-mode-unification task 12). --- --- Owns the ONE piece of Element save/restore state that is NOT subsystem state: --- the snapshot of an element's own public scalar fields (`text`, `display`, --- `opacity`, `x`, `width`, ...). Event-driven mutations to these fields (a --- release callback changing `text`, a toggle hiding a panel via `display = --- false`) must survive the per-frame Element recreation that defines immediate --- mode. Persistable captures them in `saveState` and reapplies them in --- `restoreState`, so the caller never branches on mode. --- --- This behavior is the final home for the former `Element:saveState` `_props` --- block and the former `Element:restoreState` `_props` block (~20 LOC moved out --- of Element.lua). With it in place, `Element:saveState` / `Element:restoreState` --- collapse to a pure behavior-dispatch loop and Element owns zero property- --- extraction logic — every persisted slice is owned by exactly one behavior. --- --- Attachment rule (shouldAttach): every element. Persistable attaches --- unconditionally (mirrors the pre-refactor invariant that every element's --- public scalar props were scanned). The actual snapshot is mode-gated inside --- `saveState` (immediate-mode-only, matching the legacy contract); in retained --- mode `saveState` returns nil and `restoreState` is a no-op unless a snapshot --- is explicitly passed. --- --- Registry ordering: Persistable is intentionally placed LAST in the behavior --- registry. `restoreState` applies `_props` AFTER every other behavior has --- hydrated its subsystem state, so a persisted public-prop mutation (e.g. --- `text = "mutated"`) overrides the freshly-restored TextEditor/Select state — --- preserving the legacy restore ordering (behaviors first, `_props` tail). --- --- State ownership (per the locked Behavior contract): --- * The persisted props live ON the element (they ARE the element's public --- fields). The behavior instance is stateless + immutable and shared. --- * The snapshot is returned under the `_props` key (prefixed with `_` so --- the public-prop scan itself skips it — avoiding self-recursion). - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance (mirrors Clickable / --- Themed). Element instances are created via `setmetatable({}, Element)`, so --- their metatable IS the Element class — giving access to Element._StateManager --- without threading deps through the hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ============================================================================ --- shouldAttach (class-level predicate, no element required) --- ============================================================================ - --- Every element's public scalar props are persistable, so this behavior --- attaches unconditionally. The mode gate lives inside saveState (it needs the --- runtime mode, which is only available with an element via StateManager). -local function shouldAttach() - return true -end - --- ============================================================================ --- saveState — snapshot public scalar fields (immediate-mode-only). --- ============================================================================ - --- Mirrors the former `Element:saveState` `_props` block exactly: --- * Only string keys NOT prefixed with `_` (so internal fields like --- `_renderer`, `_themeState`, `_initProps` are excluded). --- * Only scalar values (numbers, strings, booleans); tables and functions --- are excluded (children, padding, onEvent, ...). --- Returns `{ _props = {...} }` when there is at least one persistable prop and --- the element is in immediate mode; nil otherwise (retained mode no-op — --- state lives on the element directly there, so nothing to snapshot). -local function saveState(element) - local Element = ElementClass(element) - if not Element._StateManager.isImmediateMode() then - return nil - end - local props = {} - for k, v in pairs(element) do - if type(k) == "string" and k:sub(1, 1) ~= "_" and type(v) ~= "table" and type(v) ~= "function" then - props[k] = v - end - end - if next(props) then - return { _props = props } - end - return nil -end - --- ============================================================================ --- restoreState — reapply the persisted public-prop snapshot onto a fresh --- element (mode-agnostic; only fires when a `_props` slice is present). --- ============================================================================ - --- Applies persisted mutations on top of whatever the constructor + other --- behaviors already set, so event-driven changes from the previous frame --- override the declarative props of the recreated element. Runs last in the --- behavior dispatch (Persistable is the registry tail) to preserve the legacy --- restore ordering (subsystem restore first, `_props` override last). -local function restoreState(element, state) - if not state or not state._props then - return - end - for k, v in pairs(state._props) do - element[k] = v - end -end - --- ============================================================================ --- onAttach / onUpdate / onDraw / onDetach — no-ops. --- ============================================================================ - --- Persistable owns no subsystem and allocates no per-element state (the --- "state" it persists IS the element's own fields). The lifecycle is purely --- save/restore. - --- ============================================================================ --- Build the (stateless, shared, immutable) behavior instance. --- ============================================================================ - -local Persistable = Behavior.new({ - saveState = saveState, - restoreState = restoreState, - shouldAttach = shouldAttach, -}) - --- Expose the predicate at module level so callers/tests can reference it --- directly without an element instance (mirrors Behavior.shouldAttach / --- Clickable.shouldAttach). -Persistable.shouldAttach = shouldAttach - -return Persistable diff --git a/libs/flexlove/modules/behaviors/Scrollable.lua b/libs/flexlove/modules/behaviors/Scrollable.lua deleted file mode 100644 index 7c909e72..00000000 --- a/libs/flexlove/modules/behaviors/Scrollable.lua +++ /dev/null @@ -1,264 +0,0 @@ --- modules/behaviors/Scrollable.lua --- --- Concrete behavior: ScrollManager lifecycle (creation + immediate-mode --- scrollbar interaction-state restore). --- --- Scrollable owns the per-element ScrollManager instance — the subsystem that --- manages overflow detection, scrollbar geometry, scroll position, and scrollbar --- drag/hover interaction. It is the behavior-mode-unification replacement for --- the former `Element:_initScrollManager` phase (~84 LOC) of Element.new --- (behavior-mode-unification task 03 / landed as part of the task 08 capstone). --- --- Attachment rule (shouldAttach): an element owns a ScrollManager exactly when --- it declares an `overflow`, `overflowX`, or `overflowY` prop — mirroring the --- legacy `if props.overflow or props.overflowX or props.overflowY then` guard --- in `Element:_initScrollManager`. The ScrollManager is created and its --- normalized fields are exposed back onto the element (so the Renderer / --- ScrollManager delegates read `element.overflow` / `element.scrollbarWidth` --- etc.) exactly as the legacy inline phase did. --- --- Why onAttach reads `element._initProps` (not element fields): the scrollbar --- configuration props (scrollbarWidth / scrollbarColor / scrollSpeed / --- scrollbarPlacement / scrollbarBalance / invertScroll / smoothScrollEnabled / --- scrollBarStyle / scrollbarKnobOffset / hideScrollbars / scrollbarRadius / --- scrollbarPadding / scrollbarTrackColor / _scrollX / _scrollY) are listed in --- SPECIAL_PROPS and therefore NOT bound onto the element by the schema-driven --- `_applyProps` loop — they are consumed only by the ScrollManager constructor. --- The locked behavior hook signature is `(element, ...)` with no props arg, so --- the original construction props are stashed on the element as `_initProps` by --- `Element:_construct` and read back here. (`overflow` / `overflowX` / --- `overflowY` ARE bound onto the element by `_applyProps` so that --- `Element:addChild`'s scroll-container auto-size guard sees them during --- declarative-children processing in `_finalizeConstruction`, which runs BEFORE --- this onAttach; onAttach then overwrites them with the ScrollManager's --- normalized values, matching the legacy field-exposure order.) --- --- onUpdate / onDraw / saveState / restoreState are deferred to the --- behavior-driven update/draw tasks (09 / 12): the ScrollManager update, --- interaction, scrollbar drawing, and state save/restore currently stay inline --- in `Element:update` / `Element:draw` / `Element:saveState` / --- `Element:restoreState` (delegated through the ScrollManager API bound in --- `Element.init`). Those inline call sites are NOT behavioral `if` branches — --- they are unconditional 1-line delegates — so leaving them in Element does not --- regress the behavior-dispatch goals of tasks 09/12; task 09 will fold them --- into Scrollable hooks. --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (`element._scrollManager`, --- `element.overflow`, `element._scrollX`, `element._scrollbarDragging`, ...). --- * The behavior instance is stateless + immutable and shared across elements. --- * Element-class-level dependencies (`Element._ScrollManager`, --- `Element._scrollManagerDeps`, `Element._Context`, `Element._StateManager`) --- are resolved from the owning element's metatable (the Element class set by --- `Element:_construct`). - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance. --- `setmetatable({}, Element)` in `_construct` makes the instance metatable BE --- the Element class, so this yields Element._ScrollManager, --- Element._scrollManagerDeps, Element._Context, Element._StateManager without --- threading deps through the hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ---------------------------------------------------------------------------- --- shouldAttach (class-level predicate, no element required) --- ---------------------------------------------------------------------------- - --- Mirrors the legacy `if props.overflow or props.overflowX or props.overflowY` --- guard. Uses `~= nil` (rather than truthiness) so that an explicit --- `overflow = false` / `overflow = ""` does not spuriously attach — though in --- practice overflow values are always strings or unset, matching the predicate --- semantics of the other behaviors (Clickable / TextEditable / Selectable). -local function shouldAttach(props) - props = props or {} - return props.overflow ~= nil or props.overflowX ~= nil or props.overflowY ~= nil -end - --- ---------------------------------------------------------------------------- --- onAttach — create the ScrollManager + expose its fields + restore immediate- --- mode scrollbar interaction state (formerly Element:_initScrollManager). --- ---------------------------------------------------------------------------- - -local function onAttach(element) - local Element = ElementClass(element) - -- Construction props are stashed on the element by _construct (the scrollbar - -- config props are SPECIAL_PROPS and not bound as element fields). - local props = element._initProps or {} - - element._scrollManager = Element._ScrollManager.new({ - overflow = props.overflow, - overflowX = props.overflowX, - overflowY = props.overflowY, - scrollbarWidth = props.scrollbarWidth, - scrollbarColor = props.scrollbarColor, - scrollbarTrackColor = props.scrollbarTrackColor, - scrollbarRadius = props.scrollbarRadius, - scrollbarPadding = props.scrollbarPadding, - scrollSpeed = props.scrollSpeed, - invertScroll = props.invertScroll, - smoothScrollEnabled = props.smoothScrollEnabled, - scrollBarStyle = props.scrollBarStyle, - scrollbarKnobOffset = props.scrollbarKnobOffset, - hideScrollbars = props.hideScrollbars, - scrollbarPlacement = props.scrollbarPlacement, - scrollbarBalance = props.scrollbarBalance, - _scrollX = props._scrollX, - _scrollY = props._scrollY, - }, Element._scrollManagerDeps) - - -- Expose ScrollManager properties for backward compatibility (Renderer access). - local sm = element._scrollManager - element.overflow = sm.overflow - element.overflowX = sm.overflowX - element.overflowY = sm.overflowY - element.scrollbarWidth = sm.scrollbarWidth - element.scrollbarColor = sm.scrollbarColor - element.scrollbarTrackColor = sm.scrollbarTrackColor - element.scrollbarRadius = sm.scrollbarRadius - element.scrollbarPadding = sm.scrollbarPadding - element.scrollSpeed = sm.scrollSpeed - element.invertScroll = sm.invertScroll - element.scrollBarStyle = sm.scrollBarStyle - element.scrollbarKnobOffset = sm.scrollbarKnobOffset - element.hideScrollbars = sm.hideScrollbars - element.scrollbarPlacement = sm.scrollbarPlacement - element.scrollbarBalance = sm.scrollbarBalance - - -- Initialize state properties (will be synced from ScrollManager). - element._overflowX = false - element._overflowY = false - element._contentWidth = 0 - element._contentHeight = 0 - element._scrollX = 0 - element._scrollY = 0 - element._maxScrollX = 0 - element._maxScrollY = 0 - element._scrollbarHoveredVertical = false - element._scrollbarHoveredHorizontal = false - element._scrollbarDragging = false - element._hoveredScrollbar = nil - element._scrollbarDragOffset = 0 - - -- Restore scrollbar state from StateManager in immediate mode (must happen - -- before layout). Mirrors the legacy _initScrollManager restore block. - -- Mode-aware via Context.isImmediateMode (behavior-mode-unification task 11). - if Element._Context.isImmediateMode() and element._stateId and element._stateId ~= "" then - local state = Element._StateManager.getState(element._stateId) - if state and state.scrollManager then - element._scrollbarHoveredVertical = state.scrollManager._scrollbarHoveredVertical or false - element._scrollbarHoveredHorizontal = state.scrollManager._scrollbarHoveredHorizontal or false - element._scrollbarDragging = state.scrollManager._scrollbarDragging or false - element._hoveredScrollbar = state.scrollManager._hoveredScrollbar - element._scrollbarDragOffset = state.scrollManager._scrollbarDragOffset or 0 - - -- Apply to ScrollManager immediately. - sm._scrollbarHoveredVertical = element._scrollbarHoveredVertical - sm._scrollbarHoveredHorizontal = element._scrollbarHoveredHorizontal - sm._scrollbarDragging = element._scrollbarDragging - sm._hoveredScrollbar = element._hoveredScrollbar - sm._scrollbarDragOffset = element._scrollbarDragOffset - - -- Restore drag start positions for relative movement tracking. - sm._dragStartMouseX = state.scrollManager._dragStartMouseX or 0 - sm._dragStartMouseY = state.scrollManager._dragStartMouseY or 0 - sm._dragStartScrollX = state.scrollManager._dragStartScrollX or 0 - sm._dragStartScrollY = state.scrollManager._dragStartScrollY or 0 - end - end -end - --- -------------------------------------------------------------------------- --- onUpdate — scroll-position momentum + scrollbar hover/drag/press interaction --- (formerly the inline ScrollManager blocks in Element:update). --- Runs BEFORE Clickable.onUpdate in the registry so the scrollbar press flag --- is set before Clickable's EventHandler processes mouse events. --- -------------------------------------------------------------------------- - -local function onUpdate(element, dt) - local Element = ElementClass(element) - local sm = element._scrollManager - if not sm then - return - end - -- Restore scrollbar interaction state from StateManager in immediate mode - -- (no-op outside immediate mode / when no state is stored). - Element._ScrollManager.restoreImmediateState(element) - - -- Smooth-scroll / momentum interpolation. - sm:update(dt) - element:_syncScrollManagerState() - - -- Scrollbar hover / drag / press interaction. Captures the mouse here so the - -- interaction state is consistent across the rest of the frame's behaviors. - local mx, my = love.mouse.getPosition() - Element._ScrollManager.updateInteraction(element, mx, my) -end - --- -------------------------------------------------------------------------- --- onDraw — scrollbar rendering (post-children overlay). Marked --- `drawLayer = "overlay"` so Element:draw dispatches it AFTER children, so --- scrollbars paint on top of clipped child content and without parent clipping. --- -------------------------------------------------------------------------- - -local function onDraw(element, _ctx) - local overflowX = element.overflowX or element.overflow - local overflowY = element.overflowY or element.overflow - if overflowX ~= "scroll" and overflowX ~= "auto" and overflowY ~= "scroll" and overflowY ~= "auto" then - return - end - local scrollbarDims = element:_calculateScrollbarDimensions() - if not (scrollbarDims.vertical.visible or scrollbarDims.horizontal.visible) then - return - end - -- Clear any parent scissor clipping before drawing scrollbars so they render - -- fully visible (scrollbars must not be clipped by ancestor overflow). - love.graphics.setScissor() - element._renderer:drawScrollbars(element, element.x, element.y, element.width, element.height, scrollbarDims) -end - --- -------------------------------------------------------------------------- --- saveState / restoreState — ScrollManager state snapshot for immediate-mode --- recreation (formerly the inline blocks in Element:saveState/ --- Element:restoreState). Returns a table merged under the `scrollManager` key --- by Element:saveState's behavior loop, mirroring the legacy contract. --- -------------------------------------------------------------------------- - -local function saveState(element) - local sm = element._scrollManager - if not sm then - return nil - end - return { scrollManager = sm:getState() } -end - -local function restoreState(element, state) - if not state then - return - end - local sm = element._scrollManager - local smState = state.scrollManager - if sm and smState then - sm:setState(smState) - end -end - -local Scrollable = Behavior.new({ - onAttach = onAttach, - onDetach = function() end, - onUpdate = onUpdate, - onDraw = onDraw, - saveState = saveState, - restoreState = restoreState, - drawLayer = "overlay", -}) - --- Expose the predicate at module level so callers/tests can reference it --- directly without an element instance (mirrors Clickable.shouldAttach / --- Selectable.shouldAttach). -Scrollable.shouldAttach = shouldAttach - -return Scrollable diff --git a/libs/flexlove/modules/behaviors/Selectable.lua b/libs/flexlove/modules/behaviors/Selectable.lua deleted file mode 100644 index e63bb4c6..00000000 --- a/libs/flexlove/modules/behaviors/Selectable.lua +++ /dev/null @@ -1,206 +0,0 @@ --- modules/behaviors/Selectable.lua --- --- Concrete behavior: Select state-machine lifecycle for dropdown-style --- select groups. Owns the per-element Select subsystem initialization, the --- managed-frame layout sync each frame, and select save/restore across the --- immediate-mode recreation cycle. --- --- This behavior consolidates the legacy `if self._selectState` / `if --- self.selectOption` branches that previously lived inside Element.lua: --- --- * Select subsystem init (formerly Element:_initSubSystems lines ~810-825 — --- `Select.initSelectParent` / `Select.initSelectOption`). --- * Managed-frame adoption (formerly Element:_initPositioning lines ~1700- --- 1702 — `Select.adoptSelectFrame`). --- * Per-frame frame-state sync (formerly Element:update line ~2747 — --- `Select.ensureFrameState`). --- * Save/restore of select open/value/label (formerly the `select` branch of --- Element:saveState / Element:restoreState). --- --- Element retains `self._selectState` and `self.selectOption` for backward- --- compat field access; runtime state lives ON THE ELEMENT. The behavior itself --- is stateless + immutable (a single shared instance attaches to every --- selectable element). --- --- The 20 Element select-API delegate methods (openSelect, closeSelect, --- toggleSelect, isSelectOpen, getSelectValue, setSelectValue, ...) stay as --- 1-line forwarders into the Select module — the behavior owns the --- *lifecycle* (attach / update / save / restore / detach), not the API --- surface (per task 05 spec notes). --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (self._selectState, --- self.selectOption, self._selectParentElement, ...). --- * The behavior instance is stateless + immutable and shared across elements. --- * Element-class-level dependencies are resolved via `getmetatable(element)` --- (which IS the Element class set by Element._construct), so the hook --- signature stays exactly `(element, ...)` with no DI parameters. - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance. --- `setmetatable({}, Element)` in `_construct` makes the instance metatable BE --- the Element class, so this yields Element._Select, Element._Context, --- Element._StateManager, etc. without threading deps through the hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ============================================================================ --- shouldAttach (class-level predicate, no element required) --- ============================================================================ - --- Mirrors the cases that previously caused Element to initialize a Select --- subsystem. An element owns select state exactly when it declares a --- `selectParent` config (the dropdown trigger) or a `selectOption` config (an --- option inside a dropdown). Checking the props (rather than the runtime --- `_selectState`) lets shouldAttach run before onAttach initializes the --- subsystem, matching the auto-attach contract established by Clickable / --- TextEditable. -local function shouldAttach(props) - props = props or {} - return type(props.selectParent) == "table" or type(props.selectOption) == "table" -end - --- ============================================================================ --- onAttach — initialize the Select subsystem (formerly Element:_initSubSystems --- lines ~810-825) and adopt the managed frame (formerly Element:_initPositioning --- lines ~1700-1702). --- ============================================================================ - -local function onAttach(element) - local Element = ElementClass(element) - - -- Initialize the appropriate select role. Mirrors the legacy _initSubSystems - -- block exactly: selectParent → initSelectParent (sets _selectState + - -- immediate-mode restore from StateManager); selectOption → initSelectOption - -- (sets the option value/label/disabled). - if type(element.selectParent) == "table" then - Element._Select.initSelectParent(element, element.selectParent) - end - - if type(element.selectOption) == "table" then - Element._Select.initSelectOption(element, element.selectOption) - end - - -- Adopt the managed dropdown frame. This was formerly the tail of - -- _initPositioning (after the select parent's own addChild). It creates the - -- select anchor, reparents the frame under it, and syncs visibility. Moving - -- it here is safe because onAttach runs after _initPositioning: the parent's - -- own positioning is finalized, so the anchor's geometry can be computed. - if element._selectState and type(element.selectParent) == "table" and element.selectParent.selectFrame ~= nil then - Element._Select.adoptSelectFrame(element, element.selectParent.selectFrame) - end - - -- Backfill option registration for children added BEFORE this behavior - -- attached. The auto-attach pass runs at the very end of Element.new - -- (after _finalizeConstruction, which processes declarative `children`). - -- Declarative select-option children are addChild'd to this element during - -- _finalizeConstruction — at that point _selectState did not yet exist (this - -- onAttach had not run), so their registerWithSelectParent call walked the - -- parent chain, found no _selectState, and returned early. Re-scan now that - -- _selectState is initialized so these options are registered + reparented - -- into the managed frame exactly like runtime-added options. - -- (registerWithSelectParent is idempotent — it skips options already - -- registered — so this is a no-op for children added after _selectState was - -- set, e.g. the common `FlexLove.new({ parent = sp, selectOption = {...} })` - -- pattern.) - if element._selectState then - for _, child in ipairs(element.children) do - if child.selectOption then - Element._Select.registerWithSelectParent(child) - Element._Select.attachOptionToManagedFrame(child) - end - end - end -end - -local function onDetach(element) - -- Clear select-managed fields so the element can be GC'd cleanly in immediate - -- mode (formerly part of Element:_cleanup). This mirrors the select-clearing - -- block that lived in Element:_cleanup; Element:destroy separately routes - -- through Select.cleanupDestroy for full teardown (idempotent with this). - if element.selectParent then - element.selectParent.onChange = nil - end - element._selectState = nil - element._managedSelectOwner = nil - element._managedSelectFrame = nil - element._managedSelectAnchor = nil - element._managedSelectBaseOpacity = nil - element._managedSelectBaseVisibility = nil - element._managedSelectBaseDisabled = nil -end - --- ============================================================================ --- onUpdate — per-frame managed-frame layout sync (formerly Element:update --- line ~2747 — `Select.ensureFrameState`). --- ============================================================================ - -local function onUpdate(element, dt) - local Element = ElementClass(element) - Element._Select.ensureFrameState(element) -end - --- ============================================================================ --- onDraw — no-op. --- ============================================================================ - --- Select rendering is driven by the managed frame / anchor elements themselves --- (visibility synced by Select.syncManagedFrameVisibility), not by the select --- parent's draw path. The parent's own pixels are the theme/renderer's job. -local function onDraw() end - --- ============================================================================ --- saveState / restoreState — select open/value/label (formerly the `select` --- branch of Element:saveState / Element:restoreState). --- ============================================================================ - --- Returns a snapshot under the `select` key to match the legacy immediate-mode --- restoreState contract (Element:restoreState looked up state.select). The --- behavior-dispatch loop merges behavior snapshots into the top-level state --- table, so returning { select = ... } slots in identically to the old inline --- `state.select = selectState` assignment. -local function saveState(element) - local Element = ElementClass(element) - local selectState = Element._Select.saveState(element) - if selectState then - return { select = selectState } - end - return nil -end - --- Consumes the previously-saved snapshot keyed under `select`. The behavior- --- dispatch loop passes the FULL top-level state table; this hook reads only --- its own `state.select` slice, mirroring the legacy `if state.select then` --- guard in Element:restoreState. -local function restoreState(element, state) - if not state then - return - end - local Element = ElementClass(element) - if state.select then - Element._Select.restoreState(element, state.select) - end -end - --- ============================================================================ --- Build the (stateless, shared, immutable) behavior instance. --- ============================================================================ - -local Selectable = Behavior.new({ - onAttach = onAttach, - onDetach = onDetach, - onUpdate = onUpdate, - onDraw = onDraw, - saveState = saveState, - restoreState = restoreState, - shouldAttach = shouldAttach, -}) - --- Expose the predicate at module level so callers/tests can reference it --- directly without an element instance (mirrors Behavior.shouldAttach). -Selectable.shouldAttach = shouldAttach - -return Selectable diff --git a/libs/flexlove/modules/behaviors/TextEditable.lua b/libs/flexlove/modules/behaviors/TextEditable.lua deleted file mode 100644 index 7d3ee79b..00000000 --- a/libs/flexlove/modules/behaviors/TextEditable.lua +++ /dev/null @@ -1,576 +0,0 @@ --- modules/behaviors/TextEditable.lua --- --- Concrete behavior: TextEditor subsystem ownership — text editing, cursor --- management, text selection, text-related input handling, and text-editor --- state save/restore. --- --- This behavior consolidates the legacy `if self._textEditor` nil-guard --- patterns that previously lived inside Element.lua: --- --- * TextEditor creation + immediate-mode state restore (formerly --- Element:_initSubSystems lines ~813-830 — the `if self.editable then --- self._textEditor = Element._TextEditor.new {...}` block). --- * Cursor-blink update (formerly Element:update line ~2810 — --- `if self._textEditor then self._textEditor:update(self, dt) end`). --- * The 27 text-editor delegate methods (formerly Element:setText / --- getText / setCursorPosition / setSelection / focus / textinput / --- keypressed / _handleTextClick / _handleTextDrag / ...). Each was a 3-line --- nil-guard stub (check `_textEditor`, forward call, end). They are now --- module-level functions on this behavior; Element retains only 1-line --- forwarders that route through `Element._TextEditable.(self, ...)`. --- * Text-editor state save/restore (formerly the textEditor branch of --- Element:saveState / Element:restoreState), including the cursor/selection --- field sync and the text-selection drag-tracking fields --- (`_mouseDownPosition` / `_textDragOccurred`). --- --- Element retains the `self._textEditor` field for backward-compat field --- access (Renderer:drawText reads it directly for cursor/selection rendering); --- runtime state lives ON THE ELEMENT. The behavior itself is stateless + --- immutable + shared across elements. --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (`self._textEditor`, --- `self._mouseDownPosition`, `self._textDragOccurred`). The behavior --- instance is stateless + immutable and shared across all editable --- elements. --- * Element-class-level dependencies are resolved via `getmetatable(element)` --- (which IS the Element class set by Element._construct), so the hook --- signature stays exactly `(element, ...)` with no DI parameters. --- --- onDraw is a no-op: text/cursor/selection rendering stays in the Renderer's --- command buffer (Layer 4 "text"), driven by the Thamed behavior's single --- `Renderer:draw` call. The Renderer's `drawText` already reads --- `element._textEditor` for cursor/selection, so TextEditable OWNS the --- subsystem that drawText consumes, but the draw dispatch stays in the --- renderer to preserve the unified transform/scissor command-buffer ordering --- (mirrors Selectable.onDraw's no-op precedent, where rendering is owned by a --- different layer). Hoisting drawText into this behavior's onDraw would --- double-render text, since the Renderer command buffer already emits a "text" --- layer for every element. - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance (mirrors Clickable / --- Selectable). `setmetatable({}, Element)` in `_construct` makes the instance --- metatable BE the Element class, so this yields Element._TextEditor, --- Element._textEditorDeps, Element._Context, Element._StateManager, etc. --- without threading deps through the hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ============================================================================ --- shouldAttach (class-level predicate, no element required) --- ============================================================================ - --- Mirrors the spec predicate: attach when the element is text-editable OR --- carries text content. onAttach only ALLOCATES a TextEditor when --- `element.editable` is true (preserving the pre-refactor creation invariant --- "TextEditor created iff editable"), so non-editable text labels attach the --- behavior but allocate no TextEditor — their onUpdate/onDraw/saveState are --- nil-guarded no-ops, and the Element forwarders route them through the --- non-editable branch of each delegate function (reads/writes `element.text` --- directly). This keeps shouldAttach faithful to the spec while preserving --- exact pre-refactor allocation behavior. -local function shouldAttach(props) - props = props or {} - return props.editable == true or props.text ~= nil -end - --- ============================================================================ --- onAttach — create the TextEditor (formerly Element:_initSubSystems lines --- ~813-830) and restore immediate-mode TextEditor state. --- ============================================================================ - -local function onAttach(element) - local Element = ElementClass(element) - - -- Only editable elements own a TextEditor. Preserves the exact pre-refactor - -- creation guard (`if self.editable then ... end`) — non-editable text - -- elements attach the behavior (so their forwarders route through a single - -- code path) but allocate no TextEditor. - if not element.editable then - return - end - - -- Config is sourced from element fields (bound by _applyProps / _initVisualState - -- before _attachBehaviors runs at the tail of Element.new) — NOT from raw - -- props. The callbacks (onFocus/onBlur/onTextInput/onTextChange/onEnter) are - -- schema-bound element fields by this point, and `element.text` is set by - -- _initVisualState, so no `props` reference is needed here (the hook - -- signature is `(element)`). - element._textEditor = Element._TextEditor.new({ - editable = element.editable, - multiline = element.multiline, - passwordMode = element.passwordMode, - textWrap = element.textWrap, - maxLines = element.maxLines, - maxLength = element.maxLength, - placeholder = element.placeholder, - inputType = element.inputType, - textOverflow = element.textOverflow, - scrollable = element.scrollable, - autoGrow = element.autoGrow, - selectOnFocus = element.selectOnFocus, - cursorColor = element.cursorColor, - selectionColor = element.selectionColor, - cursorBlinkRate = element.cursorBlinkRate, - text = element.text or "", - onFocus = element.onFocus, - onBlur = element.onBlur, - onTextInput = element.onTextInput, - onTextChange = element.onTextChange, - onEnter = element.onEnter, - }, Element._textEditorDeps) - - -- Restore TextEditor state from StateManager in immediate mode. Mirrors the - -- legacy _initSubSystems immediate-mode restore. Safe to run here (after - -- _construct registered the element with StateManager) — the StateManager - -- lookup is sparse and returns nil for a fresh element. Mode-aware via - -- Context.isImmediateMode (behavior-mode-unification task 11). - if Element._Context.isImmediateMode() and element._stateId and element._stateId ~= "" then - local state = Element._StateManager.getState(element._stateId) - if state and state.textEditor then - element._textEditor:setState(state.textEditor, element) - end - end -end - -local function onDetach(element) - -- Clear text-input callback closures read by TextEditor / KeyboardNavigation - -- so the element's closure references can be collected in immediate mode - -- (formerly part of Element:_cleanup). The TextEditor instance itself is - -- INTENTIONALLY kept: Element:_cleanup preserves element structure for - -- inspection (released when the element is GC'd). - element.onTextInput = nil - element.onTextChange = nil - element.onEnter = nil -end - --- ============================================================================ --- onUpdate — cursor-blink animation (formerly Element:update line ~2810). --- ============================================================================ - --- Drives TextEditor:update (cursor blink + blink-pause timer). Guarded on --- `element._textEditor` because non-editable text elements attach this --- behavior (per shouldAttach) but own no TextEditor. Element:update contains --- zero text-editor references — the dispatch loop calls this hook. -local function onUpdate(element, dt) - local textEditor = element._textEditor - if textEditor then - textEditor:update(element, dt) - end -end - --- ============================================================================ --- onDraw — no-op (see file header: text rendering stays in the Renderer --- command buffer driven by the Thamed behavior's Renderer:draw call). --- ============================================================================ - -local function onDraw() end - --- ============================================================================ --- saveState / restoreState — TextEditor state + text-selection drag --- tracking (formerly the textEditor branch of Element:saveState / --- Element:restoreState, including the _mouseDownPosition / _textDragOccurred --- fields). --- ============================================================================ - --- Returns a snapshot under the `textEditor` key to match the legacy immediate- --- mode restoreState contract (Element:restoreState looked up state.textEditor). --- The behavior-dispatch loop in Element:saveState merges behavior snapshots --- into the top-level state table, so returning { textEditor = ... } slots in --- identically to the old inline `state.textEditor = self._textEditor:getState()` --- assignment. The drag-tracking fields are merged at the top level too --- (matching the legacy `state._mouseDownPosition` / `state._textDragOccurred` --- assignments) since they are text-selection state. -local function saveState(element) - local textEditor = element._textEditor - if not textEditor then - -- Non-editable text element: still persist drag-tracking fields if set - -- (they are only ever set for editable elements, but persist defensively). - local hasDragState = element._mouseDownPosition ~= nil or element._textDragOccurred ~= nil - if not hasDragState then - return nil - end - local snapshot = {} - if element._mouseDownPosition ~= nil then - snapshot._mouseDownPosition = element._mouseDownPosition - end - if element._textDragOccurred ~= nil then - snapshot._textDragOccurred = element._textDragOccurred - end - return snapshot - end - - local snapshot = { textEditor = textEditor:getState() } - if element._mouseDownPosition ~= nil then - snapshot._mouseDownPosition = element._mouseDownPosition - end - if element._textDragOccurred ~= nil then - snapshot._textDragOccurred = element._textDragOccurred - end - return snapshot -end - --- Consumes the previously-saved snapshot keyed under `textEditor` plus the --- drag-tracking fields. The behavior-dispatch loop passes the FULL top-level --- state table; this hook reads only its own slices, mirroring the legacy --- `if self._textEditor and state.textEditor then ... end` guard. -local function restoreState(element, state) - if not state then - return - end - local textEditor = element._textEditor - if textEditor and state.textEditor then - textEditor:setState(state.textEditor, element) - -- Sync TextEditor's focus/cursor/selection state to Element for theme - -- management (mirrors the legacy restoreState field sync). - element._focused = textEditor._focused - element._cursorPosition = textEditor._cursorPosition - element._selectionStart = textEditor._selectionStart - element._selectionEnd = textEditor._selectionEnd - element._textBuffer = textEditor._textBuffer - end - - -- Restore drag-tracking state for text selection (top-level keys). - if state._mouseDownPosition ~= nil then - element._mouseDownPosition = state._mouseDownPosition - end - if state._textDragOccurred ~= nil then - element._textDragOccurred = state._textDragOccurred - end -end - --- ============================================================================ --- Text-editor delegate functions. --- --- These are the module-level implementations of the 27 text-editor delegate --- methods that previously lived on Element. Each mirrors the pre-refactor --- Element method body VERBATIM (with `self` → `element`), including the --- `element._textEditor` nil-guard: the guard is required because (a) non- --- editable text elements attach this behavior (per shouldAttach) but own no --- TextEditor, and (b) Element forwards these methods BEFORE onAttach has run --- (e.g. an `onCreate` callback firing during _finalizeConstruction, which --- runs before _attachBehaviors). The nil-guards live in THIS file (not in --- Element.lua), so the Element.lua `if self._textEditor` count drops to 0. --- --- Element retains 1-line forwarders: `Element.setText = function(self, text) --- return Element._TextEditable.setText(self, text) end` (etc.), so external --- callers (EventHandler, KeyboardNavigation, game UI) keep working unchanged. --- --- The TextEditor API is mixed: most methods take the element as first arg --- (`te:method(element, ...)` — "passesSelf"); a few getters omit it --- (`te:method()`). The delegation contract is pinned by --- subsystem_delegation_test.lua, so this mapping must match TextEditor's --- method signatures exactly. --- ============================================================================ - --- --- Cursor management (passesSelf = element forwarded) ------------------ - -local function setCursorPosition(element, position) - local textEditor = element._textEditor - if textEditor then - textEditor:setCursorPosition(element, position) - end -end - -local function getCursorPosition(element) - local textEditor = element._textEditor - if textEditor then - return textEditor:getCursorPosition() - end - return 0 -end - -local function moveCursorBy(element, delta) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorBy(element, delta) - end -end - -local function moveCursorToStart(element) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorToStart(element) - end -end - -local function moveCursorToEnd(element) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorToEnd(element) - end -end - -local function moveCursorToLineStart(element) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorToLineStart(element) - end -end - -local function moveCursorToLineEnd(element) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorToLineEnd(element) - end -end - -local function moveCursorToPreviousWord(element) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorToPreviousWord(element) - end -end - -local function moveCursorToNextWord(element) - local textEditor = element._textEditor - if textEditor then - textEditor:moveCursorToNextWord(element) - end -end - --- --- Selection management ------------------------------------------------ - -local function setSelection(element, startPos, endPos) - local textEditor = element._textEditor - if textEditor then - textEditor:setSelection(element, startPos, endPos) - end -end - -local function getSelection(element) - local textEditor = element._textEditor - if textEditor then - return textEditor:getSelection() - end - return nil -end - -local function hasSelection(element) - local textEditor = element._textEditor - if textEditor ~= nil then - return textEditor:hasSelection() - end - return false -end - -local function clearSelection(element) - local textEditor = element._textEditor - if textEditor then - textEditor:clearSelection(element) - end -end - -local function selectAll(element) - local textEditor = element._textEditor - if textEditor then - textEditor:selectAll(element) - end -end - -local function getSelectedText(element) - local textEditor = element._textEditor - if textEditor then - return textEditor:getSelectedText() - end - return nil -end - -local function deleteSelection(element) - local textEditor = element._textEditor - if textEditor then - return textEditor:deleteSelection(element) - end - return false -end - --- --- Focus management ---------------------------------------------------- - -local function focus(element) - local textEditor = element._textEditor - if textEditor then - textEditor:focus(element) - end -end - -local function blur(element) - local textEditor = element._textEditor - if textEditor then - textEditor:blur(element) - end -end - -local function isFocused(element) - local textEditor = element._textEditor - if textEditor then - return textEditor:isFocused() - end - return false -end - --- --- Text buffer management (with post-delegation sync) ------------------ --- These methods sync `element.text` from the TextEditor result + drive --- auto-grow, exactly as the legacy Element methods did. - -local function getText(element) - local textEditor = element._textEditor - if textEditor then - return textEditor:getText() - end - return element.text or "" -end - -local function setText(element, text) - local textEditor = element._textEditor - if textEditor then - textEditor:setText(element, text) - element.text = textEditor:getText() -- Sync display text - textEditor:updateAutoGrowHeight(element) - return - end - element.text = text -end - -local function insertText(element, text, position) - local textEditor = element._textEditor - if textEditor then - textEditor:insertText(element, text, position) - element.text = textEditor:getText() -- Sync display text - textEditor:updateAutoGrowHeight(element) - end -end - -local function deleteText(element, startPos, endPos) - local textEditor = element._textEditor - if textEditor then - textEditor:deleteText(element, startPos, endPos) - element.text = textEditor:getText() -- Sync display text - textEditor:updateAutoGrowHeight(element) - end -end - -local function replaceText(element, startPos, endPos, newText) - local textEditor = element._textEditor - if textEditor then - textEditor:replaceText(element, startPos, endPos, newText) - element.text = textEditor:getText() -- Sync display text - textEditor:updateAutoGrowHeight(element) - end -end - --- --- Mouse text selection ------------------------------------------------ - -local function handleTextClick(element, mouseX, mouseY, clickCount) - local textEditor = element._textEditor - if textEditor then - textEditor:handleTextClick(element, mouseX, mouseY, clickCount) - -- Store mouse down position on element for drag tracking - if clickCount == 1 then - element._mouseDownPosition = textEditor:mouseToTextPosition(element, mouseX, mouseY) - end - end -end - -local function handleTextDrag(element, mouseX, mouseY) - local textEditor = element._textEditor - if textEditor then - textEditor:handleTextDrag(element, mouseX, mouseY) - element._textDragOccurred = textEditor._textDragOccurred - end -end - --- --- Keyboard input ------------------------------------------------------ - -local function textinput(element, text) - local textEditor = element._textEditor - if textEditor then - textEditor:handleTextInput(element, text) - element.text = textEditor:getText() -- Sync display text - textEditor:updateAutoGrowHeight(element) - end -end - -local function keypressed(element, key, scancode, isrepeat) - local textEditor = element._textEditor - if textEditor then - textEditor:handleKeyPress(element, key, scancode, isrepeat) - element.text = textEditor:getText() -- Sync display text - textEditor:updateAutoGrowHeight(element) - end -end - --- ============================================================================ --- Build the (stateless, shared, immutable) behavior instance + thin module --- table exposing the delegate functions (mirrors the Animated pattern). --- ============================================================================ - -local behavior = Behavior.new({ - onAttach = onAttach, - onDetach = onDetach, - onUpdate = onUpdate, - onDraw = onDraw, - saveState = saveState, - restoreState = restoreState, - shouldAttach = shouldAttach, -}) - --- Thin module table: exposes the frozen behavior instance (for the registry) --- plus the text-editor delegate functions (for Element's 1-line forwarders). --- All hooks delegate to the frozen behavior instance so dispatch sites get --- the validated, frozen implementation. shouldAttach is also exposed at module --- level (mirrors Clickable.shouldAttach) for tests/callers without an element. -local TextEditable = { - behavior = behavior, - shouldAttach = shouldAttach, - onAttach = onAttach, - onUpdate = onUpdate, - onDraw = onDraw, - saveState = saveState, - restoreState = restoreState, - -- Text-editor delegate functions (Element forwarders route through these): - setCursorPosition = setCursorPosition, - getCursorPosition = getCursorPosition, - moveCursorBy = moveCursorBy, - moveCursorToStart = moveCursorToStart, - moveCursorToEnd = moveCursorToEnd, - moveCursorToLineStart = moveCursorToLineStart, - moveCursorToLineEnd = moveCursorToLineEnd, - moveCursorToPreviousWord = moveCursorToPreviousWord, - moveCursorToNextWord = moveCursorToNextWord, - setSelection = setSelection, - getSelection = getSelection, - hasSelection = hasSelection, - clearSelection = clearSelection, - selectAll = selectAll, - getSelectedText = getSelectedText, - deleteSelection = deleteSelection, - focus = focus, - blur = blur, - isFocused = isFocused, - getText = getText, - setText = setText, - insertText = insertText, - deleteText = deleteText, - replaceText = replaceText, - _handleTextClick = handleTextClick, - _handleTextDrag = handleTextDrag, - textinput = textinput, - keypressed = keypressed, -} - --- Metatable so the module table itself satisfies the duck-typed registry --- contract (iterating `Element._behaviorRegistry` calls `behavior.shouldAttach` --- and `behavior.onAttach` / `behavior.onUpdate` directly). Falls through to the --- frozen behavior instance for every hook / isBehavior parity. -setmetatable(TextEditable, { - __index = behavior, - __tostring = function() - return "TextEditable" - end, -}) - -return TextEditable diff --git a/libs/flexlove/modules/behaviors/Themed.lua b/libs/flexlove/modules/behaviors/Themed.lua deleted file mode 100644 index bd431331..00000000 --- a/libs/flexlove/modules/behaviors/Themed.lua +++ /dev/null @@ -1,178 +0,0 @@ --- modules/behaviors/Themed.lua --- --- Concrete behavior: Renderer ownership + theme-state rendering. --- --- Themed owns the per-element Renderer instance and the single --- `Renderer:draw` call that paints the core visual layers (background, image, --- theme 9-patch, borders, text, customDraw). It is the behavior-mode-unification --- replacement for the former `_initImageAndRenderer` Renderer creation block and --- the former first `self._renderer:draw(self, backdropCanvas)` call in --- Element:draw (behavior-mode-unification task 07). --- --- Attachment rule (shouldAttach): every renderable Element. The pre-refactor --- code unconditionally created a Renderer for every Element and unconditionally --- called `Renderer:draw` in Element:draw; Themed mirrors that invariant so the --- Renderer is always available to subsystems that depend on it (TextEditor font --- / wrap delegation, ScrollManager scrollbar drawing) AND so visual rendering of --- background / border / theme / image layers is preserved for every element. --- Restricting attachment to `themeComponent`-only elements would break editable --- text fields and scrollable containers (which need a Renderer for subsystem --- delegation even when they have no theme component). The 9-patch theme-state --- rendering within `Renderer:draw` is a no-op for elements without a --- `themeComponent`, so always-attaching carries no rendering cost. --- --- Themed and Imageable are paired (both configure the same `element._renderer`): --- Themed.onAttach creates the Renderer with the theme/blur config; Imageable --- (attached for imagePath/image elements) enriches the SAME renderer instance with --- image config + deferred image loading. They share `element._renderer`. --- --- onUpdate is a no-op: theme-state transitions are DRIVEN by the Clickable --- behavior (whose onUpdate recomputes hover/press/focus and calls --- `renderer:setThemeState`). Themed only READS that state for rendering, so it has --- no per-frame update work. --- --- saveState owns the blur-region snapshot (`state.blur`): the per-frame blur --- geometry + radius/quality used by the Blur cache for invalidation (formerly --- the inline `if self.backdropBlur or self.contentBlur` block of --- Element:saveState — behavior-mode-unification task 12). restoreState is a --- no-op: blur cache data is used for invalidation, not restoration (the Blur --- cache is keyed by element id and cleared via `Blur.clearElementCache` from --- FlexLove.endFrame, not replayed through restoreState). --- --- State ownership (per the locked Behavior contract): --- * Per-element runtime state lives ON THE ELEMENT (`element._renderer`, --- `element._themeState`, `element.backdropBlur`, `element.contentBlur`). --- The behavior instance is stateless and shared. --- * `element._renderer` is recreated on attach; onDetach is a no-op — the --- reference is released when the element is GC'd (Element:_cleanup keeps --- element structure for inspection). - -local _pkg = (...):match("^(.-)behaviors%.") or "modules." -local Behavior = require(_pkg .. "Behavior") - --- Resolve the Element class from an element instance (mirrors Clickable). --- Element instances are created via `setmetatable({}, Element)`, so their --- metatable IS the Element class — giving access to Element._Renderer, --- Element._rendererDeps, etc. without threading deps through the hook signature. -local function ElementClass(element) - return getmetatable(element) -end - --- ---------------------------------------------------------------------------- --- shouldAttach (class-level predicate, no element required) --- ---------------------------------------------------------------------------- - --- Returns true for every renderable Element. See file header for the rationale: --- the pre-refactor invariant was "every Element has a Renderer; Element:draw --- always calls Renderer:draw", and Thamed is the behavior-system embodiment of --- that invariant. Returns true for `themeComponent`-bearing props (the spec's --- headline case) and for every other element so subsystems/rendering stay intact. -local function shouldAttach(props) - return true -end - --- ---------------------------------------------------------------------------- --- onAttach — create the Renderer with theme/blur config (formerly the --- Renderer.new block of Element:_initImageAndRenderer). --- ---------------------------------------------------------------------------- - -local function onAttach(element) - local Element = ElementClass(element) - - -- Create-or-reuse the Renderer. Thamed is the first render behavior in the - -- registry, so it normally creates the instance; Imageable (if attached) will - -- reuse this same instance for image config. Guarded so Imageable-onAttach- - -- first (defensive) does not clobber an existing renderer. - if element._renderer then - return - end - - -- NOTE: backgroundColor/borderColor/opacity/cornerRadius/themeComponent are - -- intentionally NOT passed here. Renderer:draw() reads them from the element - -- as the single source of truth (see Renderer.lua draw()). Only renderer-owned - -- state (theme, blur) is cached on the renderer; image config is added by the - -- Imageable behavior. border is element-sourced too. - element._renderer = Element._Renderer.new({ - theme = element.theme, - scaleCorners = element.scaleCorners, - scalingAlgorithm = element.scalingAlgorithm, - contentBlur = element.contentBlur, - backdropBlur = element.backdropBlur, - }, Element._rendererDeps) -end - --- ---------------------------------------------------------------------------- --- onDraw — the single Renderer:draw call (formerly the first call in --- Element:draw). Paints all core visual layers for this element. --- ---------------------------------------------------------------------------- - -local function onDraw(element, ctx) - local renderer = element._renderer - if not renderer then - return - end - renderer:draw(element, ctx and ctx.backdropCanvas) -end - --- ---------------------------------------------------------------------------- --- onDetach — no-op. Element:_cleanup preserves element structure for --- inspection (the original invariant), so the Renderer reference is released --- when the element is GC'd rather than torn down here. Present as an explicit --- hook so the behavior conforms to the full lifecycle contract. --- ---------------------------------------------------------------------------- - -local function onDetach() end - --- ---------------------------------------------------------------------------- --- saveState — blur-region snapshot (formerly the `blur` branch of --- Element:saveState). Returns `{ blur = {...} }` when the element configures a --- backdrop or content blur, so the Blur cache can invalidate by element id; --- nil otherwise. Mode-agnostic to match the legacy contract (the snapshot is --- only read back by the cache-invalidation path, which itself is --- immediate-mode-only via FlexLove.endFrame). --- ---------------------------------------------------------------------------- - -local function saveState(element) - if not (element.backdropBlur or element.contentBlur) then - return nil - end - local blur = { - _blurX = element.x, - _blurY = element.y, - _blurWidth = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right), - _blurHeight = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom), - } - if element.backdropBlur then - blur._backdropBlurRadius = element.backdropBlur.radius - blur._backdropBlurQuality = element.backdropBlur.quality or 5 - end - if element.contentBlur then - blur._contentBlurRadius = element.contentBlur.radius - blur._contentBlurQuality = element.contentBlur.quality or 5 - end - return { blur = blur } -end - --- restoreState — no-op: blur cache data is used for invalidation, not --- restoration (see file header). Present so the behavior conforms to the --- lifecycle contract without replaying geometry that the cache recomputes. - --- ---------------------------------------------------------------------------- --- Build the (stateless, shared, immutable) behavior instance. --- ---------------------------------------------------------------------------- - -local Themed = Behavior.new({ - onAttach = onAttach, - onDetach = onDetach, - onUpdate = function() end, - onDraw = onDraw, - saveState = saveState, - restoreState = function() end, -}) - --- Expose the predicate at module level so callers/tests can reference it --- directly without an element instance (mirrors Behavior.shouldAttach / --- Clickable.shouldAttach). -Themed.shouldAttach = shouldAttach - -return Themed diff --git a/libs/flexlove/modules/types.lua b/libs/flexlove/modules/types.lua deleted file mode 100644 index dd504797..00000000 --- a/libs/flexlove/modules/types.lua +++ /dev/null @@ -1,662 +0,0 @@ ----@class SelectOptionProps ----@field value any -- Stable option value owned by the parent select ----@field label string? -- Optional label override, falls back to the element text ----@field disabled boolean? -- Whether the option can be selected -local SelectOptionProps = {} - ----@class SelectParentProps ----@field value any -- Currently selected option value ----@field open boolean? -- Initial open state for the select container ----@field placeholder string? -- Fallback text when no option is selected ----@field selectFrame Element? -- Optional pre-instantiated dropdown container; intended to be unattached before being adopted by the select ----@field onChange fun(element:Element, value:any, option:SelectOptionProps)? -- Called when selection changes -local SelectParentProps = {} - ----@class Animation -local Animation = {} - ----@class Color -local Color = {} - ----@class Theme -local Theme = {} - ----@class ThemeManager -local ThemeManager = {} - ---=====================================-- --- For Animation.lua ---=====================================-- ----@alias EasingFunction fun(t:number): number - ----@class AnimationProps ----@field duration number -- Duration in seconds ----@field start table -- Starting values (can contain: width, height, opacity, x, y, gap, imageOpacity, backgroundColor, borderColor, textColor, padding, margin, cornerRadius, transform, etc.) ----@field final table -- Final values (same properties as start) ----@field easing string? -- Easing function name: "linear", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic", "easeInQuart", "easeOutQuart", "easeInExpo", "easeOutExpo" (default: "linear") ----@field keyframes AnimationKeyframe[]? -- Array of keyframes for complex animations ----@field onStart fun(animation:Animation, element:Element?)? -- Called when animation starts ----@field onUpdate fun(animation:Animation, element:Element?, progress:number)? -- Called each frame with progress (0-1) ----@field onComplete fun(animation:Animation, element:Element?)? -- Called when animation completes ----@field onCancel fun(animation:Animation, element:Element?)? -- Called when animation is cancelled ----@field transform TransformProps? -- Additional transform properties (legacy support) ----@field transition table? -- Transition properties (legacy support) -local AnimationProps = {} - ----@class Transform ----@field rotate number? Rotation in radians (default: 0) ----@field scaleX number? X-axis scale (default: 1) ----@field scaleY number? Y-axis scale (default: 1) ----@field translateX number? X translation in pixels (default: 0) ----@field translateY number? Y translation in pixels (default: 0) ----@field skewX number? X-axis skew in radians (default: 0) ----@field skewY number? Y-axis skew in radians (default: 0) ----@field originX number? Transform origin X (0-1, default: 0.5) ----@field originY number? Transform origin Y (0-1, default: 0.5) -local Transform = {} - ----@alias TransformProps Transform - ----@class TransitionProps ----@field duration number? ----@field easing string? ----@field delay number? ----@field onComplete fun(element:Element)? - ---=====================================-- --- For Element.lua ---=====================================-- ----@class ElementProps ----@field id string? -- Unique identifier for the element (auto-generated in immediate mode if not provided) ----@field mode "immediate"|"retained"|nil -- Lifecycle mode override: "immediate" (auto-managed state), "retained" (manual state), nil (use global mode from FlexLove.getMode(), default) ----@field parent Element? -- Parent element for hierarchical structure ----@field x number|string|CalcObject? -- X coordinate: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: 0) ----@field y number|string|CalcObject? -- Y coordinate: number (px), string ("50%", "10vh"), or CalcObject from FlexLove.calc() (default: 0) ----@field z number? -- Z-index for layering (default: 0, clamped to -999..999) ----@field tabIndex number? -- Tab navigation order: >0 (explicit order, visited first), 0 or nil (natural document order), -1 (excluded from keyboard navigation) ----@field width number|string|CalcObject? -- Width of the element: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: calculated automatically) ----@field height number|string|CalcObject? -- Height of the element: number (px), string ("50%", "10vh"), or CalcObject from FlexLove.calc() (default: calculated automatically) ----@field minWidth number|string|CalcObject? -- Minimum width constraint: number (px), string ("50%", "10vw"), or CalcObject. Clamps both fixed `width` and the flex-distributed main size when horizontal. ----@field maxWidth number|string|CalcObject? -- Maximum width constraint: number (px), string ("50%", "10vw"), or CalcObject. Clamps both fixed `width` and the flex-distributed main size when horizontal. ----@field minHeight number|string|CalcObject? -- Minimum height constraint: number (px), string ("50%", "10vh"), or CalcObject. Clamps both fixed `height` and the flex-distributed main size when vertical. ----@field maxHeight number|string|CalcObject? -- Maximum height constraint: number (px), string ("50%", "10vh"), or CalcObject. Clamps both fixed `height` and the flex-distributed main size when vertical. ----@field top number|string|CalcObject? -- Offset from top edge: number (px), string ("50%", "10vh"), or CalcObject (CSS-style positioning) ----@field right number|string|CalcObject? -- Offset from right edge: number (px), string ("50%", "10vw"), or CalcObject (CSS-style positioning) ----@field bottom number|string|CalcObject? -- Offset from bottom edge: number (px), string ("50%", "10vh"), or CalcObject (CSS-style positioning) ----@field left number|string|CalcObject? -- Offset from left edge: number (px), string ("50%", "10vw"), or CalcObject (CSS-style positioning) ----@field border Border? -- Border configuration for the element ----@field borderColor Color? -- Color of the border (default: black) ----@field opacity number? -- Element opacity 0-1 (default: 1) ----@field visibility "visible"|"hidden"? -- Element visibility (default: "visible") ----@field display boolean? -- Whether element participates in layout, rendering, and hit testing (default: true). Set false for CSS display:none behavior (zero layout space, no rendering, no hit testing). NOTE: In retained mode, toggling at runtime requires setting the parent's `_dirty = true` or calling `layoutChildren()` on the parent to trigger re-layout. ----@field backgroundColor Color? -- Background color (default: transparent) ----@field cornerRadius number|{topLeft:number?, topRight:number?, bottomLeft:number?, bottomRight:number?}? -- Corner radius: number (all corners) or table for individual corners (default: 0) ----@field gap number|string|CalcObject? -- Space between children elements: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: 0) ----@field padding number|string|CalcObject|{top:number|string|CalcObject?, right:number|string|CalcObject?, bottom:number|string|CalcObject?, left:number|string|CalcObject?, horizontal:number|string|CalcObject?, vertical:number|string|CalcObject?}? -- Padding around children: single value, string, CalcObject for all sides, or table for individual sides (default: {top=0, right=0, bottom=0, left=0}) ----@field margin number|string|CalcObject|{top:number|string|CalcObject?, right:number|string|CalcObject?, bottom:number|string|CalcObject?, left:number|string|CalcObject?, horizontal:number|string|CalcObject?, vertical:number|string|CalcObject?}? -- Margin around element: single value, string, CalcObject for all sides, or table for individual sides (default: {top=0, right=0, bottom=0, left=0}) ----@field text string? -- Text content to display (default: nil) ----@field textAlign TextAlignSpec? -- Alignment of the text content: simple string, compound string ("top-left"), or {horizontal, vertical} table (default: START) ----@field textColor Color? -- Color of the text content (default: black or theme text color) ----@field textSize number|string? -- Font size: number (px), string with units ("2vh", "10%"), or preset ("xxs"|"xs"|"sm"|"md"|"lg"|"xl"|"xxl"|"3xl"|"4xl") (default: "md" or 12px) ----@field minTextSize number? -- Minimum text size in pixels for auto-scaling ----@field maxTextSize number? -- Maximum text size in pixels for auto-scaling ----@field fontFamily string? -- Font family name from theme or path to font file (default: theme default or system default, inherits from parent) ----@field autoScaleText boolean? -- Whether text should auto-scale with window size (default: true) ----@field positioning Positioning? -- Layout positioning mode: "absolute"|"relative"|"flex"|"grid" (default: RELATIVE) ----@field flexDirection FlexDirection? -- Direction of flex layout: "horizontal"|"vertical"|"row"|"column"|"row-reverse"|"column-reverse"|"horizontal-reverse"|"vertical-reverse" (row→horizontal, column→vertical, row-reverse→horizontal-reverse, column-reverse→vertical-reverse, default: HORIZONTAL) ----@field justifyContent JustifyContent? -- Alignment of items along main axis (default: FLEX_START) ----@field alignItems AlignItems? -- Alignment of items along cross axis (default: STRETCH) ----@field alignContent AlignContent? -- Alignment of lines in multi-line flex containers (default: STRETCH) ----@field flexWrap FlexWrap? -- Whether children wrap to multiple lines: "nowrap"|"wrap"|"wrap-reverse" (default: NOWRAP) ----@field flex number|string? -- Shorthand for flexGrow, flexShrink, flexBasis: number (flex-grow only), string ("1 0 auto"), or nil (default: nil) ----@field flexGrow number? -- How much the element should grow relative to siblings (default: 0) ----@field flexShrink number? -- How much the element should shrink relative to siblings (default: 1) ----@field flexBasis number|string|CalcObject? -- Initial size before growing/shrinking: number (px), string ("50%", "10vw", "auto"), or CalcObject (default: "auto") ----@field justifySelf JustifySelf? -- Alignment of the item itself along main axis (default: AUTO) ----@field alignSelf AlignSelf? -- Alignment of the item itself along cross axis (default: AUTO) ----@field onEvent fun(element:Element, event:InputEvent)? -- Callback function for interaction events ----@field onEventDeferred boolean? -- Whether onEvent callback should be deferred until after canvases are released (default: false) ----@field onFocus fun(element:Element)? -- Callback when element receives focus ----@field onFocusDeferred boolean? -- Whether onFocus callback should be deferred (default: false) ----@field dropFocusOnSelection boolean? -- Override keyboard-navigation focus drop after Enter/Space activation (default: nil, uses KeyboardNavigation.config.dropFocusOnSelection) ----@field onBlur fun(element:Element)? -- Callback when element loses focus ----@field onBlurDeferred boolean? -- Whether onBlur callback should be deferred (default: false) ----@field onTextInput fun(element:Element, text:string)? -- Callback when text is input ----@field onTextInputDeferred boolean? -- Whether onTextInput callback should be deferred (default: false) ----@field onTextChange fun(element:Element, text:string)? -- Callback when text content changes ----@field onTextChangeDeferred boolean? -- Whether onTextChange callback should be deferred (default: false) ----@field onEnter fun(element:Element)? -- Callback when Enter key is pressed ----@field onEnterDeferred boolean? -- Whether onEnter callback should be deferred (default: false) ----@field onCreate fun(element:Element, props:table)? -- Callback when element is created, receives the element and original creation props ----@field onCreateDeferred boolean? -- Whether onCreate callback should be deferred (default: false) ----@field onTouchEvent fun(element:Element, touchEvent:InputEvent)? -- Callback for touch-specific events (touchpress, touchmove, touchrelease) ----@field onTouchEventDeferred boolean? -- Whether onTouchEvent callback should be deferred (default: false) ----@field onGesture fun(element:Element, gesture:table)? -- Callback for recognized gestures (tap, swipe, pinch, etc.) ----@field onGestureDeferred boolean? -- Whether onGesture callback should be deferred (default: false) ----@field touchEnabled boolean? -- Whether the element responds to touch events (default: true) ----@field multiTouchEnabled boolean? -- Whether the element supports multiple simultaneous touches (default: false) ----@field transform TransformProps? -- Transform properties for animations and styling ----@field transition TransitionProps? -- Transition settings for animations ----@field customDraw fun(element:Element)? -- Custom rendering callback called after standard rendering but before visual feedback (default: nil) ----@field gridRows number|table? -- Number of equal 1fr rows, or array of track specs (e.g. {"1fr","100px","auto"}) ----@field gridColumns number|table? -- Number of equal 1fr columns, or array of track specs (e.g. {"1fr","100px","auto"}) ----@field columnGap number|string|CalcObject? -- Gap between grid columns: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: 0) ----@field rowGap number|string|CalcObject? -- Gap between grid rows: number (px), string ("50%", "10vh"), or CalcObject from FlexLove.calc() (default: 0) ----@field theme string? -- Theme name to use (e.g., "space", "metal"). Defaults to theme from flexlove.init() ----@field themeComponent string? -- Theme component to use (e.g., "panel", "button", "input"). If nil, no theme is applied ----@field disabled boolean? -- Whether the element is disabled (default: false) ----@field active boolean? -- Whether the element is active/focused (for inputs, default: false) ----@field disableHighlight boolean? -- Whether to disable the pressed state highlight overlay (default: false, or true when using themeComponent) ----@field themeStateLock boolean|string? -- Lock theme state: true/"default" = lock to base state, false = normal behavior, string = specific state ("hover", "pressed", "active", "disabled") (default: false) ----@field themeComponentDisabledStates string[]? -- List of theme states to suppress visually (e.g. {"hover", "pressed"}). Interaction logic still fires. ----@field contentAutoSizingMultiplier {width:number?, height:number?}? -- Multiplier for auto-sized content dimensions (default: sourced from theme or {1, 1}) ----@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges. E.g., 2 = 2x size (overrides theme setting) ----@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-patch corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting) ----@field contentBlur {radius:number, quality:number?}? -- Blur the element's content including children (radius: pixels, quality: 1-10, default(quality): 5) ----@field backdropBlur {radius:number, quality:number?}? -- Blur content behind the element (radius: pixels, quality: 1-10, default(quality): 5) ----@field editable boolean? -- Whether the element is editable (default: false) ----@field multiline boolean? -- Whether the element supports multiple lines (default: false) ----@field textWrap boolean|"word"|"char"? -- Text wrapping mode (default: false for single-line, "word" for multi-line) ----@field maxLines number? -- Maximum number of lines (default: nil) ----@field maxLength number? -- Maximum text length in characters (default: nil) ----@field placeholder string? -- Placeholder text when empty (default: nil) ----@field passwordMode boolean? -- Whether to display text as password (default: false, disables multiline) ----@field inputType "text"|"number"|"email"|"url"? -- Input type for validation (default: "text") ----@field textOverflow "clip"|"ellipsis"|"scroll"? -- Text overflow behavior (default: "clip") ----@field scrollable boolean? -- Whether text is scrollable (default: false for single-line, true for multi-line) ----@field autoGrow boolean? -- Whether element auto-grows with text (default: false for single-line, true for multi-line) ----@field selectOnFocus boolean? -- Whether to select all text on focus (default: false) ----@field cursorColor Color? -- Cursor color (default: nil, uses textColor) ----@field selectionColor Color? -- Selection background color (default: nil, uses theme or default) ----@field cursorBlinkRate number? -- Cursor blink rate in seconds (default: 0.5) ----@field selectParent SelectParentProps? -- Parent-owned select/dropdown state and callbacks ----@field selectOption SelectOptionProps? -- Option metadata attached to a child of a select parent ----@field overflow "visible"|"hidden"|"scroll"|"auto"? -- Overflow behavior (default: "hidden") ----@field overflowX "visible"|"hidden"|"scroll"|"auto"? -- X-axis overflow (overrides overflow) ----@field overflowY "visible"|"hidden"|"scroll"|"auto"? -- Y-axis overflow (overrides overflow) ----@field scrollbarWidth number? -- Width of scrollbar track in pixels (default: 12) ----@field scrollbarColor Color? -- Scrollbar thumb color (default: Color.new(0.5, 0.5, 0.5, 0.8)) ----@field scrollbarTrackColor Color? -- Scrollbar track color (default: Color.new(0.2, 0.2, 0.2, 0.5)) ----@field scrollbarRadius number? -- Corner radius for scrollbar (default: 6) ----@field scrollbarPadding number? -- Padding between scrollbar and edge (default: 2) ----@field scrollSpeed number? -- Pixels per wheel notch (default: 20) ----@field invertScroll boolean? -- Invert mouse wheel scroll direction (default: false) ----@field smoothScrollEnabled boolean? -- Enable smooth scrolling animation for wheel events (default: false) ----@field scrollBarStyle string? -- Scrollbar style name from theme (selects from theme.scrollbars, default: uses first scrollbar or fallback rendering) ----@field scrollbarKnobOffset number|{x:number, y:number}|{horizontal:number, vertical:number}? -- Offset for scrollbar knob/handle position in pixels (number for both axes, or table for per-axis control, default: 0, adds to theme offset) ----@field scrollbarPlacement "reserve-space"|"overlay"? -- Scrollbar rendering mode: "reserve-space" (reduces content area, default) or "overlay" (renders over content) ----@field scrollbarBalance boolean? -- When true, reserve scrollbar space on both sides of content for visual balance (default: false) ----@field hideScrollbars boolean|{vertical:boolean, horizontal:boolean}? -- Hide scrollbars (boolean for both, or table for individual control, default: false) ----@field imagePath string? -- Path to image file (auto-loads via ImageCache) ----@field image love.Image? -- Image object to display ----@field objectFit "fill"|"contain"|"cover"|"scale-down"|"none"? -- Image fit mode (default: "fill") ----@field objectPosition string? -- Image position like "center center", "top left", "50% 50%" (default: "center center") ----@field imageOpacity number? -- Image opacity 0-1 (default: 1, combines with element opacity) ----@field imageRepeat "no-repeat"|"repeat"|"repeat-x"|"repeat-y"|"space"|"round"? -- Image repeat/tiling mode (default: "no-repeat") ----@field imageTint Color? -- Color to tint the image (default: nil/white, no tint) ----@field onImageLoad fun(element:Element, image:love.Image)? -- Callback when image loads successfully ----@field onImageLoadDeferred boolean? -- Whether onImageLoad callback should be deferred (default: false) ----@field onImageError fun(element:Element, error:string)? -- Callback when image fails to load ----@field onImageErrorDeferred boolean? -- Whether onImageError callback should be deferred (default: false) ----@field _scrollX number? -- Internal: scroll X position (restored in immediate mode) ----@field _scrollY number? -- Internal: scroll Y position (restored in immediate mode) ----@field children? ElementProps[] ----@field userdata table? -- User-defined data storage for custom properties ----@field ariaRole ARIA? -- ARIA role for screen readers (e.g., "button", "link", "dialog") ----@field ariaLabel string? -- Accessible name for screen readers (overrides text content) ----@field ariaDescribedBy string? -- ID of element that describes this element ----@field ariaExpanded boolean? -- Whether element is expanded/collapsed (for containers) ----@field ariaPressed boolean? -- Whether element is pressed (for toggle buttons) ----@field ariaChecked boolean? -- Whether element is checked (for checkboxes/radios) ----@field ariaDisabled boolean? -- Whether element is disabled (overrides disabled property) ----@field ariaBusy boolean? -- Whether element is processing (for live regions) ----@field ariaLive "off"|"polite"|"assertive"? -- Live region priority for announcements -local ElementProps = {} - ----@class Border ----@field top boolean|number -- true sets width to 1px, number sets width to specified pixels (default: 0) ----@field right boolean|number -- true sets width to 1px, number sets width to specified pixels (default: 0) ----@field bottom boolean|number -- true sets width to 1px, number sets width to specified pixels (default: 0) ----@field left boolean|number -- true sets width to 1px, number sets width to specified pixels (default: 0) -local Border = {} - ---=====================================-- --- For KeyboardNavigation.lua ---=====================================-- ----@class KeyboardNavigationKeyConfig ----@field next string -- Key used to move to the next focusable element ----@field previous string -- Key used to move to the previous focusable element ----@field up string -- Key used for directional navigation upward ----@field down string -- Key used for directional navigation downward ----@field left string -- Key used for directional navigation leftward ----@field right string -- Key used for directional navigation rightward ----@field activate string[] -- Keys that activate the currently focused element ----@field dismiss string -- Key used to dismiss or clear the currently focused element ----@field toggleDebug string -- Key used to toggle keyboard-navigation debug tooling ----@field inspect string -- Key used to inspect the currently focused element in developer tools -local KeyboardNavigationKeyConfig = {} - ----@class KeyboardNavigationDeveloperToolsConfig ----@field enabled boolean? -- Enable keyboard-navigation developer tools (default: true) ----@field showProperties boolean? -- Show focused element properties in developer tools (default: true) ----@field highlightColor number[]? -- RGBA color used for keyboard-navigation debug highlighting (default: {1, 0.8, 0, 0.5}) -local KeyboardNavigationDeveloperToolsConfig = {} - ----@class KeyboardNavigationFocusIndicatorConfig ----@field enabled boolean? -- Enable the keyboard focus indicator (default: true) ----@field color number[]? -- RGBA color of the focus indicator (default: {0.2, 0.6, 1.0, 0.8}) ----@field lineWidth number? -- Focus indicator stroke width in pixels (default: 2) ----@field inset number? -- Offset from the element bounds in pixels (default: -3) ----@field borderRadius number? -- Focus indicator border radius in pixels (default: 4) ----@field animationDuration number? -- Focus indicator entrance animation duration in seconds (default: 0.15) ----@field pulseEnabled boolean? -- Enable pulse animation for the focus indicator when supported ----@field pulseDuration number? -- Seconds per pulse cycle ----@field pulseScaleMin number? -- Minimum scale during pulse animation ----@field pulseScaleMax number? -- Maximum scale during pulse animation ----@field draw fun(element:Element, bounds:table, style:KeyboardNavigationFocusIndicatorConfig)? -- Custom focus indicator renderer -local KeyboardNavigationFocusIndicatorConfig = {} - ----@class KeyboardNavigationConfig ----@field enabled boolean? -- Enable or disable keyboard navigation globally (default: true) ----@field debugMode boolean? -- Enable keyboard-navigation debug logging (default: false) ----@field keys KeyboardNavigationKeyConfig? -- Key bindings used by keyboard navigation ----@field wrapAround boolean? -- Allow wrapping from last to first focusable element (default: true) ----@field directionalNavigation boolean? -- Enable arrow-key directional navigation (default: true) ----@field focusVisible boolean? -- Show the focus indicator for keyboard-driven focus (default: true) ----@field autofocusOnCreate boolean? -- Auto-focus the first focusable element on creation (default: false) ----@field dropFocusOnSelection boolean? -- Drop focus after Enter/Space activates an element (default: true) ----@field developerTools KeyboardNavigationDeveloperToolsConfig? -- Developer tool settings for keyboard navigation ----@field focusIndicator KeyboardNavigationFocusIndicatorConfig? -- Focus indicator style configuration -local KeyboardNavigationConfig = {} - ---=====================================-- --- For FlexLove.init() ---=====================================-- ----@class FlexLoveConfig ----@field baseScale {width:number?, height:number?}? -- Base resolution for responsive scaling (default: nil, no scaling) ----@field theme string|ThemeDefinition? -- Theme name (string) or ThemeDefinition to use (default: nil, no theme) ----@field immediateMode boolean? -- Enable immediate mode (React-like, recreates UI each frame) vs retained mode (default: false) ----@field autoFrameManagement boolean? -- Automatically call beginFrame/endFrame (default: false) ----@field stateRetentionFrames number? -- Number of frames to retain unused state in immediate mode (default: 60) ----@field maxStateEntries number? -- Maximum number of state entries before forcing cleanup (default: 1000) ----@field includeStackTrace boolean? -- Include stack traces in error messages (default: true) ----@field reportingLogLevel LOG_LEVEL? -- Error log level: 1: critical, 2: error, 3: warn, 4: info, 5: debug/all (default: 3:warn) ----@field errorLogTarget string? -- Error log target: "console", "file", "both" (default: "console") ----@field errorLogFile string? -- Path to error log file (default: "flexlove_errors.log") ----@field errorLogMaxSize number? -- Maximum error log file size in bytes (default: 1048576, 1MB) ----@field maxErrorLogFiles number? -- Maximum number of rotated error log files (default: 5) ----@field errorLogRotateEnabled boolean? -- Enable error log rotation (default: true) ----@field performanceMonitoring boolean? -- Enable performance monitoring (default: true) ----@field performanceHudKey string? -- Key to toggle performance HUD (default: "f3") ----@field performanceHudPosition {x:number, y:number}? -- Position of performance HUD (default: {x=10, y=10}) ----@field performanceWarningThreshold number? -- Frame time warning threshold in ms (default: 13.0) ----@field performanceCriticalThreshold number? -- Frame time critical threshold in ms (default: 16.67) ----@field performanceLogToConsole boolean? -- Log performance metrics to console (default: false) ----@field performanceWarnings boolean? -- Enable performance warnings (default: false) ----@field memoryProfiling boolean? -- Enable memory profiling (default: false, auto-enabled in immediate mode) ----@field gcStrategy string? -- Garbage collection strategy: "auto", "periodic", "manual", "disabled" (default: "auto") ----@field gcMemoryThreshold number? -- Memory threshold in MB before forcing GC (default: 100) ----@field gcInterval number? -- Frames between GC steps in periodic mode (default: 60) ----@field gcStepSize number? -- Work units per GC step, higher = more aggressive (default: 200) ----@field immediateModeBlurOptimizations boolean? -- Cache blur canvases in immediate mode to avoid re-rendering each frame (default: true) ----@field keyboardNavigation boolean|KeyboardNavigationConfig? -- Enable keyboard navigation with defaults (`true`) or provide configuration overrides ----@field debugDraw boolean? -- Enable debug draw overlay showing element boundaries with random colors (default: false) ----@field debugDrawKey string? -- Key to toggle debug draw overlay at runtime (default: nil, no toggle key) -local FlexLoveConfig = {} - ---=====================================-- --- Public FlexLove API ---=====================================-- ----@alias TextAlignCompound "top-left" | "top-center" | "top-right" | "center-left" | "center-center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right" ----@alias TextAlignSpec TextAlign | TextAlignCompound | {horizontal: TextAlign, vertical: TextAlignVertical} - ----@class FlexLoveEnums ----@field TextAlign TextAlign ----@field TextAlignVertical TextAlignVertical ----@field Positioning Positioning ----@field FlexDirection FlexDirection ----@field JustifyContent JustifyContent ----@field JustifySelf JustifySelf ----@field AlignItems AlignItems ----@field AlignSelf AlignSelf ----@field AlignContent AlignContent ----@field FlexWrap FlexWrap ----@field TextSize TextSize ----@field ImageRepeat ImageRepeat ----@field ARIA ARIA -local FlexLoveEnums = {} - ----@class AnimationKeyframe ----@field at number -- Normalized time position (0-1) ----@field values table -- Property values at this keyframe ----@field easing string|EasingFunction? -- Easing used between this and the next keyframe -local AnimationKeyframe = {} - ----@class AnimationGroupProps ----@field animations Animation[] -- Animations to coordinate ----@field mode "parallel"|"sequence"|"stagger"? -- Group playback mode (default: "parallel") ----@field stagger number? -- Delay between staggered animations in seconds (default: 0.1) ----@field onComplete fun(group:AnimationGroup)? -- Called when all animations complete ----@field onStart fun(group:AnimationGroup)? -- Called when the group starts -local AnimationGroupProps = {} - ----@class AnimationGroup ----@field animations Animation[] ----@field mode "parallel"|"sequence"|"stagger" ----@field stagger number ----@field onComplete fun(group:AnimationGroup)? ----@field onStart fun(group:AnimationGroup)? -local AnimationGroup = {} - ----@class Animation ----@field duration number ----@field start table ----@field final table ----@field elapsed number ----@field easing EasingFunction ----@field keyframes AnimationKeyframe[]? ----@field transform TransformProps? ----@field transition TransitionProps? ----@field onStart fun(animation:Animation, element:Element?)? ----@field onUpdate fun(animation:Animation, element:Element?, progress:number)? ----@field onComplete fun(animation:Animation, element:Element?)? ----@field onCancel fun(animation:Animation, element:Element?)? ----@field update fun(self:Animation, dt:number, element:table?): boolean ----@field findKeyframes fun(self:Animation, progress:number): AnimationKeyframe?, AnimationKeyframe? ----@field lerpKeyframes fun(self:Animation, prevFrame:AnimationKeyframe, nextFrame:AnimationKeyframe, easedT:number): table ----@field interpolate fun(self:Animation): table ----@field apply fun(self:Animation, element:table) ----@field pause fun(self:Animation) ----@field resume fun(self:Animation) ----@field isPaused fun(self:Animation): boolean ----@field reverse fun(self:Animation) ----@field isReversed fun(self:Animation): boolean ----@field setSpeed fun(self:Animation, speed:number) ----@field getSpeed fun(self:Animation): number ----@field seek fun(self:Animation, time:number) ----@field getState fun(self:Animation): string ----@field cancel fun(self:Animation, element:table?) ----@field reset fun(self:Animation) ----@field getProgress fun(self:Animation): number ----@field chain fun(self:Animation, nextAnimation:Animation|function): Animation ----@field delay fun(self:Animation, seconds:number): Animation ----@field repeatCount fun(self:Animation, count:number): Animation ----@field yoyo fun(self:Animation, enabled:boolean?): Animation ----@class AnimationModule ----@field Easing table -- Built-in easing functions and easing factories ----@field Transform table? -- Animation transform helpers exposed by the animation module ----@field Group AnimationGroup -- Animation group class table ----@field new fun(props:AnimationProps): Animation ----@field fade fun(duration:number, fromOpacity:number, toOpacity:number, easing:string?): Animation ----@field scale fun(duration:number, fromScale:{width:number, height:number}, toScale:{width:number, height:number}, easing:string?): Animation ----@field keyframes fun(props:{duration:number, keyframes:AnimationKeyframe[], onStart:function?, onUpdate:function?, onComplete:function?, onCancel:function?}): Animation ----@field chainSequence fun(animations:Animation[]): Animation -local AnimationModule = {} - ----@class ColorInputTable ----@field [1] number? ----@field [2] number? ----@field [3] number? ----@field [4] number? ----@field r number? ----@field g number? ----@field b number? ----@field a number? -local ColorInputTable = {} - ----@alias ColorInput string|Color|ColorInputTable - ----@class ColorModule ----@field new fun(r:number?, g:number?, b:number?, a:number?): Color ----@field fromHex fun(hexWithTag:string): Color ----@field validateColorChannel fun(value:any, max:number?): boolean, number? ----@field validateHexColor fun(hex:string): boolean, string? ----@field validateRGBColor fun(r:number, g:number, b:number, a:number?, max:number?): boolean, string? ----@field isValidColorFormat fun(value:any): string? ----@field sanitizeColor fun(value:any, default:Color?): Color ----@field parse fun(value:any): Color ----@field lerp fun(colorA:Color, colorB:Color, t:number): Color -local ColorModule = {} - ----@class ThemeManagerConfig ----@field theme string? -- Theme name override ----@field themeComponent string? -- Component name to resolve from the theme ----@field disabled boolean? -- Force disabled theme state ----@field active boolean? -- Force active theme state ----@field disableHighlight boolean? -- Disable pressed highlight overlay ----@field themeStateLock boolean|string? -- Lock the theme state to base/default or a named state ----@field themeComponentDisabledStates string[]? -- List of theme states to suppress visually ----@field scaleCorners number? -- Scale multiplier for 9-patch corners and edges ----@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for non-stretched theme regions -local ThemeManagerConfig = {} - ----@class ThemeRegion ----@field x number ----@field y number ----@field w number ----@field h number -local ThemeRegion = {} - ----@class ThemeComponent ----@field atlas string|love.Image? ----@field insets {left:number, top:number, right:number, bottom:number}? ----@field regions {topLeft:ThemeRegion, topCenter:ThemeRegion, topRight:ThemeRegion, middleLeft:ThemeRegion, middleCenter:ThemeRegion, middleRight:ThemeRegion, bottomLeft:ThemeRegion, bottomCenter:ThemeRegion, bottomRight:ThemeRegion}? ----@field stretch {horizontal:table, vertical:table}? ----@field states table? ----@field contentAutoSizingMultiplier {width:number?, height:number?}? ----@field scaleCorners number? ----@field scalingAlgorithm "nearest"|"bilinear"? ----@field knobOffset number|{x:number, y:number}|{horizontal:number, vertical:number}? -local ThemeComponent = {} - ----@class ThemeDefinition ----@field name string ----@field atlas string|love.Image? ----@field components table ----@field scrollbars table? ----@field colors table? ----@field fonts table? ----@field contentAutoSizingMultiplier {width:number?, height:number?}? -local ThemeDefinition = {} - ----@class Theme ----@field name string ----@field atlas love.Image? ----@field atlasData love.ImageData? ----@field components table ----@field scrollbars table ----@field colors table ----@field fonts table ----@field contentAutoSizingMultiplier {width:number?, height:number?}? ----@class ThemeManager ----@field theme string? ----@field themeComponent string? ----@field disabled boolean ----@field active boolean ----@field disableHighlight boolean? ----@field themeStateLock boolean|string? ----@field themeComponentDisabledStates table ----@field scaleCorners number? ----@field scalingAlgorithm "nearest"|"bilinear"? ----@field updateState fun(self:ThemeManager, isHovered:boolean, isPressed:boolean, isFocused:boolean, isDisabled:boolean): string ----@field getState fun(self:ThemeManager): string ----@field setState fun(self:ThemeManager, state:string) ----@field hasThemeComponent fun(self:ThemeManager): boolean ----@field getTheme fun(self:ThemeManager): Theme? ----@field getComponent fun(self:ThemeManager): ThemeComponent? ----@field getStateComponent fun(self:ThemeManager): ThemeComponent? ----@field getScrollbarComponent fun(self:ThemeManager, scrollbarName:string?): ThemeComponent? ----@field getStyle fun(self:ThemeManager, property:string): any? ----@field _getScaledContentPaddingForState fun(self:ThemeManager, state:string, borderBoxWidth:number, borderBoxHeight:number): table? ----@field getScaledContentPaddingForState fun(self:ThemeManager, state:string, borderBoxWidth:number, borderBoxHeight:number): table? -- deprecated, use getScaledContentPadding ----@field getScaledContentPadding fun(self:ThemeManager, borderBoxWidth:number, borderBoxHeight:number): table? ----@field getContentAutoSizingMultiplier fun(self:ThemeManager): table? ----@field getDefaultFontFamily fun(self:ThemeManager): string? ----@field setTheme fun(self:ThemeManager, themeName:string?, componentName:string?) ----@field validateThemeStateLock fun(self:ThemeManager): boolean ----@class Color ----@field r number ----@field g number ----@field b number ----@field a number ----@field toRGBA fun(self:Color): number, number, number, number ----@class ThemeModule ----@field Manager ThemeManager -- Theme manager class table ----@field new fun(definition:ThemeDefinition): Theme ----@field load fun(path:string): Theme? ----@field setActive fun(themeOrName:string|Theme) ----@field getActive fun(): Theme? ----@field getComponent fun(componentName:string, state:string?): ThemeComponent? ----@field getDefaultScrollbar fun(): ThemeComponent? ----@field getScrollbar fun(scrollbarName:string, state:string?): ThemeComponent? ----@field getFont fun(fontName:string): string? ----@field getColor fun(colorName:string): Color? ----@field hasActive fun(): boolean ----@field getRegisteredThemes fun(): table ----@field getColorNames fun(): string[] ----@field getAllColors fun(): table ----@field getColorOrDefault fun(colorName:string, fallback:Color): Color ----@field get fun(themeName:string): Theme? ----@field validateTheme fun(theme:table?, options:table?): boolean, table ----@field sanitizeTheme fun(theme:table?): table -local ThemeModule = {} - ----@class FlexLove ----@field _VERSION string ----@field _DESCRIPTION string ----@field _URL string ----@field _LICENSE string ----@field Animation AnimationModule? ----@field Color ColorModule ----@field Theme ThemeModule? ----@field enums FlexLoveEnums ----@field isReady fun(): boolean ----@field init fun(config:FlexLoveConfig?) ----@field setKeyboardNavigationDebug fun(enabled:boolean) ----@field enableKeyboardNavigation fun(config:KeyboardNavigationConfig?) ----@field deferCallback fun(callback:function) ----@field executeDeferredCallbacks fun() ----@field resize fun() ----@field setMode fun(mode:"immediate"|"retained") ----@field getMode fun(): "immediate"|"retained" ----@field beginFrame fun() ----@field endFrame fun() ----@field draw fun(gameDrawFunc:function|nil, postDrawFunc:function|nil) ----@field getElementAtPosition fun(x:number, y:number): Element? ----@field update fun(dt:number) ----@field collectGarbage fun(mode:string?, stepSize:number?): number? ----@field setGCStrategy fun(strategy:"auto"|"periodic"|"manual"|"disabled") ----@field getGCStats fun(): GCStats ----@field textinput fun(text:string) ----@field keypressed fun(key:string, scancode:string, isrepeat:boolean) ----@field wheelmoved fun(dx:number, dy:number) ----@field touchpressed fun(id:lightuserdata, x:number, y:number, dx:number, dy:number, pressure:number) ----@field touchmoved fun(id:lightuserdata, x:number, y:number, dx:number, dy:number, pressure:number) ----@field touchreleased fun(id:lightuserdata, x:number, y:number, dx:number, dy:number, pressure:number) ----@field getActiveTouchCount fun(): number ----@field getTouchOwner fun(touchId:string): Element? ----@field getById fun(id:string): Element? ----@field destroy fun() ----@field new fun(props:ElementProps, callback:function?): Element? ----@field getStateCount fun(): number ----@field clearState fun(id:string) ----@field clearAllStates fun() ----@field getStateStats fun(): table ----@field calc fun(expr:string): CalcObject ----@field getFocusedElement fun(): Element? ----@field setFocusedElement fun(element:Element?) ----@field clearFocus fun() ----@field setDebugDraw fun(enabled:boolean) ----@field getDebugDraw fun(): boolean -local FlexLove = {} - ---=====================================-- --- For State Persistence ---=====================================-- ----@class ElementStateData ----@field _focused boolean? ----@field eventHandler table? -- EventHandler state ----@field textEditor table? -- TextEditor state ----@field scrollManager table? -- ScrollManager state ----@field blur BlurCacheData? -- Blur cache invalidation data - ----@class BlurCacheData ----@field _blurX number ----@field _blurY number ----@field _blurWidth number ----@field _blurHeight number ----@field _backdropBlurRadius number? ----@field _backdropBlurQuality number? ----@field _contentBlurRadius number? ----@field _contentBlurQuality number? - ---=====================================-- --- For Calc.lua ---=====================================-- ----@class CalcDependencies ----@field ErrorHandler ErrorHandler? -- Error handler module - ----@class CalcToken ----@field type string -- Token type: "NUMBER", "UNIT", "PLUS", "MINUS", "MULTIPLY", "DIVIDE", "LPAREN", "RPAREN", "EOF" ----@field value number? -- Numeric value (for NUMBER tokens) ----@field unit string? -- Unit type: "px", "%", "vw", "vh" (for NUMBER tokens) - ----@class CalcASTNode ----@field type string -- Node type: "number", "add", "subtract", "multiply", "divide" ----@field value number? -- Numeric value (for "number" nodes) ----@field unit string? -- Unit type (for "number" nodes) ----@field left CalcASTNode? -- Left operand (for operator nodes) ----@field right CalcASTNode? -- Right operand (for operator nodes) - ----@class CalcObject ----@field _isCalc boolean -- Marker to identify calc objects (always true) ----@field _expr string -- Original expression string ----@field _ast CalcASTNode? -- Parsed abstract syntax tree (nil if parsing failed) ----@field _error string? -- Error message if parsing failed - ---=====================================-- --- For FlexLove.lua Internals ---=====================================-- ----@class GCConfig ----@field strategy string -- "auto", "periodic", "manual", or "disabled" ----@field memoryThreshold number -- MB before forcing GC ----@field interval number -- Frames between GC steps (for periodic mode) ----@field stepSize number -- Work units per GC step (higher = more aggressive) - ----@class GCState ----@field framesSinceLastGC number -- Frames elapsed since last GC ----@field lastMemory number -- Last recorded memory usage in MB ----@field gcCount number -- Total number of GC operations performed - ----@class GCStats ----@field gcCount number -- Total number of GC operations performed ----@field framesSinceLastGC number -- Frames elapsed since last GC ----@field currentMemoryMB number -- Current memory usage in MB ----@field strategy string -- Current GC strategy ----@field threshold number -- Memory threshold in MB - ----@class FlexLoveDependencies ----@field Context table -- Context module ----@field Theme Theme? -- Theme module ----@field Color Color -- Color module ----@field Calc Calc -- Calc module ----@field Units table -- Units module ----@field Blur table? -- Blur module ----@field ImageRenderer table? -- ImageRenderer module ----@field ImageScaler table? -- ImageScaler module ----@field NinePatch table? -- NinePatch module ----@field RoundedRect table -- RoundedRect module ----@field ImageCache table? -- ImageCache module ----@field utils table -- Utils module ----@field Grid table -- Grid module ----@field InputEvent table -- InputEvent module ----@field GestureRecognizer table? -- GestureRecognizer module ----@field StateManager StateManager -- StateManager module ----@field TextEditor table -- TextEditor module ----@field LayoutEngine LayoutEngine -- LayoutEngine module ----@field Renderer table -- Renderer module ----@field EventHandler EventHandler -- EventHandler module ----@field ScrollManager table -- ScrollManager module ----@field ErrorHandler ErrorHandler -- ErrorHandler module ----@field Performance Performance? -- Performance module ----@field Transform table? -- Transform module diff --git a/libs/flexlove/modules/utils.lua b/libs/flexlove/modules/utils.lua deleted file mode 100644 index 71a074c0..00000000 --- a/libs/flexlove/modules/utils.lua +++ /dev/null @@ -1,319 +0,0 @@ -local modulePath = (...):match("(.-)[^%.]+$") -local function req(name) - return require(modulePath .. name) -end - --- Focused sub-modules (utils now re-exports their surfaces as backward-compatible --- aliases so call sites needn't change). Loaded eagerly so the aliases resolve. -local NumberValidation = req("NumberValidation") -local TextSanitizer = req("TextSanitizer") -local PathValidator = req("PathValidator") -local FontCache = req("FontCache") -local Enums = req("Enums") - --- ErrorHandler is injected via init() (safeLoadImage closes over this upvalue). -local ErrorHandler = nil - -local enums = Enums.enums - --- Generic math, table, and path helpers (utils' own concern). --- All validation, font-cache, text-sanitization, and path-validation logic --- lives in the focused sub-modules above and is re-exported below. - ---- Get current keyboard modifiers state ----@return {shift:boolean, ctrl:boolean, alt:boolean, super:boolean} -local function getModifiers() - return { - shift = love.keyboard.isDown("lshift", "rshift"), - ctrl = love.keyboard.isDown("lctrl", "rctrl"), - alt = love.keyboard.isDown("lalt", "ralt"), - ---@diagnostic disable-next-line - super = love.keyboard.isDown("lgui", "rgui"), -- cmd/windows key - } -end - -local TEXT_SIZE_PRESETS = { - ["2xs"] = 0.75, - xxs = 0.75, - xs = 1.25, - sm = 1.75, - md = 2.25, - lg = 2.75, - xl = 3.5, - xxl = 4.5, - ["2xl"] = 4.5, - ["3xl"] = 5.0, - ["4xl"] = 7.0, -} - ---- Resolve text size preset to viewport units ----@param sizeValue string|number ----@return number?, string? -local function resolveTextSizePreset(sizeValue) - if type(sizeValue) == "string" then - local preset = TEXT_SIZE_PRESETS[sizeValue] - if preset then - return preset, "vh" - end - end - return nil, nil -end - ---- Auto-detect the base path where FlexLove is located ----@return string filesystemPath -local function getFlexLoveBasePath() - local info = debug.getinfo(1, "S") - if info and info.source then - local source = info.source - if source:sub(1, 1) == "@" then - source = source:sub(2) - end - - local filesystemPath = source:match("(.*/)") - if filesystemPath then - local fsPath = filesystemPath - fsPath = fsPath:gsub("^%./", "") - fsPath = fsPath:gsub("/$", "") - fsPath = fsPath:gsub("/modules$", "") - return fsPath - end - end - return "libs" -end - -local FLEXLOVE_FILESYSTEM_PATH = getFlexLoveBasePath() - ---- Helper function to resolve paths relative to FlexLove ----@param path string ----@return string -local function resolveImagePath(path) - if path:match("^/") or path:match("^[A-Z]:") then - return path - end - return FLEXLOVE_FILESYSTEM_PATH .. "/" .. path -end - --- Math utilities - ---- Clamp a value between optional min/max bounds. Either bound may be nil. ---- When both bounds are inverted (min > max), max wins (matches CSS behavior). ----@param value number Value to clamp ----@param min number|nil Minimum value (nil = no lower bound) ----@param max number|nil Maximum value (nil = no upper bound) ----@return number Clamped value -local function clamp(value, min, max) - if min and value < min then - value = min - end - if max and value > max then - value = max - end - return value -end - ---- Linear interpolation between two values ----@param a number Start value ----@param b number End value ----@param t number Interpolation factor (0-1) ----@return number Interpolated value -local function lerp(a, b, t) - return a + (b - a) * t -end - ---- Round a number to the nearest integer ----@param value number Value to round ----@return number Rounded value -local function round(value) - return math.floor(value + 0.5) -end - --- Image utilities - ---- Safely load an image with error handling ---- Returns both Image and ImageData to avoid deprecated getData() API ----@param imagePath string Path to image file ----@return love.Image?, love.ImageData?, string? Returns image, imageData, or nil with error message -local function safeLoadImage(imagePath) - local success, imageData = pcall(function() - return love.image.newImageData(imagePath) - end) - - if not success then - local errorMsg = string.format("Failed to load image data: %s - %s", imagePath, tostring(imageData)) - if ErrorHandler then - ErrorHandler:warn("utils", "RES_004", { - resourceType = "image data", - path = imagePath, - error = tostring(imageData), - }) - end - return nil, nil, errorMsg - end - - local imageSuccess, image = pcall(function() - return love.graphics.newImage(imageData) - end) - - if imageSuccess then - return image, imageData, nil - else - local errorMsg = string.format("Failed to create image: %s - %s", imagePath, tostring(image)) - if ErrorHandler then - ErrorHandler:warn("utils", "RES_004", { - resourceType = "image", - path = imagePath, - error = tostring(image), - }) - end - return nil, nil, errorMsg - end -end - --- Color manipulation utilities - ---- Brighten a color by a factor ----@param r number Red component (0-1) ----@param g number Green component (0-1) ----@param b number Blue component (0-1) ----@param a number Alpha component (0-1) ----@param factor number Brightness factor (e.g., 1.2 for 20% brighter) ----@return number, number, number, number Brightened color components -local function brightenColor(r, g, b, a, factor) - return math.min(1, r * factor), math.min(1, g * factor), math.min(1, b * factor), a -end - --- Property normalization utilities - ---- Normalize a boolean or table property with vertical/horizontal fields ----@param value boolean|table|nil Input value (boolean applies to both, table for individual control) ----@param defaultValue boolean Default value if nil (default: false) ----@return table Normalized table with vertical and horizontal fields -local function normalizeBooleanTable(value, defaultValue) - defaultValue = defaultValue or false - - if value == nil then - return { vertical = defaultValue, horizontal = defaultValue } - end - - if type(value) == "boolean" then - return { vertical = value, horizontal = value } - end - - if type(value) == "table" then - return { - vertical = value.vertical ~= nil and value.vertical or defaultValue, - horizontal = value.horizontal ~= nil and value.horizontal or defaultValue, - } - end - - return { vertical = defaultValue, horizontal = defaultValue } -end - ---- Normalize an offset value to {x, y} or {horizontal, vertical} format ----@param value number|table|nil Input value (number applies to both, table for individual control) ----@param defaultValue number Default value if nil (default: 0) ----@return table Normalized table with x/y or horizontal/vertical fields -local function normalizeOffsetTable(value, defaultValue) - defaultValue = defaultValue or 0 - - if value == nil then - return { x = defaultValue, y = defaultValue, horizontal = defaultValue, vertical = defaultValue } - end - - if type(value) == "number" then - return { x = value, y = value, horizontal = value, vertical = value } - end - - if type(value) == "table" then - -- Support both {x, y} and {horizontal, vertical} formats - local x = value.x or value.horizontal or defaultValue - local y = value.y or value.vertical or defaultValue - return { - x = x, - y = y, - horizontal = x, - vertical = y, - } - end - - return { x = defaultValue, y = defaultValue, horizontal = defaultValue, vertical = defaultValue } -end - ---- Apply content auto-sizing multiplier to a dimension ----@param value number The dimension value ----@param multiplier table? The contentAutoSizingMultiplier table {width:number?, height:number?} ----@param axis "width"|"height" Which axis to apply ----@return number The multiplied value -local function applyContentMultiplier(value, multiplier, axis) - if multiplier and multiplier[axis] then - return value * multiplier[axis] - end - return value -end - ---- Initialize dependencies ----@param deps table Dependencies: { ErrorHandler = ErrorHandler } -local function init(deps) - if type(deps) == "table" then - ErrorHandler = deps.ErrorHandler - end - -- Propagate shared ErrorHandler to focused sub-modules that need it. - NumberValidation.init({ ErrorHandler = ErrorHandler, clamp = clamp }) - TextSanitizer.init({ ErrorHandler = ErrorHandler }) - FontCache.init({ ErrorHandler = ErrorHandler, resolveImagePath = resolveImagePath }) - -- PathValidator has no external dependencies. -end - -return { - enums = enums, - FONT_CACHE = FontCache.FONT_CACHE, - resolveTextSizePreset = resolveTextSizePreset, - getModifiers = getModifiers, - TEXT_SIZE_PRESETS = TEXT_SIZE_PRESETS, - init = init, - clamp = clamp, - -- Alias for `clamp`; exposed under the size-clamping name so Element/LayoutEngine - -- and tests can reference min/max content-size clamping explicitly. - clampSize = clamp, - lerp = lerp, - round = round, - safeLoadImage = safeLoadImage, - brightenColor = brightenColor, - resolveImagePath = resolveImagePath, - normalizeBooleanTable = normalizeBooleanTable, - normalizeOffsetTable = normalizeOffsetTable, - applyContentMultiplier = applyContentMultiplier, - -- Backward-compatible aliases (delegated to focused sub-modules) - validateEnum = NumberValidation.validateEnum, - validateRange = NumberValidation.validateRange, - validateType = NumberValidation.validateType, - isNaN = NumberValidation.isNaN, - isInfinity = NumberValidation.isInfinity, - validateNumber = NumberValidation.validateNumber, - sanitizeNumber = NumberValidation.sanitizeNumber, - validateInteger = NumberValidation.validateInteger, - validatePercentage = NumberValidation.validatePercentage, - validateOpacity = NumberValidation.validateOpacity, - validateDegrees = NumberValidation.validateDegrees, - validateCoordinate = NumberValidation.validateCoordinate, - validateDimension = NumberValidation.validateDimension, - normalizePath = PathValidator.normalizePath, - sanitizePath = PathValidator.sanitizePath, - isPathSafe = PathValidator.isPathSafe, - validatePath = PathValidator.validatePath, - getFileExtension = PathValidator.getFileExtension, - hasAllowedExtension = PathValidator.hasAllowedExtension, - sanitizeText = TextSanitizer.sanitizeText, - validateTextInput = TextSanitizer.validateTextInput, - validateTextRange = TextSanitizer.validateTextRange, - escapeHtml = TextSanitizer.escapeHtml, - escapeLuaPattern = TextSanitizer.escapeLuaPattern, - stripNonPrintable = TextSanitizer.stripNonPrintable, - resolveFontPath = FontCache.resolveFontPath, - getFont = FontCache.getFont, - getFontCacheStats = FontCache.getFontCacheStats, - setFontCacheSize = FontCache.setFontCacheSize, - clearFontCache = FontCache.clearFontCache, - preloadFont = FontCache.preloadFont, - resetFontCacheStats = FontCache.resetFontCacheStats, -} diff --git a/main.lua b/main.lua index e1937fca..f8c349e8 100644 --- a/main.lua +++ b/main.lua @@ -11,7 +11,9 @@ local editorMode = os.getenv("POKEPORT_EDITOR") == "1" or POKEPORT_EDITOR_MODE == true local SwitchDiagnostics = require("src.debug.SwitchDiagnostics") +local LaunchOptions = require("src.core.LaunchOptions") local NxDisplay = require("src.core.NxDisplay") +local PlatformHooks = require("src.core.PlatformHooks") -- Lua errors: persist a redacted trace in the save dir and surface a hint. do @@ -29,6 +31,21 @@ end local Game, EditorApp, Importer, TouchEditor +-- #887: quit-to-launcher state, shared by love.load and love.quit (both need +-- it, so it is declared here rather than next to love.quit). +-- * launchedIntoGame -- a --game / POKEPORT_GAME shortcut booted this +-- session straight into a game, so there is no launcher behind it and a +-- window close must exit. Restarting instead re-read the same shortcut +-- and came right back into the game, and the next close did it again: +-- the app could not be closed at all (macOS feels this worst, where the +-- red X, Cmd+Q and the Dock's Quit are all the same quit event). +-- * RELAUNCH_MARKER -- written in the save dir just before the #785 +-- restart, so the fresh boot ignores any boot-straight-into-a-game +-- option exactly once and keeps #785's promise of landing in the +-- launcher, whatever put the game on screen this time. +local launchedIntoGame = false +local RELAUNCH_MARKER = "relaunch_to_launcher.txt" + local autopilot -- optional scripted-input dev tool (tests/autopilot.lua) local driverCo -- optional frame-driver (POKEPORT_DRIVER=file.lua): a -- coroutine that receives `Game` and yields once per @@ -313,6 +330,47 @@ function love.load(args) return end + -- The launcher draws before any game boots, so the mod loader has not run + -- and Strings has no catalog. Routing the launcher's text through Strings + -- (#767) only pays off if something fills that catalog this early, and no + -- restart could: the ordering is the same on every launch. Read the + -- enabled mods' string catalogs -- data only, no entry chunk -- so a + -- translation reaches the launcher too. Game:load replaces this with the + -- real merged catalog once a version boots. + do + local preload = require("src.mods.LauncherMods").translationStrings() + if preload then require("src.core.Strings").load({ strings = preload }) end + end + + -- LAUNCH OPTIONS: skip the launcher and boot a game directly. + -- --game red|blue|yellow (or POKEPORT_GAME / POKEPORT_LAUNCH) + -- --slot optional; picks the save slot to load + -- --launcher force the launcher even if a game is set + -- This is what a desktop shortcut, a Steam entry, or a frontend like + -- EmulationStation needs: one click into the game the player wants, with no + -- menu in between. A game that is not imported falls through to the + -- launcher on its tab rather than booting into nothing. + -- A window close that restarted us into the launcher (#785) leaves the + -- marker behind: consume it and stay on the launcher, or the shortcut below + -- would boot the same game again and that close would restart again, + -- forever (#887). Consumed on read, so the very next launch is normal. + local relaunched = love.filesystem.getInfo(RELAUNCH_MARKER) ~= nil + if relaunched then pcall(love.filesystem.remove, RELAUNCH_MARKER) end + + local launchGame, launchSlot = LaunchOptions.resolve(arg) + if launchGame and not relaunched and not LaunchOptions.forceLauncher(arg) then + if RomImporter.isReady(launchGame) then + if launchSlot then LaunchOptions.selectSlot(launchGame, launchSlot) end + -- No launcher behind this session: love.quit must exit, not restart. + launchedIntoGame = true + bootGame(launchGame) + return + end + -- Not importable yet: open the launcher already showing that game, so the + -- shortcut still lands the player where they meant to go. + LaunchOptions.pendingTab = launchGame + end + -- Interactive: the launcher always runs. Red, Blue, and Yellow are each -- live: a column shows Play when that game's ROM is already imported, or -- Choose ROM / drag-drop when it is not. Any dropped .gb is routed by its @@ -369,7 +427,11 @@ function love.update(dt) end return end - Game:update(dt) + -- Mods may wrap or veto the per-frame simulation step (pause it, react + -- to external platform state, etc.) -- see docs/modding.md's core.update + -- entry. Vanilla behavior (used when no mod claims the hook) is just + -- Game:update(dt), unconditionally, exactly as before this hook existed. + PlatformHooks.update(Game, dt) end function love.draw() @@ -597,7 +659,7 @@ function love.touchpressed(id, x, y, dx, dy, pressure) -- Android's synthesized mouse twin so Import cannot double-fire (#553). return Importer:touchpressed(id, x, y, dx, dy, pressure) end - Game:touchpressed(id, x, y) + Game:touchpressed(id, x, y, dx, dy, pressure) end function love.touchmoved(id, x, y, dx, dy, pressure) @@ -609,7 +671,7 @@ function love.touchmoved(id, x, y, dx, dy, pressure) if Importer then return Importer:touchmoved(id, x, y, dx, dy, pressure) end - Game:touchmoved(id, x, y) + Game:touchmoved(id, x, y, dx, dy, pressure) end function love.touchreleased(id, x, y, dx, dy, pressure) @@ -621,7 +683,7 @@ function love.touchreleased(id, x, y, dx, dy, pressure) if Importer then return Importer:touchreleased(id, x, y, dx, dy, pressure) end - Game:touchreleased(id, x, y) + Game:touchreleased(id, x, y, dx, dy, pressure) end function love.wheelmoved(x, y) @@ -634,7 +696,33 @@ function love.wheelmoved(x, y) Game:wheelmoved(x, y) end +-- #781: Linux X11 multi-monitor with the primary display away from desktop +-- (0,0): SDL's polled mouse state can come back in desktop-virtual +-- coordinates while the event stream stays window-relative, which strands +-- every polled consumer (launcher Kit rising-edge clicks, the pad-cursor +-- motion yield, PadCursor) on coordinates no hit test can match. Sanitize +-- the poll once here: remember the last window-relative event coordinates +-- and substitute them whenever the polled value falls outside the window. +-- Linux only -- macOS / Windows / mobile keep the stock function, and the +-- NX launcher shim still composes because it captures whatever +-- love.mouse.getPosition is at bridge time (_ensureNxPointerBridge). +local eventMouseX, eventMouseY +if love.system and love.system.getOS() == "Linux" + and love.mouse and love.mouse.getPosition then + local polledGetPosition = love.mouse.getPosition + love.mouse.getPosition = function() + local x, y = polledGetPosition() + local w, h = love.graphics.getDimensions() + if x < 0 or y < 0 or x > w or y > h then + if eventMouseX then return eventMouseX, eventMouseY end + return math.max(0, math.min(x, w)), math.max(0, math.min(y, h)) + end + return x, y + end +end + function love.mousepressed(x, y, button, istouch) + if not istouch then eventMouseX, eventMouseY = x, y end if TouchEditor then -- Android primary touch already arrived via love.touchpressed; a second -- mouse path would double-fire Done / begin a second drag. @@ -658,12 +746,19 @@ function love.mousepressed(x, y, button, istouch) if istouch and love.system.getOS() == "Android" then return end return EditorApp.mousepressed(x, y, button) end - if mouseTouch and Game and button == 1 then - Game:touchpressed("mouse", x, y) + if mouseTouch then + -- the mouse is standing in for a finger: the touch path owns it, and + -- feeding the same press back in as a mouse pointer would double it + if Game and button == 1 then Game:touchpressed("mouse", x, y) end + return end + -- #807: a real mouse reaches gameplay as a pointer event for mods; Game + -- drops synthesized istouch twins so a mobile touch that already arrived + -- through love.touchpressed cannot fire twice + if Game then Game:mousepressed(x, y, button, istouch) end end -function love.mousereleased(x, y, button) +function love.mousereleased(x, y, button, istouch) if TouchEditor then if love.system.getOS() == "Android" then return end return TouchEditor.mousereleased(x, y, button) @@ -672,20 +767,25 @@ function love.mousereleased(x, y, button) if editorMode and EditorApp.mousereleased then return EditorApp.mousereleased(x, y, button) end - if mouseTouch and Game and button == 1 then - Game:touchreleased("mouse", x, y) + if mouseTouch then + if Game and button == 1 then Game:touchreleased("mouse", x, y) end + return end + if Game then Game:mousereleased(x, y, button, istouch) end end -function love.mousemoved(x, y) +function love.mousemoved(x, y, dx, dy, istouch) + if not istouch then eventMouseX, eventMouseY = x, y end if TouchEditor then if love.system.getOS() == "Android" then return end return TouchEditor.mousemoved(x, y) end if editorMode or Importer then return end - if mouseTouch and Game and love.mouse.isDown(1) then - Game:touchmoved("mouse", x, y) + if mouseTouch then + if Game and love.mouse.isDown(1) then Game:touchmoved("mouse", x, y) end + return end + if Game then Game:mousemoved(x, y, dx, dy, istouch) end end function love.textinput(text) @@ -696,9 +796,51 @@ function love.textinput(text) end end +-- #785: set once love.quit has routed a window close into HostShell.restart, +-- so the follow-up quit event the restart itself raises (quit("restart") on +-- desktop; AppImage and Android relaunch the process instead, #575) falls +-- through to the normal shutdown below instead of restarting forever. +local quitToLauncher = false + function love.quit() if editorMode and EditorApp.quit then - return EditorApp.quit() -- return true to abort quit + -- true blocks the quit (unsaved-changes prompt). A quit that proceeds + -- must fall through to the worker shutdowns below instead of returning: + -- the bundled editor opens from a live launcher whose update-check and + -- fetch-pool workers are still parked in Channel:demand(), and returning + -- here skipped their "quit" push, so the process outlived the closed + -- window and kept the install folder locked on Windows (#727). + if EditorApp.quit() then return true end + end + -- Closing the window of a running game returns to the launcher instead of + -- exiting the app, so testing a mod does not need a relaunch every time + -- (#785). Game is only non-nil once bootGame ran; Importer non-nil means + -- the launcher (or its import) owns the window and its close still quits. + -- Scripted and headless runs (autopilot, frame driver, import-only, ROM + -- path import) keep the plain exit so they terminate as before. Nothing + -- is saved here on purpose: a window close never wrote the save, and the + -- restart path must be no worse than that, not quietly better. + local scripted = os.getenv("POKEPORT_AUTOPILOT") or os.getenv("POKEPORT_DRIVER") + or os.getenv("POKEPORT_IMPORT_ONLY") == "1" or os.getenv("POKEPORT_IMPORT_ROM") + -- #887: a shortcut session (--game / POKEPORT_GAME) has no launcher to go + -- back to and the restart would re-read the shortcut, so it exits instead. + -- + -- A platform launcher that owns "return to launcher" itself (see + -- docs/modding.md's core.quit_to_launcher entry) may veto returning to + -- this Lua launcher via that hook. Vanilla behavior (used when no mod + -- claims the hook) is exactly the condition below. + local wouldReturnToLauncher = PlatformHooks.quitToLauncher(function() + return Game and not Importer and not quitToLauncher and not scripted + and not launchedIntoGame + end) + if wouldReturnToLauncher then + quitToLauncher = true + -- Tell the fresh boot to ignore any boot-straight-into-a-game option this + -- once, so the restart really does land in the launcher (#887). A failed + -- write only costs that suppression, so it must never block the restart. + pcall(love.filesystem.write, RELAUNCH_MARKER, "1") + require("src.core.HostShell").restart() + return true -- abort this quit; the restart lands back in the launcher end pcall(function() require("src.core.DiscordPresence").shutdown() @@ -713,6 +855,12 @@ function love.quit() if package.loaded["src.update.Check"] then pcall(package.loaded["src.update.Check"].shutdown) end + -- The launcher's fetch pool is the same story: its workers idle in + -- Channel:demand(), which never returns on its own, so a launcher that ever + -- touched the network would hang the process on exit (#339's shape again). + if package.loaded["src.net.Fetch"] then + pcall(package.loaded["src.net.Fetch"].shutdown) + end end function love.filedropped(file) diff --git a/mobile/android/app/src/main/res/drawable-hdpi/love.png b/mobile/android/app/src/main/res/drawable-hdpi/love.png index da3ef3a6..b7dcebb4 100644 Binary files a/mobile/android/app/src/main/res/drawable-hdpi/love.png and b/mobile/android/app/src/main/res/drawable-hdpi/love.png differ diff --git a/mobile/android/app/src/main/res/drawable-mdpi/love.png b/mobile/android/app/src/main/res/drawable-mdpi/love.png index 82caa826..57a1055d 100644 Binary files a/mobile/android/app/src/main/res/drawable-mdpi/love.png and b/mobile/android/app/src/main/res/drawable-mdpi/love.png differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/love.png b/mobile/android/app/src/main/res/drawable-xhdpi/love.png index b2e6d1a2..7336e7ea 100644 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/love.png and b/mobile/android/app/src/main/res/drawable-xhdpi/love.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/love.png b/mobile/android/app/src/main/res/drawable-xxhdpi/love.png index 0e091788..edea8141 100644 Binary files a/mobile/android/app/src/main/res/drawable-xxhdpi/love.png and b/mobile/android/app/src/main/res/drawable-xxhdpi/love.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/love.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/love.png index 01a4634f..8dd16c82 100644 Binary files a/mobile/android/app/src/main/res/drawable-xxxhdpi/love.png and b/mobile/android/app/src/main/res/drawable-xxxhdpi/love.png differ diff --git a/mobile/android/love/src/jni/love/src/common/android.cpp b/mobile/android/love/src/jni/love/src/common/android.cpp index d7b9230f..2204e45d 100644 --- a/mobile/android/love/src/jni/love/src/common/android.cpp +++ b/mobile/android/love/src/jni/love/src/common/android.cpp @@ -37,6 +37,13 @@ #include "filesystem/physfs/PhysfsIo.h" +// #604 / #839: the SAF bridges below must hand GameActivity the exact +// directory physfs mounted as the save dir -- the same contract the iOS +// GRPickerBridge already gets (mobile/ios/patch_love_src.py, +// gr_saveDirectory) -- instead of letting Java recompute the root on its +// own, which can name a different volume on merged / adopted-SD storage. +#include "filesystem/Filesystem.h" + namespace love { namespace android @@ -183,6 +190,19 @@ void vibrate(double seconds) env->DeleteLocalRef(activity); } +// The directory physfs actually mounted as the save dir, or "" before the +// filesystem module is up. GameActivity must copy SAF picks HERE: its own +// getExternalFilesDir(null) recomputation can disagree with the mounted +// root on merged / adopted-SD storage (#604, #839). +static const char *bridgeSaveDirectory() +{ + auto fs = Module::getInstance(Module::M_FILESYSTEM); + if (fs == nullptr) + return ""; + const char *dir = fs->getSaveDirectory(); + return dir != nullptr ? dir : ""; +} + bool showFilePicker(const char *destFilename) { if (destFilename == nullptr || destFilename[0] == '\0') @@ -192,9 +212,11 @@ bool showFilePicker(const char *destFilename) jclass activity = env->FindClass("org/love2d/android/GameActivity"); jmethodID method = env->GetStaticMethodID(activity, "showFilePicker", - "(Ljava/lang/String;)Z"); + "(Ljava/lang/String;Ljava/lang/String;)Z"); jstring jname = env->NewStringUTF(destFilename); - jboolean result = env->CallStaticBooleanMethod(activity, method, jname); + jstring jsavedir = env->NewStringUTF(bridgeSaveDirectory()); + jboolean result = env->CallStaticBooleanMethod(activity, method, jname, jsavedir); + env->DeleteLocalRef(jsavedir); env->DeleteLocalRef(jname); env->DeleteLocalRef(activity); @@ -210,9 +232,11 @@ bool showCreateDocument(const char *suggestedName) jclass activity = env->FindClass("org/love2d/android/GameActivity"); jmethodID method = env->GetStaticMethodID(activity, "showCreateDocument", - "(Ljava/lang/String;)Z"); + "(Ljava/lang/String;Ljava/lang/String;)Z"); jstring jname = env->NewStringUTF(suggestedName); - jboolean result = env->CallStaticBooleanMethod(activity, method, jname); + jstring jsavedir = env->NewStringUTF(bridgeSaveDirectory()); + jboolean result = env->CallStaticBooleanMethod(activity, method, jname, jsavedir); + env->DeleteLocalRef(jsavedir); env->DeleteLocalRef(jname); env->DeleteLocalRef(activity); @@ -259,7 +283,19 @@ bool httpDownload(const char *url, const char *destPath, const char *userAgent, return false; JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jclass activity = env->FindClass("org/love2d/android/GameActivity"); + // NOT FindClass: this is the one bridge called off the main thread + // (love.thread workers in src/net/fetch_worker.lua and + // src/update/check_worker.lua). A worker is a raw pthread whose JNI + // class loader is the system one, which cannot see app classes, so + // FindClass("org/love2d/android/GameActivity") left a pending + // ClassNotFoundException and the next JNI call aborted the process -- + // opening FIND MODS killed the app on the first stats fetch. Resolving + // through the live activity instance works from any attached thread. + jobject activityObj = (jobject) SDL_AndroidGetActivity(); + if (activityObj == nullptr) + return false; + jclass activity = env->GetObjectClass(activityObj); + env->DeleteLocalRef(activityObj); // Old APK / new liblove skew: report "no transport" the same way a // missing curl does, instead of aborting on a missing method (#597). @@ -967,4 +1003,33 @@ void love_android_secondary_enable(int on) env->DeleteLocalRef(activity); } +extern "C" __attribute__((visibility("default"))) +const char *love_android_poll_secondary_touch() +{ + static thread_local std::string event; + event.clear(); + JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); + jclass activity = env->FindClass("org/love2d/android/GameActivity"); + jmethodID method = env->GetStaticMethodID(activity, "pollSecondaryDisplayTouch", + "()Ljava/lang/String;"); + if (!method) + env->ExceptionClear(); + else + { + jstring value = (jstring) env->CallStaticObjectMethod(activity, method); + if (value) + { + const char *utf = env->GetStringUTFChars(value, nullptr); + if (utf) + { + event = utf; + env->ReleaseStringUTFChars(value, utf); + } + env->DeleteLocalRef(value); + } + } + env->DeleteLocalRef(activity); + return event.empty() ? nullptr : event.c_str(); +} + #endif // LOVE_ANDROID diff --git a/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java b/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java index 04c8a827..09fa2148 100644 --- a/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java +++ b/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java @@ -116,6 +116,18 @@ public class GameActivity extends SDLActivity { // bad ROM instead of installing it (#553). private String pendingPickFilename = PICKED_ROM_FILENAME; private static final String STATE_PENDING_PICK = "pendingPickFilename"; + // Absolute save directory physfs actually mounted, as reported by the + // native bridge call that opened the picker (love/src/common/android.cpp, + // bridgeSaveDirectory). This activity used to recompute + // getExternalFilesDir(null)/save/ on its own at result time; on + // merged / adopted-SD storage that can name a different volume than the + // one LOVE mounted, so the copied pick (and pick_error.flag) landed where + // Lua never scans -- the launcher then "did nothing" after a pick (#604) + // and the folders a file manager can browse stayed empty while the game + // saved fine elsewhere (#839). Empty string means "not told yet": fall + // back to the historical computation. + private String pendingPickSaveDir = ""; + private static final String STATE_PENDING_PICK_DIR = "pendingPickSaveDir"; private static final String STATE_PENDING_CREATE = "pendingCreateSuggestedName"; // Suggested download name for the in-flight SAF create (set by showCreateDocument). private String pendingCreateSuggestedName = "export.sav"; @@ -186,6 +198,8 @@ public class GameActivity extends SDLActivity { // a recreated activity still lands under the basename it asked for. String pick = savedInstanceState.getString(STATE_PENDING_PICK); if (pick != null) pendingPickFilename = pick; + String pickDir = savedInstanceState.getString(STATE_PENDING_PICK_DIR); + if (pickDir != null) pendingPickSaveDir = pickDir; String create = savedInstanceState.getString(STATE_PENDING_CREATE); if (create != null) pendingCreateSuggestedName = create; } @@ -467,13 +481,23 @@ public class GameActivity extends SDLActivity { * @param destFilename basename under the app save identity (e.g. * picked_rom.gb, picked_mod.zip, picked_save.sav) */ + /** Legacy single-argument entry; resolves the save dir itself. */ @Keep public static boolean showFilePicker(String destFilename) { + return showFilePicker(destFilename, null); + } + + @Keep + public static boolean showFilePicker(String destFilename, String saveDir) { GameActivity self = (GameActivity) mSingleton; if (self == null) return false; if (destFilename == null || destFilename.length() == 0) { destFilename = PICKED_ROM_FILENAME; } + // Remember where LOVE's filesystem is really mounted so + // onActivityResult copies the pick there, not into a recomputed + // (possibly different-volume) root (#604, #839). + self.pendingPickSaveDir = (saveDir != null) ? saveDir : ""; // Reject path separators so a hostile JNI caller cannot escape the // save identity directory. if (destFilename.indexOf('/') >= 0 || destFilename.indexOf('\\') >= 0) { @@ -644,8 +668,14 @@ public class GameActivity extends SDLActivity { * return degrades on the Lua side (RomImporter export) to "Exported * inside the app folder", which is the correct pre-KitKat behavior. */ + /** Legacy single-argument entry; resolves the save dir itself. */ @Keep public static boolean showCreateDocument(String suggestedName) { + return showCreateDocument(suggestedName, null); + } + + @Keep + public static boolean showCreateDocument(String suggestedName, String saveDir) { if (android.os.Build.VERSION.SDK_INT < 19) return false; // (see showFilePicker for why the import side got a pre-19 path) GameActivity self = (GameActivity) mSingleton; @@ -657,9 +687,12 @@ public class GameActivity extends SDLActivity { Log.d("GameActivity", "refusing unsafe create name: " + suggestedName); return false; } - File source = new File( - new File(self.getExternalFilesDir(null), "save"), - ROM_SAVE_IDENTITY + "/" + PENDING_EXPORT_FILENAME); + // Route through the mounted save dir (#604, #839): Lua staged + // pending_export.sav where physfs writes, which is not necessarily + // where a fresh getExternalFilesDir(null) points on merged / + // adopted-SD storage. + self.pendingPickSaveDir = (saveDir != null) ? saveDir : ""; + File source = new File(self.saveIdentityDir(), PENDING_EXPORT_FILENAME); if (!source.isFile()) { Log.d("GameActivity", "no pending export at " + source); return false; @@ -680,7 +713,21 @@ public class GameActivity extends SDLActivity { } private File saveIdentityDir() { - return new File(new File(getExternalFilesDir(null), "save"), ROM_SAVE_IDENTITY); + // Prefer the mounted save dir the last bridge call reported: the + // recomputation below can name a different volume than the one LOVE + // mounted on merged / adopted-SD storage (#604, #839). + if (pendingPickSaveDir != null && pendingPickSaveDir.length() > 0) { + return new File(pendingPickSaveDir); + } + File ext = getExternalFilesDir(null); + if (ext == null) { + // Shared storage unavailable (ejected / mid-adoption): without + // this guard File(null, "save") silently built the RELATIVE + // path save/, mkdirs() failed against "/", and the + // pick was dropped with no message at all (#604). + ext = getFilesDir(); + } + return new File(new File(ext, "save"), ROM_SAVE_IDENTITY); } /** Drops a small flag file in the save identity for Lua to consume on focus. */ @@ -875,6 +922,7 @@ public class GameActivity extends SDLActivity { protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(STATE_PENDING_PICK, pendingPickFilename); + outState.putString(STATE_PENDING_PICK_DIR, pendingPickSaveDir); outState.putString(STATE_PENDING_CREATE, pendingCreateSuggestedName); } @@ -1277,6 +1325,9 @@ public class GameActivity extends SDLActivity { // in src/jni/love/src/common/android.cpp. private static volatile SecondaryPresentation secondaryPresentation; private static volatile boolean secondaryEnabled = false; + private static final int MAX_SECONDARY_TOUCHES = 32; + private static final java.util.ArrayDeque secondaryTouches = + new java.util.ArrayDeque<>(); @Keep public static void setSecondaryEnabled(final boolean on) { @@ -1329,6 +1380,7 @@ public class GameActivity extends SDLActivity { private static void teardownSecondaryDisplay() { SecondaryPresentation p = secondaryPresentation; secondaryPresentation = null; + synchronized (secondaryTouches) { secondaryTouches.clear(); } if (p != null) { try { p.dismiss(); } catch (Throwable t) {} } @@ -1347,6 +1399,13 @@ public class GameActivity extends SDLActivity { } } + @Keep + public static String pollSecondaryDisplayTouch() { + synchronized (secondaryTouches) { + return secondaryTouches.pollFirst(); + } + } + private static class SecondaryPresentation extends android.app.Presentation { private final FrameView frameView; @@ -1413,6 +1472,7 @@ public class GameActivity extends SDLActivity { private final android.graphics.Paint paint = new android.graphics.Paint(); private final Object lock = new Object(); private int fw, fh; + private int activePointer = -1; FrameView(Context context) { super(context); @@ -1434,6 +1494,52 @@ public class GameActivity extends SDLActivity { postInvalidate(); } + private void enqueueTouch(String event) { + synchronized (secondaryTouches) { + if (secondaryTouches.size() >= MAX_SECONDARY_TOUCHES) { + secondaryTouches.clear(); + secondaryTouches.addLast("cancel,0,0"); + } else { + secondaryTouches.addLast(event); + } + } + } + + private int logicalX(float x) { + return Math.min(fw - 1, Math.max(0, + (int) ((x - dst.left) * fw / dst.width()))); + } + + private int logicalY(float y) { + return Math.min(fh - 1, Math.max(0, + (int) ((y - dst.top) * fh / dst.height()))); + } + + @Override + public boolean onTouchEvent(android.view.MotionEvent event) { + synchronized (lock) { + int action = event.getActionMasked(); + if (action == android.view.MotionEvent.ACTION_DOWN && fw > 0 + && dst.contains((int) event.getX(), (int) event.getY())) { + activePointer = event.getPointerId(0); + enqueueTouch("down," + logicalX(event.getX()) + "," + + logicalY(event.getY())); + } else if (action == android.view.MotionEvent.ACTION_UP + && activePointer >= 0) { + int index = event.findPointerIndex(activePointer); + if (index >= 0 && fw > 0) { + enqueueTouch("up," + logicalX(event.getX(index)) + "," + + logicalY(event.getY(index))); + } + activePointer = -1; + } else if (action == android.view.MotionEvent.ACTION_CANCEL) { + activePointer = -1; + enqueueTouch("cancel,0,0"); + } + } + return true; + } + @Override protected void onDraw(android.graphics.Canvas canvas) { synchronized (lock) { diff --git a/mobile/ios/README.md b/mobile/ios/README.md index e1a9f48a..5ae5439c 100644 --- a/mobile/ios/README.md +++ b/mobile/ios/README.md @@ -1,130 +1,113 @@ -# iOS build (LÖVE 12.0) +# iOS build -> **Native ROM/mod/save import.** The iOS build ships a Swift -> document-picker bridge (`native/GRPickerBridge.swift` + `GRBootstrap.m`) -> that `patch_love_src.py` wires into the LÖVE tree on every build: -> -> - `love.system.pickFile("rom"|"mod"|"sav")` and `love.system.createFile` -> are exposed to Lua on iOS (same contract as love-android's SAF picker: -> picks land in the save dir as `picked_rom.gb` / `picked_mod.zip` / -> `picked_save.sav`; exports signal via `export_done.flag`). -> - The Info.plist overlay enables `UIFileSharingEnabled` + -> `LSSupportsOpeningDocumentsInPlace`, and `GRBootstrap.m` sweeps -> `.gb/.gbc/.zip/.sav` files dropped in Documents (Files app / Finder) -> into the LÖVE save dir on every activation — drop a ROM, open the app, -> and it imports with no taps. -> - `src/import/RomImporter.lua` treats iOS as a mobile platform and polls -> for picker results (iOS pickers are in-process modals, so Android's -> refocus rescan never fires). -> -> The note below about a missing "UIDocumentPicker handoff" is -> resolved by this bridge. +This directory contains the macOS/Xcode build used to package Gen1 Recomp as +an iOS app with LÖVE 12.0. -macOS + Xcode only. Fetches the **LÖVE 12.0** source tree and matching Apple -dependencies from the official [LÖVE source](https://github.com/love2d/love) -and [Apple dependencies](https://github.com/love2d/love-apple-dependencies) -repositories. `conf.lua` declares LÖVE 12.0 on iOS and 11.5 elsewhere. +## User data location -Pin file: [`LOVE_VERSION`](./LOVE_VERSION) → `12.0`. +The app uses the public iOS Documents directory as its LÖVE save directory. +There is no `pokemon-love2d` subdirectory and the app does not create a +README file there. When browsing `On My iPhone > gen1recomp` in Files, the +directory contains the app's runtime data directly, including: -## Quick start (simulator) +- installed mods and downloaded ROMs +- save files and save-state data +- options, caches, logs, and other files created by the game + +The build enables `UIFileSharingEnabled` and +`LSSupportsOpeningDocumentsInPlace`, so the same directory is available in +Files and Finder. Files copied into the app's Documents directory are used by +the game on its next activation. + +Existing installations are migrated automatically. Files from the old +private `Application Support/pokemon-love2d` directory are merged into +Documents on launch; conflicts are retained with a `.legacy` suffix. + +## Build + +Run these commands from the repository root: ```bash -# Fetch LÖVE 12.0 iOS sources and dependencies (once) + build for Simulator scripts/build_ios.sh --fetch +scripts/build_ios.sh ``` -The embedded `game.love` contains no ROM or generated game data. The current -first-boot importer has desktop file pickers only, so a production iOS release -still needs a UIDocumentPicker handoff that passes the selected ROM to LÖVE. +`--fetch` downloads the pinned LÖVE source and matching Apple dependencies +into the gitignored `love-src/` directory. It is only needed when that tree is +missing. The default build targets the iOS Simulator in Debug configuration. -Default output: an unsigned Simulator `.app` under `mobile/ios/build/` -(no Apple Developer account required). A convenience copy also lands under -`dist/ios/-/`. - -Install on a booted simulator (example): +For a physical device or a release build: ```bash -xcrun simctl install booted mobile/ios/build/Build/Products/Debug-iphonesimulator/PokemonRed.app -xcrun simctl launch booted com.theboisclub.pokemonred +scripts/build_ios.sh --device --install +scripts/build_ios.sh --device --release --install ``` -Or open `mobile/ios/love-src/platform/xcode/love.xcodeproj` in Xcode, -select the `love-ios` target, and Run on a Simulator after -`scripts/build_ios.sh --package-only` (or a full build) has placed `game.love`. +Device builds require a paired, unlocked device and a valid Apple signing +identity. Set `DEVELOPMENT_TEAM` or `CODE_SIGN_IDENTITY` when automatic +signing cannot select the intended account. Add `--ipa` to create +`dist/ios/gen1recomp.ipa`. -## Device / Release +The script verifies the final app before packaging it: -```bash -scripts/build_ios.sh --device # Debug, physical device SDK -scripts/build_ios.sh --device --release # Release configuration +- the public Documents plist settings are present +- the native picker bridge is present +- `game.love` exists and is non-empty + +If the payload is missing, the build fails instead of producing a blank app. + +## Useful options + +| Option | Purpose | +| --- | --- | +| `--fetch` | Fetch LÖVE 12.0 and Apple dependencies when `love-src/` is missing | +| `--device` | Build for `iphoneos` instead of the Simulator | +| `--release` | Use the Release configuration | +| `--install` | Install a device build on the first connected device | +| `--ipa` | Create an IPA after a device build | +| `--version X.Y.Z` | Stamp the engine and app version | +| `--package-only` | Package `game.love` and apply the iOS plist overlay without Xcode | + +`scripts/build.sh ios` delegates to this script and forwards the iOS release +option. + +## Output + +Simulator and device app bundles are copied to: + +```text +dist/ios/Debug-iphonesimulator/gen1recomp.app +dist/ios/Release-iphonesimulator/gen1recomp.app +dist/ios/Debug-iphoneos/gen1recomp.app +dist/ios/Release-iphoneos/gen1recomp.app ``` -Device builds need a signing identity and provisioning profile configured in -Xcode (or via `DEVELOPMENT_TEAM` / `CODE_SIGN_IDENTITY` env vars). This repo -does **not** store certificates, profiles, or App Store Connect secrets. +The intermediate Xcode products are under `mobile/ios/build/`. Both locations +are gitignored. -Manual out-of-band steps: - -1. Apple Developer account + App ID for `com.theboisclub.pokemonred` -2. Development or Distribution certificate + provisioning profile -3. In Xcode: open `love.xcodeproj` → target `love-ios` → Signing & Capabilities - → select your Team (or set `DEVELOPMENT_TEAM=XXXXXXXXXX` when invoking - `scripts/build_ios.sh --device`) -4. Archive / export an `.ipa` from Xcode Organizer for TestFlight / Ad Hoc - -## Layout - -| Path | Role | -|------|------| -| `LOVE_VERSION` | Engine pin (`12.0`) | -| `overlays/love-ios.plist` | Portrait-only Info.plist + display name **Pokemon Red** (copied over the upstream plist every build) | -| `love-src/` | Downloaded LÖVE 12.0 source tree (**gitignored**, do not commit) | -| `cache/` | Temporary source and dependency checkout data (**gitignored**) | -| `build/` | `xcodebuild` derived data (**gitignored**) | - -Game payload lands at: - -`love-src/platform/xcode/ios/resources/game.love` - -and is fused into the built `.app` (LÖVE auto-runs any bundled `*.love`). - -## Apple libraries dependency - -`scripts/build_ios.sh --fetch` retrieves the matching iOS libraries and the -SDL3 framework from -[love-apple-dependencies](https://github.com/love2d/love-apple-dependencies). -Re-run it if either dependency directory is absent. +The bundled game payload is staged at +`love-src/platform/xcode/ios/resources/game.love` and copied into the final +app bundle. The payload contains the game, not user-generated ROMs, mods, or +saves; those are created at runtime in Documents. ## App identity -| Field | Value | -|-------|--------| -| Display name | Pokemon Red | -| `PRODUCT_NAME` | PokemonRed | -| Bundle ID | `com.theboisclub.pokemonred` | -| Orientations | Portrait only (`UIInterfaceOrientationPortrait`) | +| Field | Default | +| --- | --- | +| Display name | `gen1recomp` | +| Product name | `gen1recomp` | +| Bundle identifier | `com.theboisclub.gen1recomp` | +| Save directory | Public `Documents` root | +| Orientation | Portrait | -Overrides are applied by the build script (`xcodebuild` settings + plist overlay) -so refreshing `love-src/` does not lose branding. +Set `GEN1_BUNDLE_ID` to use a different bundle identifier for local device +builds. -## Flags (`scripts/build_ios.sh`) +## Prerequisites -| Flag | Meaning | -|------|---------| -| *(default)* | Simulator, Debug, no signing | -| `--fetch` | Fetch the LÖVE 12.0 source tree and Apple dependencies if `love-src/` is missing | -| `--device` | Build against `iphoneos` instead of `iphonesimulator` | -| `--release` | `Release` configuration instead of `Debug` | -| `--package-only` | Zip `game.love` + apply plist overlay; skip `xcodebuild` | +- macOS with Xcode and `xcodebuild` +- the iOS and iOS Simulator platforms installed in Xcode +- a fetched `love-src/` tree, or the `--fetch` option +- the matching iOS libraries and SDL3 framework under `love-src/` -Also: `scripts/build.sh ios` delegates here (`--release` is forwarded). - -## Preconditions - -- macOS (Darwin) with Xcode + `xcodebuild` on `PATH` -- iOS platform installed in Xcode (Settings → Platforms). `xcodebuild -showsdks` - should list `iphonesimulator` / `iphoneos`. A partial install can fail IB/xib - compiles with `iOS … Platform Not Installed` even when the SDK name appears. -- `love-src/` present (`--fetch`) -- iOS libraries under `love-src/platform/xcode/ios/libraries/` and SDL3 under `love-src/platform/xcode/shared/Frameworks/` +Use `xcodebuild -showsdks` to confirm that the required SDKs are installed. diff --git a/mobile/ios/native/GRBootstrap.m b/mobile/ios/native/GRBootstrap.m index 4b56de9c..de7a59e8 100644 --- a/mobile/ios/native/GRBootstrap.m +++ b/mobile/ios/native/GRBootstrap.m @@ -16,6 +16,9 @@ static void GRBootstrapInstall(void) queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Class bridge = NSClassFromString(@"GRPickerBridge"); + if ([bridge respondsToSelector:@selector(preparePublicDocuments)]) { + [bridge performSelector:@selector(preparePublicDocuments)]; + } if ([bridge respondsToSelector:@selector(sweepInbox)]) { [bridge performSelector:@selector(sweepInbox)]; } diff --git a/mobile/ios/native/GRPickerBridge.swift b/mobile/ios/native/GRPickerBridge.swift index 9f3e3966..c571461b 100644 --- a/mobile/ios/native/GRPickerBridge.swift +++ b/mobile/ios/native/GRPickerBridge.swift @@ -26,8 +26,6 @@ public final class GRPickerBridge: NSObject { // silently doing nothing. private static var liveDelegates: [PickerDelegate] = [] - // conf.lua t.identity — where LÖVE puts the fused save directory on iOS - // (/Library/Application Support/). private static let loveIdentity = "pokemon-love2d" @objc(httpDownloadWithUrl:destination:userAgent:accept:) @@ -85,11 +83,30 @@ public final class GRPickerBridge: NSObject { types = [.zip] case "sav": destName = "picked_save.sav" - default: + // A Nintendo 64 cartridge, for mods that build assets out of one -- + // the voxel mod's Pokemon Stadium battle models are the caller this + // was added for. Its own filename on purpose: an N64 ROM landing on + // picked_rom.gb is swept up by the Game Boy importer, deleted, and + // reported to the player as a broken cartridge. + case "stadium": + destName = "picked_stadium.z64" + for ext in ["z64", "n64", "v64"] { + if let t = UTType(filenameExtension: ext) { types.append(t) } + } + case "rom", "": destName = "picked_rom.gb" for ext in ["gb", "gbc"] { if let t = UTType(filenameExtension: ext) { types.append(t) } } + // An unknown kind is REFUSED rather than treated as a Game Boy ROM. + // + // It used to fall through to picked_rom.gb, so a caller asking for a + // kind this build had never heard of got its file deleted and + // reported as a broken cartridge -- the worst possible answer to + // "I do not know that one". Returning false lets the caller find out + // and offer its own fallback. + default: + return false } // .gb/.gbc/.sav resolve to dynamic UTTypes on most devices; offering // .data as well keeps every real file selectable. The importer @@ -107,6 +124,20 @@ public final class GRPickerBridge: NSObject { return present(picker, with: delegate) } + // Which kinds presentPicker understands, comma separated. + // + // So a CALLER can ask before it calls. A mod that wants a kind this build + // predates cannot otherwise tell "refused" from "the picker would not + // open", and guessing wrong used to cost the player their ROM (see the + // default case above). Asking first turns that into a fallback the caller + // chooses rather than a file it loses. + // + // Kept beside the switch it describes, because the two drifting apart is + // the only way this can lie. + @objc public static func supportedPickerKinds() -> NSString { + return "rom,mod,sav,stadium" as NSString + } + @objc(presentExportWithName:saveDir:) public static func presentExport(name: UnsafePointer?, saveDir: UnsafePointer?) -> Bool { @@ -144,10 +175,10 @@ public final class GRPickerBridge: NSObject { // UIApplicationDidBecomeActive (see GRBootstrap.m). @objc public static func sweepInbox() { let fm = FileManager.default - guard let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first, - let appSupport = fm.urls(for: .applicationSupportDirectory, - in: .userDomainMask).first else { return } - let saveDir = appSupport.appendingPathComponent(loveIdentity, isDirectory: true) + migrateLegacySaveDirectory() + guard let docs = documentsDirectory(), + let saveDir = publicSaveDirectory() else { return } + guard docs.standardizedFileURL != saveDir.standardizedFileURL else { return } let wanted: Set = ["gb", "gbc", "zip", "sav"] guard let items = try? fm.contentsOfDirectory(at: docs, includingPropertiesForKeys: nil) else { return } @@ -164,15 +195,21 @@ public final class GRPickerBridge: NSObject { } } + @objc public static func preparePublicDocuments() { + migrateLegacySaveDirectory() + if let saveDir = publicSaveDirectory() { + ensureDirectory(saveDir) + } + } + // MARK: - Helpers private static func resolvedSaveDir(_ cstr: UnsafePointer?) -> URL? { var dir = cstr.map { String(cString: $0) } ?? "" if dir.isEmpty { - guard let appSupport = FileManager.default - .urls(for: .applicationSupportDirectory, in: .userDomainMask).first - else { return nil } - dir = appSupport.appendingPathComponent(loveIdentity).path + migrateLegacySaveDirectory() + guard let saveDir = publicSaveDirectory() else { return nil } + dir = saveDir.path } let url = URL(fileURLWithPath: dir, isDirectory: true) ensureDirectory(url) @@ -184,6 +221,75 @@ public final class GRPickerBridge: NSObject { withIntermediateDirectories: true) } + private static func documentsDirectory() -> URL? { + FileManager.default.urls(for: .documentDirectory, + in: .userDomainMask).first + } + + private static func publicSaveDirectory() -> URL? { + documentsDirectory() + } + + private static func legacySaveDirectory() -> URL? { + FileManager.default.urls(for: .applicationSupportDirectory, + in: .userDomainMask).first? + .appendingPathComponent(loveIdentity, isDirectory: true) + } + + private static func migrateLegacySaveDirectory() { + let fm = FileManager.default + guard let destination = publicSaveDirectory(), + let legacy = legacySaveDirectory(), + fm.fileExists(atPath: legacy.path) else { + return + } + ensureDirectory(destination) + mergeDirectory(from: legacy, to: destination) + try? fm.removeItem(at: legacy) + } + + private static func mergeDirectory(from source: URL, to destination: URL) { + let fm = FileManager.default + ensureDirectory(destination) + guard let items = try? fm.contentsOfDirectory(at: source, + includingPropertiesForKeys: nil) + else { return } + for item in items { + let target = destination.appendingPathComponent(item.lastPathComponent) + var sourceIsDirectory = ObjCBool(false) + fm.fileExists(atPath: item.path, isDirectory: &sourceIsDirectory) + var targetIsDirectory = ObjCBool(false) + let targetExists = fm.fileExists(atPath: target.path, + isDirectory: &targetIsDirectory) + if sourceIsDirectory.boolValue && targetExists && targetIsDirectory.boolValue { + mergeDirectory(from: item, to: target) + continue + } + if targetExists { + if !sourceIsDirectory.boolValue && !targetIsDirectory.boolValue && + fm.contentsEqual(atPath: item.path, andPath: target.path) { + try? fm.removeItem(at: item) + } else { + moveToLegacyName(item, in: destination) + } + continue + } + try? fm.moveItem(at: item, to: target) + } + } + + private static func moveToLegacyName(_ item: URL, in destination: URL) { + let fm = FileManager.default + let base = item.lastPathComponent + ".legacy" + var target = destination.appendingPathComponent(base) + var suffix = 2 + while fm.fileExists(atPath: target.path) { + target = destination.appendingPathComponent("\(base).\(suffix)") + suffix += 1 + } + try? fm.moveItem(at: item, to: target) + } + private static func copyItem(at src: URL, into dir: URL, named name: String) { let scoped = src.startAccessingSecurityScopedResource() defer { if scoped { src.stopAccessingSecurityScopedResource() } } diff --git a/mobile/ios/patch_love_src.py b/mobile/ios/patch_love_src.py index ef9d1b39..b6d6eb40 100644 --- a/mobile/ios/patch_love_src.py +++ b/mobile/ios/patch_love_src.py @@ -27,6 +27,8 @@ NATIVE_SRC = IOS_DIR / "native" NATIVE_DST = LOVE_SRC / "platform" / "xcode" / "ios" / "native" WRAP_SYSTEM = LOVE_SRC / "src" / "modules" / "system" / "wrap_System.cpp" PBXPROJ = LOVE_SRC / "platform" / "xcode" / "love.xcodeproj" / "project.pbxproj" +APPLE_MM = LOVE_SRC / "src" / "common" / "apple.mm" +FILESYSTEM_CPP = LOVE_SRC / "src" / "modules" / "filesystem" / "physfs" / "Filesystem.cpp" ENTITLEMENTS_SRC = IOS_DIR / "overlays" / "love-ios.entitlements" NATIVE_FILES = ("GRPickerBridge.swift", "GRHealthBridge.swift", "GRBootstrap.m") @@ -84,6 +86,49 @@ int w_pickFile(lua_State *L) return gr_callBridge(L, "GRPickerBridge", "presentPickerWithKind:saveDir:", kind); } +// love.system.pickFileKinds() -> "rom,mod,sav,stadium", or nil off iOS. +// +// So a caller can ask what this build's picker understands BEFORE opening it. +// An unknown kind is refused (GRPickerBridge), and a refusal looks exactly +// like a picker that would not open -- so a caller with a fallback worth +// showing needs to know which it is facing. A mod that guesses instead has +// no way back: before the refusal landed, an unrecognised kind wrote +// picked_rom.gb and the ROM importer deleted it. +// +// nil where there is no bridge at all, which reads the same as "no kinds". +int w_pickFileKinds(lua_State *L) +{ + Class cls = objc_getClass("GRPickerBridge"); + if (cls == nullptr) + { + lua_pushnil(L); + return 1; + } + // Fetched through the runtime: wrap_System.cpp is compiled as C++ rather + // than Objective-C++, so no Foundation type may be NAMED here -- writing + // `NSString` alone breaks the whole translation unit. objc_msgSend is a + // plain C entry point and `id` comes from objc/runtime.h, so the string + // is asked for its UTF8 bytes without ever being typed. + typedef id (*GRObj)(Class, SEL); + id kinds = ((GRObj)objc_msgSend)(cls, + sel_registerName("supportedPickerKinds")); + if (kinds == nullptr) + { + lua_pushnil(L); + return 1; + } + typedef const char *(*GRUTF8)(id, SEL); + const char *bytes = ((GRUTF8)objc_msgSend)(kinds, + sel_registerName("UTF8String")); + if (bytes == nullptr || bytes[0] == '\0') + { + lua_pushnil(L); + return 1; + } + lua_pushstring(L, bytes); + return 1; +} + int w_createFile(lua_State *L) { const char *name = luaL_optstring(L, 1, "export.sav"); @@ -101,6 +146,7 @@ int w_syncHealthSteps(lua_State *L) WRAP_REGISTRATION = """#ifdef LOVE_IOS { "pickFile", w_pickFile }, + { "pickFileKinds", w_pickFileKinds }, { "createFile", w_createFile }, { "syncHealthSteps", w_syncHealthSteps }, { "httpDownload", w_httpDownload }, @@ -201,19 +247,20 @@ def fail(msg): sys.exit(1) -def pristine(path: Path) -> str: +def pristine(path: Path, patched_markers=None) -> str: """Text of `path` before any of our patching: backed by a `.orig` stash. The stash is only trusted if it is itself unpatched; that protects against a stash accidentally taken after an earlier patch run. """ + patched_markers = tuple(patched_markers or (MARKER, ID_FILE_PICKER)) orig = path.with_suffix(path.suffix + ".orig") if orig.is_file(): text = orig.read_text() - if MARKER not in text and ID_FILE_PICKER not in text: + if not any(marker in text for marker in patched_markers): return text text = path.read_text() - if MARKER in text or ID_FILE_PICKER in text: + if any(marker in text for marker in patched_markers): fail(f"{path} is already patched and no pristine .orig stash exists;\n" f" delete {LOVE_SRC} and re-run scripts/build_ios.sh --fetch") orig.write_text(text) @@ -255,6 +302,62 @@ def patch_wrap_system(): "(pickFile/createFile/syncHealthSteps/httpDownload)") +def patch_public_documents(): + text = pristine( + APPLE_MM, + ("#ifdef LOVE_IOS\n" + "\t\t\tnsdir = NSDocumentDirectory;\n" + "#else\n",), + ) + original = ( + "\t\tcase USER_DIRECTORY_APPSUPPORT:\n" + "\t\t\tnsdir = NSApplicationSupportDirectory;\n" + "\t\t\tbreak;" + ) + replacement = ( + "\t\tcase USER_DIRECTORY_APPSUPPORT:\n" + "#ifdef LOVE_IOS\n" + "\t\t\tnsdir = NSDocumentDirectory;\n" + "#else\n" + "\t\t\tnsdir = NSApplicationSupportDirectory;\n" + "#endif\n" + "\t\t\tbreak;" + ) + if original not in text: + fail(f"iOS app-support path anchor not found in {APPLE_MM}") + APPLE_MM.write_text(text.replace(original, replacement, 1)) + + filesystem_text = pristine( + FILESYSTEM_CPP, + ("#ifdef LOVE_IOS\n" + "\t\t\tsuffix.clear();\n" + "#else\n",), + ) + filesystem_original = ( + "\t\tstd::string suffix;\n" + "\t\tif (isFused())\n" + "\t\t\tsuffix = std::string(LOVE_PATH_SEPARATOR) + saveIdentity;\n" + "\t\telse\n" + "\t\t\tsuffix = std::string(LOVE_PATH_SEPARATOR LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + saveIdentity;" + ) + filesystem_replacement = ( + "\t\tstd::string suffix;\n" + "#ifdef LOVE_IOS\n" + "\t\t\tsuffix.clear();\n" + "#else\n" + "\t\tif (isFused())\n" + "\t\t\tsuffix = std::string(LOVE_PATH_SEPARATOR) + saveIdentity;\n" + "\t\telse\n" + "\t\t\tsuffix = std::string(LOVE_PATH_SEPARATOR LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + saveIdentity;\n" + "#endif" + ) + if filesystem_original not in filesystem_text: + fail(f"iOS save directory suffix anchor not found in {FILESYSTEM_CPP}") + FILESYSTEM_CPP.write_text(filesystem_text.replace(filesystem_original, + filesystem_replacement, 1)) + print("patch_love_src: iOS save directory routed to Documents root") + + def patch_pbxproj(): text = pristine(PBXPROJ) @@ -318,6 +421,7 @@ def main(): if not LOVE_SRC.is_dir(): fail("love-src/ missing; run scripts/build_ios.sh --fetch first") copy_native_files() + patch_public_documents() patch_wrap_system() patch_pbxproj() diff --git a/mods/spanish_ui/README.md b/mods/spanish_ui/README.md new file mode 100644 index 00000000..b844ac80 --- /dev/null +++ b/mods/spanish_ui/README.md @@ -0,0 +1,27 @@ +# spanish_ui + +A Espanol translation of the game. + +Generated with `python3 tools/modkit.py translation spanish_ui`. See +`TRANSLATING.md` for how to work on it. + +## Status + +Nothing is translated yet: 601 strings are waiting in `lang/`. + +| Catalog | Entries | +|---|---| +| `lang/dialogue.lua` | 6 | +| `lang/strings.lua` | 577 | +| `lang/species_names.lua` | 3 | +| `lang/move_names.lua` | 4 | +| `lang/item_names.lua` | 5 | +| `lang/trainer_names.lua` | 1 | +| `lang/status_labels.lua` | 5 | + +## Layout + +- `manifest.json` - identity and the engine version range +- `main.lua` - registers whatever is filled in and skips whatever is not +- `lang/` - the catalogs; this is the whole job +- `assets/font/` - your glyph sheet diff --git a/mods/spanish_ui/TRANSLATING.md b/mods/spanish_ui/TRANSLATING.md new file mode 100644 index 00000000..f1cca9bc --- /dev/null +++ b/mods/spanish_ui/TRANSLATING.md @@ -0,0 +1,111 @@ +# Translating into Espanol + +Everything the player can read is one of two kinds of string, and they live +in different places for a reason. + +| lang/ file | What it is | Key | +|---|---|---| +| `dialogue.lua` | Every line of extracted script text | the original label, e.g. `_PalletTownText1` | +| `strings.lua` | Text the engine itself writes: battle messages, menus, link play | the English source string | +| `species.lua` `moves.lua` `items.lua` `trainers.lua` | Names | the vanilla id | +| `statuses.lua` | `PSN`, `BRN`, ... as they appear in the HUD | the status id | +| `font.lua` `charmap.lua` | Your glyph sheet and what draws what | see below | +| `naming.lua` | The letter grid for entering names | - | + +Fill in a value and it takes effect. Leave it `""` and that string stays in +English, so the game is playable at every point along the way. + +## Where the English is + +The catalogs hold keys and *your* text, never the original English. The +English lives next door, in `spanish_ui-worksheet/`, one tab-separated file per +catalog: + +``` +"_AbandonLearningText" "Abandon learning\n{RAM:wStringBuffer}?" +``` + +That directory is deliberately outside the mod. Extracted script text and +the vanilla names are ROM content, and `modkit pack` zips everything under +the mod directory, so a worksheet kept inside would end up in your release +whatever a `.gitignore` said. Keep it beside the mod, never in it. + +`lang/strings.lua` is the exception: those sources are the engine's own Lua +rather than anything out of the ROM, so there the key *is* the English and +you can translate straight from it. + +## Start with the font, not the text + +The engine draws from **glyph pages**: an image of 8x8 cells plus a charmap +saying which byte sequence draws which cell. The vanilla pages sit at `$60` +and `$80`. Anything from `0x100` up is free, so a new alphabet is added +rather than swapped in: + +```lua +-- lang/font.lua +return { + spanish_ui = { + image = "assets/font/spanish_ui.png", + base = 0x100, -- first code this page owns + glyphsPerRow = 16, + -- advance = 8, -- set this if your glyphs are not 8px wide + }, +} +``` + +```lua +-- lang/charmap.lua: sequence -> code, in the same order as the sheet +return { + ["A"] = 0x100, + ["B"] = 0x101, +} +``` + +The sheet is a plain PNG, 16 glyphs to a row by default, each cell 8x8, +black on white like `assets/generated/font.png`. Codes run left to right, +top to bottom from `base`. + +Sequences are matched **longest first**, so a multi-byte character and a +multi-character ligature both work and neither shadows the other: + +```lua +["\u{3042}"] = 0x120, -- one 3-byte character, one glyph +["ch"] = 0x121, -- two ASCII letters, one glyph +``` + +## Line length is counted in glyphs + +The dialogue box fits 18 glyphs a line, not 18 bytes. A 3-byte character +costs one column, and the engine will never cut a character in half. Your +own `\n` line breaks are respected exactly as written, so break lines where +they read best rather than where they fit English. + +If your glyphs are not 8px wide, set `advance` on the page and the box +re-measures. + +## Format directives must survive + +Some sources carry `%s` or `%d`: + +```lua +["Wild %s\nappeared!"] = "...", +``` + +Keep every directive, in a count that matches. Word order is yours to +change; the engine substitutes in the order the directives appear, so if +your language needs the name last, write the sentence with the `%s` last. +A translation whose directive count does not match the English is refused +at runtime and the English is drawn instead, with a line in the log saying +so - it will not crash a battle. + +## Checking your work + +```sh +python3 tools/modkit.py validate spanish_ui --base imported +python3 tools/modkit.py translation spanish_ui --refresh # pick up new engine strings +POKEPORT_DEV=1 scripts/run.sh # F5 hot-reloads lang/ +``` + +`--refresh` rewrites the catalogs from the current engine, keeping every +translation you have already written and reporting what changed. Run it +after pulling a new engine version. diff --git a/mods/spanish_ui/assets/font/README.md b/mods/spanish_ui/assets/font/README.md new file mode 100644 index 00000000..8f0f5890 --- /dev/null +++ b/mods/spanish_ui/assets/font/README.md @@ -0,0 +1,11 @@ +Put your glyph sheet here. + +A page is a PNG of 8x8 cells, 16 per row by default, black on white. Codes +run left to right and top to bottom starting at the page's `base`, so the +first cell is `base`, the second `base + 1`, and so on. + +`assets/generated/font.png` in the player's cache is the vanilla sheet at +the same scale; open it alongside yours to match weight and baseline. + +Declare the sheet in `lang/font.lua` and map sequences to codes in +`lang/charmap.lua`. diff --git a/mods/spanish_ui/lang/charmap.lua b/mods/spanish_ui/lang/charmap.lua new file mode 100644 index 00000000..ca0e3140 --- /dev/null +++ b/mods/spanish_ui/lang/charmap.lua @@ -0,0 +1,10 @@ +-- Which byte sequence draws which glyph code. +-- +-- Sequences are matched longest-first, so a multi-byte character and a +-- multi-character ligature both work: "ch" can be one glyph even though +-- "c" is also mapped. Codes here must land inside a page declared in +-- lang/font.lua. +return { + -- ["A"] = 0x100, + -- ["B"] = 0x101, +} diff --git a/mods/spanish_ui/lang/dialogue.lua b/mods/spanish_ui/lang/dialogue.lua new file mode 100644 index 00000000..8d3c16f3 --- /dev/null +++ b/mods/spanish_ui/lang/dialogue.lua @@ -0,0 +1,12 @@ +-- Script text +-- +-- Keyed by the original text label. The English is in the comment. + +return { + ["_FixMartText"] = "", + ["_FixRouteTrainerAfterText"] = "", + ["_FixRouteTrainerBattleText"] = "", + ["_FixRouteTrainerEndText"] = "", + ["_FixTownGreeterText"] = "", + ["_FixTownSignText"] = "", +} diff --git a/mods/spanish_ui/lang/font.lua b/mods/spanish_ui/lang/font.lua new file mode 100644 index 00000000..4b878079 --- /dev/null +++ b/mods/spanish_ui/lang/font.lua @@ -0,0 +1,13 @@ +-- Glyph pages this translation adds. Delete the entry if the vanilla +-- alphabet already covers your language. +-- +-- base is the first glyph code the page owns. 0x100 and up is free space +-- above the vanilla $60/$80 pages, so this adds an alphabet rather than +-- replacing one. Set `advance` if your glyphs are not 8px wide. +return { + -- spanish_ui = { + -- image = "assets/font/spanish_ui.png", + -- base = 0x100, + -- glyphsPerRow = 16, + -- }, +} diff --git a/mods/spanish_ui/lang/item_names.lua b/mods/spanish_ui/lang/item_names.lua new file mode 100644 index 00000000..2798d181 --- /dev/null +++ b/mods/spanish_ui/lang/item_names.lua @@ -0,0 +1,11 @@ +-- Item names +-- +-- Item names for Espanol. + +return { + ["FIX_BADGE_1"] = "", + ["FIX_BADGE_2"] = "", + ["FIX_BALL"] = "", + ["FIX_POTION"] = "", + ["FIX_TM"] = "", +} diff --git a/mods/spanish_ui/lang/move_names.lua b/mods/spanish_ui/lang/move_names.lua new file mode 100644 index 00000000..80abf686 --- /dev/null +++ b/mods/spanish_ui/lang/move_names.lua @@ -0,0 +1,10 @@ +-- Move names +-- +-- Move names for Espanol. + +return { + ["FIX_CUT"] = "", + ["FIX_EMBERISH"] = "", + ["FIX_SCRATCH"] = "", + ["FIX_TACKLE"] = "", +} diff --git a/mods/spanish_ui/lang/naming.lua b/mods/spanish_ui/lang/naming.lua new file mode 100644 index 00000000..adb0666b --- /dev/null +++ b/mods/spanish_ui/lang/naming.lua @@ -0,0 +1,41 @@ +-- The naming screen's letter grid. Return an empty table to keep the +-- English alphabet. +-- +-- Each entry is a row of cells; a cell is whatever sequence your charmap +-- maps, so a multi-byte character is one cell. The row holding a single +-- "lower case" / "UPPER CASE" cell is the case switch, and the cell +-- spelled "ED" is the confirm. +-- +-- The screen is 160x144 and NamingScreen draws cell `c` of row `r` at +-- (c * 16, 32 + r * 16), so the grid is capped at **9 columns and 6 rows**: +-- a 10th column lands at x=160 and a 7th row at y=144, both off screen. +-- That leaves 44 usable cells, exactly what vanilla uses, so Spanish +-- letters have to displace something rather than being added. +-- +-- What gives way is vanilla's `× ( ) : ; [ ]` row. Those are legal in a +-- Gen-1 nickname but nobody reaches for them, whereas Ñ is not optional in +-- Spanish -- and here it sits in its alphabetical place after N, which is +-- where a Spanish speaker will look for it. Space, and are kept. +-- +-- These glyphs exist in the Spanish cartridge's font ($CA Ñ, $BF Á, $C7 É, +-- $C9 Í, $CC Ó, $CE Ú, $C2 Ü and their lowercase). On an English ROM they +-- do not, so main.lua checks the running game's charmap first and keeps the +-- English grid rather than drawing blank cells. +return { + upper = { + { "A", "B", "C", "D", "E", "F", "G", "H", "I" }, + { "J", "K", "L", "M", "N", "Ñ", "O", "P", "Q" }, + { "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }, + { "Á", "É", "Í", "Ó", "Ú", "Ü", " ", "", "" }, + { "-", "?", "!", "♂", "♀", "/", ".", ",", "ED" }, + { "lower case" }, + }, + lower = { + { "a", "b", "c", "d", "e", "f", "g", "h", "i" }, + { "j", "k", "l", "m", "n", "ñ", "o", "p", "q" }, + { "r", "s", "t", "u", "v", "w", "x", "y", "z" }, + { "á", "é", "í", "ó", "ú", "ü", " ", "", "" }, + { "-", "?", "!", "♂", "♀", "/", ".", ",", "ED" }, + { "UPPER CASE" }, + }, +} diff --git a/mods/spanish_ui/lang/species_names.lua b/mods/spanish_ui/lang/species_names.lua new file mode 100644 index 00000000..7a4e5645 --- /dev/null +++ b/mods/spanish_ui/lang/species_names.lua @@ -0,0 +1,9 @@ +-- Species names +-- +-- Species names for Espanol. + +return { + ["FIXMON_A"] = "", + ["FIXMON_B"] = "", + ["FIXMON_C"] = "", +} diff --git a/mods/spanish_ui/lang/status_labels.lua b/mods/spanish_ui/lang/status_labels.lua new file mode 100644 index 00000000..b3cd3365 --- /dev/null +++ b/mods/spanish_ui/lang/status_labels.lua @@ -0,0 +1,11 @@ +-- Status labels +-- +-- Short enough for the battle HUD: the vanilla ones are three glyphs. + +return { + ["BRN"] = "", + ["FRZ"] = "", + ["PAR"] = "", + ["PSN"] = "", + ["SLP"] = "", +} diff --git a/mods/spanish_ui/lang/strings.lua b/mods/spanish_ui/lang/strings.lua new file mode 100644 index 00000000..c0858d3c --- /dev/null +++ b/mods/spanish_ui/lang/strings.lua @@ -0,0 +1,584 @@ +-- Engine text +-- +-- Keyed by the English source, which is also what draws if you leave +-- an entry empty. Keep any %s / %d directives. + +return { + ["%s\nflew up high!"] = "¡%s\nvoló muy alto!", + ["%s\ndug a hole!"] = "¡%s\ncavó un hoyo!", + ["%s\nmade a whirlwind!"] = "¡%s\ncreó un torbellino!", + ["%s\ntook in sunlight!"] = "¡%s\nabsorbió luz!", + ["%s\nlowered its head!"] = "¡%s\nbajó la cabeza!", + ["%s\nis glowing!"] = "¡%s\nestá brillando!", + ["The hooked\n%s\nattacked!"] = "¡El %s\nenganchado atacó!", + ["Wild %s\nappeared!"] = "¡Un %s\nsalvaje apareció!", + ["%s wants\nto fight!"] = "¡%s\nquiere luchar!", + ["The GHOST\nappeared!"] = "¡Apareció el\nFANTASMA!", + ["Go! %s!"] = "¡Ve, %s!", + ["Do it! %s!"] = "¡Hazlo, %s!", + ["Get'm! %s!"] = "¡A por él, %s!", + ["The enemy's weak!\nGet'm! %s!"] = "¡Está débil!\n¡A por él, %s!", + ["%s is out of\nuseable POKéMON!"] = "¡%s no tiene\nPOKéMON útiles!", + ["%s blacked\nout!"] = "¡%s se\ndebilitó!", + ["%s sent\nout %s!"] = "¡%s envió\na %s!", + ["PA: You're out of\nSAFARI BALLs!\nGame over!"] = "AV: ¡No te quedan\nSAFARI BALLs!\n¡Fin del juego!", + ["%s is too\nscared to move!"] = "¡%s tiene\ndemasiado miedo!", + ["%s has no\nmoves left!"] = "¡%s no tiene\nmovimientos!", + ["The move is\ndisabled!"] = "¡El movimiento\nestá anulado!", + ["No PP left for\nthis move!"] = "¡No quedan PP para\neste movimiento!", + ["But, it failed!"] = "¡Pero falló!", + ["%s\nlearned\n%s!"] = "¡%s\naprendió\n%s!", + ["POKé BALL"] = "POKé BALL", + ["%s used\nPOKé BALL!"] = "¡%s usó\nPOKé BALL!", + ["All right!\n%s was\ncaught!"] = "¡Bien!\n¡%s fue\ncapturado!", + ["GHOST: Get out...\nGet out..."] = "FANTASMA: Fuera...\nFuera...", + ["%s with-\ndrew %s!"] = "¡%s retiró\na %s!", + ["%s\nmust recharge!"] = "¡%s debe\nrecargarse!", + ["%s\nis fast asleep!"] = "¡%s está\nprofundamente dormido!", + ["%s\nis confused!"] = "¡%s está\nconfuso!", + ["%s\nwoke up!"] = "¡%s se\ndespertó!", + ["%s\nis frozen solid!"] = "¡%s está\ncongelado!", + ["%s\ncan't move!"] = "¡%s no\npuede moverse!", + ["%s\nflinched!"] = "¡%s se\namedrentó!", + ["It hurt itself in\nits confusion!"] = "¡Se hirió a sí\nmismo por confusión!", + ["%s\nused %s!"] = "¡%s usó\n%s!", + ["%s\nis charging up!"] = "¡%s está\ncargando energía!", + ["%s's\nattack missed!"] = "¡El ataque de %s\nfalló!", + ["%s's\nattack continues!"] = "¡El ataque de %s\ncontinúa!", + ["%s\nis storing energy!"] = "¡%s está\nacumulando energía!", + ["%s\nunleashed energy!"] = "¡%s liberó\nsu energía!", + ["%s's\nSUBSTITUTE broke!"] = "¡El SUSTITUTO de\n%s se rompió!", + ["The SUBSTITUTE\ntook damage for\n%s!"] = "¡El SUSTITUTO\nrecibió el daño\nde %s!", + ["%s's\nRAGE is building!"] = "¡La FURIA de %s\nva creciendo!", + ["%s\nfainted!"] = "¡%s se\ndebilitó!", + ["%s gained\n%d EXP. Points!"] = "¡%s ganó\n%d P. EXP.!", + ["%s gained\nwith EXP.ALL,\v%d EXP. Points!"] = "¡%s ganó\ncon EXP.TODOS,\v%d P. EXP.!", + ["%s gained\na boosted\v%d EXP. Points!"] = "¡%s ganó\nun extra de\v%d P. EXP.!", + ["%s grew\nto level %d!"] = "¡%s subió\nal nivel %d!", + ["%s is\nabout to use"] = "%s va a usar", + ["%s!"] = "¡%s!", + ["Will %s\nchange POKéMON?"] = "¿%s va a\ncambiar de POKéMON?", + ["%s defeated\n%s!"] = "¡%s venció\na %s!", + ["%s got ¥%d\nfor winning!"] = "¡%s ganó\n¥%d!", + ["%s learned\n%s!"] = "¡%s aprendió\n%s!", + ["{RIVAL}: Yeah! Am\nI great or what?"] = "{RIVAL}: ¡Sí! ¿Soy\ngenial o qué?", + ["Use next POKéMON?"] = "¿Sacar al siguiente?", + ["Got away safely!"] = "¡Escapaste!", + ["Can't escape!"] = "¡No puedes escapar!", + ["There's no will\nto fight!"] = "¡No hay ganas de\nluchar!", + ["%s used\nSAFARI BALL!"] = "¡%s usó\nSAFARI BALL!", + ["%s threw some\nBAIT."] = "%s echó\nCEBO.", + ["%s threw a\nROCK."] = "%s tiró una\nPIEDRA.", + ["Wild %s\nis eating!"] = "¡El %s\nsalvaje come!", + ["Wild %s\nis angry!"] = "¡El %s\nsalvaje se enfadó!", + ["Wild %s\nran!"] = "¡El %s\nsalvaje huyó!", + ["No! There's no\nrunning from a\vtrainer battle!"] = "¡No! ¡No puedes\nhuir de un combate\vcontra un entrenador!", + ["You missed the\nPOKéMON!"] = "¡Fallaste el tiro!", + ["Darn! The POKéMON\nbroke free!"] = "¡Vaya! ¡El POKéMON\nse escapó!", + ["Aww! It appeared\nto be caught!"] = "¡Oh! ¡Parecía que\nestaba capturado!", + ["Shoot! It was so\nclose too!"] = "¡Vaya! ¡Estuvo\nmuy cerca!", + ["Do you want to\ngive a nickname\nto %s?"] = "¿Quieres poner un\nmote a\n%s?", + ["NICKNAME?"] = "MOTE?", + ["New POKéDEX data\nwill be added for\n%s!"] = "¡Se añadirán datos\nnuevos a la POKéDEX\nde %s!", + ["someone's PC"] = "el PC de alguien", + ["%s was\ntransferred to\n%s!"] = "¡%s fue\ntransferido a\n%s!", + ["But every BOX\nis full!"] = "¡Pero todas las\nCAJAS están llenas!", + ["%s used\n%s!"] = "¡%s usó\n%s!", + ["The trainer\nblocked the BALL!"] = "¡El entrenador\nbloqueó la BALL!", + ["Don't be a thief!"] = "¡No seas ladrón!", + ["It dodged the\nthrown BALL!"] = "¡Esquivó la BALL!", + ["This POKéMON\ncan't be caught!"] = "¡Este POKéMON no\nse puede capturar!", + ["%s is\nalready out!"] = "¡%s ya\nestá fuera!", + ["%s picked up\n¥%d!"] = "¡%s recogió\n¥%d!", + ["FIGHT"] = "LUCHAR", + ["ITEM"] = "OBJETO", + ["RUN"] = "HUIR", + ["BALLx"] = "BALLx", + ["BAIT"] = "CEBO", + ["THROW ROCK"] = "TIRAR PIEDRA", + ["disabled!"] = "¡anulado!", + ["TYPE/"] = "TIPO/", + ["It doesn't affect\n%s!"] = "¡No afecta a\n%s!", + ["Critical hit!"] = "¡Golpe crítico!", + ["One-hit KO!"] = "¡KO en un golpe!", + ["It's super\neffective!"] = "¡Es muy eficaz!", + ["It's not very\neffective..."] = "¡No es muy\neficaz...", + ["Hit the enemy\n%d times!"] = "¡Golpeó al enemigo\n%d veces!", + ["Hit %d times!"] = "¡Golpeó %d veces!", + ["%s's\nhit with recoil!"] = "¡%s sufrió\nel retroceso!", + ["%s is\nprotected by MIST!"] = "¡%s está\nprotegido por NIEBLA!", + ["Nothing happened!"] = "¡No pasó nada!", + ["%s's\n%s\ngreatly rose!"] = "¡%s\nmejoró mucho su\n%s!", + ["%s's\n%s rose!"] = "¡%s mejoró\nsu %s!", + ["%s's\n%s fell!"] = "¡%s bajó\nsu %s!", + ["%s's\n%s\ngreatly fell!"] = "¡%s\nbajó mucho su\n%s!", + ["Fire defrosted\n%s!"] = "¡El fuego descongeló\na %s!", + ["%s\nbecame confused!"] = "¡%s se\nconfundió!", + ["%s\nwas seeded!"] = "¡%s recibió\nla DRENADORA!", + ["%s\nstarted sleeping!"] = "¡%s se\nquedó dormido!", + ["%s\nregained health!"] = "¡%s recuperó\nsalud!", + ["%s's\nprotected against\nspecial attacks!"] = "¡%s está\nprotegido de los\nataques especiales!", + ["%s\ngained armor!"] = "¡%s ganó\narmadura!", + ["%s's\nshrouded in mist!"] = "¡%s se\ncubrió de niebla!", + ["%s's\ngetting pumped!"] = "¡%s se\nestá animando!", + ["All STATUS changes\nare eliminated!"] = "¡Los cambios de\nESTADO desaparecen!", + ["%s\nhas a SUBSTITUTE!"] = "¡%s tiene\nun SUSTITUTO!", + ["Too weak to make\na SUBSTITUTE!"] = "¡Muy débil para\nhacer un SUSTITUTO!", + ["It created a\nSUBSTITUTE!"] = "¡Creó un SUSTITUTO!", + ["Converted type to\n%s's!"] = "¡Cambió su tipo al\nde %s!", + ["%s\ntransformed into\n%s!"] = "¡%s se\ntransformó en\n%s!", + ["%s's\n%s was\ndisabled!"] = "¡El %s\nde %s\nfue anulado!", + ["No effect!"] = "¡Sin efecto!", + ["Sucked health from\n%s!"] = "¡Absorbió salud de\n%s!", + ["%s's\ndream was eaten!"] = "¡Devoró el sueño\nde %s!", + ["%s\nkept going and\ncrashed!"] = "¡%s siguió\nadelante y se\nestrelló!", + ["Coins scattered\neverywhere!"] = "¡Las monedas se\ndesparramaron!", + ["%s\nran away scared!"] = "¡%s huyó\nasustado!", + ["%s\nwas blown away!"] = "¡%s salió\nvolando!", + ["%s\nran from battle!"] = "¡%s huyó\ndel combate!", + ["It didn't affect\n%s!"] = "¡No afectó a\n%s!", + ["%s\nis unaffected!"] = "¡%s no se\nvio afectado!", + ["The MIRROR MOVE\nfailed!"] = "¡El MOVIMIENTO\nESPEJO falló!", + ["%s\nfell asleep!"] = "¡%s se\nquedó dormido!", + ["%s\nwas frozen solid!"] = "¡%s se\ncongeló!", + ["%s's\nhurt by poison!"] = "¡El veneno hiere a\n%s!", + ["%s's\nbadly poisoned!"] = "¡%s está\ngravemente envenenado!", + ["%s\nwas poisoned!"] = "¡%s fue\nenvenenado!", + ["%s's\nhurt by the burn!"] = "¡La quemadura hiere\na %s!", + ["%s\nwas burned!"] = "¡%s se\nquemó!", + ["%s's\nfully paralyzed!"] = "¡%s está\ntotalmente paralizado!", + ["%s's\nparalyzed! It may\nnot attack!"] = "¡%s está\nparalizado! ¡Puede\nque no ataque!", + ["%s's\ndisabled no more!"] = "¡%s ya no\nestá anulado!", + ["%s\nsnapped out of\nconfusion!"] = "¡%s salió\nde su confusión!", + ["LEECH SEED saps\n%s!"] = "¡La DRENADORA\nabsorbe a %s!", + ["%s\nwas afflicted\nby %s!"] = "¡%s sufre\n%s!", + ["%s's\nprotected against\nstat changes!"] = "¡%s está\nprotegido de los\ncambios de estado!", + ["What will"] = "¿Qué va a hacer", + [" do?"] = "?", + ["You can't get off\nhere."] = "No puedes bajarte\naquí.", + ["%s got off\nthe BICYCLE."] = "%s se bajó\nde la BICICLETA.", + ["%s got on\nthe BICYCLE!"] = "¡%s se subió\na la BICICLETA!", + ["No cycling\nallowed here."] = "No se puede montar\naquí.", + ["No good! It's not\neven near water."] = "¡No sirve! No hay\nagua cerca.", + ["OAK: %s!\nThis isn't the\ntime to use that!"] = "OAK: ¡%s!\n¡No es momento\nde usar eso!", + ["The TOWN MAP is\nunreadable here."] = "El MAPA PUEBLO no\nse puede leer aquí.", + ["Yes! ITEMFINDER\nindicates there's\nan item nearby."] = "¡Sí! El BUSCAOBJ.\nindica que hay algo\ncerca.", + ["Nope! ITEMFINDER\nisn't responding."] = "¡No! El BUSCAOBJ.\nno responde.", + ["Booted up a TM!"] = "¡Se activó una MT!", + ["It contained\n%s!"] = "¡Contenía\n%s!", + ["USE"] = "USAR", + ["TOSS"] = "TIRAR", + ["That's too impor-\ntant to toss!"] = "¡Es demasiado\nimportante!", + ["Threw away\n%s."] = "Tiraste\n%s.", + ["PRESS A BUTTON"] = "PULSA UN BOTON", + ["ESC TO CANCEL"] = "ESC PARA CANCELAR", + ["%s :L%d"] = "%s :N%d", + ["STATS"] = "DATOS", + ["CANCEL"] = "CANCELAR", + ["What? There are\nno POKéMON here!"] = "¿Qué? ¡Aquí no hay\nningún POKéMON!", + ["You can't take\nany more POKéMON.\fDeposit POKéMON\nfirst."] = "No puedes llevar\nmás POKéMON.\fGuarda alguno\nprimero.", + ["BOX %d (WITHDRAW)"] = "CAJA %d (RETIRAR)", + ["%s is\ntaken out.\vGot %s."] = "Retirado\n%s.\vRecibes %s.", + ["You can't deposit\nthe last POKéMON!"] = "¡No puedes guardar\nel último POKéMON!", + ["Oops! This Box is\nfull of POKéMON."] = "¡Uups! Esta CAJA\nestá llena.", + ["You need at least\none POKéMON!"] = "¡Necesitas al menos\nun POKéMON!", + ["BOX %d is full!"] = "¡La CAJA %d está\nllena!", + ["%s was\nstored in Box %s."] = "%s se\nguardó en la CAJA %s.", + ["BOX %d (RELEASE)"] = "CAJA %d (SOLTAR)", + ["Once released,\n%s is\ngone forever. OK?"] = "Si lo sueltas,\n%s se\nirá para siempre. ¿OK?", + ["%s was\nreleased outside.\fBye %s!"] = "%s fue\nliberado.\f¡Adiós, %s!", + ["%sBOX %2d"] = "%sCAJA %2d", + ["When you change a\nPOKéMON BOX, data\nwill be saved. OK?"] = "Al cambiar de CAJA\nse guardarán los\ndatos. ¿OK?", + ["What?"] = "¿Qué?", + ["BOX No."] = "CAJA No.", + ["BOX No.%d"] = "CAJA No.%d", + ["Empty."] = "Vacía.", + [":L%d No.%03d"] = ":N%d No.%03d", + ["Printed BOX %d!\fSaved as\n%s\vin the save\nfolder."] = "¡CAJA %d impresa!\fGuardada como\n%s\ven la carpeta de\nguardado.", + ["Printer error!\n%s"] = "¡Error de impresión!\n%s", + ["WITHDRAW "] = "RETIRAR ", + ["DEPOSIT "] = "GUARDAR ", + ["RELEASE "] = "SOLTAR ", + ["CHANGE BOX"] = "CAMBIAR CAJA", + ["PRINT BOX"] = "IMPRIMIR CAJA", + ["SEE YA!"] = "HASTA LUEGO!", + ["YES"] = "SI", + ["NO"] = "NO", + ["GAME FREAK"] = "", + ["Nintendo"] = "", + ["Creatures inc."] = "", + ["GAME FREAK inc."] = "", + ["T H E E N D"] = "F I N", + ["HT %d′%02d″"] = "AL %d′%02d″", + ["WT %.1flb"] = "PE %.1flb", + ["Data unknown."] = "Datos desconocidos.", + [""] = "", + ["Player"] = "Jugador", + ["Huh? %s\nstopped evolving!"] = "¿Eh? ¡%s\ndejó de evolucionar!", + ["Congratulations!\nYour %s\nevolved into\n%s!"] = "¡Enhorabuena!\n¡Tu %s\nevolucionó a\n%s!", + ["evolving!"] = "evolucionando", + ["POKéDEX Seen:{NUM:wDexRatingNumMonsSeen, 1, 3}\n Owned:{NUM:wDexRatingNumMonsOwned, 1, 3}"] = "POKéDEX Vistos:{NUM:wDexRatingNumMonsSeen, 1, 3}\n Capturados:{NUM:wDexRatingNumMonsOwned, 1, 3}", + ["POKéDEX Rating{COLON}"] = "Nota POKéDEX{COLON}", + ["Keep it up!"] = "¡Sigue así!", + ["LEVEL/"] = "NIVEL/", + ["TYPE1/"] = "TIPO1/", + ["TYPE2/"] = "TIPO2/", + ["HALL OF FAME"] = "SALON DE LA FAMA", + ["PLAY TIME"] = "TIEMPO", + ["MONEY"] = "DINERO", + ["bois club games"] = "bois club games", + ["GENGAR VS NIDORINO"] = "", + ["bois club"] = "bois club", + ["Nothing here."] = "Aquí no hay nada.", + ["%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f"] = "¡%s está\nintentando aprender\v%s!\f¡Pero %s\nno puede aprender\vmás de 4!\f", + ["Delete an older\nmove to make room\vfor %s?"] = "¿Borrar un movi-\nmiento antiguo para\vaprender %s?", + ["HM techniques\ncan't be deleted!"] = "¡Los movimientos MO\nno se pueden borrar!", + ["Abandon learning\n%s?"] = "¿Dejar de aprender\n%s?", + ["1, 2 and... Poof!\f%s forgot\n%s!\fAnd...\f%s learned\n%s!"] = "¡1, 2 y... plaf!\f¡%s olvidó\n%s!\f¡Y...\f%s aprendió\n%s!", + ["%s\ndid not learn\v%s!"] = "¡%s no\naprendió\v%s!", + ["Which move should"] = "¿Qué movimiento", + ["be forgotten?"] = "hay que olvidar?", + ["YOUR NAME?"] = "TU NOMBRE?", + ["NEW NAME"] = "NUEVO NOMBRE", + ["Hello there!\nWelcome to the\vworld of POKéMON!\fMy name is OAK!\nPeople call me\vthe POKéMON PROF!"] = "¡Hola!\n¡Bienvenido al\vmundo POKéMON!\fMe llamo OAK.\nMe llaman el\vPROF. POKéMON.", + ["This world is\ninhabited by\vcreatures called\vPOKéMON!"] = "¡Este mundo está\nhabitado por unas\vcriaturas llamadas\vPOKéMON!", + ["\fFor some people,\nPOKéMON are\vpets. Others use\vthem for fights.\fMyself...\fI study POKéMON\nas a profession."] = "\fPara algunos, los\nPOKéMON son masco-\vtas. Otros luchan\vcon ellos.\fYo...\fEstudio los POKéMON\ncomo profesión.", + ["{PLAYER}!\fYour very own\nPOKéMON legend is\vabout to unfold!\fA world of dreams\nand adventures\vwith POKéMON\vawaits! Let's go!"] = "¡{PLAYER}!\f¡Tu propia leyenda\nPOKéMON está a\vpunto de comenzar!\f¡Un mundo de sueños\ny aventuras con\vPOKéMON te espera!\v¡Vamos!", + ["First, what is\nyour name?"] = "¿Cómo te llamas?", + ["This is my grand-\nson. He's been\vyour rival since\vyou were a baby.\f...Erm, what is\nhis name again?"] = "Este es mi nieto.\nHa sido tu rival\vdesde que erais\vbebés.\f...Mmm, ¿cómo se\nllamaba?", + ["HIS NAME?"] = "SU NOMBRE?", + ["_OakSpeechText2A"] = "", + ["TEXT SPEED"] = "VEL TEXTO", + ["BATTLE ANIMATION"] = "ANIMACIONES", + ["OFF"] = "NO", + ["ON"] = "SI", + ["BATTLE STYLE"] = "ESTILO COMBATE", + ["SET"] = "FIJO", + ["SHIFT"] = "CAMBIO", + ["BATTLE LAYOUT"] = "DISENO COMBATE", + ["WIDE"] = "ANCHO", + ["OG"] = "OG", + ["RULESET"] = "REGLAS", + ["MUSIC VOL"] = "VOL MUSICA", + ["SFX VOL"] = "VOL SONIDO", + ["PIKACHU VOL"] = "VOL PIKACHU", + ["MUSIC FILTER"] = "FILTRO MUSICA", + ["COLORS"] = "COLORES", + ["TILT"] = "INCLINACION", + ["GBC FX"] = "EFECTO GBC", + ["ZOOM"] = "ZOOM", + ["VOID FILL"] = "RELLENO VACIO", + ["VIDEO MODE"] = "MODO VIDEO", + ["MAX FPS"] = "FPS MAXIMO", + ["GAME SPEED"] = "VELOCIDAD JUEGO", + ["MODS"] = "MODS", + ["%d INSTALLED"] = "%d INSTALADOS", + ["CONTROLS"] = "CONTROLES", + ["TOUCH PAD"] = "CONTROL TACTIL", + ["SURE? AGAIN"] = "SEGURO? OTRA VEZ", + ["AUTO HIDE PAD"] = "OCULTAR AUTO", + ["A blinding FLASH\nlights the area!"] = "¡Un DESTELLO\nilumina la zona!", + ["No SURFing here!"] = "¡Aquí no se puede\nSURFEAR!", + ["Nothing to CUT!"] = "¡Nada que CORTAR!", + ["{RAM:wNameBuffer} used\nSTRENGTH."] = "{RAM:wNameBuffer} usó\nFUERZA.", + ["{RAM:wNameBuffer} can\nmove boulders."] = "{RAM:wNameBuffer} puede\nmover rocas.", + ["It won't have\nany effect."] = "No tendrá ningún\nefecto.", + ["%s's HP\nwas restored!"] = "¡Los PS de %s\nse recuperaron!", + ["SWITCH"] = "CAMBIAR", + ["FLY"] = "VUELO", + ["FLASH"] = "DESTELLO", + ["CUT"] = "CORTE", + ["SURF"] = "SURF", + ["STRENGTH"] = "FUERZA", + ["SOFTBOILED"] = "HUEVO SUERTE", + ["TELEPORT"] = "TELETRANSPORTE", + ["DIG"] = "EXCAVAR", + ["Use TM on which\nPOKéMON?"] = "¿Usar la MT en qué\nPOKéMON?", + ["Bring out which\nPOKéMON?"] = "¿Qué POKéMON\nquieres sacar?", + ["Choose a POKéMON."] = "Elige un POKéMON.", + ["No POKéMON!"] = "¡Ningún POKéMON!", + ["ABLE"] = "PUEDE", + ["NOT ABLE"] = "NO PUEDE", + ["FNT"] = "DEB", + ["Move to where?"] = "¿Mover a dónde?", + ["Use on which one?"] = "¿Usar en cuál?", + ["You can't carry\nany more items."] = "No puedes llevar\nmás objetos.", + ["Withdrew\n%s."] = "Retirado\n%s.", + ["No room left to\nstore items."] = "No queda sitio para\nguardar objetos.", + ["%s was\nstored via PC."] = "%s se\nguardó en el PC.", + ["Toss %s?"] = "¿Tirar %s?", + ["Threw away %s."] = "Tiraste %s.", + ["WITHDRAW ITEM"] = "RETIRAR OBJETO", + ["DEPOSIT ITEM"] = "GUARDAR OBJETO", + ["TOSS ITEM"] = "TIRAR OBJETO", + ["LOG OFF"] = "SALIR", + ["SEEN %d OWNED %d"] = "VISTOS %d CAPT. %d", + ["DATA"] = "DATOS", + ["CRY"] = "VOZ", + ["AREA"] = "ZONA", + ["PRNT"] = "IMPR", + ["Printed %s's\ndata!\fSaved as\n%s\vin the save\nfolder."] = "¡Datos de %s\nimpresos!\fGuardado como\n%s\ven la carpeta de\nguardado.", + ["QUIT"] = "SALIR", + ["%s (%s)"] = "%s (%s)", + ["%s x%d"] = "%s x%d", + ["%s to box %d"] = "%s a caja %d", + ["LOAD REPORT"] = "CARGAR PARTIDA", + ["A:CONTINUE"] = "A:CONTINUAR", + ["You don't have\nenough money."] = "No tienes dinero\nsuficiente.", + ["%s?\nThat will be\n¥%d. OK?"] = "¿%s?\nSon ¥%d.\n¿OK?", + ["Here you are!\nThank you!"] = "¡Aquí tienes!\n¡Gracias!", + ["I can't put a\nprice on that."] = "No puedo ponerle\nprecio a eso.", + ["I can pay you\n¥%d for that."] = "Te doy ¥%d\npor eso.", + ["BUY"] = "COMPRAR", + ["SELL"] = "VENDER", + ["%s lined up!\nScored %d coins!"] = "¡%s alineados!\n¡%d fichas!", + ["Darn!\nRan out of coins!"] = "¡Vaya!\n¡Sin fichas!", + ["Not enough\ncoins!"] = "¡Fichas\ninsuficientes!", + ["SLOT MACHINE"] = "MAQUINA TRAGAPERRAS", + ["COINS %4d"] = "FICHAS %4d", + ["POKéDEX"] = "POKéDEX", + ["POKéMON"] = "POKéMON", + ["SAVE"] = "GUARDAR", + ["PLAYER %s\nBADGES %d\nPOKéDEX %3d\nTIME %6d:%02d"] = "JUGADOR %s\nMEDALLAS %d\nPOKéDEX %3d\nTIEMPO %6d:%02d", + ["\fWould you like to\nSAVE the game?"] = "\f¿Quieres GUARDAR\nla partida?", + ["Now saving..."] = "Guardando...", + ["%s saved\nthe game!"] = "¡%s guardó\nla partida!", + ["OPTION"] = "OPCION", + ["LINK"] = "LINK", + ["RETURN TO MAIN\nMENU?"] = "¿VOLVER AL MENU\nPRINCIPAL?", + ["BALL"] = "BALL", + ["STATUS/"] = "ESTADO/", + ["OT/"] = "EO/", + ["EXP POINTS"] = "P. EXP.", + ["LEVEL UP"] = "SUBE NIVEL", + ["PP"] = "PP", + ["SCORE %d"] = "PUNTOS %d", + ["New record!"] = "¡Nuevo récord!", + ["HI %d"] = "MAX %d", + ["A: done"] = "A: listo", + ["PLAYER"] = "JUGADOR", + ["BADGES"] = "MEDALLAS", + ["TIME"] = "TIEMPO", + ["CONTINUE"] = "CONTINUAR", + ["NEW GAME"] = "NUEVA PARTIDA", + ["EXIT GAME"] = "SALIR DEL JUEGO", + ["POKéMON RED"] = "", + ["2026 bois club games"] = "", + ["OT/%s"] = "EO/%s", + ["NAME/%s"] = "NOMBRE/%s", + ["In battle"] = "En combate", + ["Wild battle"] = "Combate salvaje", + ["Trainer battle"] = "Combate entrenador", + ["Link battle"] = "Combate link", + ["Title screen"] = "Pantalla de título", + ["Level %d"] = "Nivel %d", + ["What?\n%s is\nevolving!\fCongratulations!\nYour %s\nevolved into\n%s!"] = "¿Qué?\n¡%s está\nevolucionando!\f¡Enhorabuena!\n¡Tu %s\nevolucionó a\n%s!", + ["Not even a nibble!"] = "¡Ni un mordisco!", + ["Oh!\nIt's a bite!"] = "¡Oh!\n¡Ha picado!", + ["It's a sculpture\nof DIGLETT."] = "Es una escultura\nde DIGLETT.", + ["Crammed full of\nPOKéMON books!"] = "¡Repleto de libros\nsobre POKéMON!", + ["There's a slew of\nPOKéMON stuff!"] = "¡Hay un montón de\ncosas POKéMON!", + ["An elevator!"] = "¡Un ascensor!", + ["INDIGO PLATEAU"] = "MESETA ANIL", + ["POKéMON LEAGUE HQ"] = "SEDE DE LA LIGA\nPOKéMON", + ["You can't carry\nany more items!"] = "¡No puedes llevar\nmás objetos!", + ["%s found\n%s!"] = "¡%s encontró\n%s!", + ["%s found\n%d coins!"] = "¡%s encontró\n%d fichas!", + ["OUT OF ORDER\nThis is broken."] = "FUERA DE SERVICIO\nEsto está roto.", + ["OUT TO LUNCH\nThis is reserved."] = "CERRADO POR COMIDA\nEsto está reservado.", + ["Someone's keys!\nThey'll be back."] = "¡Las llaves de\nalguien! Volverá.", + ["A COIN CASE is\nrequired!"] = "¡Se necesita un\nMONEDERO!", + ["You don't have\nany coins!"] = "¡No tienes fichas!", + ["{RAM}\nPOKéMON GYM\nLEADER: {RAM}"] = "{RAM}\nGIMNASIO POKéMON\nLIDER: {RAM}", + ["Nope, there's\nonly trash here."] = "No, aquí solo hay\nbasura.", + ["Darn! It needs a\nCARD KEY!"] = "¡Vaya! ¡Necesita\nuna LLAVE MAGNET.!", + ["Bingo!"] = "¡Bingo!", + ["\nThe CARD KEY\nopened the door!"] = "\n¡La LLAVE MAGNET.\nabrió la puerta!", + ["Hey! There's a\nswitch under the\ntrash!\fThe 1st electric\nlock opened!"] = "¡Hay un interruptor\nbajo la basura!\f¡Se abrió el 1er\ncierre eléctrico!", + ["The 2nd electric\nlock opened!\fThe motorized door\nopened!"] = "¡Se abrió el 2o\ncierre eléctrico!\f¡La puerta se\nabrió!", + ["Nope! There's\nonly trash here.\fHey! The electric\nlocks were reset!"] = "¡No! Aquí solo hay\nbasura.\f¡Los cierres se\nreiniciaron!", + ["TELEPORTER is\ndisplayed on the\nPC monitor."] = "El TELETRANSPORTE\naparece en el\nmonitor del PC.", + ["{PLAYER} initiated\nTELEPORTER's Cell\nSeparator!"] = "¡{PLAYER} activó el\nSeparador de Células\ndel TELETRANSPORTE!", + ["BILL's favorite\nPOKéMON list!"] = "¡La lista de POKéMON\nfavoritos de BILL!", + ["{PLAYER} got on\n{RAM:wNameBuffer}!"] = "¡{PLAYER} se subió\na {RAM:wNameBuffer}!", + ["{RAM:wNameBuffer} hacked\naway with CUT!"] = "¡{RAM:wNameBuffer} cortó\ncon CORTE!", + ["Gyaoo!"] = "¡Gyaoo!", + ["Hi there!\nMay I help you?"] = "¡Hola!\n¿Puedo ayudarte?", + ["SOMEONE'S PC"] = "EL PC DE ALGUIEN", + ["PROF.OAK's PC"] = "EL PC DEL PROF.OAK", + ["POKéDEX comp-\nletion is:\f{NUM:hDexRatingNumMonsSeen} POKéMON seen\n{NUM:hDexRatingNumMonsOwned} POKéMON owned\fPROF.OAK's\nRating:"] = "La POKéDEX está\nasí:\f{NUM:hDexRatingNumMonsSeen} POKéMON vistos\n{NUM:hDexRatingNumMonsOwned} POKéMON capturados\fNota del\nPROF.OAK:", + ["We hope to see\nyou again!"] = "¡Esperamos verte\nde nuevo!", + ["Welcome to our\nPOKéMON CENTER!"] = "¡Bienvenido a\nnuestro CENTRO\nPOKéMON!", + ["Shall we heal your\nPOKéMON?"] = "¿Curamos a tus\nPOKéMON?", + ["OK. We'll need\nyour POKéMON."] = "Bien. Necesitamos\ntus POKéMON.", + ["Your POKéMON are\nfighting fit!"] = "¡Tus POKéMON están\nen plena forma!", + ["Welcome to the\nCable Club!"] = "¡Bienvenido al Club\nde Cable!", + ["We're making\npreparations.\vPlease wait."] = "Estamos preparando\ntodo.\vEspera un momento.", + ["Please apply here.\fBefore opening\nthe link, we have\vto save the game."] = "Solicítalo aquí.\fAntes de abrir el\nlink hay que\vguardar la partida.", + ["Please come\nagain!"] = "¡Vuelve pronto!", + ["I like shorts!\nThey're comfy and\neasy to wear!"] = "¡Me gustan los\npantalones cortos!\n¡Son cómodos!", + ["%s received\nthe %s!"] = "¡%s recibió\nel %s!", + ["%s received\n%s!"] = "¡%s recibió\n%s!", + ["REPEL's effect\nwore off."] = "El efecto del REPEL\nse ha pasado.", + ["Go right ahead!"] = "¡Adelante!", + ["You don't have the\nBOULDERBADGE yet!"] = "¡Aún no tienes la\nMEDALLA ROCA!", + ["Oh! That is the\n{RAM}!"] = "¡Oh! ¡Eso es el\n{RAM}!", + ["You don't have the\n{RAM} yet!"] = "¡Aún no tienes el\n{RAM}!", + ["You need a\nBICYCLE for the\nCycling Road!"] = "¡Necesitas una\nBICICLETA para el\nCarril Bici!", + ["The boulder fell\nthrough the hole!"] = "¡La roca cayó por\nel agujero!", + ["PA: Ding-dong!\nTime's up!"] = "AV: ¡Ding-dong!\n¡Se acabó el tiempo!", + ["PA: Your SAFARI\nGAME is over!"] = "AV: ¡Tu JUEGO\nSAFARI ha terminado!", + ["PA: You're out of\nSAFARI BALLs!"] = "AV: ¡No te quedan\nSAFARI BALLs!", + ["{PLAYER} got\n%s!"] = "¡{PLAYER} consiguió\n%s!", + ["There's no more\nroom for POKéMON!\v%s was\vsent to POKéMON\vBOX %s on PC!"] = "¡No hay sitio para\nmás POKéMON!\v¡%s fue\venviado a la CAJA\vPOKéMON %s del PC!", + ["contribution is not a table"] = "", + [" [%s %s.%s]"] = " [%s %s.%s]", + ["Link battle needs\nthe same mods on\nboth games."] = "El combate link\nnecesita los mismos\nmods en los dos\njuegos.", + ["Your %s can't\nbattle on the\nother game."] = "Tu %s no puede\nluchar en el otro\njuego.", + ["Their %s isn't\nin this game.\n(%s)"] = "Su %s no está\nen este juego.\n(%s)", + ["%s wants\nto battle!"] = "¡%s quiere\nluchar!", + ["Link desync!\n%s differs.\fAre both games\nrunning the same\nmods?"] = "¡Link desincroni-\nzado!\n%s difiere.\f¿Están los dos\njuegos con los\nmismos mods?", + ["%s ran from\nthe battle!"] = "¡%s huyó del\ncombate!", + ["Items can't be\nused in a link\nbattle!"] = "¡No se pueden usar\nobjetos en un\ncombate link!", + ["%s is out of\nPOKéMON!\f%s wins!"] = "¡%s no tiene\nPOKéMON!\f¡%s gana!", + ["%s left the\nbattle."] = "%s dejó el\ncombate.", + ["%s ran out of\ntime!"] = "¡%s se quedó\nsin tiempo!", + ["Time's up! You\nforfeit the match."] = "¡Se acabó el tiempo!\nPierdes el combate.", + ["%s's %s can't\nbattle on this\ngame."] = "El %s de %s\nno puede luchar en\neste juego.", + ["%s's %s can't\nbattle on this\ngame.\n(%s)"] = "El %s de %s\nno puede luchar en\neste juego.\n(%s)", + ["%s vs %s!"] = "¡%s contra %s!", + ["Link error:\n%s"] = "Error de link:\n%s", + ["Online play runs\nvanilla for both\nplayers.\fTurn off %s\nand restart?"] = "El juego en línea\nva sin mods para\nlos dos jugadores.\f¿Desactivar %s\ny reiniciar?", + ["The link was\nbroken."] = "Se ha perdido el\nlink.", + ["Link battle\ncan't start."] = "El combate link no\npuede empezar.", + ["The trade stopped:\n%s."] = "El intercambio se\ndetuvo:\n%s.", + ["The trade was\ncancelled."] = "El intercambio se\nha cancelado.", + ["Trade completed!\f%s received\n%s!"] = "¡Intercambio hecho!\f¡%s recibió\n%s!", + ["LINK CABLE (LAN)"] = "CABLE LINK (LAN)", + ["ONLINE MATCH"] = "PARTIDA EN LINEA", + ["TOURNAMENT"] = "TORNEO", + ["HOST A GAME"] = "CREAR PARTIDA", + ["JOIN A GAME"] = "UNIRSE A PARTIDA", + ["UDP port %s"] = "Puerto UDP %s", + ["HOST ONLINE"] = "CREAR EN LINEA", + ["JOIN ONLINE"] = "UNIRSE EN LINEA", + ["Tell your friend"] = "Dile a tu amigo", + ["the code:"] = "el código:", + ["Waiting for join..."] = "Esperando...", + ["A: connect B: back"] = "A: conectar B: atrás", + ["Calling..."] = "Llamando...", + ["Friend joins at:"] = "Tu amigo entra en:", + ["Port: %s"] = "Puerto: %s", + ["TRADE"] = "INTERCAMBIO", + ["BATTLE"] = "COMBATE", + ["LEVELS:"] = "NIVELES:", + ["A: continue B: back"] = "A: seguir B: atrás", + ["Checking the"] = "Comprobando el", + ["other game..."] = "otro juego...", + ["Waiting for the"] = "Esperando a que", + ["host to choose..."] = "el anfitrión elija...", + ["A: trade anyway"] = "A: intercambiar igual", + ["YOURS"] = "TUYO", + ["THEIRS"] = "SUYO", + ["X: not on theirs"] = "X: no en el suyo", + ["A: trade B: cancel"] = "A: cambiar B: cancelar", + ["Exchanging data..."] = "Intercambiando...", + ["can't reach relay %s:%d\n(%s)"] = "", + ["That code wasn't\nfound."] = "Ese código no se\nha encontrado.", + ["That game already\nhas two players."] = "Esa partida ya\ntiene dos jugadores.", + ["That code has\nexpired."] = "Ese código ha\ncaducado.", + ["Couldn't join:\n%s"] = "No se pudo unir:\n%s", + ["no answer from\n%s"] = "", + ["That tournament\nhas already begun."] = "Ese torneo ya ha\nempezado.", + ["Can't host:\nneed %d Pokemon\nLv %s-%s."] = "No puedes crearlo:\nnecesitas %d Pokemon\nNv %s-%s.", + ["Couldn't host\nthat tournament."] = "No se pudo crear\nese torneo.", + ["Your party needs\n%d Pokemon, Lv\n%s-%s."] = "Tu equipo necesita\n%d Pokemon, Nv\n%s-%s.", + ["Couldn't join\nthat tournament."] = "No se pudo unir a\nese torneo.", + ["Link error:\nversion mismatch\nwith opponent."] = "Error de link:\nversión distinta a\nla del rival.", + ["The tournament\nconnection was\nlost."] = "Se perdió la\nconexión del torneo.", + ["Can't watch this\nmatch."] = "No se puede ver\neste combate.", + ["HOST"] = "CREAR", + ["JOIN"] = "UNIRSE", + ["START: create"] = "START: crear", + ["A: join B: back"] = "A: unirse B: atrás", + ["B: cancel"] = "B: cancelar", + ["TOURNAMENT %s"] = "TORNEO %s", + ["ROUND %d"] = "RONDA %d", + ["%s (bye)"] = "%s (pasa)", + ["%s%s vs %s%s"] = "%s%s contra %s%s", + ["(organizing --"] = "(organizando --", + ["not playing)"] = "no juega)", + ["Waiting for"] = "Esperando a que", + ["players to join:"] = "entren jugadores:", + ["A: START B: cancel"] = "A: START B: cancelar", + ["%s is the"] = "¡%s es el", + ["champion!"] = "campeón!", + ["A: continue"] = "A: continuar", + ["{PLAYER} played the\nPOKé FLUTE."] = "{PLAYER} tocó la\nFLAUTA POKé.", + ["Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!"] = "Tocaste la FLAUTA\nPOKé.\f¡Qué melodía tan\npegadiza!", + ["%s played the\nPOKé FLUTE."] = "%s tocó la\nFLAUTA POKé.", + ["All sleeping\nPOKéMON woke up!"] = "¡Todos los POKéMON\ndormidos despertaron!", + ["%s's\nhits will never\nmiss!"] = "¡Los golpes de %s\nnunca fallarán!", + ["The wild POKéMON\nran away!"] = "¡El POKéMON salvaje\nhuyó!", + ["%s's PP\nwas restored!"] = "¡Los PP de %s\nse recuperaron!", + ["%s's\nstatus returned\nto normal!"] = "¡El estado de %s\nvolvió a la\nnormalidad!", + ["%s\nis revitalized!"] = "¡%s se ha\nrevitalizado!", + ["%s\nis refusing!"] = "¡%s se\nniega!", + ["%s's %s\nrose!"] = "¡El %s de %s\nsubió!", + ["%s's PP\nincreased!"] = "¡Los PP de %s\naumentaron!", + ["%s can't\nlearn that move!"] = "¡%s no puede\naprender ese\nmovimiento!", + ["It knows that\nmove already!"] = "¡Ya conoce ese\nmovimiento!", + ["Coin count:\n%d"] = "Fichas:\n%d", + ["NO MODS INSTALLED"] = "NO HAY MODS", + ["SAVE CURRENT AS.."] = "GUARDAR ACTUAL..", + ["OPTIONS.."] = "OPCIONES..", + ["PERMISSIONS.."] = "PERMISOS..", + ["VIEW ERROR.."] = "VER ERROR..", + ["BACK"] = "ATRAS", + ["APPLY & RESTART"] = "APLICAR Y REINICIAR", + ["DISCARD CHANGES"] = "DESCARTAR CAMBIOS", + ["DATA & API ONLY"] = "SOLO DATOS Y API", + ["DISABLE BOTH?"] = "DESACTIVAR AMBOS?", + ["PROFILE NAME?"] = "NOMBRE DEL PERFIL?", + ["RENAME?"] = "RENOMBRAR?", + ["RESET DEFAULTS"] = "VALORES POR DEFECTO", + ["NO CHANGES"] = "SIN CAMBIOS", + ["A:OK"] = "A:OK", + ["B:DONE (NO RESTART)"] = "B:LISTO (SIN REINICIAR)", + ["MOD MANAGER"] = "GESTOR DE MODS", + ["Choose a mod .zip"] = "Elige un .zip de mod", + ["Choose a .sav save file"] = "Elige un archivo .sav", + ["An update is available"] = "Hay una actualización", + ["Name save slot"] = "Nombra la ranura", + ["Enter to save - Esc to cancel - empty clears"] = "Enter para guardar - Esc para cancelar - vacío la borra", + ["Add a mod index"] = "Añadir un índice de mods", + ["Paste the index URL, or its owner/repo."] = "Pega la URL del índice, o su owner/repo.", + ["Enter to add - Esc to cancel"] = "Enter para añadir - Esc para cancelar", + ["Import a ROM to play"] = "Importa una ROM para jugar", + ["RED"] = "ROJO", + ["BLUE"] = "AZUL", + ["YELLOW"] = "AMARILLO", + ["FIND MODS"] = "BUSCAR MODS", + ["%d of 3 ready"] = "%d de 3 listos", + ["Or drop the .gb/.gbc file here."] = "O arrastra aquí el archivo .gb/.gbc.", + ["ROM imported"] = "ROM importada", + ["That ROM could not be imported."] = "No se pudo importar esa ROM.", + ["Open folder"] = "Abrir carpeta", + ["%d badges - %s - %d caught"] = "%d medallas - %s - %d capturados", + ["%d of %d enabled"] = "%d de %d activados", + ["Or drop a mod .zip onto the window."] = "O arrastra un .zip de mod a la ventana.", + ["No mods installed - drop a mod .zip here to add one."] = "No hay mods - arrastra aquí un .zip para añadir uno.", + ["Refreshed - %d mods listed"] = "Actualizado - %d mods listados", + ["Added %s"] = "Añadido %s", + ["Index removed"] = "Índice eliminado", + ["Downloading %s..."] = "Descargando %s...", + ["Installed %s %s"] = "Instalado %s %s", + ["%d mods listed"] = "%d mods listados", + ["%d of %d mods"] = "%d de %d mods", + ["Mods here are listed, not reviewed - read the source and trust the author."] = "Los mods aquí se listan, no se revisan - lee el código y confía en el autor.", + ["No mod index added"] = "No hay índice de mods", + ["Add an index to browse mods. An index is a published list; paste its URL or its owner/repo."] = "Añade un índice para explorar mods. Un índice es una lista publicada; pega su URL o su owner/repo.", + ["Search mods"] = "Buscar mods", + ["This index lists no mods yet."] = "Este índice aún no lista mods.", + ["No mods match that search."] = "Ningún mod coincide con esa búsqueda.", +} diff --git a/mods/spanish_ui/lang/trainer_names.lua b/mods/spanish_ui/lang/trainer_names.lua new file mode 100644 index 00000000..c6537732 --- /dev/null +++ b/mods/spanish_ui/lang/trainer_names.lua @@ -0,0 +1,7 @@ +-- Trainer class names +-- +-- Trainer class names for Espanol. + +return { + ["OPP_FIX_YOUNGSTER"] = "", +} diff --git a/mods/spanish_ui/main.lua b/mods/spanish_ui/main.lua new file mode 100644 index 00000000..ada371d1 --- /dev/null +++ b/mods/spanish_ui/main.lua @@ -0,0 +1,127 @@ +-- spanish_ui: a translation of the game into Espanol. +-- +-- Nothing here is translated yet. Every table under lang/ starts with +-- empty strings; fill one in and it takes effect on the next boot, and +-- anything still empty keeps rendering in English. That means a +-- half-finished translation is always playable, so you can ship early and +-- fill the long tail in later. +-- +-- Read TRANSLATING.md before the first edit; the font is the part people +-- get wrong. +return function(mod) + -- mod:read is the supported way into your own directory; the catalogs are + -- plain Lua tables, so read and run them rather than require()ing them. + local function catalog(name) + local rel = "lang/" .. name .. ".lua" + local body = mod:read(rel) + if not body then return {} end + local chunk, err = loadstring(body, rel) + if not chunk then + mod.log:warn("%s has a syntax error: %s", rel, tostring(err)) + return {} + end + local ok, table_ = pcall(chunk) + if not ok or type(table_) ~= "table" then + mod.log:warn("%s did not return a table: %s", rel, tostring(table_)) + return {} + end + return table_ + end + + -- An empty value means "not translated yet", never "translate to blank". + local function each(name, apply) + local n = 0 + for key, value in pairs(catalog(name)) do + if type(value) == "string" and value ~= "" then + apply(key, value) + n = n + 1 + end + end + return n + end + + -- ---- glyphs ------------------------------------------------------- + -- Register the sheet BEFORE anything asks for a glyph on it. base is + -- the first code the page owns; 0x100 and up is free space above the + -- vanilla pages, so a new alphabet never collides with them. + for id, page in pairs(catalog("font")) do + mod.content.font:register(id, page) + end + -- charmap: which byte sequence draws which code + for seq, code in pairs(catalog("charmap")) do + mod.content.font:register("charmap:" .. seq, { seq = seq, code = code }) + end + + -- ---- text --------------------------------------------------------- + local counts = {} + counts.dialogue = each("dialogue", function(id, value) + mod.content.text:override(id, value) + end) + counts.strings = each("strings", function(source, value) + mod.content.strings:override(source, value) + end) + counts.species = each("species_names", function(id, value) + mod.content.pokemon:patch(id, { name = value }) + end) + counts.moves = each("move_names", function(id, value) + mod.content.moves:patch(id, { name = value }) + end) + counts.items = each("item_names", function(id, value) + mod.content.items:patch(id, { name = value }) + end) + counts.trainers = each("trainer_names", function(id, value) + mod.content.trainers:patch(id, { name = value }) + end) + counts.statuses = each("status_labels", function(id, value) + mod.content.statuses:patch(id, { label = value }) + end) + + -- ---- name entry --------------------------------------------------- + -- The naming screen's letter grid. Leave lang/naming.lua returning nil + -- to keep the English alphabet. + local grid = catalog("naming") + if grid.upper then + -- Only offer the accented cells when the running cartridge can actually + -- draw them. A Spanish ROM has Ñ and the accented vowels in its font + -- and the manifest maps them; an English one does not, and an + -- unmappable cell renders blank -- a naming screen with six empty keys + -- is worse than an English one. So check the charmap and fall back. + local function drawable(cells, ctx) + local font = ((ctx.game or {}).data or {}).font + local charmap = font and font.charmap + if not charmap then return false end + local have = {} + for _, entry in ipairs(charmap) do have[entry.seq] = true end + for _, row in ipairs(cells) do + for _, cell in ipairs(row) do + -- Only the non-ASCII cells are at risk; A-Z and punctuation are + -- on every page. + if cell:byte(1) and cell:byte(1) > 127 and not have[cell] then + return false + end + end + end + return true + end + local warned = false + mod.hooks:on("ui.naming.grid", function(base, ctx) + local want = ctx.lower and grid.lower or grid.upper + if not want then return base end + if not drawable(want, ctx) then + if not warned then + warned = true + mod.log:info("naming grid: this ROM has no accented glyphs, " + .. "keeping the English alphabet") + end + return base + end + return want + end) + end + + mod.events:on("game.ready", function() + local total = 0 + for _, n in pairs(counts) do total = total + n end + mod.log:info("Espanol: %d strings translated", total) + end) +end diff --git a/mods/spanish_ui/manifest.json b/mods/spanish_ui/manifest.json new file mode 100644 index 00000000..b3e45f04 --- /dev/null +++ b/mods/spanish_ui/manifest.json @@ -0,0 +1,17 @@ +{ + "id": "spanish_ui", + "name": "Espanol (interfaz)", + "version": "0.1.0", + "api": 2, + "entry": "main.lua", + "profile": "content", + "game_version": ">=0.0.0-dev <1.0.0", + "category": "LANGUAGE", + "priority": 100, + "dependencies": [], + "optional_dependencies": [], + "conflicts": [], + "incompatible": [], + "experimental": false, + "description": "Spanish for the app's own settings and menus. The game's text comes from your ROM and is untouched, so an English cartridge stays an English adventure with Spanish menus." +} \ No newline at end of file diff --git a/assets/switch/icon.jpg b/ports/switch/assets/icon.jpg similarity index 100% rename from assets/switch/icon.jpg rename to ports/switch/assets/icon.jpg diff --git a/ports/switch/assets/logo.rgba b/ports/switch/assets/logo.rgba new file mode 100644 index 00000000..cb97707a Binary files /dev/null and b/ports/switch/assets/logo.rgba differ diff --git a/ports/switch/ota-bootstrap/Makefile b/ports/switch/ota-bootstrap/Makefile new file mode 100644 index 00000000..4920b3c3 --- /dev/null +++ b/ports/switch/ota-bootstrap/Makefile @@ -0,0 +1,72 @@ +#--------------------------------------------------------------------------------- +# One-shot OTA bootstrap NRO (copied into the main launcher's romfs). +#--------------------------------------------------------------------------------- +.SUFFIXES: + +ifeq ($(strip $(DEVKITPRO)),) + +.PHONY: all +all: + @echo "DEVKITPRO not set — bootstrap builds with the launcher only." + @false + +else + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +TARGET := ota-bootstrap +BUILD := build +SOURCES := . +INCLUDES := include +ROMFS := + +APP_TITLE := gen1recomp OTA +APP_AUTHOR := bryanthaboi, port by andrewqsantos +APP_VERSION := 0.0.0 + +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE +CFLAGS := -g -Wall -O2 -ffunction-sections $(ARCH) $(DEFINES) +CFLAGS += $(INCLUDE) -D__SWITCH__ +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) +LIBS := -lnx +LIBDIRS := $(LIBNX) + +ifneq ($(BUILD),$(notdir $(CURDIR))) +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) +export DEPSDIR := $(CURDIR)/$(BUILD) +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +export LD := $(CC) +export OFILES := $(CFILES:.c=.o) +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) +export APP_ICON := $(LIBNX)/default_icon.jpg +export NROFLAGS += --icon=$(APP_ICON) +export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp + +.PHONY: $(BUILD) clean all + +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf + +else +.PHONY: all +DEPENDS := $(OFILES:.o=.d) +all: $(OUTPUT).nro +$(OUTPUT).nro: $(OUTPUT).elf $(OUTPUT).nacp +$(OUTPUT).elf: $(OFILES) +-include $(DEPENDS) +endif +endif diff --git a/ports/switch/ota-bootstrap/main.c b/ports/switch/ota-bootstrap/main.c new file mode 100644 index 00000000..0aec9352 --- /dev/null +++ b/ports/switch/ota-bootstrap/main.c @@ -0,0 +1,36 @@ +/* + * Tiny one-shot helper: swap a staged launcher NRO into place, then load the game. + * The main OTA launcher chainloads here because a running NRO cannot replace itself + * on sdmc/FAT. + */ +#include +#include +#include + +#if defined(__SWITCH__) +#include +#endif + +#define INSTALL_DIR "sdmc:/switch/gen1recomp" +#define STAGED INSTALL_DIR "/gen1recomp.nro.staged" +#define LAUNCHER INSTALL_DIR "/gen1recomp.nro" +#define GAME INSTALL_DIR "/gen1recomp-game.nro" + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + +#if defined(__SWITCH__) + remove(LAUNCHER); + if (rename(STAGED, LAUNCHER) != 0) { + return 1; + } + Result rc = envSetNextLoad(GAME, GAME); + if (R_FAILED(rc)) return 1; + return 0; +#else + fprintf(stderr, "ota-bootstrap: host stub (would rename %s -> %s, load %s)\n", STAGED, LAUNCHER, + GAME); + return 0; +#endif +} diff --git a/ports/switch/ota-launcher/Makefile b/ports/switch/ota-launcher/Makefile new file mode 100644 index 00000000..2f2c710d --- /dev/null +++ b/ports/switch/ota-launcher/Makefile @@ -0,0 +1,193 @@ +#--------------------------------------------------------------------------------- +# gen1recomp Switch native OTA launcher +# Requires: DEVKITPRO + switch-curl, switch-mbedtls, switch-zlib, switch-zziplib +# Host protocol tests: make host-test (no DEVKITPRO needed) +#--------------------------------------------------------------------------------- +.SUFFIXES: + +#--------------------------------------------------------------------------------- +# Host-only path when DEVKITPRO is unset +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPRO)),) + +.PHONY: host-test clean-host +host-test: + @mkdir -p build-host + cc -std=c11 -Wall -Wextra -Iinclude -o build-host/test_ota_protocol \ + src/ota_protocol.c host/test_ota_protocol.c + ./build-host/test_ota_protocol + +clean-host: + rm -rf build-host + +%: + @echo "DEVKITPRO not set — only 'make host-test' is available." + @echo "Run: bash scripts/switch/install_devkitpro_deps.sh" + @false + +else + +#--------------------------------------------------------------------------------- +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +TARGET := gen1recomp +BUILD := build +SOURCES := src +DATA := data +INCLUDES := include +ROMFS := romfs + +APP_TITLE := gen1recomp +APP_AUTHOR := bryanthaboi, port by andrewqsantos +# Overridable: scripts/switch/build_ota_launcher.sh passes release X.Y.Z +APP_VERSION ?= 0.0.0 + +# Prefer project Switch icon if present +ICON := $(TOPDIR)/../assets/icon.jpg +LOGO_RGBA_SRC := $(TOPDIR)/../assets/logo.rgba +LOGO_ROMFS := $(CURDIR)/$(ROMFS)/logo.rgba + +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections $(ARCH) $(DEFINES) +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lcurl -lzzip -lmbedtls -lmbedx509 -lmbedcrypto -lz -lnx + +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + ifeq ($(wildcard $(ICON)),) + # fall back to libnx default if project icon missing + export APP_ICON := $(LIBNX)/default_icon.jpg + else + export APP_ICON := $(ICON) + endif +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +BOOTSTRAP_DIR := $(TOPDIR)/../ota-bootstrap +BOOTSTRAP_ROMFS := $(CURDIR)/$(ROMFS)/ota-bootstrap.nro + +.PHONY: $(BUILD) clean all host-test sync-romfs bootstrap-romfs + +#--------------------------------------------------------------------------------- +all: sync-romfs $(BUILD) + +CACERT_URL := https://curl.se/ca/cacert.pem +CACERT_ROMFS := $(CURDIR)/$(ROMFS)/cacert.pem + +bootstrap-romfs: + @$(MAKE) --no-print-directory -C $(BOOTSTRAP_DIR) all + @mkdir -p $(CURDIR)/$(ROMFS) + @cp -f $(BOOTSTRAP_DIR)/ota-bootstrap.nro $(BOOTSTRAP_ROMFS) + +sync-romfs: bootstrap-romfs + @mkdir -p $(CURDIR)/$(ROMFS) + @[ -f "$(LOGO_RGBA_SRC)" ] || (echo "missing $(LOGO_RGBA_SRC) — run: python3 scripts/switch/bake_ota_logo.py" && exit 1) + @cp -f "$(LOGO_RGBA_SRC)" "$(LOGO_ROMFS)" + @if ! curl -sfL --time-cond $(CACERT_ROMFS) -o $(CACERT_ROMFS) $(CACERT_URL); then \ + [ -f $(CACERT_ROMFS) ] || (echo "sync-romfs: failed to fetch cacert.pem" && exit 1); \ + fi + +$(BUILD): sync-romfs + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @$(MAKE) --no-print-directory -C $(BOOTSTRAP_DIR) clean || true + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf $(TARGET).lst build-host + @rm -f $(CURDIR)/$(ROMFS)/logo.rgba $(CURDIR)/$(ROMFS)/logo.png + +host-test: + @mkdir -p build-host + cc -std=c11 -Wall -Wextra -Iinclude -o build-host/test_ota_protocol \ + src/ota_protocol.c host/test_ota_protocol.c + ./build-host/test_ota_protocol + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +all: $(OUTPUT).nro + +$(OUTPUT).nro: $(OUTPUT).elf $(OUTPUT).nacp + +$(OUTPUT).elf: $(OFILES) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- +endif diff --git a/ports/switch/ota-launcher/README.md b/ports/switch/ota-launcher/README.md new file mode 100644 index 00000000..f6865955 --- /dev/null +++ b/ports/switch/ota-launcher/README.md @@ -0,0 +1,89 @@ +# Native Switch OTA launcher + +In-console updates for Gen1Recomp on Nintendo Switch. This NRO is the **hbmenu +entry** (`gen1recomp.nro`). It checks GitHub Releases quietly (no UI when you +are already up to date or offline). Only if a newer release exists does it show +a **launcher-style screen** (black + RGB rail + project logo + flat A/B +buttons), download the same `gen1recomp-*-switch.zip` used for install, verify +SHA-256 from `sha256sums.txt`, replace **both** `gen1recomp-game.nro` and +`gen1recomp.nro` (matching NACP version for hbmenu/Sphaira), then load the game +with `envSetNextLoad`. + +The LÖVE self-updater (`src/update/Check.lua`) stays **disabled** on NX. +Wire format (also in Lua): `src/update/SwitchOta.lua`. +NACP icon: `ports/switch/assets/icon.jpg`. + +## Layout on microSD + +```text +sdmc:/switch/gen1recomp/gen1recomp.nro <- this launcher +sdmc:/switch/gen1recomp/gen1recomp-game.nro <- fused LÖVE game +sdmc:/switch/gen1recomp/version.txt <- installed X.Y.Z +sdmc:/switch/gen1recomp/pokemon-love2d/ <- saves (never touched by OTA) +``` + +## Host tests (no DEVKITPRO) + +```bash +cd ports/switch/ota-launcher +make host-test +``` + +## Switch build (DEVKITPRO) + +Needs `DEVKITPRO` with packages roughly: + +```bash +(dkp-)pacman -S --needed switch-curl switch-mbedtls switch-zlib switch-zziplib +# or: bash scripts/switch/install_devkitpro_deps.sh +``` + +```bash +export DEVKITPRO=/opt/devkitpro # typical +cd ports/switch/ota-launcher +make +# -> gen1recomp.nro +``` + +Or from repo root (as part of `--fused`): + +```bash +scripts/build_switch.sh --fetch --fused --version X.Y.Z +``` + +Standalone launcher build: + +```bash +scripts/switch/build_ota_launcher.sh +``` + +Docker fallback uses the same pin as fused builds (`scripts/switch/dkp-docker.image`). + +## OTA logo asset + +The launcher draws a pre-scaled logo from `romfs:/logo.rgba` (no PNG decoder in +the NRO). The baked blob lives at `../assets/logo.rgba` and is copied into romfs at +build time. After changing `assets/logo/logo.png`, regenerate: + +```bash +python3 scripts/switch/bake_ota_logo.py +``` + +Requires Pillow, or on macOS uses `sips` when Pillow is not installed. + +## Packaging + +`scripts/switch/pack_sd_zip.sh GAME_NRO VERSION OUT_ZIP LAUNCHER_NRO` writes both +NROs into the SD zip. That zip is also what OTA downloads. +Manifest: `scripts/switch/ota_launcher.manifest`. + +## Status / known gaps + +- Zip extraction uses `switch-zziplib` (`ota_unzip.c`) on device. +- OTA replaces game + launcher from the install zip (NACP versions stay aligned). + The running launcher cannot overwrite its own NRO on sdmc/FAT; a tiny + `ota-bootstrap.nro` (embedded in romfs) chainloads once to swap the staged + launcher, then loads the game. +- HTTPS uses Mozilla CA bundle in romfs (`cacert.pem`, fetched at build time). `ota_net_init()` mounts romfs before the quiet release check. +- Sphaira HOME forwarders cache metadata until reinstalled (see docs/switch-install.md). +- Release runner: `switch-dev` + (`install_devkitpro_deps.sh` **or** Docker) diff --git a/ports/switch/ota-launcher/host/test_ota_protocol.c b/ports/switch/ota-launcher/host/test_ota_protocol.c new file mode 100644 index 00000000..2e432f2a --- /dev/null +++ b/ports/switch/ota-launcher/host/test_ota_protocol.c @@ -0,0 +1,144 @@ +/* Host-only unit tests for ota_protocol.c — no libnx. */ +#include "ota_protocol.h" + +#include +#include + +static int g_fail = 0; + +static void expect(int cond, const char *msg) { + if (!cond) { + fprintf(stderr, "FAIL: %s\n", msg); + g_fail++; + } else { + printf("PASS: %s\n", msg); + } +} + +int main(void) { + expect(ota_compare_semver("1.2.0", "1.1.0") == 1, "semver newer"); + expect(ota_compare_semver("1.1.0", "1.1.0") == 0, "semver equal"); + expect(ota_compare_semver("1.0.0", "1.1.0") == -1, "semver older"); + expect(ota_is_ota_asset_name("gen1recomp-1.5.0-switch.zip"), "ota asset name"); + expect(!ota_is_ota_asset_name("gen1recomp-1.5.0-switch-ota.zip"), "reject legacy ota zip name"); + expect(!ota_is_ota_asset_name("gen1recomp-1.5.0.love"), "reject love payload"); + + const char *json = + "{" + "\"tag_name\":\"v1.5.0\"," + "\"assets\":[" + "{\"name\":\"gen1recomp-1.5.0-switch.zip\"," + "\"browser_download_url\":\"https://example/switch.zip\"}," + "{\"name\":\"sha256sums.txt\",\"browser_download_url\":\"https://example/sums\"}" + "]" + "}"; + ota_release_t rel; + expect(ota_parse_release(json, &rel) == 1, "parse release"); + expect(strcmp(rel.version, "1.5.0") == 0, "release version"); + expect(strcmp(rel.asset_name, "gen1recomp-1.5.0-switch.zip") == 0, "release asset"); + + ota_decision_t d; + ota_decide_update("1.4.0", &rel, &d); + expect(strcmp(d.status, "available") == 0, "decide available"); + ota_decide_update("1.5.0", &rel, &d); + expect(strcmp(d.status, "uptodate") == 0, "decide uptodate"); + + ota_release_t bad; + expect(ota_parse_release("{\"tag_name\":\"v1.5.0\",\"assets\":[]}", &bad) == 0, + "missing ota asset"); + expect(strcmp(bad.reason, "missing_ota_asset") == 0, "missing_ota_asset reason"); + + /* GitHub releases/latest shape: release-level name + fat uploader before browser_download_url. */ + const char *github_json = + "{" + "\"tag_name\":\"v0.1.70\"," + "\"name\":\"0.1.70\"," + "\"assets\":[" + "{" + "\"url\":\"https://api.github.com/repos/bryanthaboi/gen1recomp/releases/assets/502823880\"," + "\"id\":502823880," + "\"name\":\"gen1recomp-0.1.70-switch.zip\"," + "\"label\":\"\"," + "\"uploader\":{" + "\"login\":\"github-actions[bot]\"," + "\"id\":41898282," + "\"node_id\":\"MDM6Qm90NDE4OTgyODI=\"," + "\"avatar_url\":\"https://avatars.githubusercontent.com/in/15368?v=4\"," + "\"gravatar_id\":\"\"," + "\"url\":\"https://api.github.com/users/github-actions%5Bbot%5D\"," + "\"html_url\":\"https://github.com/apps/github-actions\"," + "\"followers_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/followers\"," + "\"following_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}\"," + "\"gists_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}\"," + "\"starred_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}\"," + "\"subscriptions_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions\"," + "\"organizations_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/orgs\"," + "\"repos_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/repos\"," + "\"events_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}\"," + "\"received_events_url\":\"https://api.github.com/users/github-actions%5Bbot%5D/received_events\"," + "\"type\":\"Bot\"," + "\"user_view_type\":\"public\"," + "\"site_admin\":false" + "}," + "\"content_type\":\"application/zip\"," + "\"state\":\"uploaded\"," + "\"size\":9000573," + "\"browser_download_url\":\"https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.70/gen1recomp-0.1.70-switch.zip\"" + "}" + "]" + "}"; + ota_release_t gh; + expect(ota_parse_release(github_json, &gh) == 1, "parse github-shaped release"); + expect(strcmp(gh.version, "0.1.70") == 0, "github release version"); + expect(strcmp(gh.asset_name, "gen1recomp-0.1.70-switch.zip") == 0, "github release asset"); + expect(strcmp(gh.download_url, + "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.70/gen1recomp-0.1.70-switch.zip") == + 0, + "github release download url"); + ota_decide_update("0.1.69", &gh, &d); + expect(strcmp(d.status, "available") == 0, "github release decide 0.1.69->0.1.70"); + + const char *sums = "abc123 gen1recomp-1.5.0-switch.zip\n"; + ota_verify_t v; + ota_verify_sha256("gen1recomp-1.5.0-switch.zip", "abc123", sums, &v); + expect(v.ok == 1, "sha ok"); + ota_verify_sha256("gen1recomp-1.5.0-switch.zip", "deadbeef", sums, &v); + expect(v.ok == 0 && strcmp(v.reason, "hash_mismatch") == 0, "sha mismatch"); + ota_verify_sha256("gen1recomp-1.5.0-switch.zip", "abc123", "", &v); + expect(v.ok == 0 && strcmp(v.reason, "sum_not_found") == 0, "sha missing sum rejected"); + + ota_apply_plan_t plan; + ota_plan_atomic_apply("switch/gen1recomp", "/tmp/x", &plan); + expect(strstr(plan.steps[0], "copy_to_part:") != NULL, "plan copy"); + expect(strstr(plan.steps[1], "rename:") != NULL, "plan rename"); + expect(strstr(plan.steps[2], "copy_to_part:launcher") != NULL, "plan launcher copy"); + expect(strstr(plan.steps[3], "rename:") != NULL, "plan launcher rename"); + expect(strstr(plan.steps[4], "env_set_next_load:") != NULL, "plan handoff"); + expect(strstr(plan.preserve, "pokemon-love2d") != NULL, "preserve saves"); + expect(strstr(plan.forbidden_delete, "delete:") != NULL, "forbid delete saves"); + expect(strstr(plan.forbidden_direct, "write_direct:") != NULL, "forbid direct write"); + + ota_offline_t off; + ota_offline_events_t ev = {0}; + ev.user_skip = 1; + ota_offline_policy(1.0, &ev, &off); + expect(strcmp(off.action, "play_installed") == 0, "skip plays installed"); + ev.user_skip = 0; + ev.network_ok = 0; + ota_offline_policy(1.0, &ev, &off); + expect(strcmp(off.action, "play_installed") == 0, "offline plays installed"); + ev.network_ok = 1; + ota_offline_policy(6.0, &ev, &off); + expect(strcmp(off.reason, "timeout") == 0, "timeout 6s"); + ota_offline_policy(2.0, &ev, &off); + expect(strcmp(off.action, "keep_checking") == 0, "still checking"); + + expect(OTA_CHECK_TIMEOUT_SEC == 6, "timeout constant 6"); + + if (g_fail) { + fprintf(stderr, "%d failure(s)\n", g_fail); + return 1; + } + printf("all ota_protocol host tests passed\n"); + return 0; +} diff --git a/ports/switch/ota-launcher/include/ota_fs.h b/ports/switch/ota-launcher/include/ota_fs.h new file mode 100644 index 00000000..f94d98bd --- /dev/null +++ b/ports/switch/ota-launcher/include/ota_fs.h @@ -0,0 +1,38 @@ +#ifndef GEN1_OTA_FS_H +#define GEN1_OTA_FS_H + +#include "ota_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Read installed game version from sibling version.txt or NRO nacp-less stamp file. + * Expects switch/gen1recomp/version.txt containing X.Y.Z. Returns 0 on success. */ +int ota_fs_read_installed_version(const char *install_dir, char *out, size_t out_len); + +/* SHA-256 hex (lowercase) of file. Returns 0 on success. */ +int ota_fs_sha256_file(const char *path, char *out_hex, size_t out_len); + +/* Apply verified temp payload (extracted game NRO) using atomic plan. Returns 0 on success. */ +int ota_fs_atomic_replace_game(const char *install_dir, const char *verified_game_nro, + char *err, size_t err_len); + +/* Atomically replace any named NRO under install_dir (copy→.part→rename). */ +int ota_fs_atomic_replace_nro(const char *install_dir, const char *nro_name, + const char *verified_nro, char *err, size_t err_len); + +/* Stage a new launcher + copy romfs bootstrap; chainload bootstrap_out next. + * The running launcher cannot replace its own NRO on sdmc/FAT. */ +int ota_fs_stage_launcher_bootstrap(const char *install_dir, const char *verified_launcher, + char *bootstrap_out, size_t bootstrap_out_len, char *err, + size_t err_len); + +/* Hand off to game NRO via envSetNextLoad (Switch) or no-op stub (host). */ +int ota_fs_handoff_to_game(const char *game_nro_path, char *err, size_t err_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/switch/ota-launcher/include/ota_net.h b/ports/switch/ota-launcher/include/ota_net.h new file mode 100644 index 00000000..cd0c44c1 --- /dev/null +++ b/ports/switch/ota-launcher/include/ota_net.h @@ -0,0 +1,29 @@ +#ifndef GEN1_OTA_NET_H +#define GEN1_OTA_NET_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* fraction is 0..1 when Content-Length is known, else -1 for indeterminate. */ +typedef void (*ota_net_progress_fn)(void *userdata, double fraction); + +/* Mount romfs (CA bundle) and init libcurl. Call before any HTTPS download. */ +int ota_net_init(void); +void ota_net_shutdown(void); + +/* Download URL into memory buffer (caller frees *out). Returns 0 on success. */ +int ota_net_download_buffer(const char *url, long timeout_ms, char **out, size_t *out_len, + char *err, size_t err_len); + +/* Download URL to a filesystem path. progress may be NULL. Returns 0 on success. */ +int ota_net_download_file(const char *url, const char *path, long timeout_ms, char *err, + size_t err_len, ota_net_progress_fn progress, void *progress_ud); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/switch/ota-launcher/include/ota_protocol.h b/ports/switch/ota-launcher/include/ota_protocol.h new file mode 100644 index 00000000..995efd7c --- /dev/null +++ b/ports/switch/ota-launcher/include/ota_protocol.h @@ -0,0 +1,95 @@ +#ifndef GEN1_OTA_PROTOCOL_H +#define GEN1_OTA_PROTOCOL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define OTA_CHECK_TIMEOUT_SEC 6 +#define OTA_GAME_NRO_NAME "gen1recomp-game.nro" +#define OTA_LAUNCHER_NRO_NAME "gen1recomp.nro" +#define OTA_SAVE_DIR_NAME "pokemon-love2d" +#define OTA_INSTALL_DIR "switch/gen1recomp" +#define OTA_LAUNCHER_STAGED_SUFFIX ".staged" +#define OTA_BOOTSTRAP_ROMFS "romfs:/ota-bootstrap.nro" +#define OTA_BOOTSTRAP_SD_NAME "ota-bootstrap.nro" +#define OTA_RELEASES_API \ + "https://api.github.com/repos/bryanthaboi/gen1recomp/releases/latest" + +/* Mirrors src/update/SwitchOta.lua — keep semantics in lockstep. */ + +int ota_compare_semver(const char *a, const char *b); +int ota_is_ota_asset_name(const char *name); +int ota_version_from_ota_asset(const char *name, char *out, size_t out_len); + +typedef struct { + int ok; /* 1 on success */ + char reason[64]; + char tag[64]; + char version[32]; + char asset_name[128]; + char download_url[512]; +} ota_release_t; + +int ota_parse_release(const char *json_text, ota_release_t *out); + +typedef struct { + char status[32]; /* uptodate | available | error */ + char reason[64]; + char version[32]; + char asset_name[128]; + char download_url[512]; +} ota_decision_t; + +void ota_decide_update(const char *installed_version, const ota_release_t *release, + ota_decision_t *out); + +/* sums: newline-separated sha256sums.txt body */ +int ota_lookup_sum(const char *sums_text, const char *asset_name, char *out_hex, + size_t out_len); + +typedef struct { + int ok; + char reason[64]; +} ota_verify_t; + +void ota_verify_sha256(const char *asset_name, const char *actual_hex, + const char *sums_text, ota_verify_t *out); + +typedef struct { + char steps[5][520]; /* human-readable ops */ + char preserve[256]; + char forbidden_delete[256]; + char forbidden_direct[256]; + char part_path[256]; + char game_nro[240]; + char launcher_nro[240]; + char launcher_part[256]; + char next_load[240]; +} ota_apply_plan_t; + +void ota_plan_atomic_apply(const char *install_dir, const char *verified_temp, + ota_apply_plan_t *out); + +typedef struct { + char action[32]; /* play_installed | keep_checking */ + char reason[64]; + char message[128]; +} ota_offline_t; + +typedef struct { + int user_skip; + int network_ok; /* 0 = offline/fail, 1 = ok, -1 = unknown */ + int api_error; +} ota_offline_events_t; + +void ota_offline_policy(double elapsed_sec, const ota_offline_events_t *events, + ota_offline_t *out); + +#ifdef __cplusplus +} +#endif + +#endif /* GEN1_OTA_PROTOCOL_H */ diff --git a/ports/switch/ota-launcher/include/ota_ui.h b/ports/switch/ota-launcher/include/ota_ui.h new file mode 100644 index 00000000..0ad19e6f --- /dev/null +++ b/ports/switch/ota-launcher/include/ota_ui.h @@ -0,0 +1,37 @@ +#ifndef GEN1_OTA_UI_H +#define GEN1_OTA_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Framebuffer UI matching the in-game launcher look (black + RGB rail + + * flat semantic buttons). Silent by default — only call when user-facing. */ + +/* Returns 1 = update, 0 = skip / play installed. */ +int ota_ui_prompt_update(const char *installed, const char *latest); + +/* Status screen (download / install). Redraws each call. */ +void ota_ui_show_status(const char *title, const char *detail); + +/* Progress 0..1; detail optional. */ +void ota_ui_show_progress(const char *title, const char *detail, float progress01); + +/* Error / info + wait for B. */ +void ota_ui_alert(const char *title, const char *line1, const char *line2); + +/* User-friendly error with optional technical detail and installed-version footer. */ +void ota_ui_alert_error(const char *title, const char *friendly, const char *technical, + const char *installed_version); + +/* Missing game install screen; wait for +. */ +void ota_ui_missing_game(void); + +/* Tear down framebuffer if active. Safe to call when UI never opened. */ +void ota_ui_shutdown(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/switch/ota-launcher/include/ota_unzip.h b/ports/switch/ota-launcher/include/ota_unzip.h new file mode 100644 index 00000000..83e9c9ca --- /dev/null +++ b/ports/switch/ota-launcher/include/ota_unzip.h @@ -0,0 +1,20 @@ +#ifndef GEN1_OTA_UNZIP_H +#define GEN1_OTA_UNZIP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Extract member_name from zip_path into dest_path. + * Tries exact member_name, then common prefixes (switch/gen1recomp/). + * Returns 0 on success. */ +int ota_unzip_extract_file(const char *zip_path, const char *member_name, const char *dest_path, + char *err, size_t err_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/switch/ota-launcher/romfs/.gitkeep b/ports/switch/ota-launcher/romfs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/ports/switch/ota-launcher/src/main.c b/ports/switch/ota-launcher/src/main.c new file mode 100644 index 00000000..f187e421 --- /dev/null +++ b/ports/switch/ota-launcher/src/main.c @@ -0,0 +1,281 @@ +/* + * gen1recomp Switch OTA launcher + * + * Quiet by default: checks GitHub Releases with no UI. Only shows the + * branded launcher-style screen when an update is available. Downloads the + * same SD zip (gen1recomp-*-switch.zip), verifies SHA-256, replaces game + + * launcher NROs, then envSetNextLoad. Never touches pokemon-love2d/. + * LÖVE self-updater stays off on NX. + */ + +#include "ota_fs.h" +#include "ota_net.h" +#include "ota_protocol.h" +#include "ota_ui.h" +#include "ota_unzip.h" + +#include +#include +#include + +#if defined(__SWITCH__) +#include +#include +#include +#else +#include +#include +#endif + +#define SD_INSTALL_DIR "sdmc:/switch/gen1recomp" +#define CHECK_TIMEOUT_MS (OTA_CHECK_TIMEOUT_SEC * 1000L) +#define GAME_MEMBER_IN_ZIP "switch/gen1recomp/" OTA_GAME_NRO_NAME +#define LAUNCHER_MEMBER_IN_ZIP "switch/gen1recomp/" OTA_LAUNCHER_NRO_NAME + +typedef struct { + const char *detail; + float base; + float span; +} download_progress_ctx_t; + +static void on_download_progress(void *userdata, double fraction) { + download_progress_ctx_t *ctx = (download_progress_ctx_t *)userdata; + if (!ctx) return; + float p = ctx->base; + if (fraction >= 0.0) p += ctx->span * (float)fraction; + else p += ctx->span * 0.1f; + if (p > ctx->base + ctx->span) p = ctx->base + ctx->span; + ota_ui_show_progress("Step 1/3: Downloading...", ctx->detail, p); +} + +static void show_update_error(const char *title, const char *friendly, const char *technical, + const char *installed) { + ota_ui_alert_error(title, friendly, technical, installed); +} + +static int run_update_flow(const char *install_dir) { + char installed[64] = "0.0.0"; + (void)ota_fs_read_installed_version(install_dir, installed, sizeof(installed)); + + char *json = NULL; + size_t json_len = 0; + char err[256]; + err[0] = '\0'; + + if (ota_net_download_buffer(OTA_RELEASES_API, CHECK_TIMEOUT_MS, &json, &json_len, err, + sizeof(err)) != 0) { + free(json); + return 0; /* offline / timeout — play installed, no UI */ + } + + ota_release_t rel; + if (!ota_parse_release(json, &rel)) { + free(json); + return 0; + } + free(json); + + ota_decision_t dec; + ota_decide_update(installed, &rel, &dec); + if (strcmp(dec.status, "uptodate") == 0 || strcmp(dec.status, "error") == 0) { + return 0; + } + + if (!ota_ui_prompt_update(installed, dec.version)) { + return 0; + } + + char updates_dir[192]; + char zip_path[256]; + char sums_path[256]; + snprintf(updates_dir, sizeof(updates_dir), "%s/updates", install_dir); + snprintf(zip_path, sizeof(zip_path), "%s/updates/%s", install_dir, dec.asset_name); + snprintf(sums_path, sizeof(sums_path), "%s/updates/sha256sums.txt", install_dir); + +#if defined(__SWITCH__) + mkdir(updates_dir, 0755); +#endif + + download_progress_ctx_t dl_ctx = {"This may take a minute.", 0.f, 0.5f}; + ota_ui_show_progress("Step 1/3: Downloading...", dl_ctx.detail, dl_ctx.base); + if (ota_net_download_file(dec.download_url, zip_path, 180000L, err, sizeof(err), + on_download_progress, &dl_ctx) != 0) { + show_update_error("Download failed", + "Could not download the update. Check Wi-Fi and try again.", err, installed); + return 0; + } + + ota_ui_show_progress("Step 2/3: Verifying...", "Checking file integrity.", 0.55f); + char sums_url[512]; + snprintf(sums_url, sizeof(sums_url), + "https://github.com/bryanthaboi/gen1recomp/releases/download/%s/sha256sums.txt", + rel.tag); + if (ota_net_download_file(sums_url, sums_path, CHECK_TIMEOUT_MS, err, sizeof(err), NULL, + NULL) != 0) { + remove(zip_path); + show_update_error("Could not verify", "Downloaded file could not be checked.", err, installed); + return 0; + } + + FILE *sf = fopen(sums_path, "rb"); + if (!sf) { + remove(zip_path); + show_update_error("Could not verify", "Could not read checksum file.", "fopen sums failed", + installed); + return 0; + } + fseek(sf, 0, SEEK_END); + long slen = ftell(sf); + fseek(sf, 0, SEEK_SET); + char *sums = (char *)malloc((size_t)slen + 1); + if (!sums) { + fclose(sf); + remove(zip_path); + show_update_error("Could not verify", "Not enough memory to verify update.", "malloc failed", + installed); + return 0; + } + fread(sums, 1, (size_t)slen, sf); + sums[slen] = '\0'; + fclose(sf); + + ota_ui_show_progress("Step 2/3: Verifying...", "Computing checksum...", 0.65f); + char hex[96]; + if (ota_fs_sha256_file(zip_path, hex, sizeof(hex)) != 0) { + free(sums); + remove(zip_path); + show_update_error("Could not verify", "Could not read downloaded update file.", + "sha256 file read failed", installed); + return 0; + } + ota_verify_t ver; + ota_verify_sha256(dec.asset_name, hex, sums, &ver); + free(sums); + if (!ver.ok) { + remove(zip_path); + show_update_error("Update check failed", + "Downloaded file did not match expected checksum.", ver.reason, installed); + return 0; + } + + char extracted[256]; + char extracted_launcher[256]; + snprintf(extracted, sizeof(extracted), "%s/updates/gen1recomp-game.nro.verified", install_dir); + snprintf(extracted_launcher, sizeof(extracted_launcher), "%s/updates/gen1recomp.nro.verified", + install_dir); + +#if defined(__SWITCH__) + ota_ui_show_progress("Step 3/3: Installing...", "Keeping your saves safe.", 0.8f); + if (ota_unzip_extract_file(zip_path, GAME_MEMBER_IN_ZIP, extracted, err, sizeof(err)) != 0) { + if (ota_unzip_extract_file(zip_path, OTA_GAME_NRO_NAME, extracted, err, sizeof(err)) != 0) { + remove(zip_path); + show_update_error("Could not extract", + "Update zip is missing game files or is corrupted.", err, installed); + return 0; + } + } + + if (ota_fs_atomic_replace_game(install_dir, extracted, err, sizeof(err)) != 0) { + remove(extracted); + remove(zip_path); + show_update_error("Could not install", + "Could not replace game on microSD. Free up space and try again.", err, + installed); + return 0; + } + remove(extracted); + + int have_launcher = 0; + if (ota_unzip_extract_file(zip_path, LAUNCHER_MEMBER_IN_ZIP, extracted_launcher, err, + sizeof(err)) == 0 || + ota_unzip_extract_file(zip_path, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err, + sizeof(err)) == 0) { + have_launcher = 1; + } + if (!have_launcher) { + remove(zip_path); + show_update_error("Could not extract", + "Update zip is missing launcher files.", err, installed); + return 0; + } + + char bootstrap_path[256]; + bootstrap_path[0] = '\0'; + if (ota_fs_stage_launcher_bootstrap(install_dir, extracted_launcher, bootstrap_path, + sizeof(bootstrap_path), err, sizeof(err)) != 0) { + remove(extracted_launcher); + remove(zip_path); + show_update_error("Could not install", + "Game updated but launcher could not be staged. Reinstall from the SD zip.", + err, installed); + return 0; + } + remove(extracted_launcher); + + char vpath[192]; + snprintf(vpath, sizeof(vpath), "%s/version.txt", install_dir); + FILE *vf = fopen(vpath, "wb"); + if (vf) { + fprintf(vf, "%s\n", dec.version); + fclose(vf); + } + + ota_ui_show_progress("Ready", "Finishing update...", 1.0f); + svcSleepThread(600000000ULL); + remove(zip_path); + ota_ui_shutdown(); + + if (ota_fs_handoff_to_game(bootstrap_path, err, sizeof(err)) != 0) { + show_update_error("Could not install", "Launcher bootstrap failed.", err, installed); + return 0; + } + return 2; /* chainload bootstrap; main must not hand off to game */ +#else + (void)extracted; + (void)extracted_launcher; +#endif + remove(zip_path); + ota_ui_shutdown(); + return 0; +} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + const char *install = SD_INSTALL_DIR; + int update_rc = 0; + +#if defined(__SWITCH__) + socketInitializeDefault(); + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + if (ota_net_init() == 0) { + update_rc = run_update_flow(install); + ota_net_shutdown(); + } +#else + update_rc = run_update_flow(install); +#endif + + if (update_rc == 2) { +#if defined(__SWITCH__) + socketExit(); +#endif + return 0; + } + + char game[192]; + snprintf(game, sizeof(game), "%s/%s", install, OTA_GAME_NRO_NAME); + char err[128]; + err[0] = '\0'; + if (ota_fs_handoff_to_game(game, err, sizeof(err)) != 0) { + ota_ui_missing_game(); + } + + ota_ui_shutdown(); + +#if defined(__SWITCH__) + socketExit(); +#endif + return 0; +} diff --git a/ports/switch/ota-launcher/src/ota_fs.c b/ports/switch/ota-launcher/src/ota_fs.c new file mode 100644 index 00000000..5adca96b --- /dev/null +++ b/ports/switch/ota-launcher/src/ota_fs.c @@ -0,0 +1,177 @@ +#include "ota_fs.h" + +#include +#include +#include + +#if defined(__SWITCH__) +#include +#include +#include +#include +#else +#include +#endif + +int ota_fs_read_installed_version(const char *install_dir, char *out, size_t out_len) { + if (!out || out_len == 0) return -1; + out[0] = '\0'; + char path[256]; + snprintf(path, sizeof(path), "%s/version.txt", install_dir ? install_dir : OTA_INSTALL_DIR); + FILE *fp = fopen(path, "rb"); + if (!fp) return -1; + if (!fgets(out, (int)out_len, fp)) { + fclose(fp); + return -1; + } + fclose(fp); + /* trim */ + size_t n = strlen(out); + while (n > 0 && (out[n - 1] == '\n' || out[n - 1] == '\r' || out[n - 1] == ' ')) { + out[--n] = '\0'; + } + return n > 0 ? 0 : -1; +} + +int ota_fs_sha256_file(const char *path, char *out_hex, size_t out_len) { + if (!path || !out_hex || out_len < 65) return -1; + out_hex[0] = '\0'; + FILE *fp = fopen(path, "rb"); + if (!fp) return -1; + +#if defined(__SWITCH__) + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts(&ctx, 0); + unsigned char buf[8192]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { + mbedtls_sha256_update(&ctx, buf, n); + } + fclose(fp); + unsigned char digest[32]; + mbedtls_sha256_finish(&ctx, digest); + mbedtls_sha256_free(&ctx); + for (int i = 0; i < 32; i++) snprintf(out_hex + i * 2, out_len - (size_t)(i * 2), "%02x", digest[i]); + return 0; +#else + /* Host: shell out to shasum/sha256sum for the host test path if needed. + * Protocol host tests do not call this; return not-implemented. */ + fclose(fp); + (void)out_len; + return -1; +#endif +} + +static int copy_file(const char *from, const char *to) { + FILE *in = fopen(from, "rb"); + if (!in) return -1; + FILE *out = fopen(to, "wb"); + if (!out) { + fclose(in); + return -1; + } + char buf[8192]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), in)) > 0) { + if (fwrite(buf, 1, n, out) != n) { + fclose(in); + fclose(out); + return -1; + } + } + fclose(in); + fclose(out); + return 0; +} + +int ota_fs_atomic_replace_game(const char *install_dir, const char *verified_game_nro, + char *err, size_t err_len) { + return ota_fs_atomic_replace_nro(install_dir, OTA_GAME_NRO_NAME, verified_game_nro, err, + err_len); +} + +int ota_fs_atomic_replace_nro(const char *install_dir, const char *nro_name, + const char *verified_nro, char *err, size_t err_len) { + if (!nro_name || !*nro_name || !verified_nro || !*verified_nro) { + if (err && err_len) snprintf(err, err_len, "missing nro paths"); + return -1; + } + char dest[240]; + char part[256]; + snprintf(dest, sizeof(dest), "%s/%s", install_dir ? install_dir : OTA_INSTALL_DIR, nro_name); + snprintf(part, sizeof(part), "%s.part", dest); + if (copy_file(verified_nro, part) != 0) { + if (err && err_len) snprintf(err, err_len, "copy to .part failed (%s)", nro_name); + return -1; + } + /* sdmc/FAT (Switch) and Windows do not replace an existing dest on rename. */ + remove(dest); + if (rename(part, dest) != 0) { + if (err && err_len) snprintf(err, err_len, "rename .part -> %s failed", nro_name); + remove(part); + return -1; + } + return 0; +} + +int ota_fs_stage_launcher_bootstrap(const char *install_dir, const char *verified_launcher, + char *bootstrap_out, size_t bootstrap_out_len, char *err, + size_t err_len) { + if (!verified_launcher || !*verified_launcher) { + if (err && err_len) snprintf(err, err_len, "missing staged launcher path"); + return -1; + } + if (!bootstrap_out || bootstrap_out_len == 0) { + if (err && err_len) snprintf(err, err_len, "missing bootstrap output buffer"); + return -1; + } + bootstrap_out[0] = '\0'; + + const char *base = install_dir ? install_dir : OTA_INSTALL_DIR; + char staged[256]; + char bootstrap[256]; + char updates[192]; + snprintf(staged, sizeof(staged), "%s/%s%s", base, OTA_LAUNCHER_NRO_NAME, + OTA_LAUNCHER_STAGED_SUFFIX); + snprintf(updates, sizeof(updates), "%s/updates", base); + snprintf(bootstrap, sizeof(bootstrap), "%s/updates/%s", base, OTA_BOOTSTRAP_SD_NAME); + + remove(staged); + if (copy_file(verified_launcher, staged) != 0) { + if (err && err_len) snprintf(err, err_len, "stage launcher failed"); + return -1; + } + +#if defined(__SWITCH__) + mkdir(updates, 0755); +#endif + if (copy_file(OTA_BOOTSTRAP_ROMFS, bootstrap) != 0) { + remove(staged); + if (err && err_len) snprintf(err, err_len, "extract bootstrap failed"); + return -1; + } + + snprintf(bootstrap_out, bootstrap_out_len, "%s", bootstrap); + return 0; +} + +int ota_fs_handoff_to_game(const char *game_nro_path, char *err, size_t err_len) { + if (!game_nro_path || !*game_nro_path) { + if (err && err_len) snprintf(err, err_len, "missing game path"); + return -1; + } +#if defined(__SWITCH__) + /* argv for next load: empty args string is fine */ + Result rc = envSetNextLoad(game_nro_path, game_nro_path); + if (R_FAILED(rc)) { + if (err && err_len) snprintf(err, err_len, "envSetNextLoad failed: 0x%x", rc); + return -1; + } + return 0; +#else + if (err && err_len) + snprintf(err, err_len, "handoff stub (host): would envSetNextLoad %s", game_nro_path); + return 0; /* host stub succeeds for flow tests */ +#endif +} diff --git a/ports/switch/ota-launcher/src/ota_net.c b/ports/switch/ota-launcher/src/ota_net.c new file mode 100644 index 00000000..7b897b04 --- /dev/null +++ b/ports/switch/ota-launcher/src/ota_net.c @@ -0,0 +1,185 @@ +#include "ota_net.h" + +#include +#include +#include + +#if defined(__SWITCH__) +#include +#include +#endif + +#if defined(__SWITCH__) +#define OTA_CA_BUNDLE "romfs:/cacert.pem" + +static int g_net_ready = 0; + +static int ota_ca_bundle_ready(void) { + FILE *f = fopen(OTA_CA_BUNDLE, "rb"); + if (!f) return 0; + fclose(f); + return 1; +} + +int ota_net_init(void) { + if (g_net_ready) return 0; + if (R_FAILED(romfsInit())) return -1; + if (!ota_ca_bundle_ready()) return -1; + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) return -1; + g_net_ready = 1; + return 0; +} + +void ota_net_shutdown(void) { + if (!g_net_ready) return; + curl_global_cleanup(); + romfsExit(); + g_net_ready = 0; +} + +static void ota_net_configure_tls(CURL *curl) { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(curl, CURLOPT_CAINFO, OTA_CA_BUNDLE); +} + +struct mem_buf { + char *data; + size_t len; +}; + +struct file_progress { + ota_net_progress_fn fn; + void *userdata; +}; + +static size_t write_mem(char *ptr, size_t size, size_t nmemb, void *userdata) { + struct mem_buf *m = (struct mem_buf *)userdata; + size_t n = size * nmemb; + char *p = (char *)realloc(m->data, m->len + n + 1); + if (!p) return 0; + m->data = p; + memcpy(m->data + m->len, ptr, n); + m->len += n; + m->data[m->len] = '\0'; + return n; +} + +static size_t write_file(char *ptr, size_t size, size_t nmemb, void *userdata) { + return fwrite(ptr, size, nmemb, (FILE *)userdata); +} + +static int xfer_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, + curl_off_t ulnow) { + (void)ultotal; + (void)ulnow; + struct file_progress *fp = (struct file_progress *)clientp; + if (!fp || !fp->fn) return 0; + if (dltotal > 0) { + double frac = (double)dlnow / (double)dltotal; + if (frac < 0.0) frac = 0.0; + if (frac > 1.0) frac = 1.0; + fp->fn(fp->userdata, frac); + } else if (dlnow > 0) { + fp->fn(fp->userdata, -1.0); + } + return 0; +} +#else + +int ota_net_init(void) { return 0; } +void ota_net_shutdown(void) {} + +#endif + +int ota_net_download_buffer(const char *url, long timeout_ms, char **out, size_t *out_len, + char *err, size_t err_len) { + if (out) *out = NULL; + if (out_len) *out_len = 0; + if (!url || !out) { + if (err && err_len) snprintf(err, err_len, "bad args"); + return -1; + } +#if defined(__SWITCH__) + CURL *curl = curl_easy_init(); + if (!curl) { + if (err && err_len) snprintf(err, err_len, "curl_easy_init failed"); + return -1; + } + struct mem_buf mem = {0}; + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "gen1recomp-switch-ota"); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_ms); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_mem); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mem); + ota_net_configure_tls(curl); + CURLcode rc = curl_easy_perform(curl); + curl_easy_cleanup(curl); + if (rc != CURLE_OK) { + free(mem.data); + if (err && err_len) snprintf(err, err_len, "%s", curl_easy_strerror(rc)); + return -1; + } + *out = mem.data; + if (out_len) *out_len = mem.len; + return 0; +#else + (void)timeout_ms; + if (err && err_len) + snprintf(err, err_len, "ota_net_download_buffer only available on __SWITCH__"); + return -1; +#endif +} + +int ota_net_download_file(const char *url, const char *path, long timeout_ms, char *err, + size_t err_len, ota_net_progress_fn progress, void *progress_ud) { + if (!url || !path) { + if (err && err_len) snprintf(err, err_len, "bad args"); + return -1; + } +#if defined(__SWITCH__) + FILE *fp = fopen(path, "wb"); + if (!fp) { + if (err && err_len) snprintf(err, err_len, "fopen failed: %s", path); + return -1; + } + CURL *curl = curl_easy_init(); + if (!curl) { + fclose(fp); + if (err && err_len) snprintf(err, err_len, "curl_easy_init failed"); + return -1; + } + struct file_progress fp_cb = {progress, progress_ud}; + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "gen1recomp-switch-ota"); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_ms); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + ota_net_configure_tls(curl); + if (progress) { + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfer_progress); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &fp_cb); + } + CURLcode rc = curl_easy_perform(curl); + curl_easy_cleanup(curl); + fclose(fp); + if (rc != CURLE_OK) { + if (err && err_len) snprintf(err, err_len, "%s", curl_easy_strerror(rc)); + return -1; + } + if (progress) progress(progress_ud, 1.0); + return 0; +#else + (void)timeout_ms; + (void)progress; + (void)progress_ud; + if (err && err_len) + snprintf(err, err_len, "ota_net_download_file only available on __SWITCH__"); + return -1; +#endif +} diff --git a/ports/switch/ota-launcher/src/ota_protocol.c b/ports/switch/ota-launcher/src/ota_protocol.c new file mode 100644 index 00000000..ba41b121 --- /dev/null +++ b/ports/switch/ota-launcher/src/ota_protocol.c @@ -0,0 +1,329 @@ +#include "ota_protocol.h" + +#include +#include +#include +#include + +typedef struct { + int major; + int minor; + int patch; + int ok; +} semver_t; + +static void set_reason(char *buf, size_t n, const char *r) { + if (!buf || n == 0) return; + snprintf(buf, n, "%s", r ? r : ""); +} + +static int parse_semver(const char *s, semver_t *out) { + memset(out, 0, sizeof(*out)); + if (!s || !*s) return 0; + if (s[0] == 'v' || s[0] == 'V') s++; + int maj = 0, min = 0, pat = 0; + char trail = 0; + if (sscanf(s, "%d.%d.%d%c", &maj, &min, &pat, &trail) != 3) return 0; + if (maj < 0 || min < 0 || pat < 0) return 0; + out->major = maj; + out->minor = min; + out->patch = pat; + out->ok = 1; + return 1; +} + +int ota_compare_semver(const char *a, const char *b) { + semver_t pa, pb; + int oa = parse_semver(a, &pa); + int ob = parse_semver(b, &pb); + if (!oa && !ob) return 0; + if (!oa) return -1; + if (!ob) return 1; + if (pa.major != pb.major) return pa.major < pb.major ? -1 : 1; + if (pa.minor != pb.minor) return pa.minor < pb.minor ? -1 : 1; + if (pa.patch != pb.patch) return pa.patch < pb.patch ? -1 : 1; + return 0; +} + +int ota_is_ota_asset_name(const char *name) { + int maj = 0, min = 0, pat = 0; + if (!name) return 0; + /* Unified SD/OTA asset: gen1recomp-X.Y.Z-switch.zip */ + size_t len = strlen(name); + const char *suffix = "-switch.zip"; + size_t slen = strlen(suffix); + if (len <= slen) return 0; + if (strcmp(name + len - slen, suffix) != 0) return 0; + if (strncmp(name, "gen1recomp-", 11) != 0) return 0; + if (sscanf(name + 11, "%d.%d.%d", &maj, &min, &pat) != 3) return 0; + char rebuilt[128]; + snprintf(rebuilt, sizeof(rebuilt), "gen1recomp-%d.%d.%d-switch.zip", maj, min, pat); + return strcmp(name, rebuilt) == 0; +} + +int ota_version_from_ota_asset(const char *name, char *out, size_t out_len) { + int maj = 0, min = 0, pat = 0; + if (!out || out_len == 0) return 0; + out[0] = '\0'; + if (!ota_is_ota_asset_name(name)) return 0; + if (sscanf(name + 11, "%d.%d.%d", &maj, &min, &pat) != 3) return 0; + snprintf(out, out_len, "%d.%d.%d", maj, min, pat); + return 1; +} + +static const char *find_json_string(const char *json, const char *key, char *out, size_t out_len) { + char pattern[128]; + snprintf(pattern, sizeof(pattern), "\"%s\"", key); + const char *p = strstr(json, pattern); + if (!p) return NULL; + p = strchr(p + strlen(pattern), ':'); + if (!p) return NULL; + p++; + while (*p && isspace((unsigned char)*p)) p++; + if (*p != '"') return NULL; + p++; + size_t i = 0; + while (*p && *p != '"' && i + 1 < out_len) { + if (*p == '\\' && p[1]) { + p++; + out[i++] = *p++; + continue; + } + out[i++] = *p++; + } + out[i] = '\0'; + return out; +} + +/* Opening { of the JSON object that contains pos (walk backward). */ +static const char *find_json_object_start(const char *pos, const char *json_start) { + if (!pos || !json_start || pos < json_start) return NULL; + int depth = 0; + const char *p = pos; + while (p >= json_start) { + if (*p == '}') depth++; + else if (*p == '{') { + if (depth == 0) return p; + depth--; + } + p--; + } + return NULL; +} + +/* Pointer just past the closing } of the object that starts at object_start. */ +static const char *find_json_object_end(const char *object_start) { + if (!object_start || *object_start != '{') return NULL; + int depth = 1; + const char *p = object_start + 1; + while (*p) { + if (*p == '{') depth++; + else if (*p == '}') { + depth--; + if (depth == 0) return p + 1; + } + p++; + } + return NULL; +} + +int ota_parse_release(const char *json_text, ota_release_t *out) { + memset(out, 0, sizeof(*out)); + if (!json_text || !*json_text) { + set_reason(out->reason, sizeof(out->reason), "empty_json"); + return 0; + } + if (!find_json_string(json_text, "tag_name", out->tag, sizeof(out->tag))) { + set_reason(out->reason, sizeof(out->reason), "missing_tag"); + return 0; + } + semver_t sv; + if (!parse_semver(out->tag, &sv)) { + set_reason(out->reason, sizeof(out->reason), "bad_tag"); + return 0; + } + snprintf(out->version, sizeof(out->version), "%d.%d.%d", sv.major, sv.minor, sv.patch); + + /* Scan for OTA asset name then browser_download_url inside the same asset object. */ + const char *cursor = json_text; + while ((cursor = strstr(cursor, "\"name\"")) != NULL) { + char name[128]; + if (!find_json_string(cursor, "name", name, sizeof(name))) { + cursor += 6; + continue; + } + if (!ota_is_ota_asset_name(name)) { + cursor += 6; + continue; + } + const char *asset_start = find_json_object_start(cursor, json_text); + const char *asset_end = + asset_start ? find_json_object_end(asset_start) : NULL; + if (!asset_start || !asset_end || asset_end <= cursor) { + cursor += 6; + continue; + } + char url[512]; + const char *u = NULL; + const char *scan = cursor; + while (scan < asset_end && + (scan = strstr(scan, "\"browser_download_url\"")) != NULL && scan < asset_end) { + if (find_json_string(scan, "browser_download_url", url, sizeof(url))) { + u = url; + break; + } + scan += 21; + } + if (!u || !*u) { + cursor += 6; + continue; + } + snprintf(out->asset_name, sizeof(out->asset_name), "%s", name); + snprintf(out->download_url, sizeof(out->download_url), "%s", u); + out->ok = 1; + return 1; + } + set_reason(out->reason, sizeof(out->reason), "missing_ota_asset"); + return 0; +} + +void ota_decide_update(const char *installed_version, const ota_release_t *release, + ota_decision_t *out) { + memset(out, 0, sizeof(*out)); + semver_t inst; + if (!parse_semver(installed_version, &inst)) { + snprintf(out->status, sizeof(out->status), "error"); + set_reason(out->reason, sizeof(out->reason), "bad_installed_version"); + return; + } + if (!release || !release->ok) { + snprintf(out->status, sizeof(out->status), "error"); + set_reason(out->reason, sizeof(out->reason), "bad_release"); + return; + } + if (ota_compare_semver(release->version, installed_version) <= 0) { + snprintf(out->status, sizeof(out->status), "uptodate"); + snprintf(out->version, sizeof(out->version), "%s", installed_version); + return; + } + snprintf(out->status, sizeof(out->status), "available"); + snprintf(out->version, sizeof(out->version), "%s", release->version); + snprintf(out->asset_name, sizeof(out->asset_name), "%s", release->asset_name); + snprintf(out->download_url, sizeof(out->download_url), "%s", release->download_url); +} + +int ota_lookup_sum(const char *sums_text, const char *asset_name, char *out_hex, size_t out_len) { + if (out_hex && out_len) out_hex[0] = '\0'; + if (!sums_text || !asset_name || !out_hex || out_len < 65) return 0; + const char *p = sums_text; + while (*p) { + char hex[96]; + char name[256]; + int n = 0; + if (sscanf(p, "%95s %255s%n", hex, name, &n) >= 2 && n > 0) { + const char *nm = name; + if (nm[0] == '*') nm++; + if (strncmp(nm, "./", 2) == 0) nm += 2; + if (strcmp(nm, asset_name) == 0) { + /* normalize hex to lowercase */ + size_t i; + for (i = 0; hex[i] && i + 1 < out_len; i++) + out_hex[i] = (char)tolower((unsigned char)hex[i]); + out_hex[i] = '\0'; + return 1; + } + p += n; + while (*p && *p != '\n') p++; + if (*p == '\n') p++; + continue; + } + while (*p && *p != '\n') p++; + if (*p == '\n') p++; + } + return 0; +} + +void ota_verify_sha256(const char *asset_name, const char *actual_hex, const char *sums_text, + ota_verify_t *out) { + memset(out, 0, sizeof(*out)); + if (!asset_name || !*asset_name) { + set_reason(out->reason, sizeof(out->reason), "bad_asset_name"); + return; + } + if (!sums_text) { + set_reason(out->reason, sizeof(out->reason), "missing_sums"); + return; + } + char expected[96]; + if (!ota_lookup_sum(sums_text, asset_name, expected, sizeof(expected))) { + set_reason(out->reason, sizeof(out->reason), "sum_not_found"); + return; + } + if (!actual_hex || !*actual_hex) { + set_reason(out->reason, sizeof(out->reason), "missing_actual_hash"); + return; + } + char actual[96]; + size_t i; + for (i = 0; actual_hex[i] && i + 1 < sizeof(actual); i++) + actual[i] = (char)tolower((unsigned char)actual_hex[i]); + actual[i] = '\0'; + if (strcmp(actual, expected) != 0) { + set_reason(out->reason, sizeof(out->reason), "hash_mismatch"); + return; + } + out->ok = 1; +} + +void ota_plan_atomic_apply(const char *install_dir, const char *verified_temp, + ota_apply_plan_t *out) { + memset(out, 0, sizeof(*out)); + if (!install_dir || !*install_dir) install_dir = OTA_INSTALL_DIR; + if (!verified_temp) verified_temp = ""; + snprintf(out->game_nro, sizeof(out->game_nro), "%s/%s", install_dir, OTA_GAME_NRO_NAME); + snprintf(out->part_path, sizeof(out->part_path), "%s.part", out->game_nro); + snprintf(out->launcher_nro, sizeof(out->launcher_nro), "%s/%s", install_dir, + OTA_LAUNCHER_NRO_NAME); + snprintf(out->launcher_part, sizeof(out->launcher_part), "%s.part", out->launcher_nro); + snprintf(out->next_load, sizeof(out->next_load), "%s", out->game_nro); + /* verified_temp is the game NRO; launcher replace uses a sibling verified path */ + snprintf(out->steps[0], sizeof(out->steps[0]), "copy_to_part:%s->%s", verified_temp, + out->part_path); + snprintf(out->steps[1], sizeof(out->steps[1]), "rename:%s->%s", out->part_path, out->game_nro); + snprintf(out->steps[2], sizeof(out->steps[2]), "copy_to_part:launcher->%s", out->launcher_part); + snprintf(out->steps[3], sizeof(out->steps[3]), "rename:%s->%s", out->launcher_part, + out->launcher_nro); + snprintf(out->steps[4], sizeof(out->steps[4]), "env_set_next_load:%s", out->game_nro); + snprintf(out->preserve, sizeof(out->preserve), "%s/%s", install_dir, OTA_SAVE_DIR_NAME); + snprintf(out->forbidden_delete, sizeof(out->forbidden_delete), "delete:%s/%s", install_dir, + OTA_SAVE_DIR_NAME); + snprintf(out->forbidden_direct, sizeof(out->forbidden_direct), "write_direct:%s", out->game_nro); +} + +void ota_offline_policy(double elapsed_sec, const ota_offline_events_t *events, ota_offline_t *out) { + memset(out, 0, sizeof(*out)); + ota_offline_events_t ev = {0, -1, 0}; + if (events) ev = *events; + if (ev.user_skip) { + snprintf(out->action, sizeof(out->action), "play_installed"); + set_reason(out->reason, sizeof(out->reason), "user_skip"); + snprintf(out->message, sizeof(out->message), "update skipped"); + return; + } + if (ev.api_error || ev.network_ok == 0) { + snprintf(out->action, sizeof(out->action), "play_installed"); + set_reason(out->reason, sizeof(out->reason), "offline_or_error"); + snprintf(out->message, sizeof(out->message), + "offline or update check failed — play installed version"); + return; + } + if (elapsed_sec >= (double)OTA_CHECK_TIMEOUT_SEC) { + snprintf(out->action, sizeof(out->action), "play_installed"); + set_reason(out->reason, sizeof(out->reason), "timeout"); + snprintf(out->message, sizeof(out->message), "update check timed out after %ds", + OTA_CHECK_TIMEOUT_SEC); + return; + } + snprintf(out->action, sizeof(out->action), "keep_checking"); + set_reason(out->reason, sizeof(out->reason), "in_flight"); +} diff --git a/ports/switch/ota-launcher/src/ota_ui.c b/ports/switch/ota-launcher/src/ota_ui.c new file mode 100644 index 00000000..972310b1 --- /dev/null +++ b/ports/switch/ota-launcher/src/ota_ui.c @@ -0,0 +1,595 @@ +/* + * Switch OTA UI — matches in-game launcher language (Theme.lua): + * black field, RGB version rail, flat yellow/white buttons, white ink. + * Quiet until called. Uses framebuffer + 8x8 font + optional romfs logo. + */ +#include "ota_ui.h" + +#include +#include +#include + +#if defined(__SWITCH__) +#include + +#include "../third_party/font8x8_basic.h" + +#define OTA_LOGO_ROMFS "romfs:/logo.rgba" + +#define FB_W 1280 +#define FB_H 720 + +#define TITLE_Y 200 +#define LINE_TITLE 44 +#define LINE_BODY 36 +#define LINE_SMALL 28 +#define WRAP_MAX_PX 720 + +/* Theme.PAL (0-255) -> RGBA8 */ +#define COL_BG RGBA8_MAXALPHA(0, 0, 0) +#define COL_INK RGBA8_MAXALPHA(255, 255, 255) +#define COL_DETAIL RGBA8_MAXALPHA(200, 200, 200) +#define COL_MUTED RGBA8_MAXALPHA(150, 150, 150) +#define COL_INVERSE RGBA8_MAXALPHA(0, 0, 0) +#define COL_YELLOW RGBA8_MAXALPHA(255, 214, 0) +#define COL_GREEN RGBA8_MAXALPHA(0, 255, 140) +#define COL_RAIL_R RGBA8_MAXALPHA(255, 60, 72) +#define COL_RAIL_B RGBA8_MAXALPHA(70, 150, 255) +#define COL_RAIL_G RGBA8_MAXALPHA(255, 203, 5) +#define COL_LINE RGBA8_MAXALPHA(90, 90, 90) +#define COL_RAISED RGBA8_MAXALPHA(20, 20, 20) + +static int g_ready = 0; +static Framebuffer g_fb; +static PadState g_pad; +static u32 *g_logo = NULL; +static int g_logo_w = 0, g_logo_h = 0; + +static void fill_rect(u32 *fb, u32 stride_px, int x, int y, int w, int h, u32 color) { + if (w <= 0 || h <= 0) return; + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + if (x + w > FB_W) w = FB_W - x; + if (y + h > FB_H) h = FB_H - y; + if (w <= 0 || h <= 0) return; + for (int row = 0; row < h; row++) { + u32 *line = fb + (y + row) * stride_px + x; + for (int col = 0; col < w; col++) line[col] = color; + } +} + +/* Normalize UTF-8 punctuation to ASCII; drop other non-ASCII bytes. */ +static size_t ota_ui_sanitize_ascii(const char *src, char *dst, size_t dst_len) { + if (!dst || dst_len == 0) return 0; + dst[0] = '\0'; + if (!src) return 0; + size_t w = 0; + for (size_t i = 0; src[i] && w + 1 < dst_len;) { + unsigned char c = (unsigned char)src[i]; + if (c < 0x80) { + dst[w++] = (char)c; + i++; + continue; + } + if (c == 0xE2 && src[i + 1] && src[i + 2]) { + unsigned char b2 = (unsigned char)src[i + 1]; + unsigned char b3 = (unsigned char)src[i + 2]; + if (b2 == 0x80 && b3 == 0xA6) { + if (w + 3 < dst_len) { + dst[w++] = '.'; + dst[w++] = '.'; + dst[w++] = '.'; + } + i += 3; + continue; + } + if (b2 == 0x80 && (b3 == 0x94 || b3 == 0x93)) { + dst[w++] = '-'; + i += 3; + continue; + } + if (b2 == 0x80 && (b3 == 0x98 || b3 == 0x99)) { + dst[w++] = '\''; + i += 3; + continue; + } + } + if ((c & 0xE0) == 0xC0) i += 2; + else if ((c & 0xF0) == 0xE0) i += 3; + else if ((c & 0xF8) == 0xF0) i += 4; + else i++; + } + dst[w] = '\0'; + return w; +} + +static void draw_glyph(u32 *fb, u32 stride_px, int x, int y, char ch, u32 color, int scale) { + unsigned char c = (unsigned char)ch; + if (c > 127) return; + const char *bits = font8x8_basic[c]; + for (int row = 0; row < 8; row++) { + unsigned char line = (unsigned char)bits[row]; + for (int col = 0; col < 8; col++) { + if (line & (1 << col)) { + fill_rect(fb, stride_px, x + col * scale, y + row * scale, scale, scale, color); + } + } + } +} + +static int text_width(const char *s, int scale) { + if (!s) return 0; + return (int)strlen(s) * 8 * scale; +} + +static void draw_text(u32 *fb, u32 stride_px, int x, int y, const char *s, u32 color, int scale) { + if (!s) return; + char buf[512]; + ota_ui_sanitize_ascii(s, buf, sizeof(buf)); + int cx = x; + for (const char *p = buf; *p; p++) { + if (*p == '\n') { + cx = x; + y += 8 * scale + 4; + continue; + } + draw_glyph(fb, stride_px, cx, y, *p, color, scale); + cx += 8 * scale; + } +} + +static void draw_text_centered(u32 *fb, u32 stride_px, int y, const char *s, u32 color, int scale) { + char buf[512]; + ota_ui_sanitize_ascii(s, buf, sizeof(buf)); + int w = text_width(buf, scale); + draw_text(fb, stride_px, (FB_W - w) / 2, y, buf, color, scale); +} + +static int draw_text_wrapped_centered(u32 *fb, u32 stride_px, int y, const char *s, u32 color, + int scale, int max_px) { + if (!s || !*s) return y; + char buf[512]; + ota_ui_sanitize_ascii(s, buf, sizeof(buf)); + int char_w = 8 * scale; + int max_chars = max_px / char_w; + if (max_chars < 8) max_chars = 8; + + char line[128]; + int line_len = 0; + const char *word = buf; + int lines = 0; + + while (*word) { + while (*word == ' ') word++; + if (!*word) break; + + const char *end = word; + while (*end && *end != ' ' && *end != '\n') end++; + int wlen = (int)(end - word); + + if (line_len > 0 && line_len + 1 + wlen > max_chars) { + line[line_len] = '\0'; + draw_text_centered(fb, stride_px, y, line, color, scale); + y += 8 * scale + 4; + lines++; + line_len = 0; + } + + if (wlen > max_chars) { + if (line_len > 0) { + line[line_len] = '\0'; + draw_text_centered(fb, stride_px, y, line, color, scale); + y += 8 * scale + 4; + lines++; + line_len = 0; + } + while (wlen > 0) { + int chunk = wlen > max_chars ? max_chars : wlen; + memcpy(line, word, (size_t)chunk); + line[chunk] = '\0'; + draw_text_centered(fb, stride_px, y, line, color, scale); + y += 8 * scale + 4; + lines++; + word += chunk; + wlen -= chunk; + } + word = end; + continue; + } + + if (line_len > 0) line[line_len++] = ' '; + memcpy(line + line_len, word, (size_t)wlen); + line_len += wlen; + line[line_len] = '\0'; + word = (*end == '\n') ? end + 1 : end; + if (*(end - 1) == '\n' || *end == '\n') { + draw_text_centered(fb, stride_px, y, line, color, scale); + y += 8 * scale + 4; + lines++; + line_len = 0; + if (*end == '\n') word = end + 1; + } + } + + if (line_len > 0) { + draw_text_centered(fb, stride_px, y, line, color, scale); + y += 8 * scale + 4; + lines++; + } + if (lines == 0) return y; + return y; +} + +static void draw_rail(u32 *fb, u32 stride_px) { + int h = 6; + int third = FB_W / 3; + fill_rect(fb, stride_px, 0, 0, third, h, COL_RAIL_R); + fill_rect(fb, stride_px, third, 0, third, h, COL_RAIL_B); + fill_rect(fb, stride_px, third * 2, 0, FB_W - third * 2, h, COL_RAIL_G); +} + +static void blit_logo(u32 *fb, u32 stride_px, int dst_x, int dst_y, int max_w) { + if (!g_logo || g_logo_w <= 0 || g_logo_h <= 0) return; + int dw = g_logo_w; + int dh = g_logo_h; + if (dw > max_w) { + dh = dh * max_w / dw; + dw = max_w; + } + for (int y = 0; y < dh; y++) { + int sy = y * g_logo_h / dh; + for (int x = 0; x < dw; x++) { + int sx = x * g_logo_w / dw; + u32 px = g_logo[sy * g_logo_w + sx]; + u8 a = (px >> 24) & 0xff; + if (a < 16) continue; + int dx = dst_x + x; + int dy = dst_y + y; + if (dx < 0 || dy < 0 || dx >= FB_W || dy >= FB_H) continue; + fb[dy * stride_px + dx] = px | 0xff000000u; + } + } +} + +static void load_logo(void) { + if (g_logo) return; + FILE *fp = fopen(OTA_LOGO_ROMFS, "rb"); + if (!fp) return; + + unsigned char header[8]; + if (fread(header, 1, sizeof(header), fp) != sizeof(header)) { + fclose(fp); + return; + } + + int w = (int)(header[0] | (header[1] << 8) | (header[2] << 16) | (header[3] << 24)); + int h = (int)(header[4] | (header[5] << 8) | (header[6] << 16) | (header[7] << 24)); + if (w <= 0 || h <= 0 || w > 2048 || h > 2048) { + fclose(fp); + return; + } + + size_t nbytes = (size_t)w * (size_t)h * 4u; + unsigned char *data = (unsigned char *)malloc(nbytes); + if (!data) { + fclose(fp); + return; + } + if (fread(data, 1, nbytes, fp) != nbytes) { + free(data); + fclose(fp); + return; + } + fclose(fp); + + g_logo = (u32 *)malloc((size_t)w * (size_t)h * sizeof(u32)); + if (!g_logo) { + free(data); + return; + } + for (int i = 0; i < w * h; i++) { + unsigned char *p = data + (size_t)i * 4u; + g_logo[i] = RGBA8(p[0], p[1], p[2], p[3]); + } + free(data); + g_logo_w = w; + g_logo_h = h; +} + +static int ui_ensure(void) { + if (g_ready) return 1; + NWindow *win = nwindowGetDefault(); + if (R_FAILED(framebufferCreate(&g_fb, win, FB_W, FB_H, PIXEL_FORMAT_RGBA_8888, 2))) return 0; + framebufferMakeLinear(&g_fb); + padInitializeDefault(&g_pad); + load_logo(); + g_ready = 1; + return 1; +} + +void ota_ui_shutdown(void) { + if (!g_ready) return; + framebufferClose(&g_fb); + free(g_logo); + g_logo = NULL; + g_logo_w = g_logo_h = 0; + g_ready = 0; +} + +static void draw_button(u32 *fb, u32 stride_px, int x, int y, int w, int h, u32 fill, u32 ink, + const char *label, int focused) { + fill_rect(fb, stride_px, x, y, w, h, fill); + if (focused) { + fill_rect(fb, stride_px, x - 3, y - 3, w + 6, 2, COL_INK); + fill_rect(fb, stride_px, x - 3, y + h + 1, w + 6, 2, COL_INK); + fill_rect(fb, stride_px, x - 3, y - 3, 2, h + 6, COL_INK); + fill_rect(fb, stride_px, x + w + 1, y - 3, 2, h + 6, COL_INK); + } else { + fill_rect(fb, stride_px, x, y, w, 1, COL_LINE); + fill_rect(fb, stride_px, x, y + h - 1, w, 1, COL_LINE); + fill_rect(fb, stride_px, x, y, 1, h, COL_LINE); + fill_rect(fb, stride_px, x + w - 1, y, 1, h, COL_LINE); + } + int scale = 3; + char lbl[128]; + ota_ui_sanitize_ascii(label, lbl, sizeof(lbl)); + int max_tw = w - 24; + int tw = text_width(lbl, scale); + while (scale > 2 && tw > max_tw) { + scale--; + tw = text_width(lbl, scale); + } + int tx = x + (w - tw) / 2; + int ty = y + (h - 8 * scale) / 2; + draw_text(fb, stride_px, tx, ty, lbl, ink, scale); +} + +static void draw_chrome(u32 *fb, u32 stride_px) { + fill_rect(fb, stride_px, 0, 0, FB_W, FB_H, COL_BG); + draw_rail(fb, stride_px); + int logo_max = 320; + int logo_h = g_logo ? (g_logo_h * logo_max / g_logo_w) : 40; + if (logo_h > 90) logo_h = 90; + int logo_y = 48; + if (g_logo) { + int logo_w = logo_max; + if (g_logo_w * logo_h / g_logo_h < logo_max) logo_w = g_logo_w * logo_h / g_logo_h; + blit_logo(fb, stride_px, (FB_W - logo_w) / 2, logo_y, logo_max); + } else { + draw_text_centered(fb, stride_px, logo_y + 16, "gen1recomp", COL_INK, 4); + } +} + +static void present(void (*paint)(u32 *fb, u32 stride_px, void *ctx), void *ctx) { + if (!ui_ensure()) return; + u32 stride = 0; + u32 *fb = (u32 *)framebufferBegin(&g_fb, &stride); + u32 stride_px = stride / sizeof(u32); + paint(fb, stride_px, ctx); + framebufferEnd(&g_fb); +} + +typedef struct { + const char *installed; + const char *latest; + int focus; /* 0 = Update, 1 = Play */ +} prompt_ctx_t; + +static void paint_prompt(u32 *fb, u32 stride_px, void *ctx) { + prompt_ctx_t *p = (prompt_ctx_t *)ctx; + draw_chrome(fb, stride_px); + + int y = TITLE_Y - 20; + draw_text_centered(fb, stride_px, y, "Update available", COL_INK, 3); + y += LINE_TITLE; + + char line[96]; + snprintf(line, sizeof(line), "v%s to v%s", p->installed ? p->installed : "?", + p->latest ? p->latest : "?"); + draw_text_centered(fb, stride_px, y, line, COL_YELLOW, 3); + y += LINE_TITLE + 8; + + draw_text_centered(fb, stride_px, y, "Your saves stay on this console.", COL_MUTED, 2); + y += LINE_BODY + 28; + + int bw = 600; + int bh = 64; + int bx = (FB_W - bw) / 2; + draw_button(fb, stride_px, bx, y, bw, bh, COL_YELLOW, COL_INVERSE, "(A) Update", p->focus == 0); + y += bh + 24; + draw_button(fb, stride_px, bx, y, bw, bh, COL_INK, COL_INVERSE, "(B) Play without update", + p->focus == 1); +} + +int ota_ui_prompt_update(const char *installed, const char *latest) { + if (!ui_ensure()) return 0; + prompt_ctx_t ctx = {installed, latest, 0}; + while (appletMainLoop()) { + present(paint_prompt, &ctx); + padUpdate(&g_pad); + u64 k = padGetButtonsDown(&g_pad); + if (k & HidNpadButton_A) { + int do_update = (ctx.focus == 0); + if (!do_update) ota_ui_shutdown(); + return do_update; + } + if (k & HidNpadButton_B) { + ota_ui_shutdown(); + return 0; + } + if (k & (HidNpadButton_Up | HidNpadButton_Down | HidNpadButton_Left | HidNpadButton_Right)) { + ctx.focus = 1 - ctx.focus; + } + svcSleepThread(16000000ULL); + } + ota_ui_shutdown(); + return 0; +} + +typedef struct { + const char *title; + const char *detail; + float progress; + int show_bar; +} status_ctx_t; + +static void paint_progress_bar(u32 *fb, u32 stride_px, int y, float t) { + int bw = 520; + int bh = 18; + int bx = (FB_W - bw) / 2; + if (t < 0.f) t = 0.f; + if (t > 1.f) t = 1.f; + fill_rect(fb, stride_px, bx, y, bw, bh, COL_RAISED); + fill_rect(fb, stride_px, bx, y, bw, 1, COL_LINE); + fill_rect(fb, stride_px, bx, y + bh - 1, bw, 1, COL_LINE); + fill_rect(fb, stride_px, bx, y, 1, bh, COL_LINE); + fill_rect(fb, stride_px, bx + bw - 1, y, 1, bh, COL_LINE); + int fw = (int)(bw * t); + if (fw > 0) fill_rect(fb, stride_px, bx, y, fw, bh, COL_GREEN); +} + +static void paint_status(u32 *fb, u32 stride_px, void *ctx) { + status_ctx_t *s = (status_ctx_t *)ctx; + draw_chrome(fb, stride_px); + int y = TITLE_Y; + draw_text_centered(fb, stride_px, y, s->title ? s->title : "", COL_INK, 3); + y += LINE_TITLE; + if (s->detail && s->detail[0]) { + y = draw_text_wrapped_centered(fb, stride_px, y, s->detail, COL_DETAIL, 2, WRAP_MAX_PX); + y += LINE_SMALL; + } + if (s->show_bar) paint_progress_bar(fb, stride_px, y, s->progress); +} + +void ota_ui_show_status(const char *title, const char *detail) { + status_ctx_t s = {title, detail, 0.f, 0}; + present(paint_status, &s); +} + +void ota_ui_show_progress(const char *title, const char *detail, float progress01) { + status_ctx_t s = {title, detail, progress01, 1}; + present(paint_status, &s); +} + +typedef struct { + const char *title; + const char *line1; + const char *line2; + const char *line3; +} alert_ctx_t; + +static void paint_alert(u32 *fb, u32 stride_px, void *ctx) { + alert_ctx_t *a = (alert_ctx_t *)ctx; + draw_chrome(fb, stride_px); + int y = TITLE_Y; + draw_text_centered(fb, stride_px, y, a->title ? a->title : "", COL_YELLOW, 3); + y += LINE_TITLE; + if (a->line1 && a->line1[0]) + y = draw_text_wrapped_centered(fb, stride_px, y, a->line1, COL_DETAIL, 2, WRAP_MAX_PX); + y += LINE_SMALL; + if (a->line2 && a->line2[0]) + y = draw_text_wrapped_centered(fb, stride_px, y, a->line2, COL_MUTED, 2, WRAP_MAX_PX); + y += LINE_SMALL; + if (a->line3 && a->line3[0]) + y = draw_text_wrapped_centered(fb, stride_px, y, a->line3, COL_MUTED, 2, WRAP_MAX_PX); + y += LINE_BODY + 16; + int bw = 360; + int bh = 56; + draw_button(fb, stride_px, (FB_W - bw) / 2, y, bw, bh, COL_INK, COL_INVERSE, "(B) Continue", 1); +} + +void ota_ui_alert(const char *title, const char *line1, const char *line2) { + if (!ui_ensure()) return; + alert_ctx_t a = {title, line1, line2, NULL}; + while (appletMainLoop()) { + present(paint_alert, &a); + padUpdate(&g_pad); + if (padGetButtonsDown(&g_pad) & HidNpadButton_B) break; + svcSleepThread(16000000ULL); + } + ota_ui_shutdown(); +} + +void ota_ui_alert_error(const char *title, const char *friendly, const char *technical, + const char *installed_version) { + if (!ui_ensure()) return; + char footer[96]; + snprintf(footer, sizeof(footer), "Playing installed version (v%s).", + installed_version && installed_version[0] ? installed_version : "?"); + char tech[256]; + if (technical && technical[0]) { + ota_ui_sanitize_ascii(technical, tech, sizeof(tech)); + if (strlen(tech) > 80) tech[80] = '\0'; + } else { + tech[0] = '\0'; + } + alert_ctx_t a = {title, friendly, tech[0] ? tech : NULL, footer}; + while (appletMainLoop()) { + present(paint_alert, &a); + padUpdate(&g_pad); + if (padGetButtonsDown(&g_pad) & HidNpadButton_B) break; + svcSleepThread(16000000ULL); + } + ota_ui_shutdown(); +} + +static void paint_missing(u32 *fb, u32 stride_px, void *ctx) { + (void)ctx; + draw_chrome(fb, stride_px); + int y = TITLE_Y - 10; + draw_text_centered(fb, stride_px, y, "Game files missing", COL_YELLOW, 3); + y += LINE_TITLE; + y = draw_text_wrapped_centered(fb, stride_px, y, + "Copy the Switch zip onto your microSD, then open gen1recomp again.", + COL_DETAIL, 2, WRAP_MAX_PX); + y += LINE_BODY + 16; + int bw = 320; + int bh = 56; + draw_button(fb, stride_px, (FB_W - bw) / 2, y, bw, bh, COL_INK, COL_INVERSE, "(+) Exit", 1); +} + +void ota_ui_missing_game(void) { + if (!ui_ensure()) return; + while (appletMainLoop()) { + present(paint_missing, NULL); + padUpdate(&g_pad); + if (padGetButtonsDown(&g_pad) & HidNpadButton_Plus) break; + svcSleepThread(16000000ULL); + } + ota_ui_shutdown(); +} + +#else /* host stub */ + +int ota_ui_prompt_update(const char *installed, const char *latest) { + fprintf(stderr, "[ota_ui] update %s -> %s (host stub: skip)\n", installed ? installed : "?", + latest ? latest : "?"); + return 0; +} +void ota_ui_show_status(const char *title, const char *detail) { + fprintf(stderr, "[ota_ui] %s %s\n", title ? title : "", detail ? detail : ""); +} +void ota_ui_show_progress(const char *title, const char *detail, float progress01) { + fprintf(stderr, "[ota_ui] %s %s (%.0f%%)\n", title ? title : "", detail ? detail : "", + progress01 * 100.f); +} +void ota_ui_alert(const char *title, const char *line1, const char *line2) { + fprintf(stderr, "[ota_ui] alert: %s / %s / %s\n", title ? title : "", line1 ? line1 : "", + line2 ? line2 : ""); +} +void ota_ui_alert_error(const char *title, const char *friendly, const char *technical, + const char *installed_version) { + fprintf(stderr, "[ota_ui] error: %s / %s / %s / v%s\n", title ? title : "", + friendly ? friendly : "", technical ? technical : "", + installed_version ? installed_version : "?"); +} +void ota_ui_missing_game(void) { fprintf(stderr, "[ota_ui] missing game\n"); } +void ota_ui_shutdown(void) {} + +#endif diff --git a/ports/switch/ota-launcher/src/ota_unzip.c b/ports/switch/ota-launcher/src/ota_unzip.c new file mode 100644 index 00000000..72c72425 --- /dev/null +++ b/ports/switch/ota-launcher/src/ota_unzip.c @@ -0,0 +1,89 @@ +#include "ota_unzip.h" + +#include +#include + +#if defined(__SWITCH__) +#include +#include +#endif + +#if defined(__SWITCH__) +static int extract_member(ZZIP_DIR *dir, const char *member, const char *dest_path, char *err, + size_t err_len) { + ZZIP_FILE *zf = zzip_file_open(dir, member, O_RDONLY); + if (!zf) return 1; /* not found / cannot open */ + + FILE *out = fopen(dest_path, "wb"); + if (!out) { + zzip_file_close(zf); + if (err && err_len) snprintf(err, err_len, "fopen dest failed: %s", dest_path); + return -1; + } + + char buf[8192]; + zzip_ssize_t n; + while ((n = zzip_file_read(zf, buf, sizeof(buf))) > 0) { + if ((zzip_ssize_t)fwrite(buf, 1, (size_t)n, out) != n) { + fclose(out); + zzip_file_close(zf); + if (err && err_len) snprintf(err, err_len, "fwrite failed"); + return -1; + } + } + fclose(out); + zzip_file_close(zf); + if (n < 0) { + if (err && err_len) snprintf(err, err_len, "zzip_file_read failed"); + return -1; + } + return 0; +} +#endif + +int ota_unzip_extract_file(const char *zip_path, const char *member_name, const char *dest_path, + char *err, size_t err_len) { + if (!zip_path || !member_name || !dest_path) { + if (err && err_len) snprintf(err, err_len, "bad args"); + return -1; + } +#if defined(__SWITCH__) + zzip_error_t zerr = ZZIP_NO_ERROR; + ZZIP_DIR *dir = zzip_dir_open(zip_path, &zerr); + if (!dir) { + if (err && err_len) snprintf(err, err_len, "zzip_dir_open failed (%d): %s", (int)zerr, zip_path); + return -1; + } + + char alt1[192]; + char alt2[192]; + snprintf(alt1, sizeof(alt1), "switch/gen1recomp/%s", member_name); + snprintf(alt2, sizeof(alt2), "./%s", member_name); + + const char *candidates[] = {member_name, alt1, alt2, NULL}; + int rc = -1; + if (err && err_len) err[0] = '\0'; + for (int i = 0; candidates[i]; i++) { + int t = extract_member(dir, candidates[i], dest_path, err, err_len); + if (t == 0) { + rc = 0; + break; + } + if (t < 0) { + rc = -1; + break; + } + } + if (rc != 0 && err && err_len && !err[0]) { + snprintf(err, err_len, "member not found in zip: %s", member_name); + } + zzip_dir_close(dir); + return rc; +#else + (void)zip_path; + (void)member_name; + (void)dest_path; + if (err && err_len) snprintf(err, err_len, "ota_unzip only available on __SWITCH__"); + return -1; +#endif +} diff --git a/ports/switch/ota-launcher/third_party/font8x8_basic.h b/ports/switch/ota-launcher/third_party/font8x8_basic.h new file mode 100644 index 00000000..125cf165 --- /dev/null +++ b/ports/switch/ota-launcher/third_party/font8x8_basic.h @@ -0,0 +1,152 @@ +/** + * 8x8 monochrome bitmap fonts for rendering + * Author: Daniel Hepper + * + * License: Public Domain + * + * Based on: + * // Summary: font8x8.h + * // 8x8 monochrome bitmap fonts for rendering + * // + * // Author: + * // Marcel Sondaar + * // International Business Machines (public domain VGA fonts) + * // + * // License: + * // Public Domain + * + * Fetched from: http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm + **/ + +// Constant: font8x8_basic +// Contains an 8x8 font map for unicode points U+0000 - U+007F (basic latin) +char font8x8_basic[128][8] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) + { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) + { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") + { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) + { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) + { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) + { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) + { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') + { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() + { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) + { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) + { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) + { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) + { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) + { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) + { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) + { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) + { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) + { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) + { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) + { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) + { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) + { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) + { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) + { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) + { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) + { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) + { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) + { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) + { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) + { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) + { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) + { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) + { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) + { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) + { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) + { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) + { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) + { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) + { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) + { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) + { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) + { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) + { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) + { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) + { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) + { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) + { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) + { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) + { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) + { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) + { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) + { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) + { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) + { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) + { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) + { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) + { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) + { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) + { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) + { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) + { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) + { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) + { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) + { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) + { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) + { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) + { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) + { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) + { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) + { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) + { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) + { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) + { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) + { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) + { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) + { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) + { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) + { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) + { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) + { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F +}; diff --git a/ports/uwp/Assets/LargeTile.png b/ports/uwp/Assets/LargeTile.png new file mode 100644 index 00000000..f9b2bafb Binary files /dev/null and b/ports/uwp/Assets/LargeTile.png differ diff --git a/ports/uwp/Assets/SmallTile.png b/ports/uwp/Assets/SmallTile.png new file mode 100644 index 00000000..07af1d9f Binary files /dev/null and b/ports/uwp/Assets/SmallTile.png differ diff --git a/ports/uwp/Assets/SplashScreen.png b/ports/uwp/Assets/SplashScreen.png new file mode 100644 index 00000000..5e938064 Binary files /dev/null and b/ports/uwp/Assets/SplashScreen.png differ diff --git a/ports/uwp/Assets/Square150x150Logo.png b/ports/uwp/Assets/Square150x150Logo.png new file mode 100644 index 00000000..66f7e44f Binary files /dev/null and b/ports/uwp/Assets/Square150x150Logo.png differ diff --git a/ports/uwp/Assets/Square44x44Logo.png b/ports/uwp/Assets/Square44x44Logo.png new file mode 100644 index 00000000..adf2102e Binary files /dev/null and b/ports/uwp/Assets/Square44x44Logo.png differ diff --git a/ports/uwp/Assets/StoreLogo.png b/ports/uwp/Assets/StoreLogo.png new file mode 100644 index 00000000..929abe5c Binary files /dev/null and b/ports/uwp/Assets/StoreLogo.png differ diff --git a/ports/uwp/Assets/WideTile.png b/ports/uwp/Assets/WideTile.png new file mode 100644 index 00000000..8f30a761 Binary files /dev/null and b/ports/uwp/Assets/WideTile.png differ diff --git a/ports/uwp/BUILD.md b/ports/uwp/BUILD.md new file mode 100644 index 00000000..40cddc14 --- /dev/null +++ b/ports/uwp/BUILD.md @@ -0,0 +1,124 @@ +# Gen1Recomp Xbox UWP build notes + +This is the Xbox Dev Mode package for Gen1Recomp. + +The rough shape is: + +- `Gen1RecompUWP.exe` starts LÖVE through SDL's WinRT wrapper +- the bundled LÖVE 11.5 UWP backend provides LuaJIT and the Xbox file picker +- the bundled SDL2 runtime contains the Xbox controller mapping +- ANGLE provides OpenGL ES over D3D11 +- the bundled runtime contains the audio, font, video, and compression libraries + +## What You Need + +The tested toolchain is: + +- Visual Studio 2022 17.14 +- MSVC v143 x64/x86 build tools +- C++ Universal Windows Platform tools +- Windows 11 SDK `10.0.26100.0` +- CMake 3.24 or newer +- Git for Windows +- Info-ZIP `zip` and `unzip` + +Use Visual Studio Installer to add **Universal Windows Platform development**, the v143 C++ tools, CMake tools for Windows, and Windows SDK `10.0.26100.0`. + +The x64 UWP dependencies are committed under `third_party`. Their versions, +source revisions, licences, and hashes are recorded in `third_party/manifest.json`. +No additional checkout or environment variable is required for a normal game +build. + +## Rebuild the Dependencies + +Run the dependency rebuild from the repository root: + +```powershell +.\scripts\xbox-uwp\rebuild_dependencies.ps1 +``` + +The script clones the pinned SDL2, LÖVE, LuaJIT, vcpkg, depot_tools, and ANGLE +sources when they are missing. It applies the Xbox SDL2 patch, builds the x64 +UWP Release libraries, stages the required DLLs, import libraries, headers, and +licences under `third_party`, updates every SHA-256 entry in the manifest, then +builds the Release MSIX. + +The generated source checkouts are ignored by Git. A fresh ANGLE sync is about +10 GB, so allow at least 20 GB of free disk space for all sources and build +outputs. Use `-SkipAngle` to retain the existing pinned ANGLE runtime while +rebuilding SDL2, LÖVE, LuaJIT, and the vcpkg libraries. Use `-SkipPackage` when +only the dependency bundle needs to be refreshed. The rebuild stops if a source +checkout has local changes. Remove that generated `source` directory to restore +the pinned revision. + +## Build the MSIX + +Run the Xbox build from Git Bash at the repository root: + +```bash +scripts/build_xbox_uwp.sh --release --version 1.2.3 +``` + +The build uses `scripts/pack_love.sh` to create and verify the same ROM-free +`game.love` payload used by the other release targets. It then links the UWP +host and stages LÖVE, LuaJIT, SDL2, ANGLE, and the vcpkg runtime DLLs. + +Use `--relwithdebinfo` for a package with symbols. To package a `.love` produced +by another build or downloaded from CI, pass `--game-love path/to/game.love`. +The upstream `X.Y.Z` release becomes `X.Y.Z.0` in the generated MSIX manifest. +Neither the manifest template nor `src/core/Version.lua` is edited in place. + +The manifest publisher must match the signing certificate subject. Pass it +when preparing a signed package: + +```bash +scripts/build_xbox_uwp.sh --release --version 1.2.3 \ + --publisher "CN=Gen1Recomp" +``` + +The normal build is unsigned. Release CI supplies the private PFX and password +from `XBOX_UWP_SIGNING_CERTIFICATE` and `XBOX_UWP_SIGNING_PASSWORD`; neither may +be committed. The public certificate is safe to include with the release. + +Run the offline packaging checks from Git Bash: + +```bash +bash scripts/xbox-uwp/selftest_build_xbox_uwp.sh +``` + +## Build Output + +Visual Studio package output lands under: + +```text +ports\uwp\build\release\AppPackages\Gen1RecompUWP +``` + +The build also stages the distributable archive and checksum under: + +```text +dist\xbox-uwp\gen1recomp-X.Y.Z-xbox-uwp.zip +dist\xbox-uwp\gen1recomp-X.Y.Z-xbox-uwp.zip.sha256 +``` + +The archive contains the MSIX, framework dependencies, build provenance and, +for a signed release, the public certificate. The third-party notices are +packaged inside the MSIX. Install the MSIX and dependency packages through +Xbox Device Portal. + +## Runtime Data + +The package contains no ROM, generated cache, save or mod data. The Xbox file +picker copies user-selected files into LocalState and the launcher imports them +from there. Saves, ROM cache and installed mods remain under the LÖVE save +directory in LocalState. + +The launcher also checks `LocalState\pokemon-love2d\baseroms` once at startup +for clean Red, Blue, and Yellow ROMs. Compatible files are offered on their +launcher tabs and remain in `baseroms` after import. The file picker remains +available when no compatible ROM is found. + +LuaJIT requires the `codeGeneration` capability. `removableStorage` exposes +external media to the Xbox picker. The network capabilities support relay play +and direct hosting. The package does not request full trust or broad filesystem +access. diff --git a/ports/uwp/CMakeLists.txt b/ports/uwp/CMakeLists.txt new file mode 100644 index 00000000..ed62ba93 --- /dev/null +++ b/ports/uwp/CMakeLists.txt @@ -0,0 +1,149 @@ +cmake_minimum_required(VERSION 3.24) +project(Gen1RecompUWP LANGUAGES CXX) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + message(FATAL_ERROR "Configure with a WindowsStore preset.") +endif() + +get_filename_component(GAME_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +set(THIRD_PARTY_ROOT "${CMAKE_CURRENT_LIST_DIR}/third_party") +set(LOVE_ROOT "${THIRD_PARTY_ROOT}/love") +set(SDL2_ROOT "${THIRD_PARTY_ROOT}/sdl2") +set(ANGLE_ROOT "${THIRD_PARTY_ROOT}/angle") +set(RUNTIME_ROOT "${THIRD_PARTY_ROOT}/runtime") + +set(GEN1RECOMP_VERSION "0.0.0" CACHE STRING "Gen1Recomp release version") +if(NOT GEN1RECOMP_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$") + message(FATAL_ERROR "GEN1RECOMP_VERSION must use X.Y.Z format.") +endif() +string(REPLACE "." ";" VERSION_PARTS "${GEN1RECOMP_VERSION}") +foreach(part IN LISTS VERSION_PARTS) + if(part GREATER 65535) + message(FATAL_ERROR "MSIX version components cannot exceed 65535.") + endif() +endforeach() +set(GEN1RECOMP_UWP_PUBLISHER "CN=Gen1Recomp" CACHE STRING + "Publisher subject from the MSIX signing certificate") +set(UWP_PUBLISHER_XML "${GEN1RECOMP_UWP_PUBLISHER}") +string(REPLACE "&" "&" UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}") +string(REPLACE "\"" """ UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}") +string(REPLACE "<" "<" UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}") +string(REPLACE ">" ">" UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}") +set(UWP_PACKAGE_VERSION "${GEN1RECOMP_VERSION}.0") +set(PACKAGE_MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/Package.appxmanifest") +configure_file( + "${CMAKE_CURRENT_LIST_DIR}/Package.appxmanifest.in" + "${PACKAGE_MANIFEST}" + @ONLY +) + +set(REQUIRED_FILES + "${THIRD_PARTY_ROOT}/manifest.json" + "${LOVE_ROOT}/lib/lovestatic.lib" + "${LOVE_ROOT}/lib/liblove.lib" + "${LOVE_ROOT}/lib/lua51.lib" + "${LOVE_ROOT}/bin/love.dll" + "${LOVE_ROOT}/bin/lua51.dll" + "${SDL2_ROOT}/include/SDL2/SDL.h" + "${SDL2_ROOT}/lib/SDL2.lib" + "${SDL2_ROOT}/bin/SDL2.dll" + "${ANGLE_ROOT}/bin/libEGL.dll" + "${ANGLE_ROOT}/bin/libGLESv2.dll" + "${ANGLE_ROOT}/bin/d3dcompiler_47.dll" +) +foreach(path IN LISTS REQUIRED_FILES) + if(NOT EXISTS "${path}") + message(FATAL_ERROR "Missing UWP dependency: ${path}") + endif() +endforeach() + +set(GEN1RECOMP_LOVE "${GAME_ROOT}/.bazinga/work/game.love" CACHE FILEPATH + "Path to the game.love payload produced by scripts/pack_love.sh") +if(NOT EXISTS "${GEN1RECOMP_LOVE}") + message(FATAL_ERROR + "Missing game.love payload: ${GEN1RECOMP_LOVE}\n" + "Build it with scripts/build_xbox_uwp.sh or scripts/pack_love.sh.") +endif() + +set(GAME_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/gen1recomp.love") +add_custom_command( + OUTPUT "${GAME_ARCHIVE}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${GEN1RECOMP_LOVE}" "${GAME_ARCHIVE}" + DEPENDS "${GEN1RECOMP_LOVE}" + VERBATIM +) +add_custom_target(gen1recomp_love ALL DEPENDS "${GAME_ARCHIVE}") +set_source_files_properties("${GAME_ARCHIVE}" PROPERTIES GENERATED TRUE) + +add_custom_target(verify_uwp_dependencies + COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File + "${GAME_ROOT}/scripts/xbox-uwp/verify_dependencies.ps1" + VERBATIM +) + +add_executable(${PROJECT_NAME} WIN32 "app/main.cpp") +add_dependencies(${PROJECT_NAME} gen1recomp_love verify_uwp_dependencies) +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES + VS_GLOBAL_DefaultLanguage "en-US" + VS_SDK_REFERENCES "Microsoft.VCLibs, Version=14.0" +) +target_include_directories(${PROJECT_NAME} PRIVATE "${SDL2_ROOT}/include/SDL2") +target_link_libraries(${PROJECT_NAME} PRIVATE + "${LOVE_ROOT}/lib/lovestatic.lib" + "${LOVE_ROOT}/lib/liblove.lib" + "${SDL2_ROOT}/lib/SDL2.lib" + "${LOVE_ROOT}/lib/lua51.lib" + WindowsApp.lib +) + +set(PACKAGE_ROOT_FILES + "${PACKAGE_MANIFEST}" + "${GAME_ARCHIVE}" + "${LOVE_ROOT}/bin/love.dll" + "${LOVE_ROOT}/bin/lua51.dll" + "${SDL2_ROOT}/bin/SDL2.dll" + "${ANGLE_ROOT}/bin/libEGL.dll" + "${ANGLE_ROOT}/bin/libGLESv2.dll" + "${ANGLE_ROOT}/bin/d3dcompiler_47.dll" +) +set_source_files_properties("${PACKAGE_MANIFEST}" PROPERTIES GENERATED TRUE) + +set(RUNTIME_NAMES + brotlicommon.dll brotlidec.dll bz2.dll fmt.dll freetype.dll libpng16.dll + OpenAL32.dll theora.dll theoradec.dll vorbis.dll vorbisfile.dll ogg.dll z.dll +) +foreach(name IN LISTS RUNTIME_NAMES) + set(runtime "${RUNTIME_ROOT}/bin/${name}") + if(NOT EXISTS "${runtime}") + message(FATAL_ERROR "Missing UWP runtime DLL: ${name}") + endif() + list(APPEND PACKAGE_ROOT_FILES "${runtime}") +endforeach() + +set_source_files_properties(${PACKAGE_ROOT_FILES} PROPERTIES + VS_COPY_TO_OUT_DIR Always + VS_DEPLOYMENT_CONTENT TRUE + VS_DEPLOYMENT_LOCATION "." +) + +file(GLOB PACKAGE_ASSETS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/Assets/*.png") +set_source_files_properties(${PACKAGE_ASSETS} PROPERTIES + VS_DEPLOYMENT_CONTENT TRUE + VS_DEPLOYMENT_LOCATION "Assets" +) + +file(GLOB PACKAGE_LICENSES CONFIGURE_DEPENDS "${THIRD_PARTY_ROOT}/licenses/*.txt") +set_source_files_properties(${PACKAGE_LICENSES} PROPERTIES + VS_TOOL_OVERRIDE "Content" + VS_COPY_TO_OUT_DIR Always + VS_DEPLOYMENT_CONTENT TRUE + VS_DEPLOYMENT_LOCATION "licenses" +) +target_sources(${PROJECT_NAME} PRIVATE + ${PACKAGE_ROOT_FILES} + ${PACKAGE_ASSETS} + ${PACKAGE_LICENSES} +) diff --git a/ports/uwp/CMakePresets.json b/ports/uwp/CMakePresets.json new file mode 100644 index 00000000..a97dcca6 --- /dev/null +++ b/ports/uwp/CMakePresets.json @@ -0,0 +1,41 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "uwp-common", + "hidden": true, + "generator": "Visual Studio 17 2022", + "architecture": "x64", + "cacheVariables": { + "CMAKE_SYSTEM_NAME": "WindowsStore", + "CMAKE_SYSTEM_VERSION": "10.0" + } + }, + { + "name": "uwp-relwithdebinfo", + "inherits": "uwp-common", + "displayName": "Gen1Recomp Xbox UWP (RelWithDebInfo)", + "binaryDir": "${sourceDir}/build/relwithdebinfo" + }, + { + "name": "uwp-release", + "inherits": "uwp-common", + "displayName": "Gen1Recomp Xbox UWP (Release)", + "binaryDir": "${sourceDir}/build/release" + } + ], + "buildPresets": [ + { + "name": "uwp-relwithdebinfo", + "configurePreset": "uwp-relwithdebinfo", + "configuration": "RelWithDebInfo", + "jobs": 8 + }, + { + "name": "uwp-release", + "configurePreset": "uwp-release", + "configuration": "Release", + "jobs": 8 + } + ] +} diff --git a/ports/uwp/Package.appxmanifest.in b/ports/uwp/Package.appxmanifest.in new file mode 100644 index 00000000..1aba5668 --- /dev/null +++ b/ports/uwp/Package.appxmanifest.in @@ -0,0 +1,43 @@ + + + + + + Gen1Recomp + Gen1Recomp + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ports/uwp/app/main.cpp b/ports/uwp/app/main.cpp new file mode 100644 index 00000000..1f5e8b77 --- /dev/null +++ b/ports/uwp/app/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +extern "C" int SDL_main(int argc, char **argv); + +namespace +{ + +int runLove(int, char **) +{ + std::wstring packagePath = winrt::Windows::ApplicationModel::Package::Current() + .InstalledLocation().Path().c_str(); + std::string gamePath = winrt::to_string(packagePath + L"\\gen1recomp.love"); + + char executable[] = "Gen1RecompUWP"; + char fused[] = "--fused"; + char *loveArgv[] = {executable, gamePath.data(), fused, nullptr}; + return SDL_main(3, loveArgv); +} + +} // namespace + +int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) +{ + SDL_SetHint(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, "1"); + return SDL_WinRTRunApp(runLove, nullptr); +} diff --git a/ports/uwp/third_party/README.md b/ports/uwp/third_party/README.md new file mode 100644 index 00000000..1441d685 --- /dev/null +++ b/ports/uwp/third_party/README.md @@ -0,0 +1,11 @@ +# UWP dependencies + +This directory contains the complete x64 UWP Release dependency bundle used by the package build: + +- `love` contains the LÖVE 11.5 and LuaJIT binaries. +- `sdl2` contains the matching SDL headers, import library, and runtime. +- `angle` contains the EGL and GLES runtime. +- `runtime` contains the codec, font, compression, and audio DLLs used by LÖVE. +- `licenses` contains the corresponding third party notices. + +`manifest.json` pins the source revisions and SHA-256 hashes. Run `scripts/xbox-uwp/verify_dependencies.ps1` from the repository root after updating any dependency. diff --git a/ports/uwp/third_party/angle/AUTHORS b/ports/uwp/third_party/angle/AUTHORS new file mode 100644 index 00000000..37a9c07d --- /dev/null +++ b/ports/uwp/third_party/angle/AUTHORS @@ -0,0 +1,89 @@ +# This is the official list of The ANGLE Project Authors +# for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as +# Name or Organization +# Email addresses for individuals are tracked elsewhere to avoid spam. + +Google Inc. +TransGaming Inc. +3DLabs Inc. Ltd. + +Adobe Systems Inc. +Autodesk, Inc. +BlackBerry Limited +Cable Television Laboratories, Inc. +Collabora, Ltd. +Cloud Party, Inc. +Igalia, S.L. +Imagination Technologies Ltd. +Intel Corporation +LunarG, Inc. +Mozilla Corporation +Turbulenz +Klarälvdalens Datakonsult AB +Microsoft Corporation +Microsoft Open Technologies, Inc. +NVIDIA Corporation +Opera Software ASA +The Qt Company Ltd. +Advanced Micro Devices, Inc. +LG Electronics, Inc. +IBM Inc. +AdaptVis GmbH +Samsung Electronics, Inc. +Arm Ltd. +Broadcom Inc. +Facebook, Inc. +The Khronos Group, Inc. +Numfum GmbH +Yandex LLC +Rive +Institute of Software, Chinese Academy of Sciences +Guangdong OPPO Mobile Telecommunications Corp., Ltd +Qualcomm Innovation Center, Inc. + +Jacek Caban +Mark Callow +Ginn Chen +Tibor den Ouden +Régis Fénéon +James Hauxwell +Sam Hocevar +Pierre Leveille +Jonathan Liu +Boying Lu +Aitor Moreno +Yuri O'Donnell +Josh Soref +Ma Aiguo +Maks Naumov +Jinyoung Hur +Sebastian Bergstein +James Ross-Gowan +Nickolay Artamonov +Ihsan Akmal +Andrei Volykhin +Jérôme Duval +Руслан Ижбулатов +Thomas Miller +Till Rathmann +Nick Shaforostov +Jaime Bernardo +Le Hoang Quyen +Lu Yahan +Ethan Lee +Renaud Lepage +Artem Bolgar +Wander Lairson Costa +Stephan Hartmann +SeongHwan Park +Xiaopeng Li +Akihiko Odaki +Ho Cheung +Tao Wang +Phan Quang Minh +Hongchen Yan +Andrew Sumsion diff --git a/ports/uwp/third_party/angle/LICENSE b/ports/uwp/third_party/angle/LICENSE new file mode 100644 index 00000000..0f65fd60 --- /dev/null +++ b/ports/uwp/third_party/angle/LICENSE @@ -0,0 +1,32 @@ +// Copyright 2018 The ANGLE Project Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their contributors may be used to endorse +// or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. diff --git a/ports/uwp/third_party/angle/README.md b/ports/uwp/third_party/angle/README.md new file mode 100644 index 00000000..722f0f4a --- /dev/null +++ b/ports/uwp/third_party/angle/README.md @@ -0,0 +1,7 @@ +# ANGLE UWP runtime + +`libEGL.dll` and `libGLESv2.dll` were built for x64 UWP from [SternXD/angle](https://github.com/SternXD/angle) commit `45b0b1e03400b7a10aaa9a077e196d1abcddafce`. ANGLE version `2.1.25011` and source hash `45b0b1e03400`. + +`d3dcompiler_47.dll` is the x64 Direct3D HLSL compiler redistributable from Windows SDK `10.0.26100.7705`. Current hashes are recorded in `../manifest.json`. + +ANGLE's upstream `LICENSE` and `AUTHORS` files are included beside its binaries. diff --git a/ports/uwp/third_party/angle/bin/d3dcompiler_47.dll b/ports/uwp/third_party/angle/bin/d3dcompiler_47.dll new file mode 100644 index 00000000..948cc90b Binary files /dev/null and b/ports/uwp/third_party/angle/bin/d3dcompiler_47.dll differ diff --git a/ports/uwp/third_party/angle/bin/libEGL.dll b/ports/uwp/third_party/angle/bin/libEGL.dll new file mode 100644 index 00000000..111392f6 Binary files /dev/null and b/ports/uwp/third_party/angle/bin/libEGL.dll differ diff --git a/ports/uwp/third_party/angle/bin/libGLESv2.dll b/ports/uwp/third_party/angle/bin/libGLESv2.dll new file mode 100644 index 00000000..e54e7fc1 Binary files /dev/null and b/ports/uwp/third_party/angle/bin/libGLESv2.dll differ diff --git a/ports/uwp/third_party/licenses/angle.txt b/ports/uwp/third_party/licenses/angle.txt new file mode 100644 index 00000000..0f65fd60 --- /dev/null +++ b/ports/uwp/third_party/licenses/angle.txt @@ -0,0 +1,32 @@ +// Copyright 2018 The ANGLE Project Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their contributors may be used to endorse +// or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. diff --git a/ports/uwp/third_party/licenses/brotli.txt b/ports/uwp/third_party/licenses/brotli.txt new file mode 100644 index 00000000..33b7cdd2 --- /dev/null +++ b/ports/uwp/third_party/licenses/brotli.txt @@ -0,0 +1,19 @@ +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ports/uwp/third_party/licenses/bzip2.txt b/ports/uwp/third_party/licenses/bzip2.txt new file mode 100644 index 00000000..81a37eab --- /dev/null +++ b/ports/uwp/third_party/licenses/bzip2.txt @@ -0,0 +1,42 @@ + +-------------------------------------------------------------------------- + +This program, "bzip2", the associated library "libbzip2", and all +documentation, are copyright (C) 1996-2019 Julian R Seward. All +rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Julian Seward, jseward@acm.org +bzip2/libbzip2 version 1.0.8 of 13 July 2019 + +-------------------------------------------------------------------------- diff --git a/ports/uwp/third_party/licenses/fmt.txt b/ports/uwp/third_party/licenses/fmt.txt new file mode 100644 index 00000000..1cd1ef92 --- /dev/null +++ b/ports/uwp/third_party/licenses/fmt.txt @@ -0,0 +1,27 @@ +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. diff --git a/ports/uwp/third_party/licenses/freetype.txt b/ports/uwp/third_party/licenses/freetype.txt new file mode 100644 index 00000000..6289a620 --- /dev/null +++ b/ports/uwp/third_party/licenses/freetype.txt @@ -0,0 +1,568 @@ +LICENSE.TXT: + +FREETYPE LICENSES +----------------- + +The FreeType 2 font engine is copyrighted work and cannot be used +legally without a software license. In order to make this project +usable to a vast majority of developers, we distribute it under two +mutually exclusive open-source licenses. + +This means that *you* must choose *one* of the two licenses described +below, then obey all its terms and conditions when using FreeType 2 in +any of your projects or products. + + - The FreeType License, found in the file `docs/FTL.TXT`, which is + similar to the original BSD license *with* an advertising clause + that forces you to explicitly cite the FreeType project in your + product's documentation. All details are in the license file. + This license is suited to products which don't use the GNU General + Public License. + + Note that this license is compatible to the GNU General Public + License version 3, but not version 2. + + - The GNU General Public License version 2, found in + `docs/GPLv2.TXT` (any later version can be used also), for + programs which already use the GPL. Note that the FTL is + incompatible with GPLv2 due to its advertisement clause. + +The contributed BDF and PCF drivers come with a license similar to +that of the X Window System. It is compatible to the above two +licenses (see files `src/bdf/README` and `src/pcf/README`). The same +holds for the source code files `src/base/fthash.c` and +`include/freetype/internal/fthash.h`; they were part of the BDF driver +in earlier FreeType versions. + +The gzip module uses the zlib license (see `src/gzip/zlib.h`) which +too is compatible to the above two licenses. + +The files `src/autofit/ft-hb-ft.c`, `src/autofit/ft-hb-decls.h`, +`src/autofit/ft-hb-types.h`, and `src/autofit/hb-script-list.h` +contain code taken (almost) verbatim from the HarfBuzz library, which +uses the 'Old MIT' license compatible to the above two licenses. + +The MD5 checksum support (only used for debugging in development +builds) is in the public domain. + + +--- end of LICENSE.TXT --- + + +FTL.TXT: + + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (https://freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + https://freetype.org + + +--- end of FTL.TXT --- + + +GPLv2.TXT: + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + diff --git a/ports/uwp/third_party/licenses/libpng.txt b/ports/uwp/third_party/licenses/libpng.txt new file mode 100644 index 00000000..1b765ae9 --- /dev/null +++ b/ports/uwp/third_party/licenses/libpng.txt @@ -0,0 +1,134 @@ +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE +========================================= + +PNG Reference Library License version 2 +--------------------------------------- + + * Copyright (c) 1995-2026 The PNG Reference Library Authors. + * Copyright (c) 2018-2026 Cosmin Truta. + * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. + * Copyright (c) 1996-1997 Andreas Dilger. + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +The software is supplied "as is", without warranty of any kind, +express or implied, including, without limitation, the warranties +of merchantability, fitness for a particular purpose, title, and +non-infringement. In no event shall the Copyright owners, or +anyone distributing the software, be liable for any damages or +other liability, whether in contract, tort or otherwise, arising +from, out of, or in connection with the software, or the use or +other dealings in the software, even if advised of the possibility +of such damage. + +Permission is hereby granted to use, copy, modify, and distribute +this software, or portions hereof, for any purpose, without fee, +subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you + use this software in a product, an acknowledgment in the product + documentation would be appreciated, but is not required. + + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + + +PNG Reference Library License version 1 (for libpng 0.5 through 1.6.35) +----------------------------------------------------------------------- + +libpng versions 1.0.7, July 1, 2000, through 1.6.35, July 15, 2018 are +Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are +derived from libpng-1.0.6, and are distributed according to the same +disclaimer and license as libpng-1.0.6 with the following individuals +added to the list of Contributing Authors: + + Simon-Pierre Cadieux + Eric S. Raymond + Mans Rullgard + Cosmin Truta + Gilles Vollant + James Yu + Mandar Sahastrabuddhe + Google Inc. + Vadim Barkov + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of + the library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is + with the user. + +Some files in the "contrib" directory and some configure-generated +files that are distributed with libpng have other copyright owners, and +are released under other open source licenses. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from +libpng-0.96, and are distributed according to the same disclaimer and +license as libpng-0.96, with the following individuals added to the +list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, +and are distributed according to the same disclaimer and license as +libpng-0.88, with the following individuals added to the list of +Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +Some files in the "scripts" directory have other copyright owners, +but are released under this license. + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing +Authors and Group 42, Inc. disclaim all warranties, expressed or +implied, including, without limitation, the warranties of +merchantability and of fitness for any purpose. The Contributing +Authors and Group 42, Inc. assume no liability for direct, indirect, +incidental, special, exemplary, or consequential damages, which may +result from the use of the PNG Reference Library, even if advised of +the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + + 1. The origin of this source code must not be misrepresented. + + 2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, +without fee, and encourage the use of this source code as a component +to supporting the PNG file format in commercial products. If you use +this source code in a product, acknowledgment is not required but would +be appreciated. diff --git a/ports/uwp/third_party/licenses/love.txt b/ports/uwp/third_party/licenses/love.txt new file mode 100644 index 00000000..fdc4af53 --- /dev/null +++ b/ports/uwp/third_party/licenses/love.txt @@ -0,0 +1,1355 @@ +Licensing information +===================== + +This distribution contains code from the following projects (full license text below): + + - LOVE + Website: https://love2d.org/ + License: zlib + Copyright (c) 2006-2023 LOVE Development Team + + - ENet + Website: http://enet.bespin.org/index.html + License: MIT/Expat + Copyright (c) 2002-2016 Lee Salzman + + - FreeType + Website: https://freetype.org/ + License: FreeType License + Copyright (c) 2006-2017 David Turner, Robert Wilhelm, and Werner Lemberg. + + - GLAD + Website: http://glad.dav1d.de/ + License: MIT/Expat + Copyright (c) 2013 David Herberth, modified by Sasha Szpakowski + + - glslang + Website: https://github.com/KhronosGroup/glslang + License: 3-Clause BSD + Copyright (C) 2002-2005 3Dlabs Inc. Ltd. + Copyright (C) 2013-2016 LunarG, Inc. + + - Kepler Project's lua-compat-5.3 + Website: https://github.com/keplerproject/lua-compat-5.3 + License: MIT/Expat + Copyright (c) 2015 Kepler Project. + + - lua-enet + Website: http://leafo.net/lua-enet/ + License: MIT/Expat + Copyright (C) 2011 by Leaf Corcoran + + - LuaJIT + Website: http://luajit.org/ + License: MIT/Expat + LuaJIT is Copyright (c) 2005-2016 Mike Pall + + - Lua's UTF-8 module + Website: https://www.lua.org/ + License: MIT/Expat + Copyright (C) 1994-2015 Lua.org, PUC-Rio, 2015 LOVE Development Team. + + - LuaSocket + Website: http://w3.impa.br/~diego/software/luasocket/home.html + License: MIT/Expat + Copyright (C) 2004-2013 Diego Nehab + + - LZ4 + Website: https://lz4.github.io/lz4/ + License: 2-Clause BSD + Copyright (C) 2011-2015, Yann Collet. + You can contact the author at : + - LZ4 source repository : https://github.com/Cyan4973/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c + + - LodePNG + Website: https://lodev.org/lodepng/ + Source download: https://github.com/lvandeve/lodepng + License: zlib + Copyright (c) 2005-2020 Lode Vandevenne + + - TinyEXR + Website: https://github.com/syoyo/tinyexr + License: 3-Clause BSD + Copyright (c) 2014 - 2016, Syoyo Fujita + + - UTF8-CPP + Website: https://github.com/nemtrif/utfcpp + License: Unknown, MIT/Expat-like (listed as UTF8-CPP) + Copyright 2006 Nemanja Trifunovic + + - xxHash + Website: https://cyan4973.github.io/xxHash/ + License: 2-Clause BSD + Copyright (C) 2012-2016, Yann Collet. + You can contact the author at : + - xxHash source repository : https://github.com/Cyan4973/xxHash + + - dr_flac + Website: https://github.com/mackron/dr_libs + Source download: https://github.com/mackron/dr_libs/blob/c5e5355/dr_flac.h + License: MIT/Expat + Copyright 2018 David Reid + + - stb_image + Website: https://github.com/nothings/stb + Source download: https://github.com/nothings/stb/blob/e140649ccf40818781b7e408f6228a486f6d254b/stb_image.h + License: MIT/Expat + Copyright (c) 2017 Sean Barrett + + - libmpg123 + Website: http://www.mpg123.de/ + Source download: http://sourceforge.net/projects/mpg123/files/latest/download + License: LGPL 2.1 + Copyright (c) 1995-2013 by Michael Hipp and others, free software under the terms of the LGPL v2.1 + Detailed information from the debian project: + Copyright 1995-2016 by the mpg123 project + Copyright 2009-2011 by Malcolm Boczek + Copyright 2008 Christian Weisgerber + Copyright 2006-2007 by Zuxy Meng + Copyright 2000-2002 David Olofson + Copyright 1998 Fabrice Bellard + Copyright 1997 Mikko Tommila + + - OpenAL Soft + Website: https://openal-soft.org/ + Source download: https://openal-soft.org/#download + License: Mixed, licensing information obtained from the debian project + - Alc/backends/opensl.c + License: Apache 2.0 + Copyright 2011 The Android Open Source Project + - examples/alhrtf.c examples/allatency.c examples/alloopback.c examples/alreverb.c examples/alstream.c examples/altonegen.c examples/common/alhelpers.c examples/common/sdl_sound.c utils/openal-info.c + License: MIT/Expat + Copyright © 2010, 2015 Chris Robinson + - examples/alffplay.c + License: unclear, presumed LGPL 2.1 or higher + Copyright © 2003 Fabrice Bellard + Copyright © Martin Bohme + - Alc/bs2b.c OpenAL32/Include/bs2b.h + License: MIT/Expat + Copyright 2005 by Boris Mikhaylov + - cmake/FindALSA.cmake cmake/FindFFmpeg.cmake cmake/FindJACK.cmake cmake/FindSDL2.cmake + License: 3-Clause BSD + Copyright © 2006 Matthias Kretz + Copyright © 2008 Alexander Neundorf + Copyright © 2003-2011 Kitware, Inc. + Copyright © 2009-2011 Philip Lowman + Copyright © 2011 Michael Jansen + Copyright © 2012 Benjamin Eikel + - utils/makehrtf.c (not included in distribution) + License: GPL 2 or higher (2 listed below) + Copyright 2011-2014 Christopher Fitzgerald + - Everything else: + License: LGPL 2.0 or higher (2.1 listed below) + Copyright © 1999-2014 the OpenAL team + Copyright © 2008-2015 Christopher Fitzgerald + Copyright © 2009-2015 Chris Robinson + Copyright © 2013 Anis A. Hireche + Copyright © 2013 Nasca Octavian Paul + Copyright © 2013 Mike Gorchak + Copyright © 2014 Timothy Arceri + +License text +============ + +zlib license + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +MIT/Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +FreeType License + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + Introduction + ============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + + Legal Terms + =========== + + 0. Definitions + -------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + + 1. No Warranty + -------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + + 2. Redistribution + ----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + + 3. Advertising + -------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + + 4. Contacts + ----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + http://www.freetype.org + +3-Clause BSD + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither the name of 3Dlabs Inc. Ltd. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +2-Clause BSD + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +UTF8-CPP + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +LGPL 2.1 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + Licenses are intended to guarantee your freedom to share and change + free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some + specially designated software packages--typically libraries--of the + Free Software Foundation and other authors who decide to use it. You + can use it too, but we suggest you first think carefully about whether + this license or the ordinary General Public License is the better + strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, + not price. Our General Public Licenses are designed to make sure that + you have the freedom to distribute copies of free software (and charge + for this service if you wish); that you receive source code or can get + it if you want it; that you can change the software and use pieces of + it in new free programs; and that you are informed that you can do + these things. + + To protect your rights, we need to make restrictions that forbid + distributors to deny you these rights or to ask you to surrender these + rights. These restrictions translate to certain responsibilities for + you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis + or for a fee, you must give the recipients all the rights that we gave + you. You must make sure that they, too, receive or can get the source + code. If you link other code with the library, you must provide + complete object files to the recipients, so that they can relink them + with the library after making changes to the library and recompiling + it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the + library, and (2) we offer you this license, which gives you legal + permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that + there is no warranty for the free library. Also, if the library is + modified by someone else and passed on, the recipients should know + that what they have is not the original version, so that the original + author's reputation will not be affected by problems that might be + introduced by others. + + Finally, software patents pose a constant threat to the existence of + any free program. We wish to make sure that a company cannot + effectively restrict the users of a free program by obtaining a + restrictive license from a patent holder. Therefore, we insist that + any patent license obtained for a version of the library must be + consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the + ordinary GNU General Public License. This license, the GNU Lesser + General Public License, applies to certain designated libraries, and + is quite different from the ordinary General Public License. We use + this license for certain libraries in order to permit linking those + libraries into non-free programs. + + When a program is linked with a library, whether statically or using + a shared library, the combination of the two is legally speaking a + combined work, a derivative of the original library. The ordinary + General Public License therefore permits such linking only if the + entire combination fits its criteria of freedom. The Lesser General + Public License permits more lax criteria for linking other code with + the library. + + We call this license the "Lesser" General Public License because it + does Less to protect the user's freedom than the ordinary General + Public License. It also provides other free software developers Less + of an advantage over competing non-free programs. These disadvantages + are the reason we use the ordinary General Public License for many + libraries. However, the Lesser license provides advantages in certain + special circumstances. + + For example, on rare occasions, there may be a special need to + encourage the widest possible use of a certain library, so that it becomes + a de-facto standard. To achieve this, non-free programs must be + allowed to use the library. A more frequent case is that a free + library does the same job as widely used non-free libraries. In this + case, there is little to gain by limiting the free library to free + software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free + programs enables a greater number of people to use a large body of + free software. For example, permission to use the GNU C Library in + non-free programs enables many more people to use the whole GNU + operating system, as well as its variant, the GNU/Linux operating + system. + + Although the Lesser General Public License is Less protective of the + users' freedom, it does ensure that the user of a program that is + linked with the Library has the freedom and the wherewithal to run + that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a + "work based on the library" and a "work that uses the library". The + former contains code derived from the library, whereas the latter must + be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other + program which contains a notice placed by the copyright holder or + other authorized party saying it may be distributed under the terms of + this Lesser General Public License (also called "this License"). + Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work + which has been distributed under these terms. A "work based on the + Library" means either the Library or any derivative work under + copyright law: that is to say, a work containing the Library or a + portion of it, either verbatim or with modifications and/or translated + straightforwardly into another language. (Hereinafter, translation is + included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for + making modifications to it. For a library, complete source code means + all the source code for all modules it contains, plus any associated + interface definition files, plus the scripts used to control compilation + and installation of the library. + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running a program using the Library is not restricted, and output from + such a program is covered only if its contents constitute a work based + on the Library (independent of the use of the Library in a tool for + writing it). Whether that is true depends on what the Library does + and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's + complete source code as you receive it, in any medium, provided that + you conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep intact + all the notices that refer to this License and to the absence of any + warranty; and distribute a copy of this License along with the + Library. + + You may charge a fee for the physical act of transferring a copy, + and you may at your option offer warranty protection in exchange for a + fee. + + 2. You may modify your copy or copies of the Library or any portion + of it, thus forming a work based on the Library, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Library, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote + it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so + that they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in + these notices. + + Once this change is made in a given copy, it is irreversible for + that copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of + the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or + derivative of it, under Section 2) in object code or executable form + under the terms of Sections 1 and 2 above provided that you accompany + it with the complete corresponding machine-readable source code, which + must be distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy + from a designated place, then offering equivalent access to copy the + source code from the same place satisfies the requirement to + distribute the source code, even though third parties are not + compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the + Library, but is designed to work with the Library by being compiled or + linked with it, is called a "work that uses the Library". Such a + work, in isolation, is not a derivative work of the Library, and + therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library + creates an executable that is a derivative of the Library (because it + contains portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. + Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is not. + Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data + structure layouts and accessors, and small macros and small inline + functions (ten lines or less in length), then the use of the object + file is unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section 6. + Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or + link a "work that uses the Library" with the Library to produce a + work containing portions of the Library, and distribute that work + under terms of your choice, provided that the terms permit + modification of the work for the customer's own use and reverse + engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work + during execution displays copyright notices, you must include the + copyright notice for the Library among them, as well as a reference + directing the user to the copy of this License. Also, you must do one + of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the + Library" must include any data and utility programs needed for + reproducing the executable from it. However, as a special exception, + the materials to be distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies + the executable. + + It may happen that this requirement contradicts the license + restrictions of other proprietary libraries that do not normally + accompany the operating system. Such a contradiction means you cannot + use both them and the Library together in an executable that you + distribute. + + 7. You may place library facilities that are a work based on the + Library side-by-side in a single library together with other library + facilities not covered by this License, and distribute such a combined + library, provided that the separate distribution of the work based on + the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute + the Library except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, + or rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Library or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Library (or any work based on the + Library), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties with + this License. + + 11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent + license would not permit royalty-free redistribution of the Library by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Library. + + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Library under this License may add + an explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new + versions of the Lesser General Public License from time to time. + Such new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and + "any later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a + license version number, you may choose any version ever published by + the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. Our + decision will be guided by the two goals of preserving the free status + of all derivatives of our free software and of promoting the sharing + and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE + LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU + FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE + LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A + FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest + possible use to the public, we recommend making it free software that + everyone can redistribute and change. You can do so by permitting + redistribution under these terms (or, alternatively, under the terms of the + ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is + safest to attach them to the start of each source file to most effectively + convey the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Also add information on how to contact you by electronic and paper mail. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the library, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + + That's all there is to it! + +GPL 2 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + License is intended to guarantee your freedom to share and change free + software--to make sure the software is free for all its users. This + General Public License applies to most of the Free Software + Foundation's software and to any other program whose authors commit to + using it. (Some other Free Software Foundation software is covered by + the GNU Lesser General Public License instead.) You can apply it to + your programs, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether + gratis or for a fee, you must give the recipients all the rights that + you have. You must make sure that they, too, receive or can get the + source code. And you must show them these terms so they know their + rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that redistributors of a free + program will individually obtain patent licenses, in effect making the + program proprietary. To prevent this, we have made it clear that any + patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains + a notice placed by the copyright holder saying it may be distributed + under the terms of this General Public License. The "Program", below, + refers to any such program or work, and a "work based on the Program" + means either the Program or any derivative work under copyright law: + that is to say, a work containing the Program or a portion of it, + either verbatim or with modifications and/or translated into another + language. (Hereinafter, translation is included without limitation in + the term "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running the Program is not restricted, and the output from the Program + is covered only if its contents constitute a work based on the + Program (independent of having been made by running the Program). + Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an appropriate + copyright notice and disclaimer of warranty; keep intact all the + notices that refer to this License and to the absence of any warranty; + and give any other recipients of the Program a copy of this License + along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion + of it, thus forming a work based on the Program, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Program, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source + code means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to + control compilation and installation of the executable. However, as a + special exception, the source code distributed need not include + anything that is normally distributed (in either source or binary + form) with the major components (compiler, kernel, and so on) of the + operating system on which the executable runs, unless that component + itself accompanies the executable. + + If distribution of executable or object code is made by offering + access to copy from a designated place, then offering equivalent + access to copy the source code from the same place counts as + distribution of the source code, even though third parties are not + compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt + otherwise to copy, modify, sublicense or distribute the Program is + void, and will automatically terminate your rights under this License. + However, parties who have received copies, or rights, from you under + this License will not have their licenses terminated so long as such + parties remain in full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Program at all. For example, if a patent + license would not permit royalty-free redistribution of the Program by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License + may add an explicit geographical distribution limitation excluding + those countries, so that distribution is permitted only in or among + countries not thus excluded. In such case, this License incorporates + the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions + of the General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and conditions + either of that version or of any later version published by the Free + Software Foundation. If the Program does not specify a version number of + this License, you may choose any version ever published by the Free Software + Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the author + to ask for permission. For software which is copyrighted by the Free + Software Foundation, write to the Free Software Foundation; we sometimes + make exceptions for this. Our decision will be guided by the two goals + of preserving the free status of all derivatives of our free software and + of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE + PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING + OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + convey the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the appropriate + parts of the General Public License. Of course, the commands you use may + be called something other than `show w' and `show c'; they could even be + mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program into + proprietary programs. If your program is a subroutine library, you may + consider it more useful to permit linking proprietary applications with the + library. If this is what you want to do, use the GNU Lesser General + Public License instead of this License. + +Apache 2.0 + Apache License + + Version 2.0, January 2004 + + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + You must give any other recipients of the Work or Derivative Works a copy of this License; and + You must cause any modified files to carry prominent notices stating that You changed the files; and + You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + APPENDIX: How to apply the Apache License to your work + + To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ports/uwp/third_party/licenses/luajit.txt b/ports/uwp/third_party/licenses/luajit.txt new file mode 100644 index 00000000..955d0d1e --- /dev/null +++ b/ports/uwp/third_party/licenses/luajit.txt @@ -0,0 +1,56 @@ +=============================================================================== +LuaJIT -- a Just-In-Time Compiler for Lua. https://luajit.org/ + +Copyright (C) 2005-2026 Mike Pall. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +[ MIT license: https://www.opensource.org/licenses/mit-license.php ] + +=============================================================================== +[ LuaJIT includes code from Lua 5.1/5.2, which has this license statement: ] + +Copyright (C) 1994-2012 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +[ LuaJIT includes code from dlmalloc, which has this license statement: ] + +This is a version (aka dlmalloc) of malloc/free/realloc written by +Doug Lea and released to the public domain, as explained at +https://creativecommons.org/licenses/publicdomain + +=============================================================================== diff --git a/ports/uwp/third_party/licenses/ogg.txt b/ports/uwp/third_party/licenses/ogg.txt new file mode 100644 index 00000000..6111c6c5 --- /dev/null +++ b/ports/uwp/third_party/licenses/ogg.txt @@ -0,0 +1,28 @@ +Copyright (c) 2002, Xiph.org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ports/uwp/third_party/licenses/openal-soft.txt b/ports/uwp/third_party/licenses/openal-soft.txt new file mode 100644 index 00000000..c8152d0d --- /dev/null +++ b/ports/uwp/third_party/licenses/openal-soft.txt @@ -0,0 +1,501 @@ +COPYING: + + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + +pffft Notice: + +//$ nobt + +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + * Copyright (c) 2023 Christopher Robinson + * + * Based on original fortran 77 code from FFTPACKv4 from NETLIB + * (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber + * of NCAR, in 1985. + * + * As confirmed by the NCAR fftpack software curators, the following + * FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + * released under the same terms. + * + * FFTPACK license: + * + * http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + * + * Copyright (c) 2004 the University Corporation for Atmospheric + * Research ("UCAR"). All rights reserved. Developed by NCAR's + * Computational and Information Systems Laboratory, UCAR, + * www.cisl.ucar.edu. + * + * Redistribution and use of the Software in source and binary forms, + * with or without modification, is permitted provided that the + * following conditions are met: + * + * - Neither the names of NCAR's Computational and Information Systems + * Laboratory, the University Corporation for Atmospheric Research, + * nor the names of its sponsors or contributors may be used to + * endorse or promote products derived from this Software without + * specific prior written permission. + * + * - Redistributions of source code must retain the above copyright + * notices, this list of conditions, and the disclaimer below. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the disclaimer below in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + * SOFTWARE. + * + * + * PFFFT : a Pretty Fast FFT. + * + * This file is largerly based on the original FFTPACK implementation, modified + * in order to take advantage of SIMD instructions of modern CPUs. + */ + + diff --git a/ports/uwp/third_party/licenses/sdl2.txt b/ports/uwp/third_party/licenses/sdl2.txt new file mode 100644 index 00000000..23abb73f --- /dev/null +++ b/ports/uwp/third_party/licenses/sdl2.txt @@ -0,0 +1,18 @@ +Copyright (C) 1997-2025 Sam Lantinga + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/ports/uwp/third_party/licenses/theora.txt b/ports/uwp/third_party/licenses/theora.txt new file mode 100644 index 00000000..b90629a2 --- /dev/null +++ b/ports/uwp/third_party/licenses/theora.txt @@ -0,0 +1,54 @@ +COPYING: + +Copyright (C) 2002-2009 Xiph.org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +LICENSE: + +Please see the file COPYING for the copyright license for this software. + +In addition to and irrespective of the copyright license associated +with this software, On2 Technologies, Inc. makes the following statement +regarding technology used in this software: + + On2 represents and warrants that it shall not assert any rights + relating to infringement of On2's registered patents, nor initiate + any litigation asserting such rights, against any person who, or + entity which utilizes the On2 VP3 Codec Software, including any + use, distribution, and sale of said Software; which make changes, + modifications, and improvements in said Software; and to use, + distribute, and sell said changes as well as applications for other + fields of use. + +This reference implementation is originally derived from the On2 VP3 +Codec Software, and the Theora video format is essentially compatible +with the VP3 video format, consisting of a backward-compatible superset. + + diff --git a/ports/uwp/third_party/licenses/vorbis.txt b/ports/uwp/third_party/licenses/vorbis.txt new file mode 100644 index 00000000..fb456a87 --- /dev/null +++ b/ports/uwp/third_party/licenses/vorbis.txt @@ -0,0 +1,28 @@ +Copyright (c) 2002-2020 Xiph.org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ports/uwp/third_party/licenses/zlib.txt b/ports/uwp/third_party/licenses/zlib.txt new file mode 100644 index 00000000..b7a69d05 --- /dev/null +++ b/ports/uwp/third_party/licenses/zlib.txt @@ -0,0 +1,22 @@ +Copyright notice: + + (C) 1995-2026 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/ports/uwp/third_party/love/README.md b/ports/uwp/third_party/love/README.md new file mode 100644 index 00000000..cca8b2fa --- /dev/null +++ b/ports/uwp/third_party/love/README.md @@ -0,0 +1,7 @@ +# LÖVE UWP binaries + +These x64 UWP Release binaries were built from [`caorthann-celt/love-xbox-uwp`](https://github.com/caorthann-celt/love-xbox-uwp) at commit `cdf85f28ed794c95d6f5f7e3a8f23ca9ee1fbdcb`. + +The build uses LÖVE 11.5, LuaJIT, SDL2, and ANGLE. Keep the DLLs and import libraries together; they are one binary interface. + +The normal package build consumes these files directly. Rebuilding the backend is a separate dependency maintenance task. diff --git a/ports/uwp/third_party/love/bin/love.dll b/ports/uwp/third_party/love/bin/love.dll new file mode 100644 index 00000000..306fc5c5 Binary files /dev/null and b/ports/uwp/third_party/love/bin/love.dll differ diff --git a/ports/uwp/third_party/love/bin/lua51.dll b/ports/uwp/third_party/love/bin/lua51.dll new file mode 100644 index 00000000..6ee38a88 Binary files /dev/null and b/ports/uwp/third_party/love/bin/lua51.dll differ diff --git a/ports/uwp/third_party/love/lib/liblove.lib b/ports/uwp/third_party/love/lib/liblove.lib new file mode 100644 index 00000000..ff75a98d Binary files /dev/null and b/ports/uwp/third_party/love/lib/liblove.lib differ diff --git a/ports/uwp/third_party/love/lib/lovestatic.lib b/ports/uwp/third_party/love/lib/lovestatic.lib new file mode 100644 index 00000000..af2d8ad1 Binary files /dev/null and b/ports/uwp/third_party/love/lib/lovestatic.lib differ diff --git a/ports/uwp/third_party/love/lib/lua51.lib b/ports/uwp/third_party/love/lib/lua51.lib new file mode 100644 index 00000000..1ad14ea0 Binary files /dev/null and b/ports/uwp/third_party/love/lib/lua51.lib differ diff --git a/ports/uwp/third_party/manifest.json b/ports/uwp/third_party/manifest.json new file mode 100644 index 00000000..ec790a24 --- /dev/null +++ b/ports/uwp/third_party/manifest.json @@ -0,0 +1,497 @@ +{ + "schemaVersion": 1, + "platform": "uwp", + "architecture": "x64", + "configuration": "Release", + "sources": { + "love": { + "version": "11.5", + "repository": "https://github.com/caorthann-celt/love-xbox-uwp.git", + "commit": "cdf85f28ed794c95d6f5f7e3a8f23ca9ee1fbdcb" + }, + "luajit": { + "repository": "https://github.com/LuaJIT/LuaJIT.git", + "commit": "4886b676a698acc4bbdf54adfabb3e33a8c020e8" + }, + "sdl2": { + "version": "2.32.10", + "repository": "https://github.com/libsdl-org/SDL.git", + "ref": "release-2.32.10", + "commit": "5d249570393f7a37e037abf22cd6012a4cc56a71", + "revision": "release-2.32.10-xbox-uwp", + "patch": "sdl2/patches/xbox-wgi-controller.patch" + }, + "angle": { + "repository": "https://github.com/SternXD/angle.git", + "commit": "45b0b1e03400b7a10aaa9a077e196d1abcddafce", + "gnArgs": [ + "target_os = \"winuwp\"", + "target_cpu = \"x64\"", + "is_debug = false", + "is_component_build = false", + "is_clang = false", + "use_custom_libcxx = false", + "use_custom_libcxx_for_host = false", + "angle_enable_vulkan = false", + "angle_enable_gl = false", + "angle_enable_d3d9 = false", + "angle_enable_d3d11 = true", + "angle_build_tests = false", + "treat_warnings_as_errors = false" + ] + }, + "depotTools": { + "repository": "https://chromium.googlesource.com/chromium/tools/depot_tools.git", + "commit": "edcece7fb3d5e266a0f60ceb77fb37e94bfff3ca" + }, + "vcpkg": { + "repository": "https://github.com/microsoft/vcpkg.git", + "commit": "80f9bcfa455e875d9c1bf7a7c6692d7e1e481061", + "packages": [ + "freetype[brotli,bzip2,png,zlib]", + "libogg", + "libtheora", + "libvorbis", + "openal-soft", + "zlib" + ], + "runtimeFiles": [ + "brotlicommon.dll", + "brotlidec.dll", + "bz2.dll", + "fmt.dll", + "freetype.dll", + "libpng16.dll", + "ogg.dll", + "OpenAL32.dll", + "theora.dll", + "theoradec.dll", + "vorbis.dll", + "vorbisfile.dll", + "z.dll" + ], + "licenses": { + "brotli": "brotli.txt", + "bzip2": "bzip2.txt", + "fmt": "fmt.txt", + "freetype": "freetype.txt", + "libogg": "ogg.txt", + "libpng": "libpng.txt", + "libtheora": "theora.txt", + "libvorbis": "vorbis.txt", + "openal-soft": "openal-soft.txt", + "zlib": "zlib.txt" + } + } + }, + "files": [ + { + "path": "angle/bin/d3dcompiler_47.dll", + "sha256": "A05F99734F7C4822FEFC12B367AF21FD0976ED6608752FB1E1E80B6ECE7ECBBB" + }, + { + "path": "angle/bin/libEGL.dll", + "sha256": "B0E0C6BAC9D9F0F084DC8F56083A2EEFFB866C0629A64D52B5E33616B4F90CBE" + }, + { + "path": "angle/bin/libGLESv2.dll", + "sha256": "6DE6940EDB728396CBA6F3C0D79172A8EFD33E37671A13B1721C652EECEBF4C4" + }, + { + "path": "love/bin/love.dll", + "sha256": "A3622828014F03DDB502D2A7807DC8F94432172595BE03508099BD66937FD45F" + }, + { + "path": "love/bin/lua51.dll", + "sha256": "08380FB9A7C8A8A85D3425C1FD6F1CB826D980FF31925453E353227E9A23BC70" + }, + { + "path": "love/lib/liblove.lib", + "sha256": "C93A358D5EAE8BE3D74DDFDFA767657E96E95E0151F843FEB4E587CF2D89320A" + }, + { + "path": "love/lib/lovestatic.lib", + "sha256": "15FB3A9CB3C3CDAB8B33DE5B1D1F8D8FF6CE9719E9AA7BC2CF3D7B51CDCE0C98" + }, + { + "path": "love/lib/lua51.lib", + "sha256": "A4F4F0942A432B88A3541A185B3D3D5B4D58413B3CEEE10A2E48CAF9D8439759" + }, + { + "path": "runtime/bin/brotlicommon.dll", + "sha256": "6A1D267564FE5BE6D601C1F5301F45DD0D0CCBDA2AE8975CACA4424A9FD452AF" + }, + { + "path": "runtime/bin/brotlidec.dll", + "sha256": "C12C983AEE4E7EF966982AD11D71B32456CB445B18B8E11D9A35E781619085E2" + }, + { + "path": "runtime/bin/bz2.dll", + "sha256": "28ADC0239EAF51D13F8C7075B8514420CAE92EFF14471EC6F5B56A11D9345F00" + }, + { + "path": "runtime/bin/fmt.dll", + "sha256": "B4ED319D0733EA619D11770558957AFBDF99021D86A4ED068628AE34426C6090" + }, + { + "path": "runtime/bin/freetype.dll", + "sha256": "3727B17849902FE12DED3C76BD71D2907C523CF67D1A7CB516025522E260F9D4" + }, + { + "path": "runtime/bin/libpng16.dll", + "sha256": "9B3B823A59ABCB4BA43D1E6DB4C35855AE66384106E32FD5F9D486A7DECB81AF" + }, + { + "path": "runtime/bin/ogg.dll", + "sha256": "283B0890599450AE4BE486C07241ECCC7B14B480465AF7F8D9F2A84528064A65" + }, + { + "path": "runtime/bin/OpenAL32.dll", + "sha256": "D37D04CDD1EEE2005ACF64B558DFB16F0A0D83991F3B2A75E5C69FF314054F8B" + }, + { + "path": "runtime/bin/theora.dll", + "sha256": "C90101C9C169B68F4FA155E63C71F28B271BBB68649085C8ACEFB26816663857" + }, + { + "path": "runtime/bin/theoradec.dll", + "sha256": "53A5A3A0B24F72B7D8812DE4B78FD3BB222399B9293B577A8FCF80009B72F004" + }, + { + "path": "runtime/bin/vorbis.dll", + "sha256": "A4B2D0A4C032A7E60AA297ADAE134E8A2C1BE61D7EC639CAA93AF85BF7EAC2C7" + }, + { + "path": "runtime/bin/vorbisfile.dll", + "sha256": "0BFFA35539F0A8233A8EFF4C6680F8A7CF3B9DBE3B437A3F94D5301B66A2F134" + }, + { + "path": "runtime/bin/z.dll", + "sha256": "8B86502ADC15A13C0E136FB9D63AA6583C59AD93EC608A9A7C901757354744C3" + }, + { + "path": "sdl2/bin/SDL2.dll", + "sha256": "B02D13F77221A335AB9F21304F2B4E003FE59C6DF10F21BA6D49023016D35CA8" + }, + { + "path": "sdl2/include/SDL2/begin_code.h", + "sha256": "9803DAB29FB4522CE3500AB789260E9622DF6307CE90F92AD20C1808E19C4E7B" + }, + { + "path": "sdl2/include/SDL2/close_code.h", + "sha256": "4599E9CEDBA7451FF07A6306F0B7531BEED05B1C5A65B1A2613D1B771BE43FDA" + }, + { + "path": "sdl2/include/SDL2/SDL_assert.h", + "sha256": "32FC342E0A5F3A55EA1CE2A5D9195E77215EDCB6F4595C0DC3674DCCA1CC81D8" + }, + { + "path": "sdl2/include/SDL2/SDL_atomic.h", + "sha256": "C561CE8F94DE18CCB4F708BDA45880BA6D3C9F052E166BEB5DADC5544F35221E" + }, + { + "path": "sdl2/include/SDL2/SDL_audio.h", + "sha256": "047AFC29591AC43A90F06169F67EE7D99A6256C6BBE5EFE5DE79A52565BB6853" + }, + { + "path": "sdl2/include/SDL2/SDL_bits.h", + "sha256": "63D22D1C8FE0D83BBA5A49ABA9BB700592F14462F7F7BF87E5838064047E3FA9" + }, + { + "path": "sdl2/include/SDL2/SDL_blendmode.h", + "sha256": "1985F81B886D9BC681821AF4F23D28E6E51BF51F21E5EF28CCC13BC8C7CDFBAE" + }, + { + "path": "sdl2/include/SDL2/SDL_clipboard.h", + "sha256": "3E8C73FB9B3CA5A544B70B66B683B78DB53E1A187E066912A068C0ED7BB3E8CE" + }, + { + "path": "sdl2/include/SDL2/SDL_config.h", + "sha256": "108A953419E1638AE2FF09148E2927C3AC4FF3E15B72A56FF41B542F356021BA" + }, + { + "path": "sdl2/include/SDL2/SDL_copying.h", + "sha256": "3F90BE470C18F99F4AEF39444E9374EDBA166CFEB1B90F99F35286CC33924642" + }, + { + "path": "sdl2/include/SDL2/SDL_cpuinfo.h", + "sha256": "FEE0C489ED4364C21FE0B726A47F72DF167FBCBF42CF569807B2BBF77DC30C54" + }, + { + "path": "sdl2/include/SDL2/SDL_egl.h", + "sha256": "72C119F4F7EC30F13B03D3E33D2266CB012EE103F0AD1973786D84D538A0B53D" + }, + { + "path": "sdl2/include/SDL2/SDL_endian.h", + "sha256": "7907CB3AB7B8D7BC99274F5AEF5294BEF215B01EA49D97226CBE6E911DE557D5" + }, + { + "path": "sdl2/include/SDL2/SDL_error.h", + "sha256": "0931656A5825E5F1F319F2CD3F573FC527393BB02FA7A1ED3F7FAE6C790328BE" + }, + { + "path": "sdl2/include/SDL2/SDL_events.h", + "sha256": "CEB7BC717342E652E2C432FD1DD08A5508575CEA6BD8615FB4DDEA63C071DDC4" + }, + { + "path": "sdl2/include/SDL2/SDL_filesystem.h", + "sha256": "DCFAA010C73150E7B0D56F50F5ADB3FC2E35621091A55BC5436B2FB85FE71675" + }, + { + "path": "sdl2/include/SDL2/SDL_gamecontroller.h", + "sha256": "C77C1D8287EE441843B9D8BE87B71252BDB1CE76B7BFC4FCFF46889AD8E9CFEC" + }, + { + "path": "sdl2/include/SDL2/SDL_gesture.h", + "sha256": "A5A8F8BB8EFB26860FAA9694189AAF76DF1CDB7755CCD30A8599B46B237A5D82" + }, + { + "path": "sdl2/include/SDL2/SDL_guid.h", + "sha256": "DA8FEF1D047B31E55D1B167DE301A806481B6948E7C2497779FD71934C45D5DB" + }, + { + "path": "sdl2/include/SDL2/SDL_haptic.h", + "sha256": "4267B6F039BEA7AD0F5CB37C37D63D0704AF84F73A36D2680684FF302F49F153" + }, + { + "path": "sdl2/include/SDL2/SDL_hidapi.h", + "sha256": "489A83D1073E528F82E821C23C515AF8B5A59357EC5AB132C854BB588648F59F" + }, + { + "path": "sdl2/include/SDL2/SDL_hints.h", + "sha256": "96C1056E0734F019B1DAFED5129D4A9D36253559ED8C7577A23C188BB75A48C1" + }, + { + "path": "sdl2/include/SDL2/SDL_joystick.h", + "sha256": "A39FC5DA97EDC94EABFF8616A413CF850EFB35D1DAF437A8CC16CAA837766482" + }, + { + "path": "sdl2/include/SDL2/SDL_keyboard.h", + "sha256": "F68DFA4F784F0B22949A8DF94F3C3653EA01A03AE02297D17579EB9E7EA94054" + }, + { + "path": "sdl2/include/SDL2/SDL_keycode.h", + "sha256": "291C769A05BC3E1EAD011DB3E07CB52415E1AA306F484904D203E94CBB3186CD" + }, + { + "path": "sdl2/include/SDL2/SDL_loadso.h", + "sha256": "8D3653B7B774B406A289519C74AE695EB5B52D71060712A88FCE4C72D82B1BD3" + }, + { + "path": "sdl2/include/SDL2/SDL_locale.h", + "sha256": "C4A663CE8EA4CA22F961796CAE644A04FC521FA2D0681333F7DA8A581909560C" + }, + { + "path": "sdl2/include/SDL2/SDL_log.h", + "sha256": "3342CB31280C2DBF0E5809B03E5499F0CF5EDF193EFA5E3C5B17814526A92033" + }, + { + "path": "sdl2/include/SDL2/SDL_main.h", + "sha256": "622FFCD0E7FE0C755A987C5EB9DAD6B3672774026B8250B66AD3C40D3A4DDA9A" + }, + { + "path": "sdl2/include/SDL2/SDL_messagebox.h", + "sha256": "65DF549CAB0A515145AE55325C447159AE1D60171331A7508EAC06034D25C972" + }, + { + "path": "sdl2/include/SDL2/SDL_metal.h", + "sha256": "1F3D0251522B735B0E37677EB1A0594440599FBBB2AF86BBB508606CCA001A06" + }, + { + "path": "sdl2/include/SDL2/SDL_misc.h", + "sha256": "5D5E4B42481A5EF44EC9D66159E88426BE5632262731A3C3B223277771D30006" + }, + { + "path": "sdl2/include/SDL2/SDL_mouse.h", + "sha256": "66B5353387D011A54B232931E86121CB5551E251B9491B9ED15AFE293D5856FF" + }, + { + "path": "sdl2/include/SDL2/SDL_mutex.h", + "sha256": "C5EF9A8AEB056422EF878672770E1F60AE965933693DF6D8EBD53D0E514A1794" + }, + { + "path": "sdl2/include/SDL2/SDL_name.h", + "sha256": "FDC6C648734220285056AD8B8273511C75D5BE00D17FC1EA3EB4C06526B13FA3" + }, + { + "path": "sdl2/include/SDL2/SDL_opengl_glext.h", + "sha256": "1ABB28891C9B0661B6FF3749DE2671DA5FE12BEB5BCB1833B2D6BDF5C89ECDF8" + }, + { + "path": "sdl2/include/SDL2/SDL_opengl.h", + "sha256": "C4D2857E757B2B2A83D7531CBEDA19C383AD6D16648AB9B7544FC0A74EB2FF13" + }, + { + "path": "sdl2/include/SDL2/SDL_opengles.h", + "sha256": "0542791816FCDD84B74D98347AC1B00A97BC6C2CDF31334CD9083789FFB6594A" + }, + { + "path": "sdl2/include/SDL2/SDL_opengles2_gl2.h", + "sha256": "D6EC44B1D73F3AFCE3A20AC6976DB7FB3DAC656C5190F0B563E4CE90D79A15AA" + }, + { + "path": "sdl2/include/SDL2/SDL_opengles2_gl2ext.h", + "sha256": "4FC5B0034DCDE9C125922E3E70D01489A6B335076856EE660294E1A6305A3719" + }, + { + "path": "sdl2/include/SDL2/SDL_opengles2_gl2platform.h", + "sha256": "4779BE999ACD1904458238F09C86983B7960AC40E612E05DB2DB97DB615F1E0F" + }, + { + "path": "sdl2/include/SDL2/SDL_opengles2_khrplatform.h", + "sha256": "7B1E01AAA7AD8F6FC34B5C7BDF79EBF5189BB09E2C4D2E79FC5D350623D11E83" + }, + { + "path": "sdl2/include/SDL2/SDL_opengles2.h", + "sha256": "73A3B042F7B3D296904BFF91268730D5180638F4EC1836D57258FE53EE60DF8B" + }, + { + "path": "sdl2/include/SDL2/SDL_pixels.h", + "sha256": "FEE61AEE337A3823E832F5C26B3000315D6EB95DA91BA20D4FB0B5BAC9D4967A" + }, + { + "path": "sdl2/include/SDL2/SDL_platform.h", + "sha256": "9CE62823791D7A16B9AD17AD11F4DE8DCBCDE7918F67BF6D1DD4FC625B69E50C" + }, + { + "path": "sdl2/include/SDL2/SDL_power.h", + "sha256": "FBBDF3FF13B21095623EDFC5659C1B76C6DBB1364A4DD14D8F9E5CF329048784" + }, + { + "path": "sdl2/include/SDL2/SDL_quit.h", + "sha256": "9C902ABAEA7560F98FC546BD35A04629A205D047DF5433FC30B0C1D81AF58B21" + }, + { + "path": "sdl2/include/SDL2/SDL_rect.h", + "sha256": "01DB368D4B7736E2358CD29EEC0D187D964CFD6182CFFAB57A938ADF73B6A418" + }, + { + "path": "sdl2/include/SDL2/SDL_render.h", + "sha256": "A0CF0DB1A13D0DD0D8BFDC01FFB371E2B19BE36326F85058200AC9481876C7F0" + }, + { + "path": "sdl2/include/SDL2/SDL_revision.h", + "sha256": "EF28EADA36B000C710B033317A37677B5484A4DE224547CB7293A77FA0C97195" + }, + { + "path": "sdl2/include/SDL2/SDL_rwops.h", + "sha256": "10DA91C08E96E5FCA452E06E11029908A6F237356E4DB11AF1AD13FFA2E57A47" + }, + { + "path": "sdl2/include/SDL2/SDL_scancode.h", + "sha256": "EB14F3CEDDE58358F6456D185F473D7551E8234DE260151AD0319861FF1444C2" + }, + { + "path": "sdl2/include/SDL2/SDL_sensor.h", + "sha256": "F4DABF5BAE217FA6370D8AF73ABDCBBA99AEA626F35E17A381B636AFE01BB81B" + }, + { + "path": "sdl2/include/SDL2/SDL_shape.h", + "sha256": "CBDCDD8EAD6D61A7BFA09BC4C538CC598919D1C060D70524C0828D3AF7418667" + }, + { + "path": "sdl2/include/SDL2/SDL_stdinc.h", + "sha256": "33EDC0B35512B86162D1AFFE9DF95165B4BC514A90B17238420EFE85D24CA8A1" + }, + { + "path": "sdl2/include/SDL2/SDL_surface.h", + "sha256": "92299233370D645F78D9C655CE7F102C16F9D4DDDE28A59BD82A962C49B51D94" + }, + { + "path": "sdl2/include/SDL2/SDL_system.h", + "sha256": "FFC5F3662A85F795C0B2E2B87B9FBF493BCEEDA4AACF7721A68225C5E318CA67" + }, + { + "path": "sdl2/include/SDL2/SDL_syswm.h", + "sha256": "34FEC1A5F1089BD395EE11C3A25AD0FC10E7ABC56D4A77CC6F17EB9E4E8A9607" + }, + { + "path": "sdl2/include/SDL2/SDL_test_assert.h", + "sha256": "FE826FEB054929284764718C67AE95B920729AE1336FFEEC88A23D611E6498F0" + }, + { + "path": "sdl2/include/SDL2/SDL_test_common.h", + "sha256": "B619CC3020FFDC8F8A12F5D9D4AF8B83E9B7C6C3BFC05C6502933C4B3F0103DE" + }, + { + "path": "sdl2/include/SDL2/SDL_test_compare.h", + "sha256": "713AF284D8520E910D94090396BFD2DB2B2292096EDD93E4D60AA69163B9C510" + }, + { + "path": "sdl2/include/SDL2/SDL_test_crc32.h", + "sha256": "74CB4C2F7F7130D6048A2C716260D6403620F0E358FA928136ED3560BF71A378" + }, + { + "path": "sdl2/include/SDL2/SDL_test_font.h", + "sha256": "B0B7E6AC8BCF7B9E5BF28695B05D3A084CD530C90322CBC9A0B198114B174231" + }, + { + "path": "sdl2/include/SDL2/SDL_test_fuzzer.h", + "sha256": "494E0E49882A9C2745D07DA7B04761D8D992DD90A7A29DD34B605051ED8DCEE5" + }, + { + "path": "sdl2/include/SDL2/SDL_test_harness.h", + "sha256": "30B9CCB36E07FF0442429A27540F6A796AEE073355B538BD50EF0B98CC47982E" + }, + { + "path": "sdl2/include/SDL2/SDL_test_images.h", + "sha256": "35E7AD856A201FBAAFE7EC3FC48DB47FB5D9452C292267F8936AEC5E5C77B81D" + }, + { + "path": "sdl2/include/SDL2/SDL_test_log.h", + "sha256": "3A8736E6D757666420616F83E7929493FCBE816A0CDDF10347F3446818870BE8" + }, + { + "path": "sdl2/include/SDL2/SDL_test_md5.h", + "sha256": "BE73865B584B5EB207CAB3D8DCFF6AD057286B75053B547B0238DF275E282491" + }, + { + "path": "sdl2/include/SDL2/SDL_test_memory.h", + "sha256": "556F676637FBB1A85F1998545C59ADDCA358B8B95C71812337B370184F27483F" + }, + { + "path": "sdl2/include/SDL2/SDL_test_random.h", + "sha256": "1D8C7987AE39B74D83F40FB54394FD32C61037716C77EBCAF7CE87788934C035" + }, + { + "path": "sdl2/include/SDL2/SDL_test.h", + "sha256": "55E453735FB6A14CF350F663E8C7BC9AA273DB95BB31BCF3ABBFB5B3A91A414E" + }, + { + "path": "sdl2/include/SDL2/SDL_thread.h", + "sha256": "0E546047DD407CA0E1D2D686CF2650AC77A987047184E3645F6BA2EC958DDC5C" + }, + { + "path": "sdl2/include/SDL2/SDL_timer.h", + "sha256": "D0497AD3D75701613A227134D63FDCBC62ED72F645153D52753E386EE4B498C7" + }, + { + "path": "sdl2/include/SDL2/SDL_touch.h", + "sha256": "4E5D7A083BBF5237DAF5CCF06FAED3FAE81B1CAA693E27841EBF4EC9BF8FF769" + }, + { + "path": "sdl2/include/SDL2/SDL_types.h", + "sha256": "ED0C92BED5EEC2648F744E8451F3AC5140C1EF11510175FA1B726273DBDED9ED" + }, + { + "path": "sdl2/include/SDL2/SDL_version.h", + "sha256": "EE97570D0D6507B4687076336C07FD4A7FA1F5E7986C9B61893A279950FEBCE8" + }, + { + "path": "sdl2/include/SDL2/SDL_video.h", + "sha256": "82AB63FF37834CD4BFCEE490B8E37FB892BBB0B31A1A7913EC13C329271CA5FF" + }, + { + "path": "sdl2/include/SDL2/SDL_vulkan.h", + "sha256": "98ED6B3D354191019AE208FC861799237BA8FFB11FA6BD41B3569E6CE05555B8" + }, + { + "path": "sdl2/include/SDL2/SDL.h", + "sha256": "952E89E1DC4E0DD1A7BEA6A153DF632B8CDB6E433A9B3B0702C5FFE15B9B9360" + }, + { + "path": "sdl2/lib/SDL2.lib", + "sha256": "37B330EE42407B405BD2432E79CB9B9036245309B3ACC445AD70CB8E555B7288" + } + ] +} diff --git a/ports/uwp/third_party/runtime/README.md b/ports/uwp/third_party/runtime/README.md new file mode 100644 index 00000000..9ec4890e --- /dev/null +++ b/ports/uwp/third_party/runtime/README.md @@ -0,0 +1,5 @@ +# LÖVE runtime dependencies + +These x64 UWP Release DLLs are the runtime closure used by the bundled LÖVE backend. They were installed by vcpkg at commit `80f9bcfa455e875d9c1bf7a7c6692d7e1e481061`. + +The bundle contains Brotli 1.2.0, bzip2 1.0.8#6, fmt 12.1.0, FreeType 2.14.3, libogg 1.3.6#1, libpng 1.6.58, libtheora 1.2.0, libvorbis 1.3.7#4, OpenAL Soft 1.25.1, and zlib 1.3.2. Corresponding notices are under `../licenses`. diff --git a/ports/uwp/third_party/runtime/bin/OpenAL32.dll b/ports/uwp/third_party/runtime/bin/OpenAL32.dll new file mode 100644 index 00000000..ea0e14d2 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/OpenAL32.dll differ diff --git a/ports/uwp/third_party/runtime/bin/brotlicommon.dll b/ports/uwp/third_party/runtime/bin/brotlicommon.dll new file mode 100644 index 00000000..459dace0 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/brotlicommon.dll differ diff --git a/ports/uwp/third_party/runtime/bin/brotlidec.dll b/ports/uwp/third_party/runtime/bin/brotlidec.dll new file mode 100644 index 00000000..7ece67e5 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/brotlidec.dll differ diff --git a/ports/uwp/third_party/runtime/bin/bz2.dll b/ports/uwp/third_party/runtime/bin/bz2.dll new file mode 100644 index 00000000..a5eff563 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/bz2.dll differ diff --git a/ports/uwp/third_party/runtime/bin/fmt.dll b/ports/uwp/third_party/runtime/bin/fmt.dll new file mode 100644 index 00000000..ae9a9d2a Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/fmt.dll differ diff --git a/ports/uwp/third_party/runtime/bin/freetype.dll b/ports/uwp/third_party/runtime/bin/freetype.dll new file mode 100644 index 00000000..1e431bb1 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/freetype.dll differ diff --git a/ports/uwp/third_party/runtime/bin/libpng16.dll b/ports/uwp/third_party/runtime/bin/libpng16.dll new file mode 100644 index 00000000..fddc7647 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/libpng16.dll differ diff --git a/ports/uwp/third_party/runtime/bin/ogg.dll b/ports/uwp/third_party/runtime/bin/ogg.dll new file mode 100644 index 00000000..3659783e Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/ogg.dll differ diff --git a/ports/uwp/third_party/runtime/bin/theora.dll b/ports/uwp/third_party/runtime/bin/theora.dll new file mode 100644 index 00000000..e7861703 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/theora.dll differ diff --git a/ports/uwp/third_party/runtime/bin/theoradec.dll b/ports/uwp/third_party/runtime/bin/theoradec.dll new file mode 100644 index 00000000..c9926bfb Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/theoradec.dll differ diff --git a/ports/uwp/third_party/runtime/bin/vorbis.dll b/ports/uwp/third_party/runtime/bin/vorbis.dll new file mode 100644 index 00000000..3a72086e Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/vorbis.dll differ diff --git a/ports/uwp/third_party/runtime/bin/vorbisfile.dll b/ports/uwp/third_party/runtime/bin/vorbisfile.dll new file mode 100644 index 00000000..ee9cc728 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/vorbisfile.dll differ diff --git a/ports/uwp/third_party/runtime/bin/z.dll b/ports/uwp/third_party/runtime/bin/z.dll new file mode 100644 index 00000000..66dc2628 Binary files /dev/null and b/ports/uwp/third_party/runtime/bin/z.dll differ diff --git a/ports/uwp/third_party/sdl2/README.md b/ports/uwp/third_party/sdl2/README.md new file mode 100644 index 00000000..3883d0d8 --- /dev/null +++ b/ports/uwp/third_party/sdl2/README.md @@ -0,0 +1,9 @@ +# SDL2 UWP binaries + +The bundled x64 UWP Release binaries use SDL 2.32.10 from the official `release-2.32.10` commit recorded in the dependency manifest. The rebuild script creates `source` and applies the WinRT only controller changes there. + +`patches/xbox-wgi-controller.patch` records those changes for review. They read Xbox gamepads through `IGamepad::GetCurrentReading` and expose the standard SDL button, axis, trigger, and D-pad layout. + +`SDL2.dll`, `SDL2.lib`, and the headers must be updated together. + +`scripts/xbox-uwp/build_sdl2_angle.ps1` builds the source for x64 WindowsStore with GLES enabled and desktop OpenGL and Vulkan disabled. ANGLE supplies EGL and GLES at runtime. diff --git a/ports/uwp/third_party/sdl2/bin/SDL2.dll b/ports/uwp/third_party/sdl2/bin/SDL2.dll new file mode 100644 index 00000000..0ee42904 Binary files /dev/null and b/ports/uwp/third_party/sdl2/bin/SDL2.dll differ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL.h new file mode 100644 index 00000000..0fe07133 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL.h @@ -0,0 +1,234 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL.h + * + * Main include header for the SDL library + */ + +#ifndef SDL_h_ +#define SDL_h_ + +#include "SDL_main.h" +#include "SDL_stdinc.h" +#include "SDL_assert.h" +#include "SDL_atomic.h" +#include "SDL_audio.h" +#include "SDL_clipboard.h" +#include "SDL_cpuinfo.h" +#include "SDL_endian.h" +#include "SDL_error.h" +#include "SDL_events.h" +#include "SDL_filesystem.h" +#include "SDL_gamecontroller.h" +#include "SDL_guid.h" +#include "SDL_haptic.h" +#include "SDL_hidapi.h" +#include "SDL_hints.h" +#include "SDL_joystick.h" +#include "SDL_loadso.h" +#include "SDL_log.h" +#include "SDL_messagebox.h" +#include "SDL_metal.h" +#include "SDL_mutex.h" +#include "SDL_power.h" +#include "SDL_render.h" +#include "SDL_rwops.h" +#include "SDL_sensor.h" +#include "SDL_shape.h" +#include "SDL_system.h" +#include "SDL_thread.h" +#include "SDL_timer.h" +#include "SDL_version.h" +#include "SDL_video.h" +#include "SDL_locale.h" +#include "SDL_misc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* WIKI CATEGORY: Init */ + +/* As of version 0.5, SDL is loaded dynamically into the application */ + +/** + * \name SDL_INIT_* + * + * These are the flags which may be passed to SDL_Init(). You should + * specify the subsystems which you will be using in your application. + */ +/* @{ */ +#define SDL_INIT_TIMER 0x00000001u +#define SDL_INIT_AUDIO 0x00000010u +#define SDL_INIT_VIDEO 0x00000020u /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ +#define SDL_INIT_JOYSTICK 0x00000200u /**< SDL_INIT_JOYSTICK implies SDL_INIT_EVENTS */ +#define SDL_INIT_HAPTIC 0x00001000u +#define SDL_INIT_GAMECONTROLLER 0x00002000u /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */ +#define SDL_INIT_EVENTS 0x00004000u +#define SDL_INIT_SENSOR 0x00008000u +#define SDL_INIT_NOPARACHUTE 0x00100000u /**< compatibility; this flag is ignored. */ +#define SDL_INIT_EVERYTHING ( \ + SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \ + SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \ + ) +/* @} */ + +/** + * Initialize the SDL library. + * + * SDL_Init() simply forwards to calling SDL_InitSubSystem(). Therefore, the + * two may be used interchangeably. Though for readability of your code + * SDL_InitSubSystem() might be preferred. + * + * The file I/O (for example: SDL_RWFromFile) and threading (SDL_CreateThread) + * subsystems are initialized by default. Message boxes + * (SDL_ShowSimpleMessageBox) also attempt to work without initializing the + * video subsystem, in hopes of being useful in showing an error dialog when + * SDL_Init fails. You must specifically initialize other subsystems if you + * use them in your application. + * + * Logging (such as SDL_Log) works without initialization, too. + * + * `flags` may be any of the following OR'd together: + * + * - `SDL_INIT_TIMER`: timer subsystem + * - `SDL_INIT_AUDIO`: audio subsystem + * - `SDL_INIT_VIDEO`: video subsystem; automatically initializes the events + * subsystem + * - `SDL_INIT_JOYSTICK`: joystick subsystem; automatically initializes the + * events subsystem + * - `SDL_INIT_HAPTIC`: haptic (force feedback) subsystem + * - `SDL_INIT_GAMECONTROLLER`: controller subsystem; automatically + * initializes the joystick subsystem + * - `SDL_INIT_EVENTS`: events subsystem + * - `SDL_INIT_EVERYTHING`: all of the above subsystems + * - `SDL_INIT_NOPARACHUTE`: compatibility; this flag is ignored + * + * Subsystem initialization is ref-counted, you must call SDL_QuitSubSystem() + * for each SDL_InitSubSystem() to correctly shutdown a subsystem manually (or + * call SDL_Quit() to force shutdown). If a subsystem is already loaded then + * this call will increase the ref-count and return. + * + * \param flags subsystem initialization flags. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_InitSubSystem + * \sa SDL_Quit + * \sa SDL_SetMainReady + * \sa SDL_WasInit + */ +extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags); + +/** + * Compatibility function to initialize the SDL library. + * + * In SDL2, this function and SDL_Init() are interchangeable. + * + * \param flags any of the flags used by SDL_Init(); see SDL_Init for details. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Init + * \sa SDL_Quit + * \sa SDL_QuitSubSystem + */ +extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags); + +/** + * Shut down specific SDL subsystems. + * + * If you start a subsystem using a call to that subsystem's init function + * (for example SDL_VideoInit()) instead of SDL_Init() or SDL_InitSubSystem(), + * SDL_QuitSubSystem() and SDL_WasInit() will not work. You will need to use + * that subsystem's quit function (SDL_VideoQuit()) directly instead. But + * generally, you should not be using those functions directly anyhow; use + * SDL_Init() instead. + * + * You still need to call SDL_Quit() even if you close all open subsystems + * with SDL_QuitSubSystem(). + * + * \param flags any of the flags used by SDL_Init(); see SDL_Init for details. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_InitSubSystem + * \sa SDL_Quit + */ +extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags); + +/** + * Get a mask of the specified subsystems which are currently initialized. + * + * \param flags any of the flags used by SDL_Init(); see SDL_Init for details. + * \returns a mask of all initialized subsystems if `flags` is 0, otherwise it + * returns the initialization status of the specified subsystems. + * + * The return value does not include SDL_INIT_NOPARACHUTE. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Init + * \sa SDL_InitSubSystem + */ +extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags); + +/** + * Clean up all initialized subsystems. + * + * You should call this function even if you have already shutdown each + * initialized subsystem with SDL_QuitSubSystem(). It is safe to call this + * function even in the case of errors in initialization. + * + * If you start a subsystem using a call to that subsystem's init function + * (for example SDL_VideoInit()) instead of SDL_Init() or SDL_InitSubSystem(), + * then you must use that subsystem's quit function (SDL_VideoQuit()) to shut + * it down before calling SDL_Quit(). But generally, you should not be using + * those functions directly anyhow; use SDL_Init() instead. + * + * You can use this function with atexit() to ensure that it is run when your + * application is shutdown, but it is not wise to do this from a library or + * other dynamically loaded code. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Init + * \sa SDL_QuitSubSystem + */ +extern DECLSPEC void SDLCALL SDL_Quit(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_assert.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_assert.h new file mode 100644 index 00000000..5d7e19ae --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_assert.h @@ -0,0 +1,324 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_assert_h_ +#define SDL_assert_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SDL_ASSERT_LEVEL +#ifdef SDL_DEFAULT_ASSERT_LEVEL +#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL +#elif defined(_DEBUG) || defined(DEBUG) || \ + (defined(__GNUC__) && !defined(__OPTIMIZE__)) +#define SDL_ASSERT_LEVEL 2 +#else +#define SDL_ASSERT_LEVEL 1 +#endif +#endif /* SDL_ASSERT_LEVEL */ + +/* +These are macros and not first class functions so that the debugger breaks +on the assertion line and not in some random guts of SDL, and so each +assert can have unique static variables associated with it. +*/ + +#if defined(_MSC_VER) +/* Don't include intrin.h here because it contains C++ code */ + extern void __cdecl __debugbreak(void); + #define SDL_TriggerBreakpoint() __debugbreak() +#elif _SDL_HAS_BUILTIN(__builtin_debugtrap) + #define SDL_TriggerBreakpoint() __builtin_debugtrap() +#elif ( (!defined(__NACL__)) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))) ) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" ) +#elif (defined(__GNUC__) || defined(__clang__)) && defined(__riscv) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "ebreak\n\t" ) +#elif ( defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__)) ) /* this might work on other ARM targets, but this is a known quantity... */ + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #22\n\t" ) +#elif defined(__APPLE__) && defined(__arm__) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "bkpt #22\n\t" ) +#elif defined(_WIN32) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__arm64__) || defined(__aarch64__)) ) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #0xF000\n\t" ) +#elif defined(__386__) && defined(__WATCOMC__) + #define SDL_TriggerBreakpoint() { _asm { int 0x03 } } +#elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__) + #include + #define SDL_TriggerBreakpoint() raise(SIGTRAP) +#else + /* How do we trigger breakpoints on this platform? */ + #define SDL_TriggerBreakpoint() +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ +# define SDL_FUNCTION __func__ +#elif ((defined(__GNUC__) && (__GNUC__ >= 2)) || defined(_MSC_VER) || defined (__WATCOMC__)) +# define SDL_FUNCTION __FUNCTION__ +#else +# define SDL_FUNCTION "???" +#endif +#define SDL_FILE __FILE__ +#define SDL_LINE __LINE__ + +/* +sizeof (x) makes the compiler still parse the expression even without +assertions enabled, so the code is always checked at compile time, but +doesn't actually generate code for it, so there are no side effects or +expensive checks at run time, just the constant size of what x WOULD be, +which presumably gets optimized out as unused. +This also solves the problem of... + + int somevalue = blah(); + SDL_assert(somevalue == 1); + +...which would cause compiles to complain that somevalue is unused if we +disable assertions. +*/ + +/* "while (0,0)" fools Microsoft's compiler's /W4 warning level into thinking + this condition isn't constant. And looks like an owl's face! */ +#ifdef _MSC_VER /* stupid /W4 warnings. */ +#define SDL_NULL_WHILE_LOOP_CONDITION (0,0) +#else +#define SDL_NULL_WHILE_LOOP_CONDITION (0) +#endif + +#define SDL_disabled_assert(condition) \ + do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION) + +typedef enum +{ + SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */ + SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */ + SDL_ASSERTION_ABORT, /**< Terminate the program. */ + SDL_ASSERTION_IGNORE, /**< Ignore the assert. */ + SDL_ASSERTION_ALWAYS_IGNORE /**< Ignore the assert from now on. */ +} SDL_AssertState; + +typedef struct SDL_AssertData +{ + int always_ignore; + unsigned int trigger_count; + const char *condition; + const char *filename; + int linenum; + const char *function; + const struct SDL_AssertData *next; +} SDL_AssertData; + +/* Never call this directly. Use the SDL_assert* macros. */ +extern DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData *, + const char *, + const char *, int) +#if defined(__clang__) +#if __has_feature(attribute_analyzer_noreturn) +/* this tells Clang's static analysis that we're a custom assert function, + and that the analyzer should assume the condition was always true past this + SDL_assert test. */ + __attribute__((analyzer_noreturn)) +#endif +#endif +; + +/* the do {} while(0) avoids dangling else problems: + if (x) SDL_assert(y); else blah(); + ... without the do/while, the "else" could attach to this macro's "if". + We try to handle just the minimum we need here in a macro...the loop, + the static vars, and break points. The heavy lifting is handled in + SDL_ReportAssertion(), in SDL_assert.c. +*/ +#define SDL_enabled_assert(condition) \ + do { \ + while ( !(condition) ) { \ + static struct SDL_AssertData sdl_assert_data = { 0, 0, #condition, NULL, 0, NULL, NULL }; \ + const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \ + if (sdl_assert_state == SDL_ASSERTION_RETRY) { \ + continue; /* go again. */ \ + } else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \ + SDL_TriggerBreakpoint(); \ + } \ + break; /* not retrying. */ \ + } \ + } while (SDL_NULL_WHILE_LOOP_CONDITION) + +/* Enable various levels of assertions. */ +#if SDL_ASSERT_LEVEL == 0 /* assertions disabled */ +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_disabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 1 /* release settings. */ +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 2 /* normal settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition) +#else +# error Unknown assertion level. +#endif + +/* this assertion is never disabled at any level. */ +#define SDL_assert_always(condition) SDL_enabled_assert(condition) + + +/** + * A callback that fires when an SDL assertion fails. + * + * \param data a pointer to the SDL_AssertData structure corresponding to the + * current assertion. + * \param userdata what was passed as `userdata` to SDL_SetAssertionHandler(). + * \returns an SDL_AssertState value indicating how to handle the failure. + */ +typedef SDL_AssertState (SDLCALL *SDL_AssertionHandler)( + const SDL_AssertData* data, void* userdata); + +/** + * Set an application-defined assertion handler. + * + * This function allows an application to show its own assertion UI and/or + * force the response to an assertion failure. If the application doesn't + * provide this, SDL will try to do the right thing, popping up a + * system-specific GUI dialog, and probably minimizing any fullscreen windows. + * + * This callback may fire from any thread, but it runs wrapped in a mutex, so + * it will only fire from one thread at a time. + * + * This callback is NOT reset to SDL's internal handler upon SDL_Quit()! + * + * \param handler the SDL_AssertionHandler function to call when an assertion + * fails or NULL for the default handler. + * \param userdata a pointer that is passed to `handler`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetAssertionHandler + */ +extern DECLSPEC void SDLCALL SDL_SetAssertionHandler( + SDL_AssertionHandler handler, + void *userdata); + +/** + * Get the default assertion handler. + * + * This returns the function pointer that is called by default when an + * assertion is triggered. This is an internal function provided by SDL, that + * is used for assertions when SDL_SetAssertionHandler() hasn't been used to + * provide a different function. + * + * \returns the default SDL_AssertionHandler that is called when an assert + * triggers. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_GetAssertionHandler + */ +extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetDefaultAssertionHandler(void); + +/** + * Get the current assertion handler. + * + * This returns the function pointer that is called when an assertion is + * triggered. This is either the value last passed to + * SDL_SetAssertionHandler(), or if no application-specified function is set, + * is equivalent to calling SDL_GetDefaultAssertionHandler(). + * + * The parameter `puserdata` is a pointer to a void*, which will store the + * "userdata" pointer that was passed to SDL_SetAssertionHandler(). This value + * will always be NULL for the default handler. If you don't care about this + * data, it is safe to pass a NULL pointer to this function to ignore it. + * + * \param puserdata pointer which is filled with the "userdata" pointer that + * was passed to SDL_SetAssertionHandler(). + * \returns the SDL_AssertionHandler that is called when an assert triggers. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_SetAssertionHandler + */ +extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetAssertionHandler(void **puserdata); + +/** + * Get a list of all assertion failures. + * + * This function gets all assertions triggered since the last call to + * SDL_ResetAssertionReport(), or the start of the program. + * + * The proper way to examine this data looks something like this: + * + * ```c + * const SDL_AssertData *item = SDL_GetAssertionReport(); + * while (item) { + * printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\\n", + * item->condition, item->function, item->filename, + * item->linenum, item->trigger_count, + * item->always_ignore ? "yes" : "no"); + * item = item->next; + * } + * ``` + * + * \returns a list of all failed assertions or NULL if the list is empty. This + * memory should not be modified or freed by the application. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ResetAssertionReport + */ +extern DECLSPEC const SDL_AssertData * SDLCALL SDL_GetAssertionReport(void); + +/** + * Clear the list of all assertion failures. + * + * This function will clear the list of all assertions triggered up to that + * point. Immediately following this call, SDL_GetAssertionReport will return + * no items. In addition, any previously-triggered assertions will be reset to + * a trigger_count of zero, and their always_ignore state will be false. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetAssertionReport + */ +extern DECLSPEC void SDLCALL SDL_ResetAssertionReport(void); + + +/* these had wrong naming conventions until 2.0.4. Please update your app! */ +#define SDL_assert_state SDL_AssertState +#define SDL_assert_data SDL_AssertData + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_assert_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_atomic.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_atomic.h new file mode 100644 index 00000000..226ec7c6 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_atomic.h @@ -0,0 +1,408 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryAtomic + * + * Atomic operations. + * + * IMPORTANT: If you are not an expert in concurrent lockless programming, you + * should not be using any functions in this file. You should be protecting + * your data structures with full mutexes instead. + * + * ***Seriously, here be dragons!*** + * + * You can find out a little more about lockless programming and the subtle + * issues that can arise here: + * https://learn.microsoft.com/en-us/windows/win32/dxtecharts/lockless-programming + * + * There's also lots of good information here: + * + * - https://www.1024cores.net/home/lock-free-algorithms + * - https://preshing.com/ + * + * These operations may or may not actually be implemented using processor + * specific atomic operations. When possible they are implemented as true + * processor specific atomic operations. When that is not possible the are + * implemented using locks that *do* use the available atomic operations. + * + * All of the atomic operations that modify memory are full memory barriers. + */ + +#ifndef SDL_atomic_h_ +#define SDL_atomic_h_ + +#include "SDL_stdinc.h" +#include "SDL_platform.h" + +#include "begin_code.h" + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \name SDL AtomicLock + * + * The atomic locks are efficient spinlocks using CPU instructions, + * but are vulnerable to starvation and can spin forever if a thread + * holding a lock has been terminated. For this reason you should + * minimize the code executed inside an atomic lock and never do + * expensive things like API or system calls while holding them. + * + * The atomic locks are not safe to lock recursively. + * + * Porting Note: + * The spin lock functions and type are required and can not be + * emulated because they are used in the atomic emulation code. + */ +/* @{ */ + +typedef int SDL_SpinLock; + +/** + * Try to lock a spin lock by setting it to a non-zero value. + * + * ***Please note that spinlocks are dangerous if you don't know what you're + * doing. Please be careful using any sort of spinlock!*** + * + * \param lock a pointer to a lock variable. + * \returns SDL_TRUE if the lock succeeded, SDL_FALSE if the lock is already + * held. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AtomicLock + * \sa SDL_AtomicUnlock + */ +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicTryLock(SDL_SpinLock *lock); + +/** + * Lock a spin lock by setting it to a non-zero value. + * + * ***Please note that spinlocks are dangerous if you don't know what you're + * doing. Please be careful using any sort of spinlock!*** + * + * \param lock a pointer to a lock variable. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AtomicTryLock + * \sa SDL_AtomicUnlock + */ +extern DECLSPEC void SDLCALL SDL_AtomicLock(SDL_SpinLock *lock); + +/** + * Unlock a spin lock by setting it to 0. + * + * Always returns immediately. + * + * ***Please note that spinlocks are dangerous if you don't know what you're + * doing. Please be careful using any sort of spinlock!*** + * + * \param lock a pointer to a lock variable. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AtomicLock + * \sa SDL_AtomicTryLock + */ +extern DECLSPEC void SDLCALL SDL_AtomicUnlock(SDL_SpinLock *lock); + +/* @} *//* SDL AtomicLock */ + + +/** + * The compiler barrier prevents the compiler from reordering + * reads and writes to globally visible variables across the call. + */ +#if defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__) +void _ReadWriteBarrier(void); +#pragma intrinsic(_ReadWriteBarrier) +#define SDL_CompilerBarrier() _ReadWriteBarrier() +#elif (defined(__GNUC__) && !defined(__EMSCRIPTEN__)) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120)) +/* This is correct for all CPUs when using GCC or Solaris Studio 12.1+. */ +#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory") +#elif defined(__WATCOMC__) +extern __inline void SDL_CompilerBarrier(void); +#pragma aux SDL_CompilerBarrier = "" parm [] modify exact []; +#else +#define SDL_CompilerBarrier() \ +{ SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); } +#endif + +/** + * Memory barriers are designed to prevent reads and writes from being + * reordered by the compiler and being seen out of order on multi-core CPUs. + * + * A typical pattern would be for thread A to write some data and a flag, and + * for thread B to read the flag and get the data. In this case you would + * insert a release barrier between writing the data and the flag, + * guaranteeing that the data write completes no later than the flag is + * written, and you would insert an acquire barrier between reading the flag + * and reading the data, to ensure that all the reads associated with the flag + * have completed. + * + * In this pattern you should always see a release barrier paired with an + * acquire barrier and you should gate the data reads/writes with a single + * flag variable. + * + * For more information on these semantics, take a look at the blog post: + * http://preshing.com/20120913/acquire-and-release-semantics + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC void SDLCALL SDL_MemoryBarrierReleaseFunction(void); +extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void); + +#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory") +#elif defined(__GNUC__) && defined(__aarch64__) +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory") +#elif defined(__GNUC__) && defined(__arm__) +#if 0 /* defined(__LINUX__) || defined(__ANDROID__) */ +/* Information from: + https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19 + + The Linux kernel provides a helper function which provides the right code for a memory barrier, + hard-coded at address 0xffff0fa0 +*/ +typedef void (*SDL_KernelMemoryBarrierFunc)(); +#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)() +#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)() +#elif 0 /* defined(__QNXNTO__) */ +#include + +#define SDL_MemoryBarrierRelease() __cpu_membarrier() +#define SDL_MemoryBarrierAcquire() __cpu_membarrier() +#else +#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__) +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory") +#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) +#ifdef __thumb__ +/* The mcr instruction isn't available in thumb mode, use real functions */ +#define SDL_MEMORY_BARRIER_USES_FUNCTION +#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction() +#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction() +#else +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory") +#endif /* __thumb__ */ +#else +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory") +#endif /* __LINUX__ || __ANDROID__ */ +#endif /* __GNUC__ && __arm__ */ +#else +#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120)) +/* This is correct for all CPUs on Solaris when using Solaris Studio 12.1+. */ +#include +#define SDL_MemoryBarrierRelease() __machine_rel_barrier() +#define SDL_MemoryBarrierAcquire() __machine_acq_barrier() +#else +/* This is correct for the x86 and x64 CPUs, and we'll expand this over time. */ +#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier() +#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier() +#endif +#endif + +/* "REP NOP" is PAUSE, coded for tools that don't know it by that name. */ +#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) + #define SDL_CPUPauseInstruction() __asm__ __volatile__("pause\n") /* Some assemblers can't do REP NOP, so go with PAUSE. */ +#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(__aarch64__) + #define SDL_CPUPauseInstruction() __asm__ __volatile__("yield" ::: "memory") +#elif (defined(__powerpc__) || defined(__powerpc64__)) + #define SDL_CPUPauseInstruction() __asm__ __volatile__("or 27,27,27"); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + #define SDL_CPUPauseInstruction() _mm_pause() /* this is actually "rep nop" and not a SIMD instruction. No inline asm in MSVC x86-64! */ +#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) + #define SDL_CPUPauseInstruction() __yield() +#elif defined(__WATCOMC__) && defined(__386__) + extern __inline void SDL_CPUPauseInstruction(void); + #pragma aux SDL_CPUPauseInstruction = ".686p" ".xmm2" "pause" +#else + #define SDL_CPUPauseInstruction() +#endif + + +/** + * A type representing an atomic integer value. + * + * It is a struct so people don't accidentally use numeric operations on it. + */ +typedef struct SDL_atomic_t { + int value; +} SDL_atomic_t; + +/** + * Set an atomic variable to a new value if it is currently an old value. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_atomic_t variable to be modified. + * \param oldval the old value. + * \param newval the new value. + * \returns SDL_TRUE if the atomic variable was set, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AtomicCASPtr + * \sa SDL_AtomicGet + * \sa SDL_AtomicSet + */ +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCAS(SDL_atomic_t *a, int oldval, int newval); + +/** + * Set an atomic variable to a value. + * + * This function also acts as a full memory barrier. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_atomic_t variable to be modified. + * \param v the desired value. + * \returns the previous value of the atomic variable. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_AtomicGet + */ +extern DECLSPEC int SDLCALL SDL_AtomicSet(SDL_atomic_t *a, int v); + +/** + * Get the value of an atomic variable. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_atomic_t variable. + * \returns the current value of an atomic variable. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_AtomicSet + */ +extern DECLSPEC int SDLCALL SDL_AtomicGet(SDL_atomic_t *a); + +/** + * Add to an atomic variable. + * + * This function also acts as a full memory barrier. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_atomic_t variable to be modified. + * \param v the desired value to add. + * \returns the previous value of the atomic variable. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_AtomicDecRef + * \sa SDL_AtomicIncRef + */ +extern DECLSPEC int SDLCALL SDL_AtomicAdd(SDL_atomic_t *a, int v); + +/** + * \brief Increment an atomic variable used as a reference count. + */ +#ifndef SDL_AtomicIncRef +#define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1) +#endif + +/** + * \brief Decrement an atomic variable used as a reference count. + * + * \return SDL_TRUE if the variable reached zero after decrementing, + * SDL_FALSE otherwise + */ +#ifndef SDL_AtomicDecRef +#define SDL_AtomicDecRef(a) (SDL_AtomicAdd(a, -1) == 1) +#endif + +/** + * Set a pointer to a new value if it is currently an old value. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to a pointer. + * \param oldval the old pointer value. + * \param newval the new pointer value. + * \returns SDL_TRUE if the pointer was set, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AtomicCAS + * \sa SDL_AtomicGetPtr + * \sa SDL_AtomicSetPtr + */ +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCASPtr(void **a, void *oldval, void *newval); + +/** + * Set a pointer to a value atomically. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to a pointer. + * \param v the desired pointer value. + * \returns the previous value of the pointer. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_AtomicCASPtr + * \sa SDL_AtomicGetPtr + */ +extern DECLSPEC void* SDLCALL SDL_AtomicSetPtr(void **a, void* v); + +/** + * Get the value of a pointer atomically. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to a pointer. + * \returns the current value of a pointer. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_AtomicCASPtr + * \sa SDL_AtomicSetPtr + */ +extern DECLSPEC void* SDLCALL SDL_AtomicGetPtr(void **a); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#include "close_code.h" + +#endif /* SDL_atomic_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_audio.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_audio.h new file mode 100644 index 00000000..cb76e93e --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_audio.h @@ -0,0 +1,1502 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* !!! FIXME: several functions in here need Doxygen comments. */ + +/** + * # CategoryAudio + * + * Access to the raw audio mixing buffer for the SDL library. + */ + +#ifndef SDL_audio_h_ +#define SDL_audio_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_endian.h" +#include "SDL_mutex.h" +#include "SDL_thread.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Audio format flags. + * + * These are what the 16 bits in SDL_AudioFormat currently mean... + * (Unspecified bits are always zero). + * + * ``` + * ++-----------------------sample is signed if set + * || + * || ++-----------sample is bigendian if set + * || || + * || || ++---sample is float if set + * || || || + * || || || +---sample bit size---+ + * || || || | | + * 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 + * ``` + * + * There are macros in SDL 2.0 and later to query these bits. + */ +typedef Uint16 SDL_AudioFormat; + +/** + * \name Audio flags + */ +/* @{ */ + +#define SDL_AUDIO_MASK_BITSIZE (0xFF) +#define SDL_AUDIO_MASK_DATATYPE (1<<8) +#define SDL_AUDIO_MASK_ENDIAN (1<<12) +#define SDL_AUDIO_MASK_SIGNED (1<<15) +#define SDL_AUDIO_BITSIZE(x) (x & SDL_AUDIO_MASK_BITSIZE) +#define SDL_AUDIO_ISFLOAT(x) (x & SDL_AUDIO_MASK_DATATYPE) +#define SDL_AUDIO_ISBIGENDIAN(x) (x & SDL_AUDIO_MASK_ENDIAN) +#define SDL_AUDIO_ISSIGNED(x) (x & SDL_AUDIO_MASK_SIGNED) +#define SDL_AUDIO_ISINT(x) (!SDL_AUDIO_ISFLOAT(x)) +#define SDL_AUDIO_ISLITTLEENDIAN(x) (!SDL_AUDIO_ISBIGENDIAN(x)) +#define SDL_AUDIO_ISUNSIGNED(x) (!SDL_AUDIO_ISSIGNED(x)) + +/** + * \name Audio format flags + * + * Defaults to LSB byte order. + */ +/* @{ */ +#define AUDIO_U8 0x0008 /**< Unsigned 8-bit samples */ +#define AUDIO_S8 0x8008 /**< Signed 8-bit samples */ +#define AUDIO_U16LSB 0x0010 /**< Unsigned 16-bit samples */ +#define AUDIO_S16LSB 0x8010 /**< Signed 16-bit samples */ +#define AUDIO_U16MSB 0x1010 /**< As above, but big-endian byte order */ +#define AUDIO_S16MSB 0x9010 /**< As above, but big-endian byte order */ +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB +/* @} */ + +/** + * \name int32 support + */ +/* @{ */ +#define AUDIO_S32LSB 0x8020 /**< 32-bit integer samples */ +#define AUDIO_S32MSB 0x9020 /**< As above, but big-endian byte order */ +#define AUDIO_S32 AUDIO_S32LSB +/* @} */ + +/** + * \name float32 support + */ +/* @{ */ +#define AUDIO_F32LSB 0x8120 /**< 32-bit floating point samples */ +#define AUDIO_F32MSB 0x9120 /**< As above, but big-endian byte order */ +#define AUDIO_F32 AUDIO_F32LSB +/* @} */ + +/** + * \name Native audio byte ordering + */ +/* @{ */ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#define AUDIO_S32SYS AUDIO_S32LSB +#define AUDIO_F32SYS AUDIO_F32LSB +#else +#define AUDIO_U16SYS AUDIO_U16MSB +#define AUDIO_S16SYS AUDIO_S16MSB +#define AUDIO_S32SYS AUDIO_S32MSB +#define AUDIO_F32SYS AUDIO_F32MSB +#endif +/* @} */ + +/** + * \name Allow change flags + * + * Which audio format changes are allowed when opening a device. + */ +/* @{ */ +#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 +#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 +#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0x00000008 +#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE) +/* @} */ + +/* @} *//* Audio flags */ + +/** + * This function is called when the audio device needs more data. + * + * \param userdata An application-specific parameter saved in the + * SDL_AudioSpec structure. + * \param stream A pointer to the audio data buffer. + * \param len Length of **stream** in bytes. + */ +typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, + int len); + +/** + * The calculated values in this structure are calculated by SDL_OpenAudio(). + * + * For multi-channel audio, the default SDL channel mapping is: + * + * ``` + * 2: FL FR (stereo) + * 3: FL FR LFE (2.1 surround) + * 4: FL FR BL BR (quad) + * 5: FL FR LFE BL BR (4.1 surround) + * 6: FL FR FC LFE SL SR (5.1 surround - last two can also be BL BR) + * 7: FL FR FC LFE BC SL SR (6.1 surround) + * 8: FL FR FC LFE BL BR SL SR (7.1 surround) + * ``` + */ +typedef struct SDL_AudioSpec +{ + int freq; /**< DSP frequency -- samples per second */ + SDL_AudioFormat format; /**< Audio data format */ + Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ + Uint8 silence; /**< Audio buffer silence value (calculated) */ + Uint16 samples; /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */ + Uint16 padding; /**< Necessary for some compile environments */ + Uint32 size; /**< Audio buffer size in bytes (calculated) */ + SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */ + void *userdata; /**< Userdata passed to callback (ignored for NULL callbacks). */ +} SDL_AudioSpec; + + +struct SDL_AudioCVT; +typedef void (SDLCALL * SDL_AudioFilter) (struct SDL_AudioCVT * cvt, + SDL_AudioFormat format); + +/** + * Upper limit of filters in SDL_AudioCVT + * + * The maximum number of SDL_AudioFilter functions in SDL_AudioCVT is + * currently limited to 9. The SDL_AudioCVT.filters array has 10 pointers, one + * of which is the terminating NULL pointer. + */ +#define SDL_AUDIOCVT_MAX_FILTERS 9 + +/** + * \struct SDL_AudioCVT + * \brief A structure to hold a set of audio conversion filters and buffers. + * + * Note that various parts of the conversion pipeline can take advantage + * of SIMD operations (like SSE2, for example). SDL_AudioCVT doesn't require + * you to pass it aligned data, but can possibly run much faster if you + * set both its (buf) field to a pointer that is aligned to 16 bytes, and its + * (len) field to something that's a multiple of 16, if possible. + */ +#if defined(__GNUC__) && !defined(__CHERI_PURE_CAPABILITY__) +/* This structure is 84 bytes on 32-bit architectures, make sure GCC doesn't + pad it out to 88 bytes to guarantee ABI compatibility between compilers. + This is not a concern on CHERI architectures, where pointers must be stored + at aligned locations otherwise they will become invalid, and thus structs + containing pointers cannot be packed without giving a warning or error. + vvv + The next time we rev the ABI, make sure to size the ints and add padding. +*/ +#define SDL_AUDIOCVT_PACKED __attribute__((packed)) +#else +#define SDL_AUDIOCVT_PACKED +#endif +/* */ +typedef struct SDL_AudioCVT +{ + int needed; /**< Set to 1 if conversion possible */ + SDL_AudioFormat src_format; /**< Source audio format */ + SDL_AudioFormat dst_format; /**< Target audio format */ + double rate_incr; /**< Rate conversion increment */ + Uint8 *buf; /**< Buffer to hold entire audio data */ + int len; /**< Length of original audio buffer */ + int len_cvt; /**< Length of converted audio buffer */ + int len_mult; /**< buffer must be len*len_mult big */ + double len_ratio; /**< Given len, final size is len*len_ratio */ + SDL_AudioFilter filters[SDL_AUDIOCVT_MAX_FILTERS + 1]; /**< NULL-terminated list of filter functions */ + int filter_index; /**< Current audio conversion function */ +} SDL_AUDIOCVT_PACKED SDL_AudioCVT; + + +/* Function prototypes */ + +/** + * \name Driver discovery functions + * + * These functions return the list of built in audio drivers, in the + * order that they are normally initialized by default. + */ +/* @{ */ + +/** + * Use this function to get the number of built-in audio drivers. + * + * This function returns a hardcoded number. This never returns a negative + * value; if there are no drivers compiled into this build of SDL, this + * function returns zero. The presence of a driver in this list does not mean + * it will function, it just means SDL is capable of interacting with that + * interface. For example, a build of SDL might have esound support, but if + * there's no esound server available, SDL's esound driver would fail if used. + * + * By default, SDL tries all drivers, in its preferred order, until one is + * found to be usable. + * + * \returns the number of built-in audio drivers. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetAudioDriver + */ +extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void); + +/** + * Use this function to get the name of a built in audio driver. + * + * The list of audio drivers is given in the order that they are normally + * initialized by default; the drivers that seem more reasonable to choose + * first (as far as the SDL developers believe) are earlier in the list. + * + * The names of drivers are all simple, low-ASCII identifiers, like "alsa", + * "coreaudio" or "xaudio2". These never have Unicode characters, and are not + * meant to be proper names. + * + * \param index the index of the audio driver; the value ranges from 0 to + * SDL_GetNumAudioDrivers() - 1. + * \returns the name of the audio driver at the requested index, or NULL if an + * invalid index was specified. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumAudioDrivers + */ +extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index); +/* @} */ + +/** + * \name Initialization and cleanup + * + * \internal These functions are used internally, and should not be used unless + * you have a specific need to specify the audio driver you want to + * use. You should normally use SDL_Init() or SDL_InitSubSystem(). + */ +/* @{ */ + +/** + * Use this function to initialize a particular audio driver. + * + * This function is used internally, and should not be used unless you have a + * specific need to designate the audio driver you want to use. You should + * normally use SDL_Init() or SDL_InitSubSystem(). + * + * \param driver_name the name of the desired audio driver. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AudioQuit + */ +extern DECLSPEC int SDLCALL SDL_AudioInit(const char *driver_name); + +/** + * Use this function to shut down audio if you initialized it with + * SDL_AudioInit(). + * + * This function is used internally, and should not be used unless you have a + * specific need to specify the audio driver you want to use. You should + * normally use SDL_Quit() or SDL_QuitSubSystem(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AudioInit + */ +extern DECLSPEC void SDLCALL SDL_AudioQuit(void); +/* @} */ + +/** + * Get the name of the current audio driver. + * + * The returned string points to internal static memory and thus never becomes + * invalid, even if you quit the audio subsystem and initialize a new driver + * (although such a case would return a different static string from another + * call to this function, of course). As such, you should not modify or free + * the returned string. + * + * \returns the name of the current audio driver or NULL if no driver has been + * initialized. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AudioInit + */ +extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); + +/** + * This function is a legacy means of opening the audio device. + * + * This function remains for compatibility with SDL 1.2, but also because it's + * slightly easier to use than the new functions in SDL 2.0. The new, more + * powerful, and preferred way to do this is SDL_OpenAudioDevice(). + * + * This function is roughly equivalent to: + * + * ```c + * SDL_OpenAudioDevice(NULL, 0, desired, obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); + * ``` + * + * With two notable exceptions: + * + * - If `obtained` is NULL, we use `desired` (and allow no changes), which + * means desired will be modified to have the correct values for silence, + * etc, and SDL will convert any differences between your app's specific + * request and the hardware behind the scenes. + * - The return value is always success or failure, and not a device ID, which + * means you can only have one device open at a time with this function. + * + * \param desired an SDL_AudioSpec structure representing the desired output + * format. Please refer to the SDL_OpenAudioDevice + * documentation for details on how to prepare this structure. + * \param obtained an SDL_AudioSpec structure filled in with the actual + * parameters, or NULL. + * \returns 0 if successful, placing the actual hardware parameters in the + * structure pointed to by `obtained`. + * + * If `obtained` is NULL, the audio data passed to the callback + * function will be guaranteed to be in the requested format, and + * will be automatically converted to the actual hardware audio + * format if necessary. If `obtained` is NULL, `desired` will have + * fields modified. + * + * This function returns a negative error code on failure to open the + * audio device or failure to set up the audio thread; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CloseAudio + * \sa SDL_LockAudio + * \sa SDL_PauseAudio + * \sa SDL_UnlockAudio + */ +extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, + SDL_AudioSpec * obtained); + +/** + * SDL Audio Device IDs. + * + * A successful call to SDL_OpenAudio() is always device id 1, and legacy SDL + * audio APIs assume you want this device ID. SDL_OpenAudioDevice() calls + * always returns devices >= 2 on success. The legacy calls are good both for + * backwards compatibility and when you don't care about multiple, specific, + * or capture devices. + */ +typedef Uint32 SDL_AudioDeviceID; + +/** + * Get the number of built-in audio devices. + * + * This function is only valid after successfully initializing the audio + * subsystem. + * + * Note that audio capture support is not implemented as of SDL 2.0.4, so the + * `iscapture` parameter is for future expansion and should always be zero for + * now. + * + * This function will return -1 if an explicit list of devices can't be + * determined. Returning -1 is not an error. For example, if SDL is set up to + * talk to a remote audio server, it can't list every one available on the + * Internet, but it will still allow a specific host to be specified in + * SDL_OpenAudioDevice(). + * + * In many common cases, when this function returns a value <= 0, it can still + * successfully open the default device (NULL for first argument of + * SDL_OpenAudioDevice()). + * + * This function may trigger a complete redetect of available hardware. It + * should not be called for each iteration of a loop, but rather once at the + * start of a loop: + * + * ```c + * // Don't do this: + * for (int i = 0; i < SDL_GetNumAudioDevices(0); i++) + * + * // do this instead: + * const int count = SDL_GetNumAudioDevices(0); + * for (int i = 0; i < count; ++i) { do_something_here(); } + * ``` + * + * \param iscapture zero to request playback devices, non-zero to request + * recording devices. + * \returns the number of available devices exposed by the current driver or + * -1 if an explicit list of devices can't be determined. A return + * value of -1 does not necessarily mean an error condition. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetAudioDeviceName + * \sa SDL_OpenAudioDevice + */ +extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture); + +/** + * Get the human-readable name of a specific audio device. + * + * This function is only valid after successfully initializing the audio + * subsystem. The values returned by this function reflect the latest call to + * SDL_GetNumAudioDevices(); re-call that function to redetect available + * hardware. + * + * The string returned by this function is UTF-8 encoded, read-only, and + * managed internally. You are not to free it. If you need to keep the string + * for any length of time, you should make your own copy of it, as it will be + * invalid next time any of several other SDL functions are called. + * + * \param index the index of the audio device; valid values range from 0 to + * SDL_GetNumAudioDevices() - 1. + * \param iscapture non-zero to query the list of recording devices, zero to + * query the list of output devices. + * \returns the name of the audio device at the requested index, or NULL on + * error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumAudioDevices + * \sa SDL_GetDefaultAudioInfo + */ +extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, + int iscapture); + +/** + * Get the preferred audio format of a specific audio device. + * + * This function is only valid after a successfully initializing the audio + * subsystem. The values returned by this function reflect the latest call to + * SDL_GetNumAudioDevices(); re-call that function to redetect available + * hardware. + * + * `spec` will be filled with the sample rate, sample format, and channel + * count. + * + * \param index the index of the audio device; valid values range from 0 to + * SDL_GetNumAudioDevices() - 1. + * \param iscapture non-zero to query the list of recording devices, zero to + * query the list of output devices. + * \param spec The SDL_AudioSpec to be initialized by this function. + * \returns 0 on success, nonzero on error. + * + * \since This function is available since SDL 2.0.16. + * + * \sa SDL_GetNumAudioDevices + * \sa SDL_GetDefaultAudioInfo + */ +extern DECLSPEC int SDLCALL SDL_GetAudioDeviceSpec(int index, + int iscapture, + SDL_AudioSpec *spec); + + +/** + * Get the name and preferred format of the default audio device. + * + * Some (but not all!) platforms have an isolated mechanism to get information + * about the "default" device. This can actually be a completely different + * device that's not in the list you get from SDL_GetAudioDeviceSpec(). It can + * even be a network address! (This is discussed in SDL_OpenAudioDevice().) + * + * As a result, this call is not guaranteed to be performant, as it can query + * the sound server directly every time, unlike the other query functions. You + * should call this function sparingly! + * + * `spec` will be filled with the sample rate, sample format, and channel + * count, if a default device exists on the system. If `name` is provided, + * will be filled with either a dynamically-allocated UTF-8 string or NULL. + * + * \param name A pointer to be filled with the name of the default device (can + * be NULL). Please call SDL_free() when you are done with this + * pointer! + * \param spec The SDL_AudioSpec to be initialized by this function. + * \param iscapture non-zero to query the default recording device, zero to + * query the default output device. + * \returns 0 on success, nonzero on error. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GetAudioDeviceName + * \sa SDL_GetAudioDeviceSpec + * \sa SDL_OpenAudioDevice + */ +extern DECLSPEC int SDLCALL SDL_GetDefaultAudioInfo(char **name, + SDL_AudioSpec *spec, + int iscapture); + + +/** + * Open a specific audio device. + * + * SDL_OpenAudio(), unlike this function, always acts on device ID 1. As such, + * this function will never return a 1 so as not to conflict with the legacy + * function. + * + * Please note that SDL 2.0 before 2.0.5 did not support recording; as such, + * this function would fail if `iscapture` was not zero. Starting with SDL + * 2.0.5, recording is implemented and this value can be non-zero. + * + * Passing in a `device` name of NULL requests the most reasonable default + * (and is equivalent to what SDL_OpenAudio() does to choose a device). The + * `device` name is a UTF-8 string reported by SDL_GetAudioDeviceName(), but + * some drivers allow arbitrary and driver-specific strings, such as a + * hostname/IP address for a remote audio server, or a filename in the + * diskaudio driver. + * + * An opened audio device starts out paused, and should be enabled for playing + * by calling SDL_PauseAudioDevice(devid, 0) when you are ready for your audio + * callback function to be called. Since the audio driver may modify the + * requested size of the audio buffer, you should allocate any local mixing + * buffers after you open the audio device. + * + * The audio callback runs in a separate thread in most cases; you can prevent + * race conditions between your callback and other threads without fully + * pausing playback with SDL_LockAudioDevice(). For more information about the + * callback, see SDL_AudioSpec. + * + * Managing the audio spec via 'desired' and 'obtained': + * + * When filling in the desired audio spec structure: + * + * - `desired->freq` should be the frequency in sample-frames-per-second (Hz). + * - `desired->format` should be the audio format (`AUDIO_S16SYS`, etc). + * - `desired->samples` is the desired size of the audio buffer, in _sample + * frames_ (with stereo output, two samples--left and right--would make a + * single sample frame). This number should be a power of two, and may be + * adjusted by the audio driver to a value more suitable for the hardware. + * Good values seem to range between 512 and 4096 inclusive, depending on + * the application and CPU speed. Smaller values reduce latency, but can + * lead to underflow if the application is doing heavy processing and cannot + * fill the audio buffer in time. Note that the number of sample frames is + * directly related to time by the following formula: `ms = + * (sampleframes*1000)/freq` + * - `desired->size` is the size in _bytes_ of the audio buffer, and is + * calculated by SDL_OpenAudioDevice(). You don't initialize this. + * - `desired->silence` is the value used to set the buffer to silence, and is + * calculated by SDL_OpenAudioDevice(). You don't initialize this. + * - `desired->callback` should be set to a function that will be called when + * the audio device is ready for more data. It is passed a pointer to the + * audio buffer, and the length in bytes of the audio buffer. This function + * usually runs in a separate thread, and so you should protect data + * structures that it accesses by calling SDL_LockAudioDevice() and + * SDL_UnlockAudioDevice() in your code. Alternately, you may pass a NULL + * pointer here, and call SDL_QueueAudio() with some frequency, to queue + * more audio samples to be played (or for capture devices, call + * SDL_DequeueAudio() with some frequency, to obtain audio samples). + * - `desired->userdata` is passed as the first parameter to your callback + * function. If you passed a NULL callback, this value is ignored. + * + * `allowed_changes` can have the following flags OR'd together: + * + * - `SDL_AUDIO_ALLOW_FREQUENCY_CHANGE` + * - `SDL_AUDIO_ALLOW_FORMAT_CHANGE` + * - `SDL_AUDIO_ALLOW_CHANNELS_CHANGE` + * - `SDL_AUDIO_ALLOW_SAMPLES_CHANGE` + * - `SDL_AUDIO_ALLOW_ANY_CHANGE` + * + * These flags specify how SDL should behave when a device cannot offer a + * specific feature. If the application requests a feature that the hardware + * doesn't offer, SDL will always try to get the closest equivalent. + * + * For example, if you ask for float32 audio format, but the sound card only + * supports int16, SDL will set the hardware to int16. If you had set + * SDL_AUDIO_ALLOW_FORMAT_CHANGE, SDL will change the format in the `obtained` + * structure. If that flag was *not* set, SDL will prepare to convert your + * callback's float32 audio to int16 before feeding it to the hardware and + * will keep the originally requested format in the `obtained` structure. + * + * The resulting audio specs, varying depending on hardware and on what + * changes were allowed, will then be written back to `obtained`. + * + * If your application can only handle one specific data format, pass a zero + * for `allowed_changes` and let SDL transparently handle any differences. + * + * \param device a UTF-8 string reported by SDL_GetAudioDeviceName() or a + * driver-specific name as appropriate. NULL requests the most + * reasonable default device. + * \param iscapture non-zero to specify a device should be opened for + * recording, not playback. + * \param desired an SDL_AudioSpec structure representing the desired output + * format; see SDL_OpenAudio() for more information. + * \param obtained an SDL_AudioSpec structure filled in with the actual output + * format; see SDL_OpenAudio() for more information. + * \param allowed_changes 0, or one or more flags OR'd together. + * \returns a valid device ID that is > 0 on success or 0 on failure; call + * SDL_GetError() for more information. + * + * For compatibility with SDL 1.2, this will never return 1, since + * SDL reserves that ID for the legacy SDL_OpenAudio() function. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CloseAudioDevice + * \sa SDL_GetAudioDeviceName + * \sa SDL_LockAudioDevice + * \sa SDL_OpenAudio + * \sa SDL_PauseAudioDevice + * \sa SDL_UnlockAudioDevice + */ +extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice( + const char *device, + int iscapture, + const SDL_AudioSpec *desired, + SDL_AudioSpec *obtained, + int allowed_changes); + + + +/** + * \name Audio state + * + * Get the current audio state. + */ +/* @{ */ +typedef enum +{ + SDL_AUDIO_STOPPED = 0, + SDL_AUDIO_PLAYING, + SDL_AUDIO_PAUSED +} SDL_AudioStatus; + +/** + * This function is a legacy means of querying the audio device. + * + * New programs might want to use SDL_GetAudioDeviceStatus() instead. This + * function is equivalent to calling... + * + * ```c + * SDL_GetAudioDeviceStatus(1); + * ``` + * + * ...and is only useful if you used the legacy SDL_OpenAudio() function. + * + * \returns the SDL_AudioStatus of the audio device opened by SDL_OpenAudio(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetAudioDeviceStatus + */ +extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioStatus(void); + +/** + * Use this function to get the current audio state of an audio device. + * + * \param dev the ID of an audio device previously opened with + * SDL_OpenAudioDevice(). + * \returns the SDL_AudioStatus of the specified audio device. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PauseAudioDevice + */ +extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioDeviceStatus(SDL_AudioDeviceID dev); +/* @} *//* Audio State */ + +/** + * \name Pause audio functions + * + * These functions pause and unpause the audio callback processing. + * They should be called with a parameter of 0 after opening the audio + * device to start playing sound. This is so you can safely initialize + * data for your callback function after opening the audio device. + * Silence will be written to the audio device during the pause. + */ +/* @{ */ + +/** + * This function is a legacy means of pausing the audio device. + * + * New programs might want to use SDL_PauseAudioDevice() instead. This + * function is equivalent to calling... + * + * ```c + * SDL_PauseAudioDevice(1, pause_on); + * ``` + * + * ...and is only useful if you used the legacy SDL_OpenAudio() function. + * + * \param pause_on non-zero to pause, 0 to unpause. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetAudioStatus + * \sa SDL_PauseAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on); + +/** + * Use this function to pause and unpause audio playback on a specified + * device. + * + * This function pauses and unpauses the audio callback processing for a given + * device. Newly-opened audio devices start in the paused state, so you must + * call this function with **pause_on**=0 after opening the specified audio + * device to start playing sound. This allows you to safely initialize data + * for your callback function after opening the audio device. Silence will be + * written to the audio device while paused, and the audio callback is + * guaranteed to not be called. Pausing one device does not prevent other + * unpaused devices from running their callbacks. + * + * Pausing state does not stack; even if you pause a device several times, a + * single unpause will start the device playing again, and vice versa. This is + * different from how SDL_LockAudioDevice() works. + * + * If you just need to protect a few variables from race conditions vs your + * callback, you shouldn't pause the audio device, as it will lead to dropouts + * in the audio playback. Instead, you should use SDL_LockAudioDevice(). + * + * \param dev a device opened by SDL_OpenAudioDevice(). + * \param pause_on non-zero to pause, 0 to unpause. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LockAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev, + int pause_on); +/* @} *//* Pause audio functions */ + +/** + * Load the audio data of a WAVE file into memory. + * + * Loading a WAVE file requires `src`, `spec`, `audio_buf` and `audio_len` to + * be valid pointers. The entire data portion of the file is then loaded into + * memory and decoded if necessary. + * + * If `freesrc` is non-zero, the data source gets automatically closed and + * freed before the function returns. + * + * Supported formats are RIFF WAVE files with the formats PCM (8, 16, 24, and + * 32 bits), IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and + * A-law and mu-law (8 bits). Other formats are currently unsupported and + * cause an error. + * + * If this function succeeds, the pointer returned by it is equal to `spec` + * and the pointer to the audio data allocated by the function is written to + * `audio_buf` and its length in bytes to `audio_len`. The SDL_AudioSpec + * members `freq`, `channels`, and `format` are set to the values of the audio + * data in the buffer. The `samples` member is set to a sane default and all + * others are set to zero. + * + * It's necessary to use SDL_FreeWAV() to free the audio data returned in + * `audio_buf` when it is no longer used. + * + * Because of the underspecification of the .WAV format, there are many + * problematic files in the wild that cause issues with strict decoders. To + * provide compatibility with these files, this decoder is lenient in regards + * to the truncation of the file, the fact chunk, and the size of the RIFF + * chunk. The hints `SDL_HINT_WAVE_RIFF_CHUNK_SIZE`, + * `SDL_HINT_WAVE_TRUNCATION`, and `SDL_HINT_WAVE_FACT_CHUNK` can be used to + * tune the behavior of the loading process. + * + * Any file that is invalid (due to truncation, corruption, or wrong values in + * the headers), too big, or unsupported causes an error. Additionally, any + * critical I/O error from the data source will terminate the loading process + * with an error. The function returns NULL on error and in all cases (with + * the exception of `src` being NULL), an appropriate error message will be + * set. + * + * It is required that the data source supports seeking. + * + * Example: + * + * ```c + * SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, &spec, &buf, &len); + * ``` + * + * Note that the SDL_LoadWAV macro does this same thing for you, but in a less + * messy way: + * + * ```c + * SDL_LoadWAV("sample.wav", &spec, &buf, &len); + * ``` + * + * \param src The data source for the WAVE data. + * \param freesrc If non-zero, SDL will _always_ free the data source. + * \param spec An SDL_AudioSpec that will be filled in with the wave file's + * format details. + * \param audio_buf A pointer filled with the audio data, allocated by the + * function. + * \param audio_len A pointer filled with the length of the audio data buffer + * in bytes. + * \returns This function, if successfully called, returns `spec`, which will + * be filled with the audio data format of the wave source data. + * `audio_buf` will be filled with a pointer to an allocated buffer + * containing the audio data, and `audio_len` is filled with the + * length of that audio buffer in bytes. + * + * This function returns NULL if the .WAV file cannot be opened, uses + * an unknown data format, or is corrupt; call SDL_GetError() for + * more information. + * + * When the application is done with the data returned in + * `audio_buf`, it should call SDL_FreeWAV() to dispose of it. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreeWAV + * \sa SDL_LoadWAV + */ +extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, + int freesrc, + SDL_AudioSpec * spec, + Uint8 ** audio_buf, + Uint32 * audio_len); + +/** + * Loads a WAV from a file. + * + * Compatibility convenience function. + */ +#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \ + SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len) + +/** + * Free data previously allocated with SDL_LoadWAV() or SDL_LoadWAV_RW(). + * + * After a WAVE file has been opened with SDL_LoadWAV() or SDL_LoadWAV_RW() + * its data can eventually be freed with SDL_FreeWAV(). It is safe to call + * this function with a NULL pointer. + * + * \param audio_buf a pointer to the buffer created by SDL_LoadWAV() or + * SDL_LoadWAV_RW(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadWAV + * \sa SDL_LoadWAV_RW + */ +extern DECLSPEC void SDLCALL SDL_FreeWAV(Uint8 * audio_buf); + +/** + * Initialize an SDL_AudioCVT structure for conversion. + * + * Before an SDL_AudioCVT structure can be used to convert audio data it must + * be initialized with source and destination information. + * + * This function will zero out every field of the SDL_AudioCVT, so it must be + * called before the application fills in the final buffer information. + * + * Once this function has returned successfully, and reported that a + * conversion is necessary, the application fills in the rest of the fields in + * SDL_AudioCVT, now that it knows how large a buffer it needs to allocate, + * and then can call SDL_ConvertAudio() to complete the conversion. + * + * \param cvt an SDL_AudioCVT structure filled in with audio conversion + * information. + * \param src_format the source format of the audio data; for more info see + * SDL_AudioFormat. + * \param src_channels the number of channels in the source. + * \param src_rate the frequency (sample-frames-per-second) of the source. + * \param dst_format the destination format of the audio data; for more info + * see SDL_AudioFormat. + * \param dst_channels the number of channels in the destination. + * \param dst_rate the frequency (sample-frames-per-second) of the + * destination. + * \returns 1 if the audio filter is prepared, 0 if no conversion is needed, + * or a negative error code on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ConvertAudio + */ +extern DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT * cvt, + SDL_AudioFormat src_format, + Uint8 src_channels, + int src_rate, + SDL_AudioFormat dst_format, + Uint8 dst_channels, + int dst_rate); + +/** + * Convert audio data to a desired audio format. + * + * This function does the actual audio data conversion, after the application + * has called SDL_BuildAudioCVT() to prepare the conversion information and + * then filled in the buffer details. + * + * Once the application has initialized the `cvt` structure using + * SDL_BuildAudioCVT(), allocated an audio buffer and filled it with audio + * data in the source format, this function will convert the buffer, in-place, + * to the desired format. + * + * The data conversion may go through several passes; any given pass may + * possibly temporarily increase the size of the data. For example, SDL might + * expand 16-bit data to 32 bits before resampling to a lower frequency, + * shrinking the data size after having grown it briefly. Since the supplied + * buffer will be both the source and destination, converting as necessary + * in-place, the application must allocate a buffer that will fully contain + * the data during its largest conversion pass. After SDL_BuildAudioCVT() + * returns, the application should set the `cvt->len` field to the size, in + * bytes, of the source data, and allocate a buffer that is `cvt->len * + * cvt->len_mult` bytes long for the `buf` field. + * + * The source data should be copied into this buffer before the call to + * SDL_ConvertAudio(). Upon successful return, this buffer will contain the + * converted audio, and `cvt->len_cvt` will be the size of the converted data, + * in bytes. Any bytes in the buffer past `cvt->len_cvt` are undefined once + * this function returns. + * + * \param cvt an SDL_AudioCVT structure that was previously set up by + * SDL_BuildAudioCVT(). + * \returns 0 if the conversion was completed successfully or a negative error + * code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BuildAudioCVT + */ +extern DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT * cvt); + +/* SDL_AudioStream is a new audio conversion interface. + The benefits vs SDL_AudioCVT: + - it can handle resampling data in chunks without generating + artifacts, when it doesn't have the complete buffer available. + - it can handle incoming data in any variable size. + - You push data as you have it, and pull it when you need it + */ +/* this is opaque to the outside world. */ +struct _SDL_AudioStream; +typedef struct _SDL_AudioStream SDL_AudioStream; + +/** + * Create a new audio stream. + * + * \param src_format The format of the source audio. + * \param src_channels The number of channels of the source audio. + * \param src_rate The sampling rate of the source audio. + * \param dst_format The format of the desired audio output. + * \param dst_channels The number of channels of the desired audio output. + * \param dst_rate The sampling rate of the desired audio output. + * \returns 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamAvailable + * \sa SDL_AudioStreamFlush + * \sa SDL_AudioStreamClear + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC SDL_AudioStream * SDLCALL SDL_NewAudioStream(const SDL_AudioFormat src_format, + const Uint8 src_channels, + const int src_rate, + const SDL_AudioFormat dst_format, + const Uint8 dst_channels, + const int dst_rate); + +/** + * Add data to be converted/resampled to the stream. + * + * \param stream The stream the audio data is being added to. + * \param buf A pointer to the audio data to add. + * \param len The number of bytes to write to the stream. + * \returns 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamAvailable + * \sa SDL_AudioStreamFlush + * \sa SDL_AudioStreamClear + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC int SDLCALL SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len); + +/** + * Get converted/resampled data from the stream + * + * \param stream The stream the audio is being requested from. + * \param buf A buffer to fill with audio data. + * \param len The maximum number of bytes to fill. + * \returns the number of bytes read from the stream, or -1 on error. + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamAvailable + * \sa SDL_AudioStreamFlush + * \sa SDL_AudioStreamClear + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC int SDLCALL SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, int len); + +/** + * Get the number of converted/resampled bytes available. + * + * The stream may be buffering data behind the scenes until it has enough to + * resample correctly, so this number might be lower than what you expect, or + * even be zero. Add more data or flush the stream if you need the data now. + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamFlush + * \sa SDL_AudioStreamClear + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC int SDLCALL SDL_AudioStreamAvailable(SDL_AudioStream *stream); + +/** + * Tell the stream that you're done sending data, and anything being buffered + * should be converted/resampled and made available immediately. + * + * It is legal to add more data to a stream after flushing, but there will be + * audio gaps in the output. Generally this is intended to signal the end of + * input, so the complete output becomes available. + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamAvailable + * \sa SDL_AudioStreamClear + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC int SDLCALL SDL_AudioStreamFlush(SDL_AudioStream *stream); + +/** + * Clear any pending data in the stream without converting it + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamAvailable + * \sa SDL_AudioStreamFlush + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC void SDLCALL SDL_AudioStreamClear(SDL_AudioStream *stream); + +/** + * Free an audio stream + * + * \since This function is available since SDL 2.0.7. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamAvailable + * \sa SDL_AudioStreamFlush + * \sa SDL_AudioStreamClear + */ +extern DECLSPEC void SDLCALL SDL_FreeAudioStream(SDL_AudioStream *stream); + +/** + * Maximum volume allowed in calls to SDL_MixAudio and SDL_MixAudioFormat. + */ +#define SDL_MIX_MAXVOLUME 128 + +/** + * This function is a legacy means of mixing audio. + * + * This function is equivalent to calling... + * + * ```c + * SDL_MixAudioFormat(dst, src, format, len, volume); + * ``` + * + * ...where `format` is the obtained format of the audio device from the + * legacy SDL_OpenAudio() function. + * + * \param dst the destination for the mixed audio. + * \param src the source audio buffer to be mixed. + * \param len the length of the audio buffer in bytes. + * \param volume ranges from 0 - 128, and should be set to SDL_MIX_MAXVOLUME + * for full audio volume. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_MixAudioFormat + */ +extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 * dst, const Uint8 * src, + Uint32 len, int volume); + +/** + * Mix audio data in a specified format. + * + * This takes an audio buffer `src` of `len` bytes of `format` data and mixes + * it into `dst`, performing addition, volume adjustment, and overflow + * clipping. The buffer pointed to by `dst` must also be `len` bytes of + * `format` data. + * + * This is provided for convenience -- you can mix your own audio data. + * + * Do not use this function for mixing together more than two streams of + * sample data. The output from repeated application of this function may be + * distorted by clipping, because there is no accumulator with greater range + * than the input (not to mention this being an inefficient way of doing it). + * + * It is a common misconception that this function is required to write audio + * data to an output stream in an audio callback. While you can do that, + * SDL_MixAudioFormat() is really only needed when you're mixing a single + * audio stream with a volume adjustment. + * + * \param dst the destination for the mixed audio. + * \param src the source audio buffer to be mixed. + * \param format the SDL_AudioFormat structure representing the desired audio + * format. + * \param len the length of the audio buffer in bytes. + * \param volume ranges from 0 - 128, and should be set to SDL_MIX_MAXVOLUME + * for full audio volume. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst, + const Uint8 * src, + SDL_AudioFormat format, + Uint32 len, int volume); + +/** + * Queue more audio on non-callback devices. + * + * If you are looking to retrieve queued audio from a non-callback capture + * device, you want SDL_DequeueAudio() instead. SDL_QueueAudio() will return + * -1 to signify an error if you use it with capture devices. + * + * SDL offers two ways to feed audio to the device: you can either supply a + * callback that SDL triggers with some frequency to obtain more audio (pull + * method), or you can supply no callback, and then SDL will expect you to + * supply data at regular intervals (push method) with this function. + * + * There are no limits on the amount of data you can queue, short of + * exhaustion of address space. Queued data will drain to the device as + * necessary without further intervention from you. If the device needs audio + * but there is not enough queued, it will play silence to make up the + * difference. This means you will have skips in your audio playback if you + * aren't routinely queueing sufficient data. + * + * This function copies the supplied data, so you are safe to free it when the + * function returns. This function is thread-safe, but queueing to the same + * device from two threads at once does not promise which buffer will be + * queued first. + * + * You may not queue audio on a device that is using an application-supplied + * callback; doing so returns an error. You have to use the audio callback or + * queue audio with this function, but not both. + * + * You should not call SDL_LockAudio() on the device before queueing; SDL + * handles locking internally for this function. + * + * Note that SDL2 does not support planar audio. You will need to resample + * from planar audio formats into a non-planar one (see SDL_AudioFormat) + * before queuing audio. + * + * \param dev the device ID to which we will queue audio. + * \param data the data to queue to the device for later playback. + * \param len the number of bytes (not samples!) to which `data` points. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_ClearQueuedAudio + * \sa SDL_GetQueuedAudioSize + */ +extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len); + +/** + * Dequeue more audio on non-callback devices. + * + * If you are looking to queue audio for output on a non-callback playback + * device, you want SDL_QueueAudio() instead. SDL_DequeueAudio() will always + * return 0 if you use it with playback devices. + * + * SDL offers two ways to retrieve audio from a capture device: you can either + * supply a callback that SDL triggers with some frequency as the device + * records more audio data, (push method), or you can supply no callback, and + * then SDL will expect you to retrieve data at regular intervals (pull + * method) with this function. + * + * There are no limits on the amount of data you can queue, short of + * exhaustion of address space. Data from the device will keep queuing as + * necessary without further intervention from you. This means you will + * eventually run out of memory if you aren't routinely dequeueing data. + * + * Capture devices will not queue data when paused; if you are expecting to + * not need captured audio for some length of time, use SDL_PauseAudioDevice() + * to stop the capture device from queueing more data. This can be useful + * during, say, level loading times. When unpaused, capture devices will start + * queueing data from that point, having flushed any capturable data available + * while paused. + * + * This function is thread-safe, but dequeueing from the same device from two + * threads at once does not promise which thread will dequeue data first. + * + * You may not dequeue audio from a device that is using an + * application-supplied callback; doing so returns an error. You have to use + * the audio callback, or dequeue audio with this function, but not both. + * + * You should not call SDL_LockAudio() on the device before dequeueing; SDL + * handles locking internally for this function. + * + * \param dev the device ID from which we will dequeue audio. + * \param data a pointer into where audio data should be copied. + * \param len the number of bytes (not samples!) to which (data) points. + * \returns the number of bytes dequeued, which could be less than requested; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_ClearQueuedAudio + * \sa SDL_GetQueuedAudioSize + */ +extern DECLSPEC Uint32 SDLCALL SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len); + +/** + * Get the number of bytes of still-queued audio. + * + * For playback devices: this is the number of bytes that have been queued for + * playback with SDL_QueueAudio(), but have not yet been sent to the hardware. + * + * Once we've sent it to the hardware, this function can not decide the exact + * byte boundary of what has been played. It's possible that we just gave the + * hardware several kilobytes right before you called this function, but it + * hasn't played any of it yet, or maybe half of it, etc. + * + * For capture devices, this is the number of bytes that have been captured by + * the device and are waiting for you to dequeue. This number may grow at any + * time, so this only informs of the lower-bound of available data. + * + * You may not queue or dequeue audio on a device that is using an + * application-supplied callback; calling this function on such a device + * always returns 0. You have to use the audio callback or queue audio, but + * not both. + * + * You should not call SDL_LockAudio() on the device before querying; SDL + * handles locking internally for this function. + * + * \param dev the device ID of which we will query queued audio size. + * \returns the number of bytes (not samples!) of queued audio. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_ClearQueuedAudio + * \sa SDL_QueueAudio + * \sa SDL_DequeueAudio + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev); + +/** + * Drop any queued audio data waiting to be sent to the hardware. + * + * Immediately after this call, SDL_GetQueuedAudioSize() will return 0. For + * output devices, the hardware will start playing silence if more audio isn't + * queued. For capture devices, the hardware will start filling the empty + * queue with new data if the capture device isn't paused. + * + * This will not prevent playback of queued audio that's already been sent to + * the hardware, as we can not undo that, so expect there to be some fraction + * of a second of audio that might still be heard. This can be useful if you + * want to, say, drop any pending music or any unprocessed microphone input + * during a level change in your game. + * + * You may not queue or dequeue audio on a device that is using an + * application-supplied callback; calling this function on such a device + * always returns 0. You have to use the audio callback or queue audio, but + * not both. + * + * You should not call SDL_LockAudio() on the device before clearing the + * queue; SDL handles locking internally for this function. + * + * This function always succeeds and thus returns void. + * + * \param dev the device ID of which to clear the audio queue. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_GetQueuedAudioSize + * \sa SDL_QueueAudio + * \sa SDL_DequeueAudio + */ +extern DECLSPEC void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID dev); + + +/** + * \name Audio lock functions + * + * The lock manipulated by these functions protects the callback function. + * During a SDL_LockAudio()/SDL_UnlockAudio() pair, you can be guaranteed that + * the callback function is not running. Do not call these from the callback + * function or you will cause deadlock. + */ +/* @{ */ + +/** + * This function is a legacy means of locking the audio device. + * + * New programs might want to use SDL_LockAudioDevice() instead. This function + * is equivalent to calling... + * + * ```c + * SDL_LockAudioDevice(1); + * ``` + * + * ...and is only useful if you used the legacy SDL_OpenAudio() function. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LockAudioDevice + * \sa SDL_UnlockAudio + * \sa SDL_UnlockAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_LockAudio(void); + +/** + * Use this function to lock out the audio callback function for a specified + * device. + * + * The lock manipulated by these functions protects the audio callback + * function specified in SDL_OpenAudioDevice(). During a + * SDL_LockAudioDevice()/SDL_UnlockAudioDevice() pair, you can be guaranteed + * that the callback function for that device is not running, even if the + * device is not paused. While a device is locked, any other unpaused, + * unlocked devices may still run their callbacks. + * + * Calling this function from inside your audio callback is unnecessary. SDL + * obtains this lock before calling your function, and releases it when the + * function returns. + * + * You should not hold the lock longer than absolutely necessary. If you hold + * it too long, you'll experience dropouts in your audio playback. Ideally, + * your application locks the device, sets a few variables and unlocks again. + * Do not do heavy work while holding the lock for a device. + * + * It is safe to lock the audio device multiple times, as long as you unlock + * it an equivalent number of times. The callback will not run until the + * device has been unlocked completely in this way. If your application fails + * to unlock the device appropriately, your callback will never run, you might + * hear repeating bursts of audio, and SDL_CloseAudioDevice() will probably + * deadlock. + * + * Internally, the audio device lock is a mutex; if you lock from two threads + * at once, not only will you block the audio callback, you'll block the other + * thread. + * + * \param dev the ID of the device to be locked. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_UnlockAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_LockAudioDevice(SDL_AudioDeviceID dev); + +/** + * This function is a legacy means of unlocking the audio device. + * + * New programs might want to use SDL_UnlockAudioDevice() instead. This + * function is equivalent to calling... + * + * ```c + * SDL_UnlockAudioDevice(1); + * ``` + * + * ...and is only useful if you used the legacy SDL_OpenAudio() function. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LockAudio + * \sa SDL_UnlockAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_UnlockAudio(void); + +/** + * Use this function to unlock the audio callback function for a specified + * device. + * + * This function should be paired with a previous SDL_LockAudioDevice() call. + * + * \param dev the ID of the device to be unlocked. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LockAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_UnlockAudioDevice(SDL_AudioDeviceID dev); +/* @} *//* Audio lock functions */ + +/** + * This function is a legacy means of closing the audio device. + * + * This function is equivalent to calling... + * + * ```c + * SDL_CloseAudioDevice(1); + * ``` + * + * ...and is only useful if you used the legacy SDL_OpenAudio() function. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_OpenAudio + */ +extern DECLSPEC void SDLCALL SDL_CloseAudio(void); + +/** + * Use this function to shut down audio processing and close the audio device. + * + * The application should close open audio devices once they are no longer + * needed. Calling this function will wait until the device's audio callback + * is not running, release the audio hardware and then clean up internal + * state. No further audio will play from this device once this function + * returns. + * + * This function may block briefly while pending audio data is played by the + * hardware, so that applications don't drop the last buffer of data they + * supplied. + * + * The device ID is invalid as soon as the device is closed, and is eligible + * for reuse in a new SDL_OpenAudioDevice() call immediately. + * + * \param dev an audio device previously opened with SDL_OpenAudioDevice(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_OpenAudioDevice + */ +extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_audio_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_bits.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_bits.h new file mode 100644 index 00000000..747f5565 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_bits.h @@ -0,0 +1,132 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryBits + * + * Functions for fiddling with bits and bitmasks. + */ + +#ifndef SDL_bits_h_ +#define SDL_bits_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file SDL_bits.h + */ + +/** + * Get the index of the most significant bit. Result is undefined when called + * with 0. This operation can also be stated as "count leading zeroes" and + * "log base 2". + * + * \return the index of the most significant bit, or -1 if the value is 0. + */ +#if defined(__WATCOMC__) && defined(__386__) +extern __inline int _SDL_bsr_watcom(Uint32); +#pragma aux _SDL_bsr_watcom = \ + "bsr eax, eax" \ + parm [eax] nomemory \ + value [eax] \ + modify exact [eax] nomemory; +#endif + +/** + * Use this function to get the index of the most significant (set) bit in a + * + * \param x the number to find the MSB of. + * \returns the index of the most significant bit of x, or -1 if x is 0. + */ +SDL_FORCE_INLINE int +SDL_MostSignificantBitIndex32(Uint32 x) +{ +#if defined(__GNUC__) && (__GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) + /* Count Leading Zeroes builtin in GCC. + * http://gcc.gnu.org/onlinedocs/gcc-4.3.4/gcc/Other-Builtins.html + */ + if (x == 0) { + return -1; + } + return 31 - __builtin_clz(x); +#elif defined(__WATCOMC__) && defined(__386__) + if (x == 0) { + return -1; + } + return _SDL_bsr_watcom(x); +#elif defined(_MSC_VER) + unsigned long index; + if (_BitScanReverse(&index, x)) { + return index; + } + return -1; +#else + /* Based off of Bit Twiddling Hacks by Sean Eron Anderson + * , released in the public domain. + * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog + */ + const Uint32 b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000}; + const int S[] = {1, 2, 4, 8, 16}; + + int msbIndex = 0; + int i; + + if (x == 0) { + return -1; + } + + for (i = 4; i >= 0; i--) + { + if (x & b[i]) + { + x >>= S[i]; + msbIndex |= S[i]; + } + } + + return msbIndex; +#endif +} + +SDL_FORCE_INLINE SDL_bool +SDL_HasExactlyOneBitSet32(Uint32 x) +{ + if (x && !(x & (x - 1))) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_bits_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_blendmode.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_blendmode.h new file mode 100644 index 00000000..c0c68113 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_blendmode.h @@ -0,0 +1,199 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryBlendmode + * + * Header file declaring the SDL_BlendMode enumeration + */ + +#ifndef SDL_blendmode_h_ +#define SDL_blendmode_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The blend mode used in SDL_RenderCopy() and drawing operations. + */ +typedef enum SDL_BlendMode +{ + SDL_BLENDMODE_NONE = 0x00000000, /**< no blending + dstRGBA = srcRGBA */ + SDL_BLENDMODE_BLEND = 0x00000001, /**< alpha blending + dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA)) + dstA = srcA + (dstA * (1-srcA)) */ + SDL_BLENDMODE_ADD = 0x00000002, /**< additive blending + dstRGB = (srcRGB * srcA) + dstRGB + dstA = dstA */ + SDL_BLENDMODE_MOD = 0x00000004, /**< color modulate + dstRGB = srcRGB * dstRGB + dstA = dstA */ + SDL_BLENDMODE_MUL = 0x00000008, /**< color multiply + dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA)) + dstA = dstA */ + SDL_BLENDMODE_INVALID = 0x7FFFFFFF + + /* Additional custom blend modes can be returned by SDL_ComposeCustomBlendMode() */ + +} SDL_BlendMode; + +/** + * The blend operation used when combining source and destination pixel + * components + */ +typedef enum SDL_BlendOperation +{ + SDL_BLENDOPERATION_ADD = 0x1, /**< dst + src: supported by all renderers */ + SDL_BLENDOPERATION_SUBTRACT = 0x2, /**< src - dst : supported by D3D9, D3D11, OpenGL, OpenGLES */ + SDL_BLENDOPERATION_REV_SUBTRACT = 0x3, /**< dst - src : supported by D3D9, D3D11, OpenGL, OpenGLES */ + SDL_BLENDOPERATION_MINIMUM = 0x4, /**< min(dst, src) : supported by D3D9, D3D11 */ + SDL_BLENDOPERATION_MAXIMUM = 0x5 /**< max(dst, src) : supported by D3D9, D3D11 */ +} SDL_BlendOperation; + +/** + * The normalized factor used to multiply pixel components + */ +typedef enum SDL_BlendFactor +{ + SDL_BLENDFACTOR_ZERO = 0x1, /**< 0, 0, 0, 0 */ + SDL_BLENDFACTOR_ONE = 0x2, /**< 1, 1, 1, 1 */ + SDL_BLENDFACTOR_SRC_COLOR = 0x3, /**< srcR, srcG, srcB, srcA */ + SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR = 0x4, /**< 1-srcR, 1-srcG, 1-srcB, 1-srcA */ + SDL_BLENDFACTOR_SRC_ALPHA = 0x5, /**< srcA, srcA, srcA, srcA */ + SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA = 0x6, /**< 1-srcA, 1-srcA, 1-srcA, 1-srcA */ + SDL_BLENDFACTOR_DST_COLOR = 0x7, /**< dstR, dstG, dstB, dstA */ + SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR = 0x8, /**< 1-dstR, 1-dstG, 1-dstB, 1-dstA */ + SDL_BLENDFACTOR_DST_ALPHA = 0x9, /**< dstA, dstA, dstA, dstA */ + SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA = 0xA /**< 1-dstA, 1-dstA, 1-dstA, 1-dstA */ +} SDL_BlendFactor; + +/** + * Compose a custom blend mode for renderers. + * + * The functions SDL_SetRenderDrawBlendMode and SDL_SetTextureBlendMode accept + * the SDL_BlendMode returned by this function if the renderer supports it. + * + * A blend mode controls how the pixels from a drawing operation (source) get + * combined with the pixels from the render target (destination). First, the + * components of the source and destination pixels get multiplied with their + * blend factors. Then, the blend operation takes the two products and + * calculates the result that will get stored in the render target. + * + * Expressed in pseudocode, it would look like this: + * + * ```c + * dstRGB = colorOperation(srcRGB * srcColorFactor, dstRGB * dstColorFactor); + * dstA = alphaOperation(srcA * srcAlphaFactor, dstA * dstAlphaFactor); + * ``` + * + * Where the functions `colorOperation(src, dst)` and `alphaOperation(src, + * dst)` can return one of the following: + * + * - `src + dst` + * - `src - dst` + * - `dst - src` + * - `min(src, dst)` + * - `max(src, dst)` + * + * The red, green, and blue components are always multiplied with the first, + * second, and third components of the SDL_BlendFactor, respectively. The + * fourth component is not used. + * + * The alpha component is always multiplied with the fourth component of the + * SDL_BlendFactor. The other components are not used in the alpha + * calculation. + * + * Support for these blend modes varies for each renderer. To check if a + * specific SDL_BlendMode is supported, create a renderer and pass it to + * either SDL_SetRenderDrawBlendMode or SDL_SetTextureBlendMode. They will + * return with an error if the blend mode is not supported. + * + * This list describes the support of custom blend modes for each renderer in + * SDL 2.0.6. All renderers support the four blend modes listed in the + * SDL_BlendMode enumeration. + * + * - **direct3d**: Supports all operations with all factors. However, some + * factors produce unexpected results with `SDL_BLENDOPERATION_MINIMUM` and + * `SDL_BLENDOPERATION_MAXIMUM`. + * - **direct3d11**: Same as Direct3D 9. + * - **opengl**: Supports the `SDL_BLENDOPERATION_ADD` operation with all + * factors. OpenGL versions 1.1, 1.2, and 1.3 do not work correctly with SDL + * 2.0.6. + * - **opengles**: Supports the `SDL_BLENDOPERATION_ADD` operation with all + * factors. Color and alpha factors need to be the same. OpenGL ES 1 + * implementation specific: May also support `SDL_BLENDOPERATION_SUBTRACT` + * and `SDL_BLENDOPERATION_REV_SUBTRACT`. May support color and alpha + * operations being different from each other. May support color and alpha + * factors being different from each other. + * - **opengles2**: Supports the `SDL_BLENDOPERATION_ADD`, + * `SDL_BLENDOPERATION_SUBTRACT`, `SDL_BLENDOPERATION_REV_SUBTRACT` + * operations with all factors. + * - **psp**: No custom blend mode support. + * - **software**: No custom blend mode support. + * + * Some renderers do not provide an alpha component for the default render + * target. The `SDL_BLENDFACTOR_DST_ALPHA` and + * `SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA` factors do not have an effect in this + * case. + * + * \param srcColorFactor the SDL_BlendFactor applied to the red, green, and + * blue components of the source pixels. + * \param dstColorFactor the SDL_BlendFactor applied to the red, green, and + * blue components of the destination pixels. + * \param colorOperation the SDL_BlendOperation used to combine the red, + * green, and blue components of the source and + * destination pixels. + * \param srcAlphaFactor the SDL_BlendFactor applied to the alpha component of + * the source pixels. + * \param dstAlphaFactor the SDL_BlendFactor applied to the alpha component of + * the destination pixels. + * \param alphaOperation the SDL_BlendOperation used to combine the alpha + * component of the source and destination pixels. + * \returns an SDL_BlendMode that represents the chosen factors and + * operations. + * + * \since This function is available since SDL 2.0.6. + * + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_GetRenderDrawBlendMode + * \sa SDL_SetTextureBlendMode + * \sa SDL_GetTextureBlendMode + */ +extern DECLSPEC SDL_BlendMode SDLCALL SDL_ComposeCustomBlendMode(SDL_BlendFactor srcColorFactor, + SDL_BlendFactor dstColorFactor, + SDL_BlendOperation colorOperation, + SDL_BlendFactor srcAlphaFactor, + SDL_BlendFactor dstAlphaFactor, + SDL_BlendOperation alphaOperation); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_blendmode_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_clipboard.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_clipboard.h new file mode 100644 index 00000000..2ae16a1d --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_clipboard.h @@ -0,0 +1,141 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryClipboard + * + * Include file for SDL clipboard handling + */ + +#ifndef SDL_clipboard_h_ +#define SDL_clipboard_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Function prototypes */ + +/** + * Put UTF-8 text into the clipboard. + * + * \param text the text to store in the clipboard. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetClipboardText + * \sa SDL_HasClipboardText + */ +extern DECLSPEC int SDLCALL SDL_SetClipboardText(const char *text); + +/** + * Get UTF-8 text from the clipboard, which must be freed with SDL_free(). + * + * This functions returns empty string if there was not enough memory left for + * a copy of the clipboard's content. + * + * \returns the clipboard text on success or an empty string on failure; call + * SDL_GetError() for more information. Caller must call SDL_free() + * on the returned pointer when done with it (even if there was an + * error). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HasClipboardText + * \sa SDL_SetClipboardText + */ +extern DECLSPEC char * SDLCALL SDL_GetClipboardText(void); + +/** + * Query whether the clipboard exists and contains a non-empty text string. + * + * \returns SDL_TRUE if the clipboard has text, or SDL_FALSE if it does not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetClipboardText + * \sa SDL_SetClipboardText + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardText(void); + +/** + * Put UTF-8 text into the primary selection. + * + * \param text the text to store in the primary selection. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.26.0. + * + * \sa SDL_GetPrimarySelectionText + * \sa SDL_HasPrimarySelectionText + */ +extern DECLSPEC int SDLCALL SDL_SetPrimarySelectionText(const char *text); + +/** + * Get UTF-8 text from the primary selection, which must be freed with + * SDL_free(). + * + * This functions returns empty string if there was not enough memory left for + * a copy of the primary selection's content. + * + * \returns the primary selection text on success or an empty string on + * failure; call SDL_GetError() for more information. Caller must + * call SDL_free() on the returned pointer when done with it (even if + * there was an error). + * + * \since This function is available since SDL 2.26.0. + * + * \sa SDL_HasPrimarySelectionText + * \sa SDL_SetPrimarySelectionText + */ +extern DECLSPEC char * SDLCALL SDL_GetPrimarySelectionText(void); + +/** + * Query whether the primary selection exists and contains a non-empty text + * string. + * + * \returns SDL_TRUE if the primary selection has text, or SDL_FALSE if it + * does not. + * + * \since This function is available since SDL 2.26.0. + * + * \sa SDL_GetPrimarySelectionText + * \sa SDL_SetPrimarySelectionText + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasPrimarySelectionText(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_clipboard_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_config.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_config.h new file mode 100644 index 00000000..35088ffa --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_config.h @@ -0,0 +1,571 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_config_h_ +#define SDL_config_h_ + +/** + * \file SDL_config.h.in + * + * This is a set of defines to configure the SDL features + */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +/* C language features */ +/* #undef const */ +/* #undef inline */ +/* #undef volatile */ + +/* C datatypes */ +/* Define SIZEOF_VOIDP for 64/32 architectures */ +#if defined(__LP64__) || defined(_LP64) || defined(_WIN64) +#define SIZEOF_VOIDP 8 +#else +#define SIZEOF_VOIDP 4 +#endif + +/* #undef HAVE_GCC_ATOMICS */ +/* #undef HAVE_GCC_SYNC_LOCK_TEST_AND_SET */ + +/* Comment this if you want to build without any C library requirements */ +/* #undef HAVE_LIBC */ +#ifdef HAVE_LIBC + +/* Useful headers */ +/* #undef STDC_HEADERS */ +/* #undef HAVE_ALLOCA_H */ +/* #undef HAVE_CTYPE_H */ +/* #undef HAVE_FLOAT_H */ +/* #undef HAVE_ICONV_H */ +/* #undef HAVE_INTTYPES_H */ +/* #undef HAVE_LIMITS_H */ +/* #undef HAVE_MALLOC_H */ +/* #undef HAVE_MATH_H */ +/* #undef HAVE_MEMORY_H */ +/* #undef HAVE_SIGNAL_H */ +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +/* #undef HAVE_STDIO_H */ +/* #undef HAVE_STDLIB_H */ +/* #undef HAVE_STRINGS_H */ +/* #undef HAVE_STRING_H */ +/* #undef HAVE_SYS_TYPES_H */ +/* #undef HAVE_WCHAR_H */ +/* #undef HAVE_LINUX_INPUT_H */ +/* #undef HAVE_PTHREAD_NP_H */ +/* #undef HAVE_LIBUNWIND_H */ + +/* C library functions */ +/* #undef HAVE_DLOPEN */ +/* #undef HAVE_MALLOC */ +/* #undef HAVE_CALLOC */ +/* #undef HAVE_REALLOC */ +/* #undef HAVE_FREE */ +/* #undef HAVE_ALLOCA */ +#ifndef __WIN32__ /* Don't use C runtime versions of these on Windows */ +/* #undef HAVE_GETENV */ +/* #undef HAVE_SETENV */ +/* #undef HAVE_PUTENV */ +/* #undef HAVE_UNSETENV */ +#endif +/* #undef HAVE_QSORT */ +/* #undef HAVE_BSEARCH */ +/* #undef HAVE_ABS */ +/* #undef HAVE_BCOPY */ +/* #undef HAVE_MEMSET */ +/* #undef HAVE_MEMCPY */ +/* #undef HAVE_MEMMOVE */ +/* #undef HAVE_MEMCMP */ +/* #undef HAVE_WCSLEN */ +/* #undef HAVE_WCSLCPY */ +/* #undef HAVE_WCSLCAT */ +/* #undef HAVE__WCSDUP */ +/* #undef HAVE_WCSDUP */ +/* #undef HAVE_WCSSTR */ +/* #undef HAVE_WCSCMP */ +/* #undef HAVE_WCSNCMP */ +/* #undef HAVE_WCSCASECMP */ +/* #undef HAVE__WCSICMP */ +/* #undef HAVE_WCSNCASECMP */ +/* #undef HAVE__WCSNICMP */ +/* #undef HAVE_STRLEN */ +/* #undef HAVE_STRLCPY */ +/* #undef HAVE_STRLCAT */ +/* #undef HAVE__STRREV */ +/* #undef HAVE__STRUPR */ +/* #undef HAVE__STRLWR */ +/* #undef HAVE_INDEX */ +/* #undef HAVE_RINDEX */ +/* #undef HAVE_STRCHR */ +/* #undef HAVE_STRRCHR */ +/* #undef HAVE_STRSTR */ +/* #undef HAVE_STRTOK_R */ +/* #undef HAVE_ITOA */ +/* #undef HAVE__LTOA */ +/* #undef HAVE__UITOA */ +/* #undef HAVE__ULTOA */ +/* #undef HAVE_STRTOL */ +/* #undef HAVE_STRTOUL */ +/* #undef HAVE__I64TOA */ +/* #undef HAVE__UI64TOA */ +/* #undef HAVE_STRTOLL */ +/* #undef HAVE_STRTOULL */ +/* #undef HAVE_STRTOD */ +/* #undef HAVE_ATOI */ +/* #undef HAVE_ATOF */ +/* #undef HAVE_STRCMP */ +/* #undef HAVE_STRNCMP */ +/* #undef HAVE__STRICMP */ +/* #undef HAVE_STRCASECMP */ +/* #undef HAVE__STRNICMP */ +/* #undef HAVE_STRNCASECMP */ +/* #undef HAVE_STRCASESTR */ +/* #undef HAVE_SSCANF */ +/* #undef HAVE_VSSCANF */ +/* #undef HAVE_VSNPRINTF */ +/* #undef HAVE_M_PI */ +/* #undef HAVE_ACOS */ +/* #undef HAVE_ACOSF */ +/* #undef HAVE_ASIN */ +/* #undef HAVE_ASINF */ +/* #undef HAVE_ATAN */ +/* #undef HAVE_ATANF */ +/* #undef HAVE_ATAN2 */ +/* #undef HAVE_ATAN2F */ +/* #undef HAVE_CEIL */ +/* #undef HAVE_CEILF */ +/* #undef HAVE_COPYSIGN */ +/* #undef HAVE_COPYSIGNF */ +/* #undef HAVE_COS */ +/* #undef HAVE_COSF */ +/* #undef HAVE_EXP */ +/* #undef HAVE_EXPF */ +/* #undef HAVE_FABS */ +/* #undef HAVE_FABSF */ +/* #undef HAVE_FLOOR */ +/* #undef HAVE_FLOORF */ +/* #undef HAVE_FMOD */ +/* #undef HAVE_FMODF */ +/* #undef HAVE_LOG */ +/* #undef HAVE_LOGF */ +/* #undef HAVE_LOG10 */ +/* #undef HAVE_LOG10F */ +/* #undef HAVE_LROUND */ +/* #undef HAVE_LROUNDF */ +/* #undef HAVE_POW */ +/* #undef HAVE_POWF */ +/* #undef HAVE_ROUND */ +/* #undef HAVE_ROUNDF */ +/* #undef HAVE_SCALBN */ +/* #undef HAVE_SCALBNF */ +/* #undef HAVE_SIN */ +/* #undef HAVE_SINF */ +/* #undef HAVE_SQRT */ +/* #undef HAVE_SQRTF */ +/* #undef HAVE_TAN */ +/* #undef HAVE_TANF */ +/* #undef HAVE_TRUNC */ +/* #undef HAVE_TRUNCF */ +/* #undef HAVE_FOPEN64 */ +/* #undef HAVE_FSEEKO */ +/* #undef HAVE_FSEEKO64 */ +/* #undef HAVE_MEMFD_CREATE */ +/* #undef HAVE_POSIX_FALLOCATE */ +/* #undef HAVE_SIGACTION */ +/* #undef HAVE_SIGTIMEDWAIT */ +/* #undef HAVE_SA_SIGACTION */ +/* #undef HAVE_SETJMP */ +/* #undef HAVE_NANOSLEEP */ +/* #undef HAVE_SYSCONF */ +/* #undef HAVE_SYSCTLBYNAME */ +/* #undef HAVE_CLOCK_GETTIME */ +/* #undef HAVE_GETPAGESIZE */ +/* #undef HAVE_MPROTECT */ +/* #undef HAVE_ICONV */ +/* #undef SDL_USE_LIBICONV */ +/* #undef HAVE_PTHREAD_SETNAME_NP */ +/* #undef HAVE_PTHREAD_SET_NAME_NP */ +/* #undef HAVE_SEM_TIMEDWAIT */ +/* #undef HAVE_GETAUXVAL */ +/* #undef HAVE_ELF_AUX_INFO */ +/* #undef HAVE_POLL */ +/* #undef HAVE__EXIT */ + +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +/* #undef HAVE_FLOAT_H */ +#endif /* HAVE_LIBC */ + +/* #undef HAVE_ALTIVEC_H */ +/* #undef HAVE_DBUS_DBUS_H */ +/* #undef HAVE_FCITX */ +/* #undef HAVE_IBUS_IBUS_H */ +/* #undef HAVE_SYS_INOTIFY_H */ +/* #undef HAVE_INOTIFY_INIT */ +/* #undef HAVE_INOTIFY_INIT1 */ +/* #undef HAVE_INOTIFY */ +/* #undef HAVE_LIBUSB */ +/* #undef HAVE_O_CLOEXEC */ + +/* Apple platforms might be building universal binaries, where Intel builds + can use immintrin.h but other architectures can't. */ +#ifdef __APPLE__ +# if defined(__has_include) && (defined(__i386__) || defined(__x86_64)) +# if __has_include() +# define HAVE_IMMINTRIN_H 1 +# endif +# endif +#else /* non-Apple platforms can use the normal CMake check for this. */ +#define HAVE_IMMINTRIN_H 1 +#endif + +/* #undef HAVE_LIBUDEV_H */ +/* #undef HAVE_LIBSAMPLERATE_H */ +/* #undef HAVE_LIBDECOR_H */ + +#define HAVE_D3D_H 1 +#define HAVE_D3D11_H 1 +#define HAVE_D3D12_H 1 +#define HAVE_DDRAW_H 1 +#define HAVE_DSOUND_H 1 +/* #undef HAVE_DINPUT_H */ +#define HAVE_XINPUT_H 1 +#define HAVE_WINDOWS_GAMING_INPUT_H 1 +#define HAVE_DXGI_H 1 + +#define HAVE_MMDEVICEAPI_H 1 +#define HAVE_AUDIOCLIENT_H 1 +#define HAVE_TPCSHRD_H 1 +#define HAVE_SENSORSAPI_H 1 +#define HAVE_ROAPI_H 1 +#define HAVE_SHELLSCALINGAPI_H 1 + +/* #undef USE_POSIX_SPAWN */ + +/* SDL internal assertion support */ +#if 0 +/* #undef SDL_DEFAULT_ASSERT_LEVEL */ +#endif + +/* Allow disabling of core subsystems */ +/* #undef SDL_ATOMIC_DISABLED */ +/* #undef SDL_AUDIO_DISABLED */ +/* #undef SDL_CPUINFO_DISABLED */ +/* #undef SDL_EVENTS_DISABLED */ +/* #undef SDL_FILE_DISABLED */ +/* #undef SDL_JOYSTICK_DISABLED */ +/* #undef SDL_HAPTIC_DISABLED */ +/* #undef SDL_HIDAPI_DISABLED */ +/* #undef SDL_SENSOR_DISABLED */ +/* #undef SDL_LOADSO_DISABLED */ +/* #undef SDL_RENDER_DISABLED */ +/* #undef SDL_THREADS_DISABLED */ +/* #undef SDL_TIMERS_DISABLED */ +/* #undef SDL_VIDEO_DISABLED */ +/* #undef SDL_POWER_DISABLED */ +/* #undef SDL_FILESYSTEM_DISABLED */ +/* #undef SDL_LOCALE_DISABLED */ +/* #undef SDL_MISC_DISABLED */ + +/* Enable various audio drivers */ +/* #undef SDL_AUDIO_DRIVER_ALSA */ +/* #undef SDL_AUDIO_DRIVER_ALSA_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_ANDROID */ +/* #undef SDL_AUDIO_DRIVER_OPENSLES */ +/* #undef SDL_AUDIO_DRIVER_AAUDIO */ +/* #undef SDL_AUDIO_DRIVER_ARTS */ +/* #undef SDL_AUDIO_DRIVER_ARTS_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_COREAUDIO */ +#define SDL_AUDIO_DRIVER_DISK 1 +/* #undef SDL_AUDIO_DRIVER_DSOUND */ +#define SDL_AUDIO_DRIVER_DUMMY 1 +/* #undef SDL_AUDIO_DRIVER_EMSCRIPTEN */ +/* #undef SDL_AUDIO_DRIVER_ESD */ +/* #undef SDL_AUDIO_DRIVER_ESD_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_FUSIONSOUND */ +/* #undef SDL_AUDIO_DRIVER_FUSIONSOUND_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_HAIKU */ +/* #undef SDL_AUDIO_DRIVER_JACK */ +/* #undef SDL_AUDIO_DRIVER_JACK_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_NAS */ +/* #undef SDL_AUDIO_DRIVER_NAS_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_NETBSD */ +/* #undef SDL_AUDIO_DRIVER_OSS */ +/* #undef SDL_AUDIO_DRIVER_PAUDIO */ +/* #undef SDL_AUDIO_DRIVER_PIPEWIRE */ +/* #undef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_PULSEAUDIO */ +/* #undef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_QSA */ +/* #undef SDL_AUDIO_DRIVER_SNDIO */ +/* #undef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_SUNAUDIO */ +#define SDL_AUDIO_DRIVER_WASAPI 1 +/* #undef SDL_AUDIO_DRIVER_WINMM */ +/* #undef SDL_AUDIO_DRIVER_OS2 */ +/* #undef SDL_AUDIO_DRIVER_VITA */ +/* #undef SDL_AUDIO_DRIVER_PSP */ +/* #undef SDL_AUDIO_DRIVER_PS2 */ +/* #undef SDL_AUDIO_DRIVER_N3DS */ + +/* Enable various input drivers */ +/* #undef SDL_INPUT_LINUXEV */ +/* #undef SDL_INPUT_LINUXKD */ +/* #undef SDL_INPUT_FBSDKBIO */ +/* #undef SDL_INPUT_WSCONS */ +/* #undef SDL_JOYSTICK_ANDROID */ +/* #undef SDL_JOYSTICK_HAIKU */ +#define SDL_JOYSTICK_WGI 1 +/* #undef SDL_JOYSTICK_DINPUT */ +/* #undef SDL_JOYSTICK_XINPUT */ +/* #undef SDL_JOYSTICK_DUMMY */ +/* #undef SDL_JOYSTICK_IOKIT */ +/* #undef SDL_JOYSTICK_MFI */ +/* #undef SDL_JOYSTICK_LINUX */ +/* #undef SDL_JOYSTICK_OS2 */ +/* #undef SDL_JOYSTICK_USBHID */ +/* #undef SDL_HAVE_MACHINE_JOYSTICK_H */ +#define SDL_JOYSTICK_HIDAPI 1 +/* #undef SDL_JOYSTICK_RAWINPUT */ +/* #undef SDL_JOYSTICK_EMSCRIPTEN */ +#define SDL_JOYSTICK_VIRTUAL 1 +/* #undef SDL_JOYSTICK_VITA */ +/* #undef SDL_JOYSTICK_PSP */ +/* #undef SDL_JOYSTICK_PS2 */ +/* #undef SDL_JOYSTICK_N3DS */ +#define SDL_HAPTIC_DUMMY 1 +/* #undef SDL_HAPTIC_LINUX */ +/* #undef SDL_HAPTIC_IOKIT */ +/* #undef SDL_HAPTIC_DINPUT */ +/* #undef SDL_HAPTIC_XINPUT */ +/* #undef SDL_HAPTIC_ANDROID */ +/* #undef SDL_LIBUSB_DYNAMIC */ +/* #undef SDL_UDEV_DYNAMIC */ + +/* Enable various sensor drivers */ +/* #undef SDL_SENSOR_ANDROID */ +/* #undef SDL_SENSOR_COREMOTION */ +/* #undef SDL_SENSOR_WINDOWS */ +#define SDL_SENSOR_DUMMY 1 +/* #undef SDL_SENSOR_VITA */ +/* #undef SDL_SENSOR_N3DS */ + +/* Enable various shared object loading systems */ +/* #undef SDL_LOADSO_DLOPEN */ +/* #undef SDL_LOADSO_DUMMY */ +/* #undef SDL_LOADSO_LDG */ +#define SDL_LOADSO_WINDOWS 1 +/* #undef SDL_LOADSO_OS2 */ + +/* Enable various threading systems */ +#define SDL_THREAD_GENERIC_COND_SUFFIX 1 +/* #undef SDL_THREAD_PTHREAD */ +/* #undef SDL_THREAD_PTHREAD_RECURSIVE_MUTEX */ +/* #undef SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP */ +#define SDL_THREAD_WINDOWS 1 +/* #undef SDL_THREAD_OS2 */ +/* #undef SDL_THREAD_VITA */ +/* #undef SDL_THREAD_PSP */ +/* #undef SDL_THREAD_PS2 */ +/* #undef SDL_THREAD_N3DS */ + +/* Enable various timer systems */ +/* #undef SDL_TIMER_HAIKU */ +/* #undef SDL_TIMER_DUMMY */ +/* #undef SDL_TIMER_UNIX */ +#define SDL_TIMER_WINDOWS 1 +/* #undef SDL_TIMER_OS2 */ +/* #undef SDL_TIMER_VITA */ +/* #undef SDL_TIMER_PSP */ +/* #undef SDL_TIMER_PS2 */ +/* #undef SDL_TIMER_N3DS */ + +/* Enable various video drivers */ +/* #undef SDL_VIDEO_DRIVER_ANDROID */ +/* #undef SDL_VIDEO_DRIVER_EMSCRIPTEN */ +/* #undef SDL_VIDEO_DRIVER_HAIKU */ +/* #undef SDL_VIDEO_DRIVER_COCOA */ +/* #undef SDL_VIDEO_DRIVER_UIKIT */ +/* #undef SDL_VIDEO_DRIVER_DIRECTFB */ +/* #undef SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_OFFSCREEN 1 +/* #undef SDL_VIDEO_DRIVER_WINDOWS */ +#define SDL_VIDEO_DRIVER_WINRT 1 +/* #undef SDL_VIDEO_DRIVER_WAYLAND */ +/* #undef SDL_VIDEO_DRIVER_RPI */ +/* #undef SDL_VIDEO_DRIVER_VIVANTE */ +/* #undef SDL_VIDEO_DRIVER_VIVANTE_VDK */ +/* #undef SDL_VIDEO_DRIVER_OS2 */ +/* #undef SDL_VIDEO_DRIVER_QNX */ +/* #undef SDL_VIDEO_DRIVER_RISCOS */ +/* #undef SDL_VIDEO_DRIVER_PSP */ +/* #undef SDL_VIDEO_DRIVER_PS2 */ + +/* #undef SDL_VIDEO_DRIVER_KMSDRM */ +/* #undef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC */ +/* #undef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM */ + +/* #undef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON */ +/* #undef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR */ + +/* #undef SDL_VIDEO_DRIVER_X11 */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR */ +/* #undef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS */ +/* #undef SDL_VIDEO_DRIVER_X11_XCURSOR */ +/* #undef SDL_VIDEO_DRIVER_X11_XDBE */ +/* #undef SDL_VIDEO_DRIVER_X11_XINPUT2 */ +/* #undef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */ +/* #undef SDL_VIDEO_DRIVER_X11_XFIXES */ +/* #undef SDL_VIDEO_DRIVER_X11_XRANDR */ +/* #undef SDL_VIDEO_DRIVER_X11_XSCRNSAVER */ +/* #undef SDL_VIDEO_DRIVER_X11_XSHAPE */ +/* #undef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS */ +/* #undef SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM */ +/* #undef SDL_VIDEO_DRIVER_VITA */ +/* #undef SDL_VIDEO_DRIVER_N3DS */ + +/* #undef SDL_VIDEO_RENDER_D3D */ +#define SDL_VIDEO_RENDER_D3D11 1 +/* #undef SDL_VIDEO_RENDER_D3D12 */ +/* #undef SDL_VIDEO_RENDER_OGL */ +/* #undef SDL_VIDEO_RENDER_OGL_ES */ +#define SDL_VIDEO_RENDER_OGL_ES2 1 +/* #undef SDL_VIDEO_RENDER_DIRECTFB */ +/* #undef SDL_VIDEO_RENDER_METAL */ +/* #undef SDL_VIDEO_RENDER_VITA_GXM */ +/* #undef SDL_VIDEO_RENDER_PS2 */ +/* #undef SDL_VIDEO_RENDER_PSP */ + +/* Enable OpenGL support */ +/* #undef SDL_VIDEO_OPENGL */ +/* #undef SDL_VIDEO_OPENGL_ES */ +#define SDL_VIDEO_OPENGL_ES2 1 +/* #undef SDL_VIDEO_OPENGL_BGL */ +/* #undef SDL_VIDEO_OPENGL_CGL */ +/* #undef SDL_VIDEO_OPENGL_GLX */ +/* #undef SDL_VIDEO_OPENGL_WGL */ +#define SDL_VIDEO_OPENGL_EGL 1 +/* #undef SDL_VIDEO_OPENGL_OSMESA */ +/* #undef SDL_VIDEO_OPENGL_OSMESA_DYNAMIC */ + +/* Enable Vulkan support */ +/* #undef SDL_VIDEO_VULKAN */ + +/* Enable Metal support */ +/* #undef SDL_VIDEO_METAL */ + +/* Enable system power support */ +/* #undef SDL_POWER_ANDROID */ +/* #undef SDL_POWER_LINUX */ +/* #undef SDL_POWER_WINDOWS */ +#define SDL_POWER_WINRT 1 +/* #undef SDL_POWER_MACOSX */ +/* #undef SDL_POWER_UIKIT */ +/* #undef SDL_POWER_HAIKU */ +/* #undef SDL_POWER_EMSCRIPTEN */ +/* #undef SDL_POWER_HARDWIRED */ +/* #undef SDL_POWER_VITA */ +/* #undef SDL_POWER_PSP */ +/* #undef SDL_POWER_N3DS */ + +/* Enable system filesystem support */ +/* #undef SDL_FILESYSTEM_ANDROID */ +/* #undef SDL_FILESYSTEM_HAIKU */ +/* #undef SDL_FILESYSTEM_COCOA */ +/* #undef SDL_FILESYSTEM_DUMMY */ +/* #undef SDL_FILESYSTEM_RISCOS */ +/* #undef SDL_FILESYSTEM_UNIX */ +#define SDL_FILESYSTEM_WINDOWS 1 +/* #undef SDL_FILESYSTEM_EMSCRIPTEN */ +/* #undef SDL_FILESYSTEM_OS2 */ +/* #undef SDL_FILESYSTEM_VITA */ +/* #undef SDL_FILESYSTEM_PSP */ +/* #undef SDL_FILESYSTEM_PS2 */ +/* #undef SDL_FILESYSTEM_N3DS */ + +/* Enable misc subsystem */ +/* #undef SDL_MISC_DUMMY */ + +/* Enable locale subsystem */ +/* #undef SDL_LOCALE_DUMMY */ + +/* Enable assembly routines */ +/* #undef SDL_ALTIVEC_BLITTERS */ +/* #undef SDL_ARM_SIMD_BLITTERS */ +/* #undef SDL_ARM_NEON_BLITTERS */ + +/* Whether SDL_DYNAMIC_API needs dlopen */ +/* #undef DYNAPI_NEEDS_DLOPEN */ + +/* Enable dynamic libsamplerate support */ +/* #undef SDL_LIBSAMPLERATE_DYNAMIC */ + +/* Enable ime support */ +/* #undef SDL_USE_IME */ + +/* Platform specific definitions */ +/* #undef SDL_IPHONE_KEYBOARD */ +/* #undef SDL_IPHONE_LAUNCHSCREEN */ + +/* #undef SDL_VIDEO_VITA_PIB */ +/* #undef SDL_VIDEO_VITA_PVR */ +/* #undef SDL_VIDEO_VITA_PVR_OGL */ + +/* #undef SDL_HAVE_LIBDECOR_GET_MIN_MAX */ + +#if !defined(HAVE_STDINT_H) && !defined(_STDINT_H_) +/* Most everything except Visual Studio 2008 and earlier has stdint.h now */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#define _UINTPTR_T_DEFINED +#endif +#endif /* Visual Studio 2008 */ +#endif /* !_STDINT_H_ && !HAVE_STDINT_H */ + +#endif /* SDL_config_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_copying.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_copying.h new file mode 100644 index 00000000..bde74318 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_copying.h @@ -0,0 +1,20 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_cpuinfo.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_cpuinfo.h new file mode 100644 index 00000000..696a03bf --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_cpuinfo.h @@ -0,0 +1,603 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: CPUInfo */ + +/** + * # CategoryCPUInfo + * + * CPU feature detection for SDL. + * + * These functions are largely concerned with reporting if the system has + * access to various SIMD instruction sets, but also has other important info + * to share, such as number of logical CPU cores. + */ + +#ifndef SDL_cpuinfo_h_ +#define SDL_cpuinfo_h_ + +#include "SDL_stdinc.h" + +/* Need to do this here because intrin.h has C++ code in it */ +/* Visual Studio 2005 has a bug where intrin.h conflicts with winnt.h */ +#if defined(_MSC_VER) && (_MSC_VER >= 1500) && (defined(_M_IX86) || defined(_M_X64)) +#ifdef __clang__ +/* As of Clang 11, '_m_prefetchw' is conflicting with the winnt.h's version, + so we define the needed '_m_prefetch' here as a pseudo-header, until the issue is fixed. */ + +#ifndef __PRFCHWINTRIN_H +#define __PRFCHWINTRIN_H + +static __inline__ void __attribute__((__always_inline__, __nodebug__)) +_m_prefetch(void *__P) +{ + __builtin_prefetch (__P, 0, 3 /* _MM_HINT_T0 */); +} + +#endif /* __PRFCHWINTRIN_H */ +#endif /* __clang__ */ +#include +#ifndef _WIN64 +#ifndef __MMX__ +#define __MMX__ +#endif +/* +#ifndef __3dNOW__ +#define __3dNOW__ +#endif +*/ +#endif +#ifndef __SSE__ +#define __SSE__ +#endif +#ifndef __SSE2__ +#define __SSE2__ +#endif +#ifndef __SSE3__ +#define __SSE3__ +#endif +#elif defined(__MINGW64_VERSION_MAJOR) +#include +#if !defined(SDL_DISABLE_ARM_NEON_H) && defined(__ARM_NEON) +# include +#endif +#else +/* altivec.h redefining bool causes a number of problems, see bugs 3993 and 4392, so you need to explicitly define SDL_ENABLE_ALTIVEC_H to have it included. */ +#if defined(HAVE_ALTIVEC_H) && defined(__ALTIVEC__) && !defined(__APPLE_ALTIVEC__) && defined(SDL_ENABLE_ALTIVEC_H) +#include +#endif +#if !defined(SDL_DISABLE_ARM_NEON_H) +# if defined(__ARM_NEON) +# include +# elif defined(__WINDOWS__) || defined(__WINRT__) || defined(__GDK__) +/* Visual Studio doesn't define __ARM_ARCH, but _M_ARM (if set, always 7), and _M_ARM64 (if set, always 1). */ +# if defined(_M_ARM) +# include +# include +# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */ +# endif +# if defined (_M_ARM64) +# include +# include +# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */ +# define __ARM_ARCH 8 +# endif +# endif +#endif +#endif /* compiler version */ + +#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H) +#include +#endif +#if defined(__loongarch_sx) && !defined(SDL_DISABLE_LSX_H) +#include +#define __LSX__ +#endif +#if defined(__loongarch_asx) && !defined(SDL_DISABLE_LASX_H) +#include +#define __LASX__ +#endif +#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H) && \ + (defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)) +#include +#else +#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H) +#include +#endif +#if defined(__SSE__) && !defined(SDL_DISABLE_XMMINTRIN_H) +#include +#endif +#if defined(__SSE2__) && !defined(SDL_DISABLE_EMMINTRIN_H) +#include +#endif +#if defined(__SSE3__) && !defined(SDL_DISABLE_PMMINTRIN_H) +#include +#endif +#endif /* HAVE_IMMINTRIN_H */ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* This is a guess for the cacheline size used for padding. + * Most x86 processors have a 64 byte cache line. + * The 64-bit PowerPC processors have a 128 byte cache line. + * We'll use the larger value to be generally safe. + */ +#define SDL_CACHELINE_SIZE 128 + +/** + * Get the number of CPU cores available. + * + * \returns the total number of logical CPU cores. On CPUs that include + * technologies such as hyperthreading, the number of logical cores + * may be more than the number of physical cores. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_GetCPUCount(void); + +/** + * Determine the L1 cache line size of the CPU. + * + * This is useful for determining multi-threaded structure padding or SIMD + * prefetch sizes. + * + * \returns the L1 cache line size of the CPU, in bytes. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_GetCPUCacheLineSize(void); + +/** + * Determine whether the CPU has the RDTSC instruction. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has the RDTSC instruction or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasRDTSC(void); + +/** + * Determine whether the CPU has AltiVec features. + * + * This always returns false on CPUs that aren't using PowerPC instruction + * sets. + * + * \returns SDL_TRUE if the CPU has AltiVec features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void); + +/** + * Determine whether the CPU has MMX features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has MMX features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void); + +/** + * Determine whether the CPU has 3DNow! features. + * + * This always returns false on CPUs that aren't using AMD instruction sets. + * + * \returns SDL_TRUE if the CPU has 3DNow! features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNow(void); + +/** + * Determine whether the CPU has SSE features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has SSE features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void); + +/** + * Determine whether the CPU has SSE2 features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has SSE2 features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void); + +/** + * Determine whether the CPU has SSE3 features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has SSE3 features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE3(void); + +/** + * Determine whether the CPU has SSE4.1 features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has SSE4.1 features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE41(void); + +/** + * Determine whether the CPU has SSE4.2 features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has SSE4.2 features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE42(void); + +/** + * Determine whether the CPU has AVX features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has AVX features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX2 + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void); + +/** + * Determine whether the CPU has AVX2 features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has AVX2 features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_Has3DNow + * \sa SDL_HasAltiVec + * \sa SDL_HasAVX + * \sa SDL_HasMMX + * \sa SDL_HasRDTSC + * \sa SDL_HasSSE + * \sa SDL_HasSSE2 + * \sa SDL_HasSSE3 + * \sa SDL_HasSSE41 + * \sa SDL_HasSSE42 + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void); + +/** + * Determine whether the CPU has AVX-512F (foundation) features. + * + * This always returns false on CPUs that aren't using Intel instruction sets. + * + * \returns SDL_TRUE if the CPU has AVX-512F features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.9. + * + * \sa SDL_HasAVX + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX512F(void); + +/** + * Determine whether the CPU has ARM SIMD (ARMv6) features. + * + * This is different from ARM NEON, which is a different instruction set. + * + * This always returns false on CPUs that aren't using ARM instruction sets. + * + * \returns SDL_TRUE if the CPU has ARM SIMD features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_HasNEON + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasARMSIMD(void); + +/** + * Determine whether the CPU has NEON (ARM SIMD) features. + * + * This always returns false on CPUs that aren't using ARM instruction sets. + * + * \returns SDL_TRUE if the CPU has ARM NEON features or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void); + +/** + * Determine whether the CPU has LSX (LOONGARCH SIMD) features. + * + * This always returns false on CPUs that aren't using LOONGARCH instruction + * sets. + * + * \returns SDL_TRUE if the CPU has LOONGARCH LSX features or SDL_FALSE if + * not. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasLSX(void); + +/** + * Determine whether the CPU has LASX (LOONGARCH SIMD) features. + * + * This always returns false on CPUs that aren't using LOONGARCH instruction + * sets. + * + * \returns SDL_TRUE if the CPU has LOONGARCH LASX features or SDL_FALSE if + * not. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasLASX(void); + +/** + * Get the amount of RAM configured in the system. + * + * \returns the amount of RAM configured in the system in MiB. + * + * \since This function is available since SDL 2.0.1. + */ +extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void); + +/** + * Report the alignment this system needs for SIMD allocations. + * + * This will return the minimum number of bytes to which a pointer must be + * aligned to be compatible with SIMD instructions on the current machine. For + * example, if the machine supports SSE only, it will return 16, but if it + * supports AVX-512F, it'll return 64 (etc). This only reports values for + * instruction sets SDL knows about, so if your SDL build doesn't have + * SDL_HasAVX512F(), then it might return 16 for the SSE support it sees and + * not 64 for the AVX-512 instructions that exist but SDL doesn't know about. + * Plan accordingly. + * + * \returns the alignment in bytes needed for available, known SIMD + * instructions. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC size_t SDLCALL SDL_SIMDGetAlignment(void); + +/** + * Allocate memory in a SIMD-friendly way. + * + * This will allocate a block of memory that is suitable for use with SIMD + * instructions. Specifically, it will be properly aligned and padded for the + * system's supported vector instructions. + * + * The memory returned will be padded such that it is safe to read or write an + * incomplete vector at the end of the memory block. This can be useful so you + * don't have to drop back to a scalar fallback at the end of your SIMD + * processing loop to deal with the final elements without overflowing the + * allocated buffer. + * + * You must free this memory with SDL_FreeSIMD(), not free() or SDL_free() or + * delete[], etc. + * + * Note that SDL will only deal with SIMD instruction sets it is aware of; for + * example, SDL 2.0.8 knows that SSE wants 16-byte vectors (SDL_HasSSE()), and + * AVX2 wants 32 bytes (SDL_HasAVX2()), but doesn't know that AVX-512 wants + * 64. To be clear: if you can't decide to use an instruction set with an + * SDL_Has*() function, don't use that instruction set with memory allocated + * through here. + * + * SDL_AllocSIMD(0) will return a non-NULL pointer, assuming the system isn't + * out of memory, but you are not allowed to dereference it (because you only + * own zero bytes of that buffer). + * + * \param len The length, in bytes, of the block to allocate. The actual + * allocated block might be larger due to padding, etc. + * \returns a pointer to the newly-allocated block, NULL if out of memory. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_SIMDGetAlignment + * \sa SDL_SIMDRealloc + * \sa SDL_SIMDFree + */ +extern DECLSPEC void * SDLCALL SDL_SIMDAlloc(const size_t len); + +/** + * Reallocate memory obtained from SDL_SIMDAlloc + * + * It is not valid to use this function on a pointer from anything but + * SDL_SIMDAlloc(). It can't be used on pointers from malloc, realloc, + * SDL_malloc, memalign, new[], etc. + * + * \param mem The pointer obtained from SDL_SIMDAlloc. This function also + * accepts NULL, at which point this function is the same as + * calling SDL_SIMDAlloc with a NULL pointer. + * \param len The length, in bytes, of the block to allocated. The actual + * allocated block might be larger due to padding, etc. Passing 0 + * will return a non-NULL pointer, assuming the system isn't out of + * memory. + * \returns a pointer to the newly-reallocated block, NULL if out of memory. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_SIMDGetAlignment + * \sa SDL_SIMDAlloc + * \sa SDL_SIMDFree + */ +extern DECLSPEC void * SDLCALL SDL_SIMDRealloc(void *mem, const size_t len); + +/** + * Deallocate memory obtained from SDL_SIMDAlloc + * + * It is not valid to use this function on a pointer from anything but + * SDL_SIMDAlloc() or SDL_SIMDRealloc(). It can't be used on pointers from + * malloc, realloc, SDL_malloc, memalign, new[], etc. + * + * However, SDL_SIMDFree(NULL) is a legal no-op. + * + * The memory pointed to by `ptr` is no longer valid for access upon return, + * and may be returned to the system or reused by a future allocation. The + * pointer passed to this function is no longer safe to dereference once this + * function returns, and should be discarded. + * + * \param ptr The pointer, returned from SDL_SIMDAlloc or SDL_SIMDRealloc, to + * deallocate. NULL is a legal no-op. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_SIMDAlloc + * \sa SDL_SIMDRealloc + */ +extern DECLSPEC void SDLCALL SDL_SIMDFree(void *ptr); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_cpuinfo_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_egl.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_egl.h new file mode 100644 index 00000000..31290ec2 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_egl.h @@ -0,0 +1,2351 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * This is a simple file to encapsulate the EGL API headers. + */ + +#if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(SDL_USE_BUILTIN_OPENGL_DEFINITIONS) + +#if defined(__vita__) || defined(__psp2__) +#include +#endif + +#include +#include + +#else /* _MSC_VER */ + +/* EGL headers for Visual Studio */ + +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Khronos platform-specific types and definitions. + * + * The master copy of khrplatform.h is maintained in the Khronos EGL + * Registry repository at https://github.com/KhronosGroup/EGL-Registry + * The last semantic modification to khrplatform.h was at commit ID: + * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 + * + * Adopters may modify this file to suit their platform. Adopters are + * encouraged to submit platform specific modifications to the Khronos + * group so that they can be included in future versions of this file. + * Please submit changes by filing pull requests or issues on + * the EGL Registry repository linked above. + * + * + * See the Implementer's Guidelines for information about where this file + * should be located on your system and for more details of its use: + * http://www.khronos.org/registry/implementers_guide.pdf + * + * This file should be included as + * #include + * by Khronos client API header files that use its types and defines. + * + * The types in khrplatform.h should only be used to define API-specific types. + * + * Types defined in khrplatform.h: + * khronos_int8_t signed 8 bit + * khronos_uint8_t unsigned 8 bit + * khronos_int16_t signed 16 bit + * khronos_uint16_t unsigned 16 bit + * khronos_int32_t signed 32 bit + * khronos_uint32_t unsigned 32 bit + * khronos_int64_t signed 64 bit + * khronos_uint64_t unsigned 64 bit + * khronos_intptr_t signed same number of bits as a pointer + * khronos_uintptr_t unsigned same number of bits as a pointer + * khronos_ssize_t signed size + * khronos_usize_t unsigned size + * khronos_float_t signed 32 bit floating point + * khronos_time_ns_t unsigned 64 bit time in nanoseconds + * khronos_utime_nanoseconds_t unsigned time interval or absolute time in + * nanoseconds + * khronos_stime_nanoseconds_t signed time interval in nanoseconds + * khronos_boolean_enum_t enumerated boolean type. This should + * only be used as a base type when a client API's boolean type is + * an enum. Client APIs which use an integer or other type for + * booleans cannot use this as the base type for their boolean. + * + * Tokens defined in khrplatform.h: + * + * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. + * + * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. + * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. + * + * Calling convention macros defined in this file: + * KHRONOS_APICALL + * KHRONOS_APIENTRY + * KHRONOS_APIATTRIBUTES + * + * These may be used in function prototypes as: + * + * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( + * int arg1, + * int arg2) KHRONOS_APIATTRIBUTES; + */ + +#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) +# define KHRONOS_STATIC 1 +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APICALL + *------------------------------------------------------------------------- + * This precedes the return type of the function in the function prototype. + */ +#if defined(KHRONOS_STATIC) + /* If the preprocessor constant KHRONOS_STATIC is defined, make the + * header compatible with static linking. */ +# define KHRONOS_APICALL +#elif defined(_WIN32) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIENTRY + *------------------------------------------------------------------------- + * This follows the return type of the function and precedes the function + * name in the function prototype. + */ +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) + /* Win32 but not WinCE */ +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIATTRIBUTES + *------------------------------------------------------------------------- + * This follows the closing parenthesis of the function prototype arguments. + */ +#if defined (__ARMCC_2__) +#define KHRONOS_APIATTRIBUTES __softfp +#else +#define KHRONOS_APIATTRIBUTES +#endif + +/*------------------------------------------------------------------------- + * basic type definitions + *-----------------------------------------------------------------------*/ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) + + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 +/* + * To support platform where unsigned long cannot be used interchangeably with + * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. + * Ideally, we could just use (u)intptr_t everywhere, but this could result in + * ABI breakage if khronos_uintptr_t is changed from unsigned long to + * unsigned long long or similar (this results in different C++ name mangling). + * To avoid changes for existing platforms, we restrict usage of intptr_t to + * platforms where the size of a pointer is larger than the size of long. + */ +#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) +#if __SIZEOF_POINTER__ > __SIZEOF_LONG__ +#define KHRONOS_USE_INTPTR_T +#endif +#endif + +#elif defined(__VMS ) || defined(__sgi) + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) + +/* + * Win32 + */ +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) + +/* + * Sun or Digital + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif /* __arch64__ */ +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 + +/* + * Hypothetical platform with no float or int64 support + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#define KHRONOS_SUPPORT_INT64 0 +#define KHRONOS_SUPPORT_FLOAT 0 + +#else + +/* + * Generic fallback + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#endif + + +/* + * Types that are (so far) the same on all platforms + */ +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +/* + * Types that differ between LLP64 and LP64 architectures - in LLP64, + * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears + * to be the only LLP64 architecture in current use. + */ +#ifdef KHRONOS_USE_INTPTR_T +typedef intptr_t khronos_intptr_t; +typedef uintptr_t khronos_uintptr_t; +#elif defined(_WIN64) +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +#endif + +#if defined(_WIN64) +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + +#if KHRONOS_SUPPORT_FLOAT +/* + * Float type + */ +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +/* Time types + * + * These types can be used to represent a time interval in nanoseconds or + * an absolute Unadjusted System Time. Unadjusted System Time is the number + * of nanoseconds since some arbitrary system event (e.g. since the last + * time the system booted). The Unadjusted System Time is an unsigned + * 64 bit value that wraps back to 0 every 584 years. Time intervals + * may be either signed or unsigned. + */ +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +/* + * Dummy value used to pad enum types to 32 bits. + */ +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +/* + * Enumerated boolean type + * + * Values other than zero should be considered to be true. Therefore + * comparisons should not be made against KHRONOS_TRUE. + */ +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +#endif /* __khrplatform_h_ */ + + +#ifndef __eglplatform_h_ +#define __eglplatform_h_ + +/* +** Copyright 2007-2020 The Khronos Group Inc. +** SPDX-License-Identifier: Apache-2.0 +*/ + +/* Platform-specific types and definitions for egl.h + * + * Adopters may modify khrplatform.h and this file to suit their platform. + * You are encouraged to submit all modifications to the Khronos group so that + * they can be included in future versions of this file. Please submit changes + * by filing an issue or pull request on the public Khronos EGL Registry, at + * https://www.github.com/KhronosGroup/EGL-Registry/ + */ + +/*#include */ + +/* Macros used in EGL function prototype declarations. + * + * EGL functions should be prototyped as: + * + * EGLAPI return-type EGLAPIENTRY eglFunction(arguments); + * typedef return-type (EXPAPIENTRYP PFNEGLFUNCTIONPROC) (arguments); + * + * KHRONOS_APICALL and KHRONOS_APIENTRY are defined in KHR/khrplatform.h + */ + +#ifndef EGLAPI +#define EGLAPI KHRONOS_APICALL +#endif + +#ifndef EGLAPIENTRY +#define EGLAPIENTRY KHRONOS_APIENTRY +#endif +#define EGLAPIENTRYP EGLAPIENTRY* + +/* The types NativeDisplayType, NativeWindowType, and NativePixmapType + * are aliases of window-system-dependent types, such as X Display * or + * Windows Device Context. They must be defined in platform-specific + * code below. The EGL-prefixed versions of Native*Type are the same + * types, renamed in EGL 1.3 so all types in the API start with "EGL". + * + * Khronos STRONGLY RECOMMENDS that you use the default definitions + * provided below, since these changes affect both binary and source + * portability of applications using EGL running on different EGL + * implementations. + */ + +#if defined(EGL_NO_PLATFORM_SPECIFIC_TYPES) + +typedef void *EGLNativeDisplayType; +typedef void *EGLNativePixmapType; +typedef void *EGLNativeWindowType; + +#elif defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +typedef HDC EGLNativeDisplayType; +typedef HBITMAP EGLNativePixmapType; +typedef HWND EGLNativeWindowType; + +#elif defined(__EMSCRIPTEN__) + +typedef int EGLNativeDisplayType; +typedef int EGLNativePixmapType; +typedef int EGLNativeWindowType; + +#elif defined(__WINSCW__) || defined(__SYMBIAN32__) /* Symbian */ + +typedef int EGLNativeDisplayType; +typedef void *EGLNativePixmapType; +typedef void *EGLNativeWindowType; + +#elif defined(WL_EGL_PLATFORM) + +typedef struct wl_display *EGLNativeDisplayType; +typedef struct wl_egl_pixmap *EGLNativePixmapType; +typedef struct wl_egl_window *EGLNativeWindowType; + +#elif defined(__GBM__) + +typedef struct gbm_device *EGLNativeDisplayType; +typedef struct gbm_bo *EGLNativePixmapType; +typedef void *EGLNativeWindowType; + +#elif defined(__ANDROID__) || defined(ANDROID) + +struct ANativeWindow; +struct egl_native_pixmap_t; + +typedef void* EGLNativeDisplayType; +typedef struct egl_native_pixmap_t* EGLNativePixmapType; +typedef struct ANativeWindow* EGLNativeWindowType; + +#elif defined(USE_OZONE) + +typedef intptr_t EGLNativeDisplayType; +typedef intptr_t EGLNativePixmapType; +typedef intptr_t EGLNativeWindowType; + +#elif defined(USE_X11) + +/* X11 (tentative) */ +#include +#include + +typedef Display *EGLNativeDisplayType; +typedef Pixmap EGLNativePixmapType; +typedef Window EGLNativeWindowType; + +#elif defined(__unix__) + +typedef void *EGLNativeDisplayType; +typedef khronos_uintptr_t EGLNativePixmapType; +typedef khronos_uintptr_t EGLNativeWindowType; + +#elif defined(__APPLE__) + +typedef int EGLNativeDisplayType; +typedef void *EGLNativePixmapType; +typedef void *EGLNativeWindowType; + +#elif defined(__HAIKU__) + +#include + +typedef void *EGLNativeDisplayType; +typedef khronos_uintptr_t EGLNativePixmapType; +typedef khronos_uintptr_t EGLNativeWindowType; + +#elif defined(__Fuchsia__) + +typedef void *EGLNativeDisplayType; +typedef khronos_uintptr_t EGLNativePixmapType; +typedef khronos_uintptr_t EGLNativeWindowType; + +#else +#error "Platform not recognized" +#endif + +/* EGL 1.2 types, renamed for consistency in EGL 1.3 */ +typedef EGLNativeDisplayType NativeDisplayType; +typedef EGLNativePixmapType NativePixmapType; +typedef EGLNativeWindowType NativeWindowType; + + +/* Define EGLint. This must be a signed integral type large enough to contain + * all legal attribute names and values passed into and out of EGL, whether + * their type is boolean, bitmask, enumerant (symbolic constant), integer, + * handle, or other. While in general a 32-bit integer will suffice, if + * handles are 64 bit types, then EGLint should be defined as a signed 64-bit + * integer type. + */ +typedef khronos_int32_t EGLint; + + +/* C++ / C typecast macros for special EGL handle values */ +#if defined(__cplusplus) +#define EGL_CAST(type, value) (static_cast(value)) +#else +#define EGL_CAST(type, value) ((type) (value)) +#endif + +#endif /* __eglplatform_h */ + + +#ifndef __egl_h_ +#define __egl_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: Apache-2.0 +** +** This header is generated from the Khronos EGL XML API Registry. +** The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** http://www.khronos.org/registry/egl +** +** Khronos $Git commit SHA1: 6fb1daea15 $ on $Git commit date: 2022-05-25 09:41:13 -0600 $ +*/ + +/*#include */ + +#ifndef EGL_EGL_PROTOTYPES +#define EGL_EGL_PROTOTYPES 1 +#endif + +/* Generated on date 20220525 */ + +/* Generated C header for: + * API: egl + * Versions considered: .* + * Versions emitted: .* + * Default extensions included: None + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef EGL_VERSION_1_0 +#define EGL_VERSION_1_0 1 +typedef unsigned int EGLBoolean; +typedef void *EGLDisplay; +/*#include */ +/*#include */ +typedef void *EGLConfig; +typedef void *EGLSurface; +typedef void *EGLContext; +typedef void (*__eglMustCastToProperFunctionPointerType)(void); +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_BAD_ACCESS 0x3002 +#define EGL_BAD_ALLOC 0x3003 +#define EGL_BAD_ATTRIBUTE 0x3004 +#define EGL_BAD_CONFIG 0x3005 +#define EGL_BAD_CONTEXT 0x3006 +#define EGL_BAD_CURRENT_SURFACE 0x3007 +#define EGL_BAD_DISPLAY 0x3008 +#define EGL_BAD_MATCH 0x3009 +#define EGL_BAD_NATIVE_PIXMAP 0x300A +#define EGL_BAD_NATIVE_WINDOW 0x300B +#define EGL_BAD_PARAMETER 0x300C +#define EGL_BAD_SURFACE 0x300D +#define EGL_BLUE_SIZE 0x3022 +#define EGL_BUFFER_SIZE 0x3020 +#define EGL_CONFIG_CAVEAT 0x3027 +#define EGL_CONFIG_ID 0x3028 +#define EGL_CORE_NATIVE_ENGINE 0x305B +#define EGL_DEPTH_SIZE 0x3025 +#define EGL_DONT_CARE EGL_CAST(EGLint,-1) +#define EGL_DRAW 0x3059 +#define EGL_EXTENSIONS 0x3055 +#define EGL_FALSE 0 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_HEIGHT 0x3056 +#define EGL_LARGEST_PBUFFER 0x3058 +#define EGL_LEVEL 0x3029 +#define EGL_MAX_PBUFFER_HEIGHT 0x302A +#define EGL_MAX_PBUFFER_PIXELS 0x302B +#define EGL_MAX_PBUFFER_WIDTH 0x302C +#define EGL_NATIVE_RENDERABLE 0x302D +#define EGL_NATIVE_VISUAL_ID 0x302E +#define EGL_NATIVE_VISUAL_TYPE 0x302F +#define EGL_NONE 0x3038 +#define EGL_NON_CONFORMANT_CONFIG 0x3051 +#define EGL_NOT_INITIALIZED 0x3001 +#define EGL_NO_CONTEXT EGL_CAST(EGLContext,0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay,0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface,0) +#define EGL_PBUFFER_BIT 0x0001 +#define EGL_PIXMAP_BIT 0x0002 +#define EGL_READ 0x305A +#define EGL_RED_SIZE 0x3024 +#define EGL_SAMPLES 0x3031 +#define EGL_SAMPLE_BUFFERS 0x3032 +#define EGL_SLOW_CONFIG 0x3050 +#define EGL_STENCIL_SIZE 0x3026 +#define EGL_SUCCESS 0x3000 +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_TRANSPARENT_BLUE_VALUE 0x3035 +#define EGL_TRANSPARENT_GREEN_VALUE 0x3036 +#define EGL_TRANSPARENT_RED_VALUE 0x3037 +#define EGL_TRANSPARENT_RGB 0x3052 +#define EGL_TRANSPARENT_TYPE 0x3034 +#define EGL_TRUE 1 +#define EGL_VENDOR 0x3053 +#define EGL_VERSION 0x3054 +#define EGL_WIDTH 0x3057 +#define EGL_WINDOW_BIT 0x0004 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCHOOSECONFIGPROC) (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOPYBUFFERSPROC) (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target); +typedef EGLContext (EGLAPIENTRYP PFNEGLCREATECONTEXTPROC) (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPBUFFERSURFACEPROC) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPIXMAPSURFACEPROC) (EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEWINDOWSURFACEPROC) (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYCONTEXTPROC) (EGLDisplay dpy, EGLContext ctx); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSURFACEPROC) (EGLDisplay dpy, EGLSurface surface); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCONFIGATTRIBPROC) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCONFIGSPROC) (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETCURRENTDISPLAYPROC) (void); +typedef EGLSurface (EGLAPIENTRYP PFNEGLGETCURRENTSURFACEPROC) (EGLint readdraw); +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETDISPLAYPROC) (EGLNativeDisplayType display_id); +typedef EGLint (EGLAPIENTRYP PFNEGLGETERRORPROC) (void); +typedef __eglMustCastToProperFunctionPointerType (EGLAPIENTRYP PFNEGLGETPROCADDRESSPROC) (const char *procname); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLINITIALIZEPROC) (EGLDisplay dpy, EGLint *major, EGLint *minor); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLMAKECURRENTPROC) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYCONTEXTPROC) (EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYSTRINGPROC) (EGLDisplay dpy, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSPROC) (EGLDisplay dpy, EGLSurface surface); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLTERMINATEPROC) (EGLDisplay dpy); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITGLPROC) (void); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITNATIVEPROC) (EGLint engine); +#if EGL_EGL_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLAPI EGLBoolean EGLAPIENTRY eglCopyBuffers (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target); +EGLAPI EGLContext EGLAPIENTRY eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferSurface (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurface (EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext (EGLDisplay dpy, EGLContext ctx); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface (EGLDisplay dpy, EGLSurface surface); +EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); +EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay (void); +EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface (EGLint readdraw); +EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay (EGLNativeDisplayType display_id); +EGLAPI EGLint EGLAPIENTRY eglGetError (void); +EGLAPI __eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress (const char *procname); +EGLAPI EGLBoolean EGLAPIENTRY eglInitialize (EGLDisplay dpy, EGLint *major, EGLint *minor); +EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext (EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); +EGLAPI const char *EGLAPIENTRY eglQueryString (EGLDisplay dpy, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers (EGLDisplay dpy, EGLSurface surface); +EGLAPI EGLBoolean EGLAPIENTRY eglTerminate (EGLDisplay dpy); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL (void); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative (EGLint engine); +#endif +#endif /* EGL_VERSION_1_0 */ + +#ifndef EGL_VERSION_1_1 +#define EGL_VERSION_1_1 1 +#define EGL_BACK_BUFFER 0x3084 +#define EGL_BIND_TO_TEXTURE_RGB 0x3039 +#define EGL_BIND_TO_TEXTURE_RGBA 0x303A +#define EGL_CONTEXT_LOST 0x300E +#define EGL_MIN_SWAP_INTERVAL 0x303B +#define EGL_MAX_SWAP_INTERVAL 0x303C +#define EGL_MIPMAP_TEXTURE 0x3082 +#define EGL_MIPMAP_LEVEL 0x3083 +#define EGL_NO_TEXTURE 0x305C +#define EGL_TEXTURE_2D 0x305F +#define EGL_TEXTURE_FORMAT 0x3080 +#define EGL_TEXTURE_RGB 0x305D +#define EGL_TEXTURE_RGBA 0x305E +#define EGL_TEXTURE_TARGET 0x3081 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDTEXIMAGEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint buffer); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASETEXIMAGEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint buffer); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSURFACEATTRIBPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPINTERVALPROC) (EGLDisplay dpy, EGLint interval); +#if EGL_EGL_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglBindTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer); +EGLAPI EGLBoolean EGLAPIENTRY eglReleaseTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer); +EGLAPI EGLBoolean EGLAPIENTRY eglSurfaceAttrib (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); +EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval (EGLDisplay dpy, EGLint interval); +#endif +#endif /* EGL_VERSION_1_1 */ + +#ifndef EGL_VERSION_1_2 +#define EGL_VERSION_1_2 1 +typedef unsigned int EGLenum; +typedef void *EGLClientBuffer; +#define EGL_ALPHA_FORMAT 0x3088 +#define EGL_ALPHA_FORMAT_NONPRE 0x308B +#define EGL_ALPHA_FORMAT_PRE 0x308C +#define EGL_ALPHA_MASK_SIZE 0x303E +#define EGL_BUFFER_PRESERVED 0x3094 +#define EGL_BUFFER_DESTROYED 0x3095 +#define EGL_CLIENT_APIS 0x308D +#define EGL_COLORSPACE 0x3087 +#define EGL_COLORSPACE_sRGB 0x3089 +#define EGL_COLORSPACE_LINEAR 0x308A +#define EGL_COLOR_BUFFER_TYPE 0x303F +#define EGL_CONTEXT_CLIENT_TYPE 0x3097 +#define EGL_DISPLAY_SCALING 10000 +#define EGL_HORIZONTAL_RESOLUTION 0x3090 +#define EGL_LUMINANCE_BUFFER 0x308F +#define EGL_LUMINANCE_SIZE 0x303D +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENVG_BIT 0x0002 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_OPENVG_API 0x30A1 +#define EGL_OPENVG_IMAGE 0x3096 +#define EGL_PIXEL_ASPECT_RATIO 0x3092 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_RENDER_BUFFER 0x3086 +#define EGL_RGB_BUFFER 0x308E +#define EGL_SINGLE_BUFFER 0x3085 +#define EGL_SWAP_BEHAVIOR 0x3093 +#define EGL_UNKNOWN EGL_CAST(EGLint,-1) +#define EGL_VERTICAL_RESOLUTION 0x3091 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDAPIPROC) (EGLenum api); +typedef EGLenum (EGLAPIENTRYP PFNEGLQUERYAPIPROC) (void); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPBUFFERFROMCLIENTBUFFERPROC) (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASETHREADPROC) (void); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITCLIENTPROC) (void); +#if EGL_EGL_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI (EGLenum api); +EGLAPI EGLenum EGLAPIENTRY eglQueryAPI (void); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferFromClientBuffer (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglReleaseThread (void); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient (void); +#endif +#endif /* EGL_VERSION_1_2 */ + +#ifndef EGL_VERSION_1_3 +#define EGL_VERSION_1_3 1 +#define EGL_CONFORMANT 0x3042 +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 +#define EGL_MATCH_NATIVE_PIXMAP 0x3041 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_VG_ALPHA_FORMAT 0x3088 +#define EGL_VG_ALPHA_FORMAT_NONPRE 0x308B +#define EGL_VG_ALPHA_FORMAT_PRE 0x308C +#define EGL_VG_ALPHA_FORMAT_PRE_BIT 0x0040 +#define EGL_VG_COLORSPACE 0x3087 +#define EGL_VG_COLORSPACE_sRGB 0x3089 +#define EGL_VG_COLORSPACE_LINEAR 0x308A +#define EGL_VG_COLORSPACE_LINEAR_BIT 0x0020 +#endif /* EGL_VERSION_1_3 */ + +#ifndef EGL_VERSION_1_4 +#define EGL_VERSION_1_4 1 +#define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType,0) +#define EGL_MULTISAMPLE_RESOLVE_BOX_BIT 0x0200 +#define EGL_MULTISAMPLE_RESOLVE 0x3099 +#define EGL_MULTISAMPLE_RESOLVE_DEFAULT 0x309A +#define EGL_MULTISAMPLE_RESOLVE_BOX 0x309B +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_SWAP_BEHAVIOR_PRESERVED_BIT 0x0400 +typedef EGLContext (EGLAPIENTRYP PFNEGLGETCURRENTCONTEXTPROC) (void); +#if EGL_EGL_PROTOTYPES +EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext (void); +#endif +#endif /* EGL_VERSION_1_4 */ + +#ifndef EGL_VERSION_1_5 +#define EGL_VERSION_1_5 1 +typedef void *EGLSync; +typedef intptr_t EGLAttrib; +typedef khronos_utime_nanoseconds_t EGLTime; +typedef void *EGLImage; +#define EGL_CONTEXT_MAJOR_VERSION 0x3098 +#define EGL_CONTEXT_MINOR_VERSION 0x30FB +#define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY 0x31BD +#define EGL_NO_RESET_NOTIFICATION 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET 0x31BF +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define EGL_CONTEXT_OPENGL_DEBUG 0x31B0 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE 0x31B1 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS 0x31B2 +#define EGL_OPENGL_ES3_BIT 0x00000040 +#define EGL_CL_EVENT_HANDLE 0x309C +#define EGL_SYNC_CL_EVENT 0x30FE +#define EGL_SYNC_CL_EVENT_COMPLETE 0x30FF +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE 0x30F0 +#define EGL_SYNC_TYPE 0x30F7 +#define EGL_SYNC_STATUS 0x30F1 +#define EGL_SYNC_CONDITION 0x30F8 +#define EGL_SIGNALED 0x30F2 +#define EGL_UNSIGNALED 0x30F3 +#define EGL_SYNC_FLUSH_COMMANDS_BIT 0x0001 +#define EGL_FOREVER 0xFFFFFFFFFFFFFFFFull +#define EGL_TIMEOUT_EXPIRED 0x30F5 +#define EGL_CONDITION_SATISFIED 0x30F6 +#define EGL_NO_SYNC EGL_CAST(EGLSync,0) +#define EGL_SYNC_FENCE 0x30F9 +#define EGL_GL_COLORSPACE 0x309D +#define EGL_GL_COLORSPACE_SRGB 0x3089 +#define EGL_GL_COLORSPACE_LINEAR 0x308A +#define EGL_GL_RENDERBUFFER 0x30B9 +#define EGL_GL_TEXTURE_2D 0x30B1 +#define EGL_GL_TEXTURE_LEVEL 0x30BC +#define EGL_GL_TEXTURE_3D 0x30B2 +#define EGL_GL_TEXTURE_ZOFFSET 0x30BD +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x30B3 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x30B4 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x30B5 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x30B6 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x30B7 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x30B8 +#define EGL_IMAGE_PRESERVED 0x30D2 +#define EGL_NO_IMAGE EGL_CAST(EGLImage,0) +typedef EGLSync (EGLAPIENTRYP PFNEGLCREATESYNCPROC) (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCPROC) (EGLDisplay dpy, EGLSync sync); +typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBPROC) (EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); +typedef EGLImage (EGLAPIENTRYP PFNEGLCREATEIMAGEPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEPROC) (EGLDisplay dpy, EGLImage image); +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC) (EGLenum platform, void *native_display, const EGLAttrib *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags); +#if EGL_EGL_PROTOTYPES +EGLAPI EGLSync EGLAPIENTRY eglCreateSync (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySync (EGLDisplay dpy, EGLSync sync); +EGLAPI EGLint EGLAPIENTRY eglClientWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); +EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttrib (EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); +EGLAPI EGLImage EGLAPIENTRY eglCreateImage (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImage (EGLDisplay dpy, EGLImage image); +EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplay (EGLenum platform, void *native_display, const EGLAttrib *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurface (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags); +#endif +#endif /* EGL_VERSION_1_5 */ + +#ifdef __cplusplus +} +#endif + +#endif /* __egl_h_ */ + + +#ifndef __eglext_h_ +#define __eglext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: Apache-2.0 +** +** This header is generated from the Khronos EGL XML API Registry. +** The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** http://www.khronos.org/registry/egl +** +** Khronos $Git commit SHA1: 6fb1daea15 $ on $Git commit date: 2022-05-25 09:41:13 -0600 $ +*/ + +/*#include */ + +#define EGL_EGLEXT_VERSION 20220525 + +/* Generated C header for: + * API: egl + * Versions considered: .* + * Versions emitted: _nomatch_^ + * Default extensions included: egl + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef EGL_KHR_cl_event +#define EGL_KHR_cl_event 1 +#define EGL_CL_EVENT_HANDLE_KHR 0x309C +#define EGL_SYNC_CL_EVENT_KHR 0x30FE +#define EGL_SYNC_CL_EVENT_COMPLETE_KHR 0x30FF +#endif /* EGL_KHR_cl_event */ + +#ifndef EGL_KHR_cl_event2 +#define EGL_KHR_cl_event2 1 +typedef void *EGLSyncKHR; +typedef intptr_t EGLAttribKHR; +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNC64KHRPROC) (EGLDisplay dpy, EGLenum type, const EGLAttribKHR *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateSync64KHR (EGLDisplay dpy, EGLenum type, const EGLAttribKHR *attrib_list); +#endif +#endif /* EGL_KHR_cl_event2 */ + +#ifndef EGL_KHR_client_get_all_proc_addresses +#define EGL_KHR_client_get_all_proc_addresses 1 +#endif /* EGL_KHR_client_get_all_proc_addresses */ + +#ifndef EGL_KHR_config_attribs +#define EGL_KHR_config_attribs 1 +#define EGL_CONFORMANT_KHR 0x3042 +#define EGL_VG_COLORSPACE_LINEAR_BIT_KHR 0x0020 +#define EGL_VG_ALPHA_FORMAT_PRE_BIT_KHR 0x0040 +#endif /* EGL_KHR_config_attribs */ + +#ifndef EGL_KHR_context_flush_control +#define EGL_KHR_context_flush_control 1 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 +#endif /* EGL_KHR_context_flush_control */ + +#ifndef EGL_KHR_create_context +#define EGL_KHR_create_context 1 +#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 +#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB +#define EGL_CONTEXT_FLAGS_KHR 0x30FC +#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD +#define EGL_NO_RESET_NOTIFICATION_KHR 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31BF +#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 +#define EGL_OPENGL_ES3_BIT_KHR 0x00000040 +#endif /* EGL_KHR_create_context */ + +#ifndef EGL_KHR_create_context_no_error +#define EGL_KHR_create_context_no_error 1 +#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 +#endif /* EGL_KHR_create_context_no_error */ + +#ifndef EGL_KHR_debug +#define EGL_KHR_debug 1 +typedef void *EGLLabelKHR; +typedef void *EGLObjectKHR; +typedef void (EGLAPIENTRY *EGLDEBUGPROCKHR)(EGLenum error,const char *command,EGLint messageType,EGLLabelKHR threadLabel,EGLLabelKHR objectLabel,const char* message); +#define EGL_OBJECT_THREAD_KHR 0x33B0 +#define EGL_OBJECT_DISPLAY_KHR 0x33B1 +#define EGL_OBJECT_CONTEXT_KHR 0x33B2 +#define EGL_OBJECT_SURFACE_KHR 0x33B3 +#define EGL_OBJECT_IMAGE_KHR 0x33B4 +#define EGL_OBJECT_SYNC_KHR 0x33B5 +#define EGL_OBJECT_STREAM_KHR 0x33B6 +#define EGL_DEBUG_MSG_CRITICAL_KHR 0x33B9 +#define EGL_DEBUG_MSG_ERROR_KHR 0x33BA +#define EGL_DEBUG_MSG_WARN_KHR 0x33BB +#define EGL_DEBUG_MSG_INFO_KHR 0x33BC +#define EGL_DEBUG_CALLBACK_KHR 0x33B8 +typedef EGLint (EGLAPIENTRYP PFNEGLDEBUGMESSAGECONTROLKHRPROC) (EGLDEBUGPROCKHR callback, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEBUGKHRPROC) (EGLint attribute, EGLAttrib *value); +typedef EGLint (EGLAPIENTRYP PFNEGLLABELOBJECTKHRPROC) (EGLDisplay display, EGLenum objectType, EGLObjectKHR object, EGLLabelKHR label); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLint EGLAPIENTRY eglDebugMessageControlKHR (EGLDEBUGPROCKHR callback, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDebugKHR (EGLint attribute, EGLAttrib *value); +EGLAPI EGLint EGLAPIENTRY eglLabelObjectKHR (EGLDisplay display, EGLenum objectType, EGLObjectKHR object, EGLLabelKHR label); +#endif +#endif /* EGL_KHR_debug */ + +#ifndef EGL_KHR_display_reference +#define EGL_KHR_display_reference 1 +#define EGL_TRACK_REFERENCES_KHR 0x3352 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBKHRPROC) (EGLDisplay dpy, EGLint name, EGLAttrib *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribKHR (EGLDisplay dpy, EGLint name, EGLAttrib *value); +#endif +#endif /* EGL_KHR_display_reference */ + +#ifndef EGL_KHR_fence_sync +#define EGL_KHR_fence_sync 1 +typedef khronos_utime_nanoseconds_t EGLTimeKHR; +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR 0x30F0 +#define EGL_SYNC_CONDITION_KHR 0x30F8 +#define EGL_SYNC_FENCE_KHR 0x30F9 +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync); +typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateSyncKHR (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySyncKHR (EGLDisplay dpy, EGLSyncKHR sync); +EGLAPI EGLint EGLAPIENTRY eglClientWaitSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout); +EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttribKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_fence_sync */ + +#ifndef EGL_KHR_get_all_proc_addresses +#define EGL_KHR_get_all_proc_addresses 1 +#endif /* EGL_KHR_get_all_proc_addresses */ + +#ifndef EGL_KHR_gl_colorspace +#define EGL_KHR_gl_colorspace 1 +#define EGL_GL_COLORSPACE_KHR 0x309D +#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 +#define EGL_GL_COLORSPACE_LINEAR_KHR 0x308A +#endif /* EGL_KHR_gl_colorspace */ + +#ifndef EGL_KHR_gl_renderbuffer_image +#define EGL_KHR_gl_renderbuffer_image 1 +#define EGL_GL_RENDERBUFFER_KHR 0x30B9 +#endif /* EGL_KHR_gl_renderbuffer_image */ + +#ifndef EGL_KHR_gl_texture_2D_image +#define EGL_KHR_gl_texture_2D_image 1 +#define EGL_GL_TEXTURE_2D_KHR 0x30B1 +#define EGL_GL_TEXTURE_LEVEL_KHR 0x30BC +#endif /* EGL_KHR_gl_texture_2D_image */ + +#ifndef EGL_KHR_gl_texture_3D_image +#define EGL_KHR_gl_texture_3D_image 1 +#define EGL_GL_TEXTURE_3D_KHR 0x30B2 +#define EGL_GL_TEXTURE_ZOFFSET_KHR 0x30BD +#endif /* EGL_KHR_gl_texture_3D_image */ + +#ifndef EGL_KHR_gl_texture_cubemap_image +#define EGL_KHR_gl_texture_cubemap_image 1 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR 0x30B3 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR 0x30B4 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR 0x30B5 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR 0x30B6 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR 0x30B7 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR 0x30B8 +#endif /* EGL_KHR_gl_texture_cubemap_image */ + +#ifndef EGL_KHR_image +#define EGL_KHR_image 1 +typedef void *EGLImageKHR; +#define EGL_NATIVE_PIXMAP_KHR 0x30B0 +#define EGL_NO_IMAGE_KHR EGL_CAST(EGLImageKHR,0) +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLImageKHR EGLAPIENTRY eglCreateImageKHR (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImageKHR (EGLDisplay dpy, EGLImageKHR image); +#endif +#endif /* EGL_KHR_image */ + +#ifndef EGL_KHR_image_base +#define EGL_KHR_image_base 1 +#define EGL_IMAGE_PRESERVED_KHR 0x30D2 +#endif /* EGL_KHR_image_base */ + +#ifndef EGL_KHR_image_pixmap +#define EGL_KHR_image_pixmap 1 +#endif /* EGL_KHR_image_pixmap */ + +#ifndef EGL_KHR_lock_surface +#define EGL_KHR_lock_surface 1 +#define EGL_READ_SURFACE_BIT_KHR 0x0001 +#define EGL_WRITE_SURFACE_BIT_KHR 0x0002 +#define EGL_LOCK_SURFACE_BIT_KHR 0x0080 +#define EGL_OPTIMAL_FORMAT_BIT_KHR 0x0100 +#define EGL_MATCH_FORMAT_KHR 0x3043 +#define EGL_FORMAT_RGB_565_EXACT_KHR 0x30C0 +#define EGL_FORMAT_RGB_565_KHR 0x30C1 +#define EGL_FORMAT_RGBA_8888_EXACT_KHR 0x30C2 +#define EGL_FORMAT_RGBA_8888_KHR 0x30C3 +#define EGL_MAP_PRESERVE_PIXELS_KHR 0x30C4 +#define EGL_LOCK_USAGE_HINT_KHR 0x30C5 +#define EGL_BITMAP_POINTER_KHR 0x30C6 +#define EGL_BITMAP_PITCH_KHR 0x30C7 +#define EGL_BITMAP_ORIGIN_KHR 0x30C8 +#define EGL_BITMAP_PIXEL_RED_OFFSET_KHR 0x30C9 +#define EGL_BITMAP_PIXEL_GREEN_OFFSET_KHR 0x30CA +#define EGL_BITMAP_PIXEL_BLUE_OFFSET_KHR 0x30CB +#define EGL_BITMAP_PIXEL_ALPHA_OFFSET_KHR 0x30CC +#define EGL_BITMAP_PIXEL_LUMINANCE_OFFSET_KHR 0x30CD +#define EGL_LOWER_LEFT_KHR 0x30CE +#define EGL_UPPER_LEFT_KHR 0x30CF +typedef EGLBoolean (EGLAPIENTRYP PFNEGLLOCKSURFACEKHRPROC) (EGLDisplay dpy, EGLSurface surface, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNLOCKSURFACEKHRPROC) (EGLDisplay dpy, EGLSurface surface); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglLockSurfaceKHR (EGLDisplay dpy, EGLSurface surface, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglUnlockSurfaceKHR (EGLDisplay dpy, EGLSurface surface); +#endif +#endif /* EGL_KHR_lock_surface */ + +#ifndef EGL_KHR_lock_surface2 +#define EGL_KHR_lock_surface2 1 +#define EGL_BITMAP_PIXEL_SIZE_KHR 0x3110 +#endif /* EGL_KHR_lock_surface2 */ + +#ifndef EGL_KHR_lock_surface3 +#define EGL_KHR_lock_surface3 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACE64KHRPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLAttribKHR *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface64KHR (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLAttribKHR *value); +#endif +#endif /* EGL_KHR_lock_surface3 */ + +#ifndef EGL_KHR_mutable_render_buffer +#define EGL_KHR_mutable_render_buffer 1 +#define EGL_MUTABLE_RENDER_BUFFER_BIT_KHR 0x1000 +#endif /* EGL_KHR_mutable_render_buffer */ + +#ifndef EGL_KHR_no_config_context +#define EGL_KHR_no_config_context 1 +#define EGL_NO_CONFIG_KHR EGL_CAST(EGLConfig,0) +#endif /* EGL_KHR_no_config_context */ + +#ifndef EGL_KHR_partial_update +#define EGL_KHR_partial_update 1 +#define EGL_BUFFER_AGE_KHR 0x313D +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETDAMAGEREGIONKHRPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSetDamageRegionKHR (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#endif +#endif /* EGL_KHR_partial_update */ + +#ifndef EGL_KHR_platform_android +#define EGL_KHR_platform_android 1 +#define EGL_PLATFORM_ANDROID_KHR 0x3141 +#endif /* EGL_KHR_platform_android */ + +#ifndef EGL_KHR_platform_gbm +#define EGL_KHR_platform_gbm 1 +#define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif /* EGL_KHR_platform_gbm */ + +#ifndef EGL_KHR_platform_wayland +#define EGL_KHR_platform_wayland 1 +#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 +#endif /* EGL_KHR_platform_wayland */ + +#ifndef EGL_KHR_platform_x11 +#define EGL_KHR_platform_x11 1 +#define EGL_PLATFORM_X11_KHR 0x31D5 +#define EGL_PLATFORM_X11_SCREEN_KHR 0x31D6 +#endif /* EGL_KHR_platform_x11 */ + +#ifndef EGL_KHR_reusable_sync +#define EGL_KHR_reusable_sync 1 +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_SYNC_STATUS_KHR 0x30F1 +#define EGL_SIGNALED_KHR 0x30F2 +#define EGL_UNSIGNALED_KHR 0x30F3 +#define EGL_TIMEOUT_EXPIRED_KHR 0x30F5 +#define EGL_CONDITION_SATISFIED_KHR 0x30F6 +#define EGL_SYNC_TYPE_KHR 0x30F7 +#define EGL_SYNC_REUSABLE_KHR 0x30FA +#define EGL_SYNC_FLUSH_COMMANDS_BIT_KHR 0x0001 +#define EGL_FOREVER_KHR 0xFFFFFFFFFFFFFFFFull +#define EGL_NO_SYNC_KHR EGL_CAST(EGLSyncKHR,0) +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSIGNALSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLenum mode); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSignalSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLenum mode); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_reusable_sync */ + +#ifndef EGL_KHR_stream +#define EGL_KHR_stream 1 +typedef void *EGLStreamKHR; +typedef khronos_uint64_t EGLuint64KHR; +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_NO_STREAM_KHR EGL_CAST(EGLStreamKHR,0) +#define EGL_CONSUMER_LATENCY_USEC_KHR 0x3210 +#define EGL_PRODUCER_FRAME_KHR 0x3212 +#define EGL_CONSUMER_FRAME_KHR 0x3213 +#define EGL_STREAM_STATE_KHR 0x3214 +#define EGL_STREAM_STATE_CREATED_KHR 0x3215 +#define EGL_STREAM_STATE_CONNECTING_KHR 0x3216 +#define EGL_STREAM_STATE_EMPTY_KHR 0x3217 +#define EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR 0x3218 +#define EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR 0x3219 +#define EGL_STREAM_STATE_DISCONNECTED_KHR 0x321A +#define EGL_BAD_STREAM_KHR 0x321B +#define EGL_BAD_STATE_KHR 0x321C +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMKHRPROC) (EGLDisplay dpy, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSTREAMKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMU64KHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLuint64KHR *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamKHR (EGLDisplay dpy, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyStreamKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint *value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamu64KHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLuint64KHR *value); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_stream */ + +#ifndef EGL_KHR_stream_attrib +#define EGL_KHR_stream_attrib 1 +#ifdef KHRONOS_SUPPORT_INT64 +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMATTRIBKHRPROC) (EGLDisplay dpy, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETSTREAMATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERACQUIREATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERRELEASEATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamAttribKHR (EGLDisplay dpy, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglSetStreamAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib *value); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerAcquireAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerReleaseAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_stream_attrib */ + +#ifndef EGL_KHR_stream_consumer_gltexture +#define EGL_KHR_stream_consumer_gltexture 1 +#ifdef EGL_KHR_stream +#define EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR 0x321E +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERACQUIREKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERRELEASEKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerGLTextureExternalKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerAcquireKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerReleaseKHR (EGLDisplay dpy, EGLStreamKHR stream); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_consumer_gltexture */ + +#ifndef EGL_KHR_stream_cross_process_fd +#define EGL_KHR_stream_cross_process_fd 1 +typedef int EGLNativeFileDescriptorKHR; +#ifdef EGL_KHR_stream +#define EGL_NO_FILE_DESCRIPTOR_KHR EGL_CAST(EGLNativeFileDescriptorKHR,-1) +typedef EGLNativeFileDescriptorKHR (EGLAPIENTRYP PFNEGLGETSTREAMFILEDESCRIPTORKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMFROMFILEDESCRIPTORKHRPROC) (EGLDisplay dpy, EGLNativeFileDescriptorKHR file_descriptor); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLNativeFileDescriptorKHR EGLAPIENTRY eglGetStreamFileDescriptorKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamFromFileDescriptorKHR (EGLDisplay dpy, EGLNativeFileDescriptorKHR file_descriptor); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_cross_process_fd */ + +#ifndef EGL_KHR_stream_fifo +#define EGL_KHR_stream_fifo 1 +#ifdef EGL_KHR_stream +#define EGL_STREAM_FIFO_LENGTH_KHR 0x31FC +#define EGL_STREAM_TIME_NOW_KHR 0x31FD +#define EGL_STREAM_TIME_CONSUMER_KHR 0x31FE +#define EGL_STREAM_TIME_PRODUCER_KHR 0x31FF +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMTIMEKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLTimeKHR *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamTimeKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLTimeKHR *value); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_fifo */ + +#ifndef EGL_KHR_stream_producer_aldatalocator +#define EGL_KHR_stream_producer_aldatalocator 1 +#ifdef EGL_KHR_stream +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_producer_aldatalocator */ + +#ifndef EGL_KHR_stream_producer_eglsurface +#define EGL_KHR_stream_producer_eglsurface 1 +#ifdef EGL_KHR_stream +#define EGL_STREAM_BIT_KHR 0x0800 +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATESTREAMPRODUCERSURFACEKHRPROC) (EGLDisplay dpy, EGLConfig config, EGLStreamKHR stream, const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSurface EGLAPIENTRY eglCreateStreamProducerSurfaceKHR (EGLDisplay dpy, EGLConfig config, EGLStreamKHR stream, const EGLint *attrib_list); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_producer_eglsurface */ + +#ifndef EGL_KHR_surfaceless_context +#define EGL_KHR_surfaceless_context 1 +#endif /* EGL_KHR_surfaceless_context */ + +#ifndef EGL_KHR_swap_buffers_with_damage +#define EGL_KHR_swap_buffers_with_damage 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC) (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects); +#endif +#endif /* EGL_KHR_swap_buffers_with_damage */ + +#ifndef EGL_KHR_vg_parent_image +#define EGL_KHR_vg_parent_image 1 +#define EGL_VG_PARENT_IMAGE_KHR 0x30BA +#endif /* EGL_KHR_vg_parent_image */ + +#ifndef EGL_KHR_wait_sync +#define EGL_KHR_wait_sync 1 +typedef EGLint (EGLAPIENTRYP PFNEGLWAITSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLint EGLAPIENTRY eglWaitSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags); +#endif +#endif /* EGL_KHR_wait_sync */ + +#ifndef EGL_ANDROID_GLES_layers +#define EGL_ANDROID_GLES_layers 1 +#endif /* EGL_ANDROID_GLES_layers */ + +#ifndef EGL_ANDROID_blob_cache +#define EGL_ANDROID_blob_cache 1 +typedef khronos_ssize_t EGLsizeiANDROID; +typedef void (*EGLSetBlobFuncANDROID) (const void *key, EGLsizeiANDROID keySize, const void *value, EGLsizeiANDROID valueSize); +typedef EGLsizeiANDROID (*EGLGetBlobFuncANDROID) (const void *key, EGLsizeiANDROID keySize, void *value, EGLsizeiANDROID valueSize); +typedef void (EGLAPIENTRYP PFNEGLSETBLOBCACHEFUNCSANDROIDPROC) (EGLDisplay dpy, EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI void EGLAPIENTRY eglSetBlobCacheFuncsANDROID (EGLDisplay dpy, EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get); +#endif +#endif /* EGL_ANDROID_blob_cache */ + +#ifndef EGL_ANDROID_create_native_client_buffer +#define EGL_ANDROID_create_native_client_buffer 1 +#define EGL_NATIVE_BUFFER_USAGE_ANDROID 0x3143 +#define EGL_NATIVE_BUFFER_USAGE_PROTECTED_BIT_ANDROID 0x00000001 +#define EGL_NATIVE_BUFFER_USAGE_RENDERBUFFER_BIT_ANDROID 0x00000002 +#define EGL_NATIVE_BUFFER_USAGE_TEXTURE_BIT_ANDROID 0x00000004 +typedef EGLClientBuffer (EGLAPIENTRYP PFNEGLCREATENATIVECLIENTBUFFERANDROIDPROC) (const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLClientBuffer EGLAPIENTRY eglCreateNativeClientBufferANDROID (const EGLint *attrib_list); +#endif +#endif /* EGL_ANDROID_create_native_client_buffer */ + +#ifndef EGL_ANDROID_framebuffer_target +#define EGL_ANDROID_framebuffer_target 1 +#define EGL_FRAMEBUFFER_TARGET_ANDROID 0x3147 +#endif /* EGL_ANDROID_framebuffer_target */ + +#ifndef EGL_ANDROID_front_buffer_auto_refresh +#define EGL_ANDROID_front_buffer_auto_refresh 1 +#define EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID 0x314C +#endif /* EGL_ANDROID_front_buffer_auto_refresh */ + +#ifndef EGL_ANDROID_get_frame_timestamps +#define EGL_ANDROID_get_frame_timestamps 1 +typedef khronos_stime_nanoseconds_t EGLnsecsANDROID; +#define EGL_TIMESTAMP_PENDING_ANDROID EGL_CAST(EGLnsecsANDROID,-2) +#define EGL_TIMESTAMP_INVALID_ANDROID EGL_CAST(EGLnsecsANDROID,-1) +#define EGL_TIMESTAMPS_ANDROID 0x3430 +#define EGL_COMPOSITE_DEADLINE_ANDROID 0x3431 +#define EGL_COMPOSITE_INTERVAL_ANDROID 0x3432 +#define EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID 0x3433 +#define EGL_REQUESTED_PRESENT_TIME_ANDROID 0x3434 +#define EGL_RENDERING_COMPLETE_TIME_ANDROID 0x3435 +#define EGL_COMPOSITION_LATCH_TIME_ANDROID 0x3436 +#define EGL_FIRST_COMPOSITION_START_TIME_ANDROID 0x3437 +#define EGL_LAST_COMPOSITION_START_TIME_ANDROID 0x3438 +#define EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID 0x3439 +#define EGL_DISPLAY_PRESENT_TIME_ANDROID 0x343A +#define EGL_DEQUEUE_READY_TIME_ANDROID 0x343B +#define EGL_READS_DONE_TIME_ANDROID 0x343C +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCOMPOSITORTIMINGANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLint numTimestamps, const EGLint *names, EGLnsecsANDROID *values); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETNEXTFRAMEIDANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLuint64KHR *frameId); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLint timestamp); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETFRAMETIMESTAMPSANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLuint64KHR frameId, EGLint numTimestamps, const EGLint *timestamps, EGLnsecsANDROID *values); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglGetCompositorTimingSupportedANDROID (EGLDisplay dpy, EGLSurface surface, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglGetCompositorTimingANDROID (EGLDisplay dpy, EGLSurface surface, EGLint numTimestamps, const EGLint *names, EGLnsecsANDROID *values); +EGLAPI EGLBoolean EGLAPIENTRY eglGetNextFrameIdANDROID (EGLDisplay dpy, EGLSurface surface, EGLuint64KHR *frameId); +EGLAPI EGLBoolean EGLAPIENTRY eglGetFrameTimestampSupportedANDROID (EGLDisplay dpy, EGLSurface surface, EGLint timestamp); +EGLAPI EGLBoolean EGLAPIENTRY eglGetFrameTimestampsANDROID (EGLDisplay dpy, EGLSurface surface, EGLuint64KHR frameId, EGLint numTimestamps, const EGLint *timestamps, EGLnsecsANDROID *values); +#endif +#endif /* EGL_ANDROID_get_frame_timestamps */ + +#ifndef EGL_ANDROID_get_native_client_buffer +#define EGL_ANDROID_get_native_client_buffer 1 +struct AHardwareBuffer; +typedef EGLClientBuffer (EGLAPIENTRYP PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC) (const struct AHardwareBuffer *buffer); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLClientBuffer EGLAPIENTRY eglGetNativeClientBufferANDROID (const struct AHardwareBuffer *buffer); +#endif +#endif /* EGL_ANDROID_get_native_client_buffer */ + +#ifndef EGL_ANDROID_image_native_buffer +#define EGL_ANDROID_image_native_buffer 1 +#define EGL_NATIVE_BUFFER_ANDROID 0x3140 +#endif /* EGL_ANDROID_image_native_buffer */ + +#ifndef EGL_ANDROID_native_fence_sync +#define EGL_ANDROID_native_fence_sync 1 +#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 +#define EGL_SYNC_NATIVE_FENCE_FD_ANDROID 0x3145 +#define EGL_SYNC_NATIVE_FENCE_SIGNALED_ANDROID 0x3146 +#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1 +typedef EGLint (EGLAPIENTRYP PFNEGLDUPNATIVEFENCEFDANDROIDPROC) (EGLDisplay dpy, EGLSyncKHR sync); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLint EGLAPIENTRY eglDupNativeFenceFDANDROID (EGLDisplay dpy, EGLSyncKHR sync); +#endif +#endif /* EGL_ANDROID_native_fence_sync */ + +#ifndef EGL_ANDROID_presentation_time +#define EGL_ANDROID_presentation_time 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLPRESENTATIONTIMEANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLnsecsANDROID time); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglPresentationTimeANDROID (EGLDisplay dpy, EGLSurface surface, EGLnsecsANDROID time); +#endif +#endif /* EGL_ANDROID_presentation_time */ + +#ifndef EGL_ANDROID_recordable +#define EGL_ANDROID_recordable 1 +#define EGL_RECORDABLE_ANDROID 0x3142 +#endif /* EGL_ANDROID_recordable */ + +#ifndef EGL_ANGLE_d3d_share_handle_client_buffer +#define EGL_ANGLE_d3d_share_handle_client_buffer 1 +#define EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE 0x3200 +#endif /* EGL_ANGLE_d3d_share_handle_client_buffer */ + +#ifndef EGL_ANGLE_device_d3d +#define EGL_ANGLE_device_d3d 1 +#define EGL_D3D9_DEVICE_ANGLE 0x33A0 +#define EGL_D3D11_DEVICE_ANGLE 0x33A1 +#endif /* EGL_ANGLE_device_d3d */ + +#ifndef EGL_ANGLE_query_surface_pointer +#define EGL_ANGLE_query_surface_pointer 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACEPOINTERANGLEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, void **value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurfacePointerANGLE (EGLDisplay dpy, EGLSurface surface, EGLint attribute, void **value); +#endif +#endif /* EGL_ANGLE_query_surface_pointer */ + +#ifndef EGL_ANGLE_surface_d3d_texture_2d_share_handle +#define EGL_ANGLE_surface_d3d_texture_2d_share_handle 1 +#endif /* EGL_ANGLE_surface_d3d_texture_2d_share_handle */ + +#ifndef EGL_ANGLE_sync_control_rate +#define EGL_ANGLE_sync_control_rate 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETMSCRATEANGLEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *numerator, EGLint *denominator); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglGetMscRateANGLE (EGLDisplay dpy, EGLSurface surface, EGLint *numerator, EGLint *denominator); +#endif +#endif /* EGL_ANGLE_sync_control_rate */ + +#ifndef EGL_ANGLE_window_fixed_size +#define EGL_ANGLE_window_fixed_size 1 +#define EGL_FIXED_SIZE_ANGLE 0x3201 +#endif /* EGL_ANGLE_window_fixed_size */ + +#ifndef EGL_ARM_image_format +#define EGL_ARM_image_format 1 +#define EGL_COLOR_COMPONENT_TYPE_UNSIGNED_INTEGER_ARM 0x3287 +#define EGL_COLOR_COMPONENT_TYPE_INTEGER_ARM 0x3288 +#endif /* EGL_ARM_image_format */ + +#ifndef EGL_ARM_implicit_external_sync +#define EGL_ARM_implicit_external_sync 1 +#define EGL_SYNC_PRIOR_COMMANDS_IMPLICIT_EXTERNAL_ARM 0x328A +#endif /* EGL_ARM_implicit_external_sync */ + +#ifndef EGL_ARM_pixmap_multisample_discard +#define EGL_ARM_pixmap_multisample_discard 1 +#define EGL_DISCARD_SAMPLES_ARM 0x3286 +#endif /* EGL_ARM_pixmap_multisample_discard */ + +#ifndef EGL_EXT_bind_to_front +#define EGL_EXT_bind_to_front 1 +#define EGL_FRONT_BUFFER_EXT 0x3464 +#endif /* EGL_EXT_bind_to_front */ + +#ifndef EGL_EXT_buffer_age +#define EGL_EXT_buffer_age 1 +#define EGL_BUFFER_AGE_EXT 0x313D +#endif /* EGL_EXT_buffer_age */ + +#ifndef EGL_EXT_client_extensions +#define EGL_EXT_client_extensions 1 +#endif /* EGL_EXT_client_extensions */ + +#ifndef EGL_EXT_client_sync +#define EGL_EXT_client_sync 1 +#define EGL_SYNC_CLIENT_EXT 0x3364 +#define EGL_SYNC_CLIENT_SIGNAL_EXT 0x3365 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCLIENTSIGNALSYNCEXTPROC) (EGLDisplay dpy, EGLSync sync, const EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglClientSignalSyncEXT (EGLDisplay dpy, EGLSync sync, const EGLAttrib *attrib_list); +#endif +#endif /* EGL_EXT_client_sync */ + +#ifndef EGL_EXT_compositor +#define EGL_EXT_compositor 1 +#define EGL_PRIMARY_COMPOSITOR_CONTEXT_EXT 0x3460 +#define EGL_EXTERNAL_REF_ID_EXT 0x3461 +#define EGL_COMPOSITOR_DROP_NEWEST_FRAME_EXT 0x3462 +#define EGL_COMPOSITOR_KEEP_NEWEST_FRAME_EXT 0x3463 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETCONTEXTLISTEXTPROC) (const EGLint *external_ref_ids, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETCONTEXTATTRIBUTESEXTPROC) (EGLint external_ref_id, const EGLint *context_attributes, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETWINDOWLISTEXTPROC) (EGLint external_ref_id, const EGLint *external_win_ids, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETWINDOWATTRIBUTESEXTPROC) (EGLint external_win_id, const EGLint *window_attributes, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORBINDTEXWINDOWEXTPROC) (EGLint external_win_id); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETSIZEEXTPROC) (EGLint external_win_id, EGLint width, EGLint height); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSWAPPOLICYEXTPROC) (EGLint external_win_id, EGLint policy); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetContextListEXT (const EGLint *external_ref_ids, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetContextAttributesEXT (EGLint external_ref_id, const EGLint *context_attributes, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetWindowListEXT (EGLint external_ref_id, const EGLint *external_win_ids, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetWindowAttributesEXT (EGLint external_win_id, const EGLint *window_attributes, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorBindTexWindowEXT (EGLint external_win_id); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetSizeEXT (EGLint external_win_id, EGLint width, EGLint height); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSwapPolicyEXT (EGLint external_win_id, EGLint policy); +#endif +#endif /* EGL_EXT_compositor */ + +#ifndef EGL_EXT_config_select_group +#define EGL_EXT_config_select_group 1 +#define EGL_CONFIG_SELECT_GROUP_EXT 0x34C0 +#endif /* EGL_EXT_config_select_group */ + +#ifndef EGL_EXT_create_context_robustness +#define EGL_EXT_create_context_robustness 1 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT 0x30BF +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT 0x3138 +#define EGL_NO_RESET_NOTIFICATION_EXT 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_EXT 0x31BF +#endif /* EGL_EXT_create_context_robustness */ + +#ifndef EGL_EXT_device_base +#define EGL_EXT_device_base 1 +typedef void *EGLDeviceEXT; +#define EGL_NO_DEVICE_EXT EGL_CAST(EGLDeviceEXT,0) +#define EGL_BAD_DEVICE_EXT 0x322B +#define EGL_DEVICE_EXT 0x322C +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICEATTRIBEXTPROC) (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYDEVICESTRINGEXTPROC) (EGLDeviceEXT device, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICESEXTPROC) (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBEXTPROC) (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDeviceAttribEXT (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryDeviceStringEXT (EGLDeviceEXT device, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDevicesEXT (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +#endif +#endif /* EGL_EXT_device_base */ + +#ifndef EGL_EXT_device_drm +#define EGL_EXT_device_drm 1 +#define EGL_DRM_DEVICE_FILE_EXT 0x3233 +#define EGL_DRM_MASTER_FD_EXT 0x333C +#endif /* EGL_EXT_device_drm */ + +#ifndef EGL_EXT_device_drm_render_node +#define EGL_EXT_device_drm_render_node 1 +#define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377 +#endif /* EGL_EXT_device_drm_render_node */ + +#ifndef EGL_EXT_device_enumeration +#define EGL_EXT_device_enumeration 1 +#endif /* EGL_EXT_device_enumeration */ + +#ifndef EGL_EXT_device_openwf +#define EGL_EXT_device_openwf 1 +#define EGL_OPENWF_DEVICE_ID_EXT 0x3237 +#define EGL_OPENWF_DEVICE_EXT 0x333D +#endif /* EGL_EXT_device_openwf */ + +#ifndef EGL_EXT_device_persistent_id +#define EGL_EXT_device_persistent_id 1 +#define EGL_DEVICE_UUID_EXT 0x335C +#define EGL_DRIVER_UUID_EXT 0x335D +#define EGL_DRIVER_NAME_EXT 0x335E +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICEBINARYEXTPROC) (EGLDeviceEXT device, EGLint name, EGLint max_size, void *value, EGLint *size); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDeviceBinaryEXT (EGLDeviceEXT device, EGLint name, EGLint max_size, void *value, EGLint *size); +#endif +#endif /* EGL_EXT_device_persistent_id */ + +#ifndef EGL_EXT_device_query +#define EGL_EXT_device_query 1 +#endif /* EGL_EXT_device_query */ + +#ifndef EGL_EXT_device_query_name +#define EGL_EXT_device_query_name 1 +#define EGL_RENDERER_EXT 0x335F +#endif /* EGL_EXT_device_query_name */ + +#ifndef EGL_EXT_explicit_device +#define EGL_EXT_explicit_device 1 +#endif /* EGL_EXT_explicit_device */ + +#ifndef EGL_EXT_gl_colorspace_bt2020_linear +#define EGL_EXT_gl_colorspace_bt2020_linear 1 +#define EGL_GL_COLORSPACE_BT2020_LINEAR_EXT 0x333F +#endif /* EGL_EXT_gl_colorspace_bt2020_linear */ + +#ifndef EGL_EXT_gl_colorspace_bt2020_pq +#define EGL_EXT_gl_colorspace_bt2020_pq 1 +#define EGL_GL_COLORSPACE_BT2020_PQ_EXT 0x3340 +#endif /* EGL_EXT_gl_colorspace_bt2020_pq */ + +#ifndef EGL_EXT_gl_colorspace_display_p3 +#define EGL_EXT_gl_colorspace_display_p3 1 +#define EGL_GL_COLORSPACE_DISPLAY_P3_EXT 0x3363 +#endif /* EGL_EXT_gl_colorspace_display_p3 */ + +#ifndef EGL_EXT_gl_colorspace_display_p3_linear +#define EGL_EXT_gl_colorspace_display_p3_linear 1 +#define EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT 0x3362 +#endif /* EGL_EXT_gl_colorspace_display_p3_linear */ + +#ifndef EGL_EXT_gl_colorspace_display_p3_passthrough +#define EGL_EXT_gl_colorspace_display_p3_passthrough 1 +#define EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT 0x3490 +#endif /* EGL_EXT_gl_colorspace_display_p3_passthrough */ + +#ifndef EGL_EXT_gl_colorspace_scrgb +#define EGL_EXT_gl_colorspace_scrgb 1 +#define EGL_GL_COLORSPACE_SCRGB_EXT 0x3351 +#endif /* EGL_EXT_gl_colorspace_scrgb */ + +#ifndef EGL_EXT_gl_colorspace_scrgb_linear +#define EGL_EXT_gl_colorspace_scrgb_linear 1 +#define EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT 0x3350 +#endif /* EGL_EXT_gl_colorspace_scrgb_linear */ + +#ifndef EGL_EXT_image_dma_buf_import +#define EGL_EXT_image_dma_buf_import 1 +#define EGL_LINUX_DMA_BUF_EXT 0x3270 +#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 +#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 +#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 +#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 +#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 +#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 +#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 +#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 +#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 +#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A +#define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B +#define EGL_SAMPLE_RANGE_HINT_EXT 0x327C +#define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D +#define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E +#define EGL_ITU_REC601_EXT 0x327F +#define EGL_ITU_REC709_EXT 0x3280 +#define EGL_ITU_REC2020_EXT 0x3281 +#define EGL_YUV_FULL_RANGE_EXT 0x3282 +#define EGL_YUV_NARROW_RANGE_EXT 0x3283 +#define EGL_YUV_CHROMA_SITING_0_EXT 0x3284 +#define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285 +#endif /* EGL_EXT_image_dma_buf_import */ + +#ifndef EGL_EXT_image_dma_buf_import_modifiers +#define EGL_EXT_image_dma_buf_import_modifiers 1 +#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 +#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 +#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 +#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 +#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 +#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 +#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 +#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 +#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 +#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 +#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDmaBufFormatsEXT (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDmaBufModifiersEXT (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +#endif +#endif /* EGL_EXT_image_dma_buf_import_modifiers */ + +#ifndef EGL_EXT_image_gl_colorspace +#define EGL_EXT_image_gl_colorspace 1 +#define EGL_GL_COLORSPACE_DEFAULT_EXT 0x314D +#endif /* EGL_EXT_image_gl_colorspace */ + +#ifndef EGL_EXT_image_implicit_sync_control +#define EGL_EXT_image_implicit_sync_control 1 +#define EGL_IMPORT_SYNC_TYPE_EXT 0x3470 +#define EGL_IMPORT_IMPLICIT_SYNC_EXT 0x3471 +#define EGL_IMPORT_EXPLICIT_SYNC_EXT 0x3472 +#endif /* EGL_EXT_image_implicit_sync_control */ + +#ifndef EGL_EXT_multiview_window +#define EGL_EXT_multiview_window 1 +#define EGL_MULTIVIEW_VIEW_COUNT_EXT 0x3134 +#endif /* EGL_EXT_multiview_window */ + +#ifndef EGL_EXT_output_base +#define EGL_EXT_output_base 1 +typedef void *EGLOutputLayerEXT; +typedef void *EGLOutputPortEXT; +#define EGL_NO_OUTPUT_LAYER_EXT EGL_CAST(EGLOutputLayerEXT,0) +#define EGL_NO_OUTPUT_PORT_EXT EGL_CAST(EGLOutputPortEXT,0) +#define EGL_BAD_OUTPUT_LAYER_EXT 0x322D +#define EGL_BAD_OUTPUT_PORT_EXT 0x322E +#define EGL_SWAP_INTERVAL_EXT 0x322F +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETOUTPUTLAYERSEXTPROC) (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputLayerEXT *layers, EGLint max_layers, EGLint *num_layers); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETOUTPUTPORTSEXTPROC) (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputPortEXT *ports, EGLint max_ports, EGLint *num_ports); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLOUTPUTLAYERATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYOUTPUTLAYERATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYOUTPUTLAYERSTRINGEXTPROC) (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLOUTPUTPORTATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYOUTPUTPORTATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYOUTPUTPORTSTRINGEXTPROC) (EGLDisplay dpy, EGLOutputPortEXT port, EGLint name); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglGetOutputLayersEXT (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputLayerEXT *layers, EGLint max_layers, EGLint *num_layers); +EGLAPI EGLBoolean EGLAPIENTRY eglGetOutputPortsEXT (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputPortEXT *ports, EGLint max_ports, EGLint *num_ports); +EGLAPI EGLBoolean EGLAPIENTRY eglOutputLayerAttribEXT (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryOutputLayerAttribEXT (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryOutputLayerStringEXT (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglOutputPortAttribEXT (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryOutputPortAttribEXT (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryOutputPortStringEXT (EGLDisplay dpy, EGLOutputPortEXT port, EGLint name); +#endif +#endif /* EGL_EXT_output_base */ + +#ifndef EGL_EXT_output_drm +#define EGL_EXT_output_drm 1 +#define EGL_DRM_CRTC_EXT 0x3234 +#define EGL_DRM_PLANE_EXT 0x3235 +#define EGL_DRM_CONNECTOR_EXT 0x3236 +#endif /* EGL_EXT_output_drm */ + +#ifndef EGL_EXT_output_openwf +#define EGL_EXT_output_openwf 1 +#define EGL_OPENWF_PIPELINE_ID_EXT 0x3238 +#define EGL_OPENWF_PORT_ID_EXT 0x3239 +#endif /* EGL_EXT_output_openwf */ + +#ifndef EGL_EXT_pixel_format_float +#define EGL_EXT_pixel_format_float 1 +#define EGL_COLOR_COMPONENT_TYPE_EXT 0x3339 +#define EGL_COLOR_COMPONENT_TYPE_FIXED_EXT 0x333A +#define EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT 0x333B +#endif /* EGL_EXT_pixel_format_float */ + +#ifndef EGL_EXT_platform_base +#define EGL_EXT_platform_base 1 +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT (EGLenum platform, void *native_display, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurfaceEXT (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurfaceEXT (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +#endif +#endif /* EGL_EXT_platform_base */ + +#ifndef EGL_EXT_platform_device +#define EGL_EXT_platform_device 1 +#define EGL_PLATFORM_DEVICE_EXT 0x313F +#endif /* EGL_EXT_platform_device */ + +#ifndef EGL_EXT_platform_wayland +#define EGL_EXT_platform_wayland 1 +#define EGL_PLATFORM_WAYLAND_EXT 0x31D8 +#endif /* EGL_EXT_platform_wayland */ + +#ifndef EGL_EXT_platform_x11 +#define EGL_EXT_platform_x11 1 +#define EGL_PLATFORM_X11_EXT 0x31D5 +#define EGL_PLATFORM_X11_SCREEN_EXT 0x31D6 +#endif /* EGL_EXT_platform_x11 */ + +#ifndef EGL_EXT_platform_xcb +#define EGL_EXT_platform_xcb 1 +#define EGL_PLATFORM_XCB_EXT 0x31DC +#define EGL_PLATFORM_XCB_SCREEN_EXT 0x31DE +#endif /* EGL_EXT_platform_xcb */ + +#ifndef EGL_EXT_present_opaque +#define EGL_EXT_present_opaque 1 +#define EGL_PRESENT_OPAQUE_EXT 0x31DF +#endif /* EGL_EXT_present_opaque */ + +#ifndef EGL_EXT_protected_content +#define EGL_EXT_protected_content 1 +#define EGL_PROTECTED_CONTENT_EXT 0x32C0 +#endif /* EGL_EXT_protected_content */ + +#ifndef EGL_EXT_protected_surface +#define EGL_EXT_protected_surface 1 +#endif /* EGL_EXT_protected_surface */ + +#ifndef EGL_EXT_stream_consumer_egloutput +#define EGL_EXT_stream_consumer_egloutput 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMEROUTPUTEXTPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLOutputLayerEXT layer); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerOutputEXT (EGLDisplay dpy, EGLStreamKHR stream, EGLOutputLayerEXT layer); +#endif +#endif /* EGL_EXT_stream_consumer_egloutput */ + +#ifndef EGL_EXT_surface_CTA861_3_metadata +#define EGL_EXT_surface_CTA861_3_metadata 1 +#define EGL_CTA861_3_MAX_CONTENT_LIGHT_LEVEL_EXT 0x3360 +#define EGL_CTA861_3_MAX_FRAME_AVERAGE_LEVEL_EXT 0x3361 +#endif /* EGL_EXT_surface_CTA861_3_metadata */ + +#ifndef EGL_EXT_surface_SMPTE2086_metadata +#define EGL_EXT_surface_SMPTE2086_metadata 1 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT 0x3341 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT 0x3342 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT 0x3343 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT 0x3344 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT 0x3345 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT 0x3346 +#define EGL_SMPTE2086_WHITE_POINT_X_EXT 0x3347 +#define EGL_SMPTE2086_WHITE_POINT_Y_EXT 0x3348 +#define EGL_SMPTE2086_MAX_LUMINANCE_EXT 0x3349 +#define EGL_SMPTE2086_MIN_LUMINANCE_EXT 0x334A +#define EGL_METADATA_SCALING_EXT 50000 +#endif /* EGL_EXT_surface_SMPTE2086_metadata */ + +#ifndef EGL_EXT_surface_compression +#define EGL_EXT_surface_compression 1 +#define EGL_SURFACE_COMPRESSION_EXT 0x34B0 +#define EGL_SURFACE_COMPRESSION_PLANE1_EXT 0x328E +#define EGL_SURFACE_COMPRESSION_PLANE2_EXT 0x328F +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_NONE_EXT 0x34B1 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_DEFAULT_EXT 0x34B2 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_1BPC_EXT 0x34B4 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_2BPC_EXT 0x34B5 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_3BPC_EXT 0x34B6 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_4BPC_EXT 0x34B7 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_5BPC_EXT 0x34B8 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_6BPC_EXT 0x34B9 +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_7BPC_EXT 0x34BA +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_8BPC_EXT 0x34BB +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_9BPC_EXT 0x34BC +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_10BPC_EXT 0x34BD +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_11BPC_EXT 0x34BE +#define EGL_SURFACE_COMPRESSION_FIXED_RATE_12BPC_EXT 0x34BF +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSUPPORTEDCOMPRESSIONRATESEXTPROC) (EGLDisplay dpy, EGLConfig config, const EGLAttrib *attrib_list, EGLint *rates, EGLint rate_size, EGLint *num_rates); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySupportedCompressionRatesEXT (EGLDisplay dpy, EGLConfig config, const EGLAttrib *attrib_list, EGLint *rates, EGLint rate_size, EGLint *num_rates); +#endif +#endif /* EGL_EXT_surface_compression */ + +#ifndef EGL_EXT_swap_buffers_with_damage +#define EGL_EXT_swap_buffers_with_damage 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects); +#endif +#endif /* EGL_EXT_swap_buffers_with_damage */ + +#ifndef EGL_EXT_sync_reuse +#define EGL_EXT_sync_reuse 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNSIGNALSYNCEXTPROC) (EGLDisplay dpy, EGLSync sync, const EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglUnsignalSyncEXT (EGLDisplay dpy, EGLSync sync, const EGLAttrib *attrib_list); +#endif +#endif /* EGL_EXT_sync_reuse */ + +#ifndef EGL_EXT_yuv_surface +#define EGL_EXT_yuv_surface 1 +#define EGL_YUV_ORDER_EXT 0x3301 +#define EGL_YUV_NUMBER_OF_PLANES_EXT 0x3311 +#define EGL_YUV_SUBSAMPLE_EXT 0x3312 +#define EGL_YUV_DEPTH_RANGE_EXT 0x3317 +#define EGL_YUV_CSC_STANDARD_EXT 0x330A +#define EGL_YUV_PLANE_BPP_EXT 0x331A +#define EGL_YUV_BUFFER_EXT 0x3300 +#define EGL_YUV_ORDER_YUV_EXT 0x3302 +#define EGL_YUV_ORDER_YVU_EXT 0x3303 +#define EGL_YUV_ORDER_YUYV_EXT 0x3304 +#define EGL_YUV_ORDER_UYVY_EXT 0x3305 +#define EGL_YUV_ORDER_YVYU_EXT 0x3306 +#define EGL_YUV_ORDER_VYUY_EXT 0x3307 +#define EGL_YUV_ORDER_AYUV_EXT 0x3308 +#define EGL_YUV_SUBSAMPLE_4_2_0_EXT 0x3313 +#define EGL_YUV_SUBSAMPLE_4_2_2_EXT 0x3314 +#define EGL_YUV_SUBSAMPLE_4_4_4_EXT 0x3315 +#define EGL_YUV_DEPTH_RANGE_LIMITED_EXT 0x3318 +#define EGL_YUV_DEPTH_RANGE_FULL_EXT 0x3319 +#define EGL_YUV_CSC_STANDARD_601_EXT 0x330B +#define EGL_YUV_CSC_STANDARD_709_EXT 0x330C +#define EGL_YUV_CSC_STANDARD_2020_EXT 0x330D +#define EGL_YUV_PLANE_BPP_0_EXT 0x331B +#define EGL_YUV_PLANE_BPP_8_EXT 0x331C +#define EGL_YUV_PLANE_BPP_10_EXT 0x331D +#endif /* EGL_EXT_yuv_surface */ + +#ifndef EGL_HI_clientpixmap +#define EGL_HI_clientpixmap 1 +struct EGLClientPixmapHI { + void *pData; + EGLint iWidth; + EGLint iHeight; + EGLint iStride; +}; +#define EGL_CLIENT_PIXMAP_POINTER_HI 0x8F74 +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPIXMAPSURFACEHIPROC) (EGLDisplay dpy, EGLConfig config, struct EGLClientPixmapHI *pixmap); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurfaceHI (EGLDisplay dpy, EGLConfig config, struct EGLClientPixmapHI *pixmap); +#endif +#endif /* EGL_HI_clientpixmap */ + +#ifndef EGL_HI_colorformats +#define EGL_HI_colorformats 1 +#define EGL_COLOR_FORMAT_HI 0x8F70 +#define EGL_COLOR_RGB_HI 0x8F71 +#define EGL_COLOR_RGBA_HI 0x8F72 +#define EGL_COLOR_ARGB_HI 0x8F73 +#endif /* EGL_HI_colorformats */ + +#ifndef EGL_IMG_context_priority +#define EGL_IMG_context_priority 1 +#define EGL_CONTEXT_PRIORITY_LEVEL_IMG 0x3100 +#define EGL_CONTEXT_PRIORITY_HIGH_IMG 0x3101 +#define EGL_CONTEXT_PRIORITY_MEDIUM_IMG 0x3102 +#define EGL_CONTEXT_PRIORITY_LOW_IMG 0x3103 +#endif /* EGL_IMG_context_priority */ + +#ifndef EGL_IMG_image_plane_attribs +#define EGL_IMG_image_plane_attribs 1 +#define EGL_NATIVE_BUFFER_MULTIPLANE_SEPARATE_IMG 0x3105 +#define EGL_NATIVE_BUFFER_PLANE_OFFSET_IMG 0x3106 +#endif /* EGL_IMG_image_plane_attribs */ + +#ifndef EGL_MESA_drm_image +#define EGL_MESA_drm_image 1 +#define EGL_DRM_BUFFER_FORMAT_MESA 0x31D0 +#define EGL_DRM_BUFFER_USE_MESA 0x31D1 +#define EGL_DRM_BUFFER_FORMAT_ARGB32_MESA 0x31D2 +#define EGL_DRM_BUFFER_MESA 0x31D3 +#define EGL_DRM_BUFFER_STRIDE_MESA 0x31D4 +#define EGL_DRM_BUFFER_USE_SCANOUT_MESA 0x00000001 +#define EGL_DRM_BUFFER_USE_SHARE_MESA 0x00000002 +#define EGL_DRM_BUFFER_USE_CURSOR_MESA 0x00000004 +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEDRMIMAGEMESAPROC) (EGLDisplay dpy, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDRMIMAGEMESAPROC) (EGLDisplay dpy, EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLImageKHR EGLAPIENTRY eglCreateDRMImageMESA (EGLDisplay dpy, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglExportDRMImageMESA (EGLDisplay dpy, EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride); +#endif +#endif /* EGL_MESA_drm_image */ + +#ifndef EGL_MESA_image_dma_buf_export +#define EGL_MESA_image_dma_buf_export 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC) (EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, EGLuint64KHR *modifiers); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDMABUFIMAGEMESAPROC) (EGLDisplay dpy, EGLImageKHR image, int *fds, EGLint *strides, EGLint *offsets); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglExportDMABUFImageQueryMESA (EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, EGLuint64KHR *modifiers); +EGLAPI EGLBoolean EGLAPIENTRY eglExportDMABUFImageMESA (EGLDisplay dpy, EGLImageKHR image, int *fds, EGLint *strides, EGLint *offsets); +#endif +#endif /* EGL_MESA_image_dma_buf_export */ + +#ifndef EGL_MESA_platform_gbm +#define EGL_MESA_platform_gbm 1 +#define EGL_PLATFORM_GBM_MESA 0x31D7 +#endif /* EGL_MESA_platform_gbm */ + +#ifndef EGL_MESA_platform_surfaceless +#define EGL_MESA_platform_surfaceless 1 +#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD +#endif /* EGL_MESA_platform_surfaceless */ + +#ifndef EGL_MESA_query_driver +#define EGL_MESA_query_driver 1 +typedef char *(EGLAPIENTRYP PFNEGLGETDISPLAYDRIVERCONFIGPROC) (EGLDisplay dpy); +typedef const char *(EGLAPIENTRYP PFNEGLGETDISPLAYDRIVERNAMEPROC) (EGLDisplay dpy); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI char *EGLAPIENTRY eglGetDisplayDriverConfig (EGLDisplay dpy); +EGLAPI const char *EGLAPIENTRY eglGetDisplayDriverName (EGLDisplay dpy); +#endif +#endif /* EGL_MESA_query_driver */ + +#ifndef EGL_NOK_swap_region +#define EGL_NOK_swap_region 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSREGIONNOKPROC) (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersRegionNOK (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#endif +#endif /* EGL_NOK_swap_region */ + +#ifndef EGL_NOK_swap_region2 +#define EGL_NOK_swap_region2 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSREGION2NOKPROC) (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersRegion2NOK (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#endif +#endif /* EGL_NOK_swap_region2 */ + +#ifndef EGL_NOK_texture_from_pixmap +#define EGL_NOK_texture_from_pixmap 1 +#define EGL_Y_INVERTED_NOK 0x307F +#endif /* EGL_NOK_texture_from_pixmap */ + +#ifndef EGL_NV_3dvision_surface +#define EGL_NV_3dvision_surface 1 +#define EGL_AUTO_STEREO_NV 0x3136 +#endif /* EGL_NV_3dvision_surface */ + +#ifndef EGL_NV_context_priority_realtime +#define EGL_NV_context_priority_realtime 1 +#define EGL_CONTEXT_PRIORITY_REALTIME_NV 0x3357 +#endif /* EGL_NV_context_priority_realtime */ + +#ifndef EGL_NV_coverage_sample +#define EGL_NV_coverage_sample 1 +#define EGL_COVERAGE_BUFFERS_NV 0x30E0 +#define EGL_COVERAGE_SAMPLES_NV 0x30E1 +#endif /* EGL_NV_coverage_sample */ + +#ifndef EGL_NV_coverage_sample_resolve +#define EGL_NV_coverage_sample_resolve 1 +#define EGL_COVERAGE_SAMPLE_RESOLVE_NV 0x3131 +#define EGL_COVERAGE_SAMPLE_RESOLVE_DEFAULT_NV 0x3132 +#define EGL_COVERAGE_SAMPLE_RESOLVE_NONE_NV 0x3133 +#endif /* EGL_NV_coverage_sample_resolve */ + +#ifndef EGL_NV_cuda_event +#define EGL_NV_cuda_event 1 +#define EGL_CUDA_EVENT_HANDLE_NV 0x323B +#define EGL_SYNC_CUDA_EVENT_NV 0x323C +#define EGL_SYNC_CUDA_EVENT_COMPLETE_NV 0x323D +#endif /* EGL_NV_cuda_event */ + +#ifndef EGL_NV_depth_nonlinear +#define EGL_NV_depth_nonlinear 1 +#define EGL_DEPTH_ENCODING_NV 0x30E2 +#define EGL_DEPTH_ENCODING_NONE_NV 0 +#define EGL_DEPTH_ENCODING_NONLINEAR_NV 0x30E3 +#endif /* EGL_NV_depth_nonlinear */ + +#ifndef EGL_NV_device_cuda +#define EGL_NV_device_cuda 1 +#define EGL_CUDA_DEVICE_NV 0x323A +#endif /* EGL_NV_device_cuda */ + +#ifndef EGL_NV_native_query +#define EGL_NV_native_query 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYNATIVEDISPLAYNVPROC) (EGLDisplay dpy, EGLNativeDisplayType *display_id); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYNATIVEWINDOWNVPROC) (EGLDisplay dpy, EGLSurface surf, EGLNativeWindowType *window); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYNATIVEPIXMAPNVPROC) (EGLDisplay dpy, EGLSurface surf, EGLNativePixmapType *pixmap); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryNativeDisplayNV (EGLDisplay dpy, EGLNativeDisplayType *display_id); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryNativeWindowNV (EGLDisplay dpy, EGLSurface surf, EGLNativeWindowType *window); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryNativePixmapNV (EGLDisplay dpy, EGLSurface surf, EGLNativePixmapType *pixmap); +#endif +#endif /* EGL_NV_native_query */ + +#ifndef EGL_NV_post_convert_rounding +#define EGL_NV_post_convert_rounding 1 +#endif /* EGL_NV_post_convert_rounding */ + +#ifndef EGL_NV_post_sub_buffer +#define EGL_NV_post_sub_buffer 1 +#define EGL_POST_SUB_BUFFER_SUPPORTED_NV 0x30BE +typedef EGLBoolean (EGLAPIENTRYP PFNEGLPOSTSUBBUFFERNVPROC) (EGLDisplay dpy, EGLSurface surface, EGLint x, EGLint y, EGLint width, EGLint height); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglPostSubBufferNV (EGLDisplay dpy, EGLSurface surface, EGLint x, EGLint y, EGLint width, EGLint height); +#endif +#endif /* EGL_NV_post_sub_buffer */ + +#ifndef EGL_NV_quadruple_buffer +#define EGL_NV_quadruple_buffer 1 +#define EGL_QUADRUPLE_BUFFER_NV 0x3231 +#endif /* EGL_NV_quadruple_buffer */ + +#ifndef EGL_NV_robustness_video_memory_purge +#define EGL_NV_robustness_video_memory_purge 1 +#define EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x334C +#endif /* EGL_NV_robustness_video_memory_purge */ + +#ifndef EGL_NV_stream_consumer_eglimage +#define EGL_NV_stream_consumer_eglimage 1 +#define EGL_STREAM_CONSUMER_IMAGE_NV 0x3373 +#define EGL_STREAM_IMAGE_ADD_NV 0x3374 +#define EGL_STREAM_IMAGE_REMOVE_NV 0x3375 +#define EGL_STREAM_IMAGE_AVAILABLE_NV 0x3376 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMIMAGECONSUMERCONNECTNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLint num_modifiers, const EGLuint64KHR *modifiers, const EGLAttrib *attrib_list); +typedef EGLint (EGLAPIENTRYP PFNEGLQUERYSTREAMCONSUMEREVENTNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLTime timeout, EGLenum *event, EGLAttrib *aux); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMACQUIREIMAGENVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLImage *pImage, EGLSync sync); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMRELEASEIMAGENVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLImage image, EGLSync sync); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamImageConsumerConnectNV (EGLDisplay dpy, EGLStreamKHR stream, EGLint num_modifiers, const EGLuint64KHR *modifiers, const EGLAttrib *attrib_list); +EGLAPI EGLint EGLAPIENTRY eglQueryStreamConsumerEventNV (EGLDisplay dpy, EGLStreamKHR stream, EGLTime timeout, EGLenum *event, EGLAttrib *aux); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamAcquireImageNV (EGLDisplay dpy, EGLStreamKHR stream, EGLImage *pImage, EGLSync sync); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamReleaseImageNV (EGLDisplay dpy, EGLStreamKHR stream, EGLImage image, EGLSync sync); +#endif +#endif /* EGL_NV_stream_consumer_eglimage */ + +#ifndef EGL_NV_stream_consumer_gltexture_yuv +#define EGL_NV_stream_consumer_gltexture_yuv 1 +#define EGL_YUV_PLANE0_TEXTURE_UNIT_NV 0x332C +#define EGL_YUV_PLANE1_TEXTURE_UNIT_NV 0x332D +#define EGL_YUV_PLANE2_TEXTURE_UNIT_NV 0x332E +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALATTRIBSNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerGLTextureExternalAttribsNV (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#endif +#endif /* EGL_NV_stream_consumer_gltexture_yuv */ + +#ifndef EGL_NV_stream_cross_display +#define EGL_NV_stream_cross_display 1 +#define EGL_STREAM_CROSS_DISPLAY_NV 0x334E +#endif /* EGL_NV_stream_cross_display */ + +#ifndef EGL_NV_stream_cross_object +#define EGL_NV_stream_cross_object 1 +#define EGL_STREAM_CROSS_OBJECT_NV 0x334D +#endif /* EGL_NV_stream_cross_object */ + +#ifndef EGL_NV_stream_cross_partition +#define EGL_NV_stream_cross_partition 1 +#define EGL_STREAM_CROSS_PARTITION_NV 0x323F +#endif /* EGL_NV_stream_cross_partition */ + +#ifndef EGL_NV_stream_cross_process +#define EGL_NV_stream_cross_process 1 +#define EGL_STREAM_CROSS_PROCESS_NV 0x3245 +#endif /* EGL_NV_stream_cross_process */ + +#ifndef EGL_NV_stream_cross_system +#define EGL_NV_stream_cross_system 1 +#define EGL_STREAM_CROSS_SYSTEM_NV 0x334F +#endif /* EGL_NV_stream_cross_system */ + +#ifndef EGL_NV_stream_dma +#define EGL_NV_stream_dma 1 +#define EGL_STREAM_DMA_NV 0x3371 +#define EGL_STREAM_DMA_SERVER_NV 0x3372 +#endif /* EGL_NV_stream_dma */ + +#ifndef EGL_NV_stream_fifo_next +#define EGL_NV_stream_fifo_next 1 +#define EGL_PENDING_FRAME_NV 0x3329 +#define EGL_STREAM_TIME_PENDING_NV 0x332A +#endif /* EGL_NV_stream_fifo_next */ + +#ifndef EGL_NV_stream_fifo_synchronous +#define EGL_NV_stream_fifo_synchronous 1 +#define EGL_STREAM_FIFO_SYNCHRONOUS_NV 0x3336 +#endif /* EGL_NV_stream_fifo_synchronous */ + +#ifndef EGL_NV_stream_flush +#define EGL_NV_stream_flush 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMFLUSHNVPROC) (EGLDisplay dpy, EGLStreamKHR stream); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamFlushNV (EGLDisplay dpy, EGLStreamKHR stream); +#endif +#endif /* EGL_NV_stream_flush */ + +#ifndef EGL_NV_stream_frame_limits +#define EGL_NV_stream_frame_limits 1 +#define EGL_PRODUCER_MAX_FRAME_HINT_NV 0x3337 +#define EGL_CONSUMER_MAX_FRAME_HINT_NV 0x3338 +#endif /* EGL_NV_stream_frame_limits */ + +#ifndef EGL_NV_stream_metadata +#define EGL_NV_stream_metadata 1 +#define EGL_MAX_STREAM_METADATA_BLOCKS_NV 0x3250 +#define EGL_MAX_STREAM_METADATA_BLOCK_SIZE_NV 0x3251 +#define EGL_MAX_STREAM_METADATA_TOTAL_SIZE_NV 0x3252 +#define EGL_PRODUCER_METADATA_NV 0x3253 +#define EGL_CONSUMER_METADATA_NV 0x3254 +#define EGL_PENDING_METADATA_NV 0x3328 +#define EGL_METADATA0_SIZE_NV 0x3255 +#define EGL_METADATA1_SIZE_NV 0x3256 +#define EGL_METADATA2_SIZE_NV 0x3257 +#define EGL_METADATA3_SIZE_NV 0x3258 +#define EGL_METADATA0_TYPE_NV 0x3259 +#define EGL_METADATA1_TYPE_NV 0x325A +#define EGL_METADATA2_TYPE_NV 0x325B +#define EGL_METADATA3_TYPE_NV 0x325C +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBNVPROC) (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETSTREAMMETADATANVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLint n, EGLint offset, EGLint size, const void *data); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMMETADATANVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum name, EGLint n, EGLint offset, EGLint size, void *data); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribNV (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +EGLAPI EGLBoolean EGLAPIENTRY eglSetStreamMetadataNV (EGLDisplay dpy, EGLStreamKHR stream, EGLint n, EGLint offset, EGLint size, const void *data); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamMetadataNV (EGLDisplay dpy, EGLStreamKHR stream, EGLenum name, EGLint n, EGLint offset, EGLint size, void *data); +#endif +#endif /* EGL_NV_stream_metadata */ + +#ifndef EGL_NV_stream_origin +#define EGL_NV_stream_origin 1 +#define EGL_STREAM_FRAME_ORIGIN_X_NV 0x3366 +#define EGL_STREAM_FRAME_ORIGIN_Y_NV 0x3367 +#define EGL_STREAM_FRAME_MAJOR_AXIS_NV 0x3368 +#define EGL_CONSUMER_AUTO_ORIENTATION_NV 0x3369 +#define EGL_PRODUCER_AUTO_ORIENTATION_NV 0x336A +#define EGL_LEFT_NV 0x336B +#define EGL_RIGHT_NV 0x336C +#define EGL_TOP_NV 0x336D +#define EGL_BOTTOM_NV 0x336E +#define EGL_X_AXIS_NV 0x336F +#define EGL_Y_AXIS_NV 0x3370 +#endif /* EGL_NV_stream_origin */ + +#ifndef EGL_NV_stream_remote +#define EGL_NV_stream_remote 1 +#define EGL_STREAM_STATE_INITIALIZING_NV 0x3240 +#define EGL_STREAM_TYPE_NV 0x3241 +#define EGL_STREAM_PROTOCOL_NV 0x3242 +#define EGL_STREAM_ENDPOINT_NV 0x3243 +#define EGL_STREAM_LOCAL_NV 0x3244 +#define EGL_STREAM_PRODUCER_NV 0x3247 +#define EGL_STREAM_CONSUMER_NV 0x3248 +#define EGL_STREAM_PROTOCOL_FD_NV 0x3246 +#endif /* EGL_NV_stream_remote */ + +#ifndef EGL_NV_stream_reset +#define EGL_NV_stream_reset 1 +#define EGL_SUPPORT_RESET_NV 0x3334 +#define EGL_SUPPORT_REUSE_NV 0x3335 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLRESETSTREAMNVPROC) (EGLDisplay dpy, EGLStreamKHR stream); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglResetStreamNV (EGLDisplay dpy, EGLStreamKHR stream); +#endif +#endif /* EGL_NV_stream_reset */ + +#ifndef EGL_NV_stream_socket +#define EGL_NV_stream_socket 1 +#define EGL_STREAM_PROTOCOL_SOCKET_NV 0x324B +#define EGL_SOCKET_HANDLE_NV 0x324C +#define EGL_SOCKET_TYPE_NV 0x324D +#endif /* EGL_NV_stream_socket */ + +#ifndef EGL_NV_stream_socket_inet +#define EGL_NV_stream_socket_inet 1 +#define EGL_SOCKET_TYPE_INET_NV 0x324F +#endif /* EGL_NV_stream_socket_inet */ + +#ifndef EGL_NV_stream_socket_unix +#define EGL_NV_stream_socket_unix 1 +#define EGL_SOCKET_TYPE_UNIX_NV 0x324E +#endif /* EGL_NV_stream_socket_unix */ + +#ifndef EGL_NV_stream_sync +#define EGL_NV_stream_sync 1 +#define EGL_SYNC_NEW_FRAME_NV 0x321F +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESTREAMSYNCNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum type, const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateStreamSyncNV (EGLDisplay dpy, EGLStreamKHR stream, EGLenum type, const EGLint *attrib_list); +#endif +#endif /* EGL_NV_stream_sync */ + +#ifndef EGL_NV_sync +#define EGL_NV_sync 1 +typedef void *EGLSyncNV; +typedef khronos_utime_nanoseconds_t EGLTimeNV; +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE_NV 0x30E6 +#define EGL_SYNC_STATUS_NV 0x30E7 +#define EGL_SIGNALED_NV 0x30E8 +#define EGL_UNSIGNALED_NV 0x30E9 +#define EGL_SYNC_FLUSH_COMMANDS_BIT_NV 0x0001 +#define EGL_FOREVER_NV 0xFFFFFFFFFFFFFFFFull +#define EGL_ALREADY_SIGNALED_NV 0x30EA +#define EGL_TIMEOUT_EXPIRED_NV 0x30EB +#define EGL_CONDITION_SATISFIED_NV 0x30EC +#define EGL_SYNC_TYPE_NV 0x30ED +#define EGL_SYNC_CONDITION_NV 0x30EE +#define EGL_SYNC_FENCE_NV 0x30EF +#define EGL_NO_SYNC_NV EGL_CAST(EGLSyncNV,0) +typedef EGLSyncNV (EGLAPIENTRYP PFNEGLCREATEFENCESYNCNVPROC) (EGLDisplay dpy, EGLenum condition, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCNVPROC) (EGLSyncNV sync); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLFENCENVPROC) (EGLSyncNV sync); +typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCNVPROC) (EGLSyncNV sync, EGLint flags, EGLTimeNV timeout); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSIGNALSYNCNVPROC) (EGLSyncNV sync, EGLenum mode); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBNVPROC) (EGLSyncNV sync, EGLint attribute, EGLint *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncNV EGLAPIENTRY eglCreateFenceSyncNV (EGLDisplay dpy, EGLenum condition, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySyncNV (EGLSyncNV sync); +EGLAPI EGLBoolean EGLAPIENTRY eglFenceNV (EGLSyncNV sync); +EGLAPI EGLint EGLAPIENTRY eglClientWaitSyncNV (EGLSyncNV sync, EGLint flags, EGLTimeNV timeout); +EGLAPI EGLBoolean EGLAPIENTRY eglSignalSyncNV (EGLSyncNV sync, EGLenum mode); +EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttribNV (EGLSyncNV sync, EGLint attribute, EGLint *value); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_NV_sync */ + +#ifndef EGL_NV_system_time +#define EGL_NV_system_time 1 +typedef khronos_utime_nanoseconds_t EGLuint64NV; +#ifdef KHRONOS_SUPPORT_INT64 +typedef EGLuint64NV (EGLAPIENTRYP PFNEGLGETSYSTEMTIMEFREQUENCYNVPROC) (void); +typedef EGLuint64NV (EGLAPIENTRYP PFNEGLGETSYSTEMTIMENVPROC) (void); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLuint64NV EGLAPIENTRY eglGetSystemTimeFrequencyNV (void); +EGLAPI EGLuint64NV EGLAPIENTRY eglGetSystemTimeNV (void); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_NV_system_time */ + +#ifndef EGL_NV_triple_buffer +#define EGL_NV_triple_buffer 1 +#define EGL_TRIPLE_BUFFER_NV 0x3230 +#endif /* EGL_NV_triple_buffer */ + +#ifndef EGL_TIZEN_image_native_buffer +#define EGL_TIZEN_image_native_buffer 1 +#define EGL_NATIVE_BUFFER_TIZEN 0x32A0 +#endif /* EGL_TIZEN_image_native_buffer */ + +#ifndef EGL_TIZEN_image_native_surface +#define EGL_TIZEN_image_native_surface 1 +#define EGL_NATIVE_SURFACE_TIZEN 0x32A1 +#endif /* EGL_TIZEN_image_native_surface */ + +#ifndef EGL_WL_bind_wayland_display +#define EGL_WL_bind_wayland_display 1 +#define PFNEGLBINDWAYLANDDISPLAYWL PFNEGLBINDWAYLANDDISPLAYWLPROC +#define PFNEGLUNBINDWAYLANDDISPLAYWL PFNEGLUNBINDWAYLANDDISPLAYWLPROC +#define PFNEGLQUERYWAYLANDBUFFERWL PFNEGLQUERYWAYLANDBUFFERWLPROC +struct wl_display; +struct wl_resource; +#define EGL_WAYLAND_BUFFER_WL 0x31D5 +#define EGL_WAYLAND_PLANE_WL 0x31D6 +#define EGL_TEXTURE_Y_U_V_WL 0x31D7 +#define EGL_TEXTURE_Y_UV_WL 0x31D8 +#define EGL_TEXTURE_Y_XUXV_WL 0x31D9 +#define EGL_TEXTURE_EXTERNAL_WL 0x31DA +#define EGL_WAYLAND_Y_INVERTED_WL 0x31DB +typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWLPROC) (EGLDisplay dpy, struct wl_display *display); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWLPROC) (EGLDisplay dpy, struct wl_display *display); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWLPROC) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglBindWaylandDisplayWL (EGLDisplay dpy, struct wl_display *display); +EGLAPI EGLBoolean EGLAPIENTRY eglUnbindWaylandDisplayWL (EGLDisplay dpy, struct wl_display *display); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryWaylandBufferWL (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); +#endif +#endif /* EGL_WL_bind_wayland_display */ + +#ifndef EGL_WL_create_wayland_buffer_from_image +#define EGL_WL_create_wayland_buffer_from_image 1 +#define PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC +struct wl_buffer; +typedef struct wl_buffer *(EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC) (EGLDisplay dpy, EGLImageKHR image); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI struct wl_buffer *EGLAPIENTRY eglCreateWaylandBufferFromImageWL (EGLDisplay dpy, EGLImageKHR image); +#endif +#endif /* EGL_WL_create_wayland_buffer_from_image */ + +#ifdef __cplusplus +} +#endif + +#endif /* __eglext_h_ */ + +#endif /* _MSC_VER */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_endian.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_endian.h new file mode 100644 index 00000000..5be66eaf --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_endian.h @@ -0,0 +1,396 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryEndian + * + * Functions for reading and writing endian-specific values + */ + +#ifndef SDL_endian_h_ +#define SDL_endian_h_ + +#include "SDL_stdinc.h" + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +/* As of Clang 11, '_m_prefetchw' is conflicting with the winnt.h's version, + so we define the needed '_m_prefetch' here as a pseudo-header, until the issue is fixed. */ +#ifdef __clang__ +#ifndef __PRFCHWINTRIN_H +#define __PRFCHWINTRIN_H +static __inline__ void __attribute__((__always_inline__, __nodebug__)) +_m_prefetch(void *__P) +{ + __builtin_prefetch(__P, 0, 3 /* _MM_HINT_T0 */); +} +#endif /* __PRFCHWINTRIN_H */ +#endif /* __clang__ */ + +#include +#endif + +/** + * \name The two types of endianness + */ +/* @{ */ +#define SDL_LIL_ENDIAN 1234 +#define SDL_BIG_ENDIAN 4321 +/* @} */ + +#ifndef SDL_BYTEORDER /* Not defined in SDL_config.h? */ +#ifdef __linux__ +#include +#define SDL_BYTEORDER __BYTE_ORDER +#elif defined(__sun) && defined(__SVR4) /* Solaris */ +#include +#if defined(_LITTLE_ENDIAN) +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#elif defined(_BIG_ENDIAN) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#error Unsupported endianness +#endif +#elif defined(__OpenBSD__) || defined(__DragonFly__) +#include +#define SDL_BYTEORDER BYTE_ORDER +#elif defined(__FreeBSD__) || defined(__NetBSD__) +#include +#define SDL_BYTEORDER BYTE_ORDER +/* predefs from newer gcc and clang versions: */ +#elif defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && defined(__BYTE_ORDER__) +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#error Unsupported endianness +#endif /**/ +#else +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(__powerpc__) || defined(__PPC__) || \ + defined(__sparc__) || defined(__sparc) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#endif +#endif /* __linux__ */ +#endif /* !SDL_BYTEORDER */ + +#ifndef SDL_FLOATWORDORDER /* Not defined in SDL_config.h? */ +/* predefs from newer gcc versions: */ +#if defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && defined(__FLOAT_WORD_ORDER__) +#if (__FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define SDL_FLOATWORDORDER SDL_LIL_ENDIAN +#elif (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__) +#define SDL_FLOATWORDORDER SDL_BIG_ENDIAN +#else +#error Unsupported endianness +#endif /**/ +#elif defined(__MAVERICK__) +/* For Maverick, float words are always little-endian. */ +#define SDL_FLOATWORDORDER SDL_LIL_ENDIAN +#elif (defined(__arm__) || defined(__thumb__)) && !defined(__VFP_FP__) && !defined(__ARM_EABI__) +/* For FPA, float words are always big-endian. */ +#define SDL_FLOATWORDORDER SDL_BIG_ENDIAN +#else +/* By default, assume that floats words follow the memory system mode. */ +#define SDL_FLOATWORDORDER SDL_BYTEORDER +#endif /* __FLOAT_WORD_ORDER__ */ +#endif /* !SDL_FLOATWORDORDER */ + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file SDL_endian.h + */ + +/* various modern compilers may have builtin swap */ +#if defined(__GNUC__) || defined(__clang__) +# define HAS_BUILTIN_BSWAP16 (_SDL_HAS_BUILTIN(__builtin_bswap16)) || \ + (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +# define HAS_BUILTIN_BSWAP32 (_SDL_HAS_BUILTIN(__builtin_bswap32)) || \ + (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +# define HAS_BUILTIN_BSWAP64 (_SDL_HAS_BUILTIN(__builtin_bswap64)) || \ + (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + + /* this one is broken */ +# define HAS_BROKEN_BSWAP (__GNUC__ == 2 && __GNUC_MINOR__ <= 95) +#else +# define HAS_BUILTIN_BSWAP16 0 +# define HAS_BUILTIN_BSWAP32 0 +# define HAS_BUILTIN_BSWAP64 0 +# define HAS_BROKEN_BSWAP 0 +#endif + +#if HAS_BUILTIN_BSWAP16 +#define SDL_Swap16(x) __builtin_bswap16(x) +#elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && !defined(__ICL) +#pragma intrinsic(_byteswap_ushort) +#define SDL_Swap16(x) _byteswap_ushort(x) +#elif defined(__i386__) && !HAS_BROKEN_BSWAP +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0": "=q"(x):"0"(x)); + return x; +} +#elif defined(__x86_64__) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0": "=Q"(x):"0"(x)); + return x; +} +#elif (defined(__powerpc__) || defined(__ppc__)) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + int result; + + __asm__("rlwimi %0,%2,8,16,23": "=&r"(result):"0"(x >> 8), "r"(x)); + return (Uint16)result; +} +#elif (defined(__m68k__) && !defined(__mcoldfire__)) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("rorw #8,%0": "=d"(x): "0"(x):"cc"); + return x; +} +#elif defined(__WATCOMC__) && defined(__386__) +extern __inline Uint16 SDL_Swap16(Uint16); +#pragma aux SDL_Swap16 = \ + "xchg al, ah" \ + parm [ax] \ + modify [ax]; +#else + +/** + * Use this function to swap the byte order of a 16-bit value. + * + * \param x the value to be swapped. + * \returns the swapped value. + * + * \sa SDL_SwapBE16 + * \sa SDL_SwapLE16 + */ +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + return SDL_static_cast(Uint16, ((x << 8) | (x >> 8))); +} +#endif + +#if HAS_BUILTIN_BSWAP32 +#define SDL_Swap32(x) __builtin_bswap32(x) +#elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && !defined(__ICL) +#pragma intrinsic(_byteswap_ulong) +#define SDL_Swap32(x) _byteswap_ulong(x) +#elif defined(__i386__) && !HAS_BROKEN_BSWAP +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("bswap %0": "=r"(x):"0"(x)); + return x; +} +#elif defined(__x86_64__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("bswapl %0": "=r"(x):"0"(x)); + return x; +} +#elif (defined(__powerpc__) || defined(__ppc__)) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + Uint32 result; + + __asm__("rlwimi %0,%2,24,16,23": "=&r"(result): "0" (x>>24), "r"(x)); + __asm__("rlwimi %0,%2,8,8,15" : "=&r"(result): "0" (result), "r"(x)); + __asm__("rlwimi %0,%2,24,0,7" : "=&r"(result): "0" (result), "r"(x)); + return result; +} +#elif (defined(__m68k__) && !defined(__mcoldfire__)) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0": "=d"(x): "0"(x):"cc"); + return x; +} +#elif defined(__WATCOMC__) && defined(__386__) +extern __inline Uint32 SDL_Swap32(Uint32); +#pragma aux SDL_Swap32 = \ + "bswap eax" \ + parm [eax] \ + modify [eax]; +#else + +/** + * Use this function to swap the byte order of a 32-bit value. + * + * \param x the value to be swapped. + * \returns the swapped value. + * + * \sa SDL_SwapBE32 + * \sa SDL_SwapLE32 + */ +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + return SDL_static_cast(Uint32, ((x << 24) | ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | (x >> 24))); +} +#endif + +#if HAS_BUILTIN_BSWAP64 +#define SDL_Swap64(x) __builtin_bswap64(x) +#elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && !defined(__ICL) +#pragma intrinsic(_byteswap_uint64) +#define SDL_Swap64(x) _byteswap_uint64(x) +#elif defined(__i386__) && !HAS_BROKEN_BSWAP +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + union { + struct { + Uint32 a, b; + } s; + Uint64 u; + } v; + v.u = x; + __asm__("bswapl %0 ; bswapl %1 ; xchgl %0,%1" + : "=r"(v.s.a), "=r"(v.s.b) + : "0" (v.s.a), "1"(v.s.b)); + return v.u; +} +#elif defined(__x86_64__) +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + __asm__("bswapq %0": "=r"(x):"0"(x)); + return x; +} +#elif defined(__WATCOMC__) && defined(__386__) +extern __inline Uint64 SDL_Swap64(Uint64); +#pragma aux SDL_Swap64 = \ + "bswap eax" \ + "bswap edx" \ + "xchg eax,edx" \ + parm [eax edx] \ + modify [eax edx]; +#else + +/** + * Use this function to swap the byte order of a 64-bit value. + * + * \param x the value to be swapped. + * \returns the swapped value. + * + * \sa SDL_SwapBE64 + * \sa SDL_SwapLE64 + */ +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + Uint32 hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x >>= 32; + hi = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x = SDL_Swap32(lo); + x <<= 32; + x |= SDL_Swap32(hi); + return (x); +} +#endif + + +/** + * Use this function to swap the byte order of a floating point value. + * + * \param x the value to be swapped. + * \returns the swapped value. + * + * \sa SDL_SwapFloatBE + * \sa SDL_SwapFloatLE + */ +SDL_FORCE_INLINE float +SDL_SwapFloat(float x) +{ + union { + float f; + Uint32 ui32; + } swapper; + swapper.f = x; + swapper.ui32 = SDL_Swap32(swapper.ui32); + return swapper.f; +} + +/* remove extra macros */ +#undef HAS_BROKEN_BSWAP +#undef HAS_BUILTIN_BSWAP16 +#undef HAS_BUILTIN_BSWAP32 +#undef HAS_BUILTIN_BSWAP64 + +/** + * \name Swap to native + * Byteswap item from the specified endianness to the native endianness. + */ +/* @{ */ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define SDL_SwapLE16(X) (X) +#define SDL_SwapLE32(X) (X) +#define SDL_SwapLE64(X) (X) +#define SDL_SwapFloatLE(X) (X) +#define SDL_SwapBE16(X) SDL_Swap16(X) +#define SDL_SwapBE32(X) SDL_Swap32(X) +#define SDL_SwapBE64(X) SDL_Swap64(X) +#define SDL_SwapFloatBE(X) SDL_SwapFloat(X) +#else +#define SDL_SwapLE16(X) SDL_Swap16(X) +#define SDL_SwapLE32(X) SDL_Swap32(X) +#define SDL_SwapLE64(X) SDL_Swap64(X) +#define SDL_SwapFloatLE(X) SDL_SwapFloat(X) +#define SDL_SwapBE16(X) (X) +#define SDL_SwapBE32(X) (X) +#define SDL_SwapBE64(X) (X) +#define SDL_SwapFloatBE(X) (X) +#endif +/* @} *//* Swap to native */ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_endian_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_error.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_error.h new file mode 100644 index 00000000..8d9cde0e --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_error.h @@ -0,0 +1,163 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryError + * + * Simple error message routines for SDL. + */ + +#ifndef SDL_error_h_ +#define SDL_error_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Public functions */ + + +/** + * Set the SDL error message for the current thread. + * + * Calling this function will replace any previous error message that was set. + * + * This function always returns -1, since SDL frequently uses -1 to signify an + * failing result, leading to this idiom: + * + * ```c + * if (error_code) { + * return SDL_SetError("This operation has failed: %d", error_code); + * } + * ``` + * + * \param fmt a printf()-style message format string. + * \param ... additional parameters matching % tokens in the `fmt` string, if + * any. + * \returns always -1. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ClearError + * \sa SDL_GetError + */ +extern DECLSPEC int SDLCALL SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); + +/** + * Retrieve a message about the last error that occurred on the current + * thread. + * + * It is possible for multiple errors to occur before calling SDL_GetError(). + * Only the last error is returned. + * + * The message is only applicable when an SDL function has signaled an error. + * You must check the return values of SDL function calls to determine when to + * appropriately call SDL_GetError(). You should *not* use the results of + * SDL_GetError() to decide if an error has occurred! Sometimes SDL will set + * an error string even when reporting success. + * + * SDL will *not* clear the error string for successful API calls. You *must* + * check return values for failure cases before you can assume the error + * string applies. + * + * Error strings are set per-thread, so an error set in a different thread + * will not interfere with the current thread's operation. + * + * The returned string is internally allocated and must not be freed by the + * application. + * + * \returns a message with information about the specific error that occurred, + * or an empty string if there hasn't been an error message set since + * the last call to SDL_ClearError(). The message is only applicable + * when an SDL function has signaled an error. You must check the + * return values of SDL function calls to determine when to + * appropriately call SDL_GetError(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ClearError + * \sa SDL_SetError + */ +extern DECLSPEC const char *SDLCALL SDL_GetError(void); + +/** + * Get the last error message that was set for the current thread. + * + * This allows the caller to copy the error string into a provided buffer, but + * otherwise operates exactly the same as SDL_GetError(). + * + * \param errstr A buffer to fill with the last error message that was set for + * the current thread. + * \param maxlen The size of the buffer pointed to by the errstr parameter. + * \returns the pointer passed in as the `errstr` parameter. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_GetError + */ +extern DECLSPEC char * SDLCALL SDL_GetErrorMsg(char *errstr, int maxlen); + +/** + * Clear any previous error message for this thread. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetError + * \sa SDL_SetError + */ +extern DECLSPEC void SDLCALL SDL_ClearError(void); + +/** + * \name Internal error functions + * + * \internal + * Private error reporting function - used internally. + */ +/* @{ */ +#define SDL_OutOfMemory() SDL_Error(SDL_ENOMEM) +#define SDL_Unsupported() SDL_Error(SDL_UNSUPPORTED) +#define SDL_InvalidParamError(param) SDL_SetError("Parameter '%s' is invalid", (param)) +typedef enum +{ + SDL_ENOMEM, + SDL_EFREAD, + SDL_EFWRITE, + SDL_EFSEEK, + SDL_UNSUPPORTED, + SDL_LASTERROR +} SDL_errorcode; +/* SDL_Error() unconditionally returns -1. */ +extern DECLSPEC int SDLCALL SDL_Error(SDL_errorcode code); +/* @} *//* Internal error functions */ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_error_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_events.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_events.h new file mode 100644 index 00000000..b9596c0e --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_events.h @@ -0,0 +1,1196 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryEvents + * + * Include file for SDL event handling. + */ + +#ifndef SDL_events_h_ +#define SDL_events_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" +#include "SDL_keyboard.h" +#include "SDL_mouse.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "SDL_quit.h" +#include "SDL_gesture.h" +#include "SDL_touch.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* General keyboard/mouse state definitions */ +#define SDL_RELEASED 0 +#define SDL_PRESSED 1 + +/** + * The types of events that can be delivered. + */ +typedef enum SDL_EventType +{ + SDL_FIRSTEVENT = 0, /**< Unused (do not remove) */ + + /* Application events */ + SDL_QUIT = 0x100, /**< User-requested quit */ + + /* These application events have special meaning on iOS, see README-ios.md for details */ + SDL_APP_TERMINATING, /**< The application is being terminated by the OS + Called on iOS in applicationWillTerminate() + Called on Android in onDestroy() + */ + SDL_APP_LOWMEMORY, /**< The application is low on memory, free memory if possible. + Called on iOS in applicationDidReceiveMemoryWarning() + Called on Android in onLowMemory() + */ + SDL_APP_WILLENTERBACKGROUND, /**< The application is about to enter the background + Called on iOS in applicationWillResignActive() + Called on Android in onPause() + */ + SDL_APP_DIDENTERBACKGROUND, /**< The application did enter the background and may not get CPU for some time + Called on iOS in applicationDidEnterBackground() + Called on Android in onPause() + */ + SDL_APP_WILLENTERFOREGROUND, /**< The application is about to enter the foreground + Called on iOS in applicationWillEnterForeground() + Called on Android in onResume() + */ + SDL_APP_DIDENTERFOREGROUND, /**< The application is now interactive + Called on iOS in applicationDidBecomeActive() + Called on Android in onResume() + */ + + SDL_LOCALECHANGED, /**< The user's locale preferences have changed. */ + + /* Display events */ + SDL_DISPLAYEVENT = 0x150, /**< Display state change */ + + /* Window events */ + SDL_WINDOWEVENT = 0x200, /**< Window state change */ + SDL_SYSWMEVENT, /**< System specific event */ + + /* Keyboard events */ + SDL_KEYDOWN = 0x300, /**< Key pressed */ + SDL_KEYUP, /**< Key released */ + SDL_TEXTEDITING, /**< Keyboard text editing (composition) */ + SDL_TEXTINPUT, /**< Keyboard text input */ + SDL_KEYMAPCHANGED, /**< Keymap changed due to a system event such as an + input language or keyboard layout change. + */ + SDL_TEXTEDITING_EXT, /**< Extended keyboard text editing (composition) */ + + /* Mouse events */ + SDL_MOUSEMOTION = 0x400, /**< Mouse moved */ + SDL_MOUSEBUTTONDOWN, /**< Mouse button pressed */ + SDL_MOUSEBUTTONUP, /**< Mouse button released */ + SDL_MOUSEWHEEL, /**< Mouse wheel motion */ + + /* Joystick events */ + SDL_JOYAXISMOTION = 0x600, /**< Joystick axis motion */ + SDL_JOYBALLMOTION, /**< Joystick trackball motion */ + SDL_JOYHATMOTION, /**< Joystick hat position change */ + SDL_JOYBUTTONDOWN, /**< Joystick button pressed */ + SDL_JOYBUTTONUP, /**< Joystick button released */ + SDL_JOYDEVICEADDED, /**< A new joystick has been inserted into the system */ + SDL_JOYDEVICEREMOVED, /**< An opened joystick has been removed */ + SDL_JOYBATTERYUPDATED, /**< Joystick battery level change */ + + /* Game controller events */ + SDL_CONTROLLERAXISMOTION = 0x650, /**< Game controller axis motion */ + SDL_CONTROLLERBUTTONDOWN, /**< Game controller button pressed */ + SDL_CONTROLLERBUTTONUP, /**< Game controller button released */ + SDL_CONTROLLERDEVICEADDED, /**< A new Game controller has been inserted into the system */ + SDL_CONTROLLERDEVICEREMOVED, /**< An opened Game controller has been removed */ + SDL_CONTROLLERDEVICEREMAPPED, /**< The controller mapping was updated */ + SDL_CONTROLLERTOUCHPADDOWN, /**< Game controller touchpad was touched */ + SDL_CONTROLLERTOUCHPADMOTION, /**< Game controller touchpad finger was moved */ + SDL_CONTROLLERTOUCHPADUP, /**< Game controller touchpad finger was lifted */ + SDL_CONTROLLERSENSORUPDATE, /**< Game controller sensor was updated */ + SDL_CONTROLLERUPDATECOMPLETE_RESERVED_FOR_SDL3, + SDL_CONTROLLERSTEAMHANDLEUPDATED, /**< Game controller Steam handle has changed */ + + /* Touch events */ + SDL_FINGERDOWN = 0x700, + SDL_FINGERUP, + SDL_FINGERMOTION, + + /* Gesture events */ + SDL_DOLLARGESTURE = 0x800, + SDL_DOLLARRECORD, + SDL_MULTIGESTURE, + + /* Clipboard events */ + SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard or primary selection changed */ + + /* Drag and drop events */ + SDL_DROPFILE = 0x1000, /**< The system requests a file open */ + SDL_DROPTEXT, /**< text/plain drag-and-drop event */ + SDL_DROPBEGIN, /**< A new set of drops is beginning (NULL filename) */ + SDL_DROPCOMPLETE, /**< Current set of drops is now complete (NULL filename) */ + + /* Audio hotplug events */ + SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */ + SDL_AUDIODEVICEREMOVED, /**< An audio device has been removed. */ + + /* Sensor events */ + SDL_SENSORUPDATE = 0x1200, /**< A sensor was updated */ + + /* Render events */ + SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */ + SDL_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */ + + /* Internal events */ + SDL_POLLSENTINEL = 0x7F00, /**< Signals the end of an event poll cycle */ + + /** Events SDL_USEREVENT through SDL_LASTEVENT are for your use, + * and should be allocated with SDL_RegisterEvents() + */ + SDL_USEREVENT = 0x8000, + + /** + * This last event is only for bounding internal arrays + */ + SDL_LASTEVENT = 0xFFFF +} SDL_EventType; + +/** + * Fields shared by every event + */ +typedef struct SDL_CommonEvent +{ + Uint32 type; + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ +} SDL_CommonEvent; + +/** + * Display state change event data (event.display.*) + */ +typedef struct SDL_DisplayEvent +{ + Uint32 type; /**< SDL_DISPLAYEVENT */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 display; /**< The associated display index */ + Uint8 event; /**< SDL_DisplayEventID */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint32 data1; /**< event dependent data */ +} SDL_DisplayEvent; + +/** + * Window state change event data (event.window.*) + */ +typedef struct SDL_WindowEvent +{ + Uint32 type; /**< SDL_WINDOWEVENT */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The associated window */ + Uint8 event; /**< SDL_WindowEventID */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint32 data1; /**< event dependent data */ + Sint32 data2; /**< event dependent data */ +} SDL_WindowEvent; + +/** + * Keyboard button event structure (event.key.*) + */ +typedef struct SDL_KeyboardEvent +{ + Uint32 type; /**< SDL_KEYDOWN or SDL_KEYUP */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with keyboard focus, if any */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + Uint8 repeat; /**< Non-zero if this is a key repeat */ + Uint8 padding2; + Uint8 padding3; + SDL_Keysym keysym; /**< The key that was pressed or released */ +} SDL_KeyboardEvent; + +#define SDL_TEXTEDITINGEVENT_TEXT_SIZE (32) + +/** + * Keyboard text editing event structure (event.edit.*) + */ +typedef struct SDL_TextEditingEvent +{ + Uint32 type; /**< SDL_TEXTEDITING */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with keyboard focus, if any */ + char text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; /**< The editing text */ + Sint32 start; /**< The start cursor of selected editing text */ + Sint32 length; /**< The length of selected editing text */ +} SDL_TextEditingEvent; + +/** + * Extended keyboard text editing event structure (event.editExt.*) when text + * would be truncated if stored in the text buffer SDL_TextEditingEvent + */ +typedef struct SDL_TextEditingExtEvent +{ + Uint32 type; /**< SDL_TEXTEDITING_EXT */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with keyboard focus, if any */ + char* text; /**< The editing text, which should be freed with SDL_free(), and will not be NULL */ + Sint32 start; /**< The start cursor of selected editing text */ + Sint32 length; /**< The length of selected editing text */ +} SDL_TextEditingExtEvent; + +/** + * The maximum bytes of text that can be supplied in an SDL_TextInputEvent. + */ +#define SDL_TEXTINPUTEVENT_TEXT_SIZE (32) + +/** + * Keyboard text input event structure (event.text.*) + * + * `text` is limited to SDL_TEXTINPUTEVENT_TEXT_SIZE bytes. If the incoming + * string is larger than this, SDL will split it and send it in pieces, across + * multiple events. The string is in UTF-8 format, and if split, SDL + * guarantees that it will not split in the middle of a UTF-8 sequence, so any + * event will only contain complete codepoints. However, if there are several + * codepoints that go together into a single glyph (like an emoji "thumbs up" + * followed by a skin color), they may be split between events. + * + * This event will never be delivered unless text input is enabled by calling + * SDL_StartTextInput(). Text input is enabled by default on desktop + * platforms, and disabled by default on mobile platforms! + * + * \sa SDL_StartTextInput + * \sa SDL_StopTextInput + */ +typedef struct SDL_TextInputEvent +{ + Uint32 type; /**< SDL_TEXTINPUT */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with keyboard focus, if any */ + char text[SDL_TEXTINPUTEVENT_TEXT_SIZE]; /**< The input text; UTF-8 encoded. */ +} SDL_TextInputEvent; + +/** + * Mouse motion event structure (event.motion.*) + */ +typedef struct SDL_MouseMotionEvent +{ + Uint32 type; /**< SDL_MOUSEMOTION */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + Uint32 state; /**< The current button state */ + Sint32 x; /**< X coordinate, relative to window */ + Sint32 y; /**< Y coordinate, relative to window */ + Sint32 xrel; /**< The relative motion in the X direction */ + Sint32 yrel; /**< The relative motion in the Y direction */ +} SDL_MouseMotionEvent; + +/** + * Mouse button event structure (event.button.*) + */ +typedef struct SDL_MouseButtonEvent +{ + Uint32 type; /**< SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + Uint8 button; /**< The mouse button index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + Uint8 clicks; /**< 1 for single-click, 2 for double-click, etc. */ + Uint8 padding1; + Sint32 x; /**< X coordinate, relative to window */ + Sint32 y; /**< Y coordinate, relative to window */ +} SDL_MouseButtonEvent; + +/** + * Mouse wheel event structure (event.wheel.*) + */ +typedef struct SDL_MouseWheelEvent +{ + Uint32 type; /**< SDL_MOUSEWHEEL */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + Sint32 x; /**< The amount scrolled horizontally, positive to the right and negative to the left */ + Sint32 y; /**< The amount scrolled vertically, positive away from the user and negative toward the user */ + Uint32 direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */ + float preciseX; /**< The amount scrolled horizontally, positive to the right and negative to the left, with float precision (added in 2.0.18) */ + float preciseY; /**< The amount scrolled vertically, positive away from the user and negative toward the user, with float precision (added in 2.0.18) */ + Sint32 mouseX; /**< X coordinate, relative to window (added in 2.26.0) */ + Sint32 mouseY; /**< Y coordinate, relative to window (added in 2.26.0) */ +} SDL_MouseWheelEvent; + +/** + * Joystick axis motion event structure (event.jaxis.*) + */ +typedef struct SDL_JoyAxisEvent +{ + Uint32 type; /**< SDL_JOYAXISMOTION */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 axis; /**< The joystick axis index */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 value; /**< The axis value (range: -32768 to 32767) */ + Uint16 padding4; +} SDL_JoyAxisEvent; + +/** + * Joystick trackball motion event structure (event.jball.*) + */ +typedef struct SDL_JoyBallEvent +{ + Uint32 type; /**< SDL_JOYBALLMOTION */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 ball; /**< The joystick trackball index */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 xrel; /**< The relative motion in the X direction */ + Sint16 yrel; /**< The relative motion in the Y direction */ +} SDL_JoyBallEvent; + +/** + * Joystick hat position change event structure (event.jhat.*) + */ +typedef struct SDL_JoyHatEvent +{ + Uint32 type; /**< SDL_JOYHATMOTION */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 hat; /**< The joystick hat index */ + Uint8 value; /**< The hat position value. + * \sa SDL_HAT_LEFTUP SDL_HAT_UP SDL_HAT_RIGHTUP + * \sa SDL_HAT_LEFT SDL_HAT_CENTERED SDL_HAT_RIGHT + * \sa SDL_HAT_LEFTDOWN SDL_HAT_DOWN SDL_HAT_RIGHTDOWN + * + * Note that zero means the POV is centered. + */ + Uint8 padding1; + Uint8 padding2; +} SDL_JoyHatEvent; + +/** + * Joystick button event structure (event.jbutton.*) + */ +typedef struct SDL_JoyButtonEvent +{ + Uint32 type; /**< SDL_JOYBUTTONDOWN or SDL_JOYBUTTONUP */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 button; /**< The joystick button index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + Uint8 padding1; + Uint8 padding2; +} SDL_JoyButtonEvent; + +/** + * Joystick device event structure (event.jdevice.*) + * + * SDL will send JOYSTICK_ADDED events for devices that are already plugged in + * during SDL_Init. + * + * \sa SDL_ControllerDeviceEvent + */ +typedef struct SDL_JoyDeviceEvent +{ + Uint32 type; /**< SDL_JOYDEVICEADDED or SDL_JOYDEVICEREMOVED */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED event */ +} SDL_JoyDeviceEvent; + +/** + * Joysick battery level change event structure (event.jbattery.*) + */ +typedef struct SDL_JoyBatteryEvent +{ + Uint32 type; /**< SDL_JOYBATTERYUPDATED */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + SDL_JoystickPowerLevel level; /**< The joystick battery level */ +} SDL_JoyBatteryEvent; + +/** + * Game controller axis motion event structure (event.caxis.*) + */ +typedef struct SDL_ControllerAxisEvent +{ + Uint32 type; /**< SDL_CONTROLLERAXISMOTION */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 axis; /**< The controller axis (SDL_GameControllerAxis) */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 value; /**< The axis value (range: -32768 to 32767) */ + Uint16 padding4; +} SDL_ControllerAxisEvent; + + +/** + * Game controller button event structure (event.cbutton.*) + */ +typedef struct SDL_ControllerButtonEvent +{ + Uint32 type; /**< SDL_CONTROLLERBUTTONDOWN or SDL_CONTROLLERBUTTONUP */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 button; /**< The controller button (SDL_GameControllerButton) */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + Uint8 padding1; + Uint8 padding2; +} SDL_ControllerButtonEvent; + + +/** + * Controller device event structure (event.cdevice.*) + * + * Joysticks that are supported game controllers receive both an + * SDL_JoyDeviceEvent and an SDL_ControllerDeviceEvent. + * + * SDL will send CONTROLLERDEVICEADDED events for joysticks that are already + * plugged in during SDL_Init() and are recognized as game controllers. + */ +typedef struct SDL_ControllerDeviceEvent +{ + Uint32 type; /**< SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED, SDL_CONTROLLERDEVICEREMAPPED, or SDL_CONTROLLERSTEAMHANDLEUPDATED */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */ +} SDL_ControllerDeviceEvent; + +/** + * Game controller touchpad event structure (event.ctouchpad.*) + */ +typedef struct SDL_ControllerTouchpadEvent +{ + Uint32 type; /**< SDL_CONTROLLERTOUCHPADDOWN or SDL_CONTROLLERTOUCHPADMOTION or SDL_CONTROLLERTOUCHPADUP */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Sint32 touchpad; /**< The index of the touchpad */ + Sint32 finger; /**< The index of the finger on the touchpad */ + float x; /**< Normalized in the range 0...1 with 0 being on the left */ + float y; /**< Normalized in the range 0...1 with 0 being at the top */ + float pressure; /**< Normalized in the range 0...1 */ +} SDL_ControllerTouchpadEvent; + +/** + * Game controller sensor event structure (event.csensor.*) + */ +typedef struct SDL_ControllerSensorEvent +{ + Uint32 type; /**< SDL_CONTROLLERSENSORUPDATE */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_JoystickID which; /**< The joystick instance id */ + Sint32 sensor; /**< The type of the sensor, one of the values of SDL_SensorType */ + float data[3]; /**< Up to 3 values from the sensor, as defined in SDL_sensor.h */ + Uint64 timestamp_us; /**< The timestamp of the sensor reading in microseconds, if the hardware provides this information. */ +} SDL_ControllerSensorEvent; + +/** + * Audio device event structure (event.adevice.*) + */ +typedef struct SDL_AudioDeviceEvent +{ + Uint32 type; /**< SDL_AUDIODEVICEADDED, or SDL_AUDIODEVICEREMOVED */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 which; /**< The audio device index for the ADDED event (valid until next SDL_GetNumAudioDevices() call), SDL_AudioDeviceID for the REMOVED event */ + Uint8 iscapture; /**< zero if an output device, non-zero if a capture device. */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; +} SDL_AudioDeviceEvent; + + +/** + * Touch finger event structure (event.tfinger.*) + */ +typedef struct SDL_TouchFingerEvent +{ + Uint32 type; /**< SDL_FINGERMOTION or SDL_FINGERDOWN or SDL_FINGERUP */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_TouchID touchId; /**< The touch device id */ + SDL_FingerID fingerId; + float x; /**< Normalized in the range 0...1 */ + float y; /**< Normalized in the range 0...1 */ + float dx; /**< Normalized in the range -1...1 */ + float dy; /**< Normalized in the range -1...1 */ + float pressure; /**< Normalized in the range 0...1 */ + Uint32 windowID; /**< The window underneath the finger, if any */ +} SDL_TouchFingerEvent; + + +/** + * Multiple Finger Gesture Event (event.mgesture.*) + */ +typedef struct SDL_MultiGestureEvent +{ + Uint32 type; /**< SDL_MULTIGESTURE */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_TouchID touchId; /**< The touch device id */ + float dTheta; + float dDist; + float x; + float y; + Uint16 numFingers; + Uint16 padding; +} SDL_MultiGestureEvent; + + +/** + * Dollar Gesture Event (event.dgesture.*) + */ +typedef struct SDL_DollarGestureEvent +{ + Uint32 type; /**< SDL_DOLLARGESTURE or SDL_DOLLARRECORD */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_TouchID touchId; /**< The touch device id */ + SDL_GestureID gestureId; + Uint32 numFingers; + float error; + float x; /**< Normalized center of gesture */ + float y; /**< Normalized center of gesture */ +} SDL_DollarGestureEvent; + + +/** + * An event used to request a file open by the system (event.drop.*) + * + * This event is enabled by default, you can disable it with SDL_EventState(). + * + * If this event is enabled, you must free the filename in the event. + */ +typedef struct SDL_DropEvent +{ + Uint32 type; /**< SDL_DROPBEGIN or SDL_DROPFILE or SDL_DROPTEXT or SDL_DROPCOMPLETE */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + char *file; /**< The file name, which should be freed with SDL_free(), is NULL on begin/complete */ + Uint32 windowID; /**< The window that was dropped on, if any */ +} SDL_DropEvent; + + +/** + * Sensor event structure (event.sensor.*) + */ +typedef struct SDL_SensorEvent +{ + Uint32 type; /**< SDL_SENSORUPDATE */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Sint32 which; /**< The instance ID of the sensor */ + float data[6]; /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */ + Uint64 timestamp_us; /**< The timestamp of the sensor reading in microseconds, if the hardware provides this information. */ +} SDL_SensorEvent; + +/** + * The "quit requested" event + */ +typedef struct SDL_QuitEvent +{ + Uint32 type; /**< SDL_QUIT */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ +} SDL_QuitEvent; + +/** + * A user-defined event type (event.user.*) + */ +typedef struct SDL_UserEvent +{ + Uint32 type; /**< SDL_USEREVENT through SDL_LASTEVENT-1 */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + Uint32 windowID; /**< The associated window if any */ + Sint32 code; /**< User defined event code */ + void *data1; /**< User defined data pointer */ + void *data2; /**< User defined data pointer */ +} SDL_UserEvent; + + +struct SDL_SysWMmsg; +typedef struct SDL_SysWMmsg SDL_SysWMmsg; + +/** + * A video driver dependent system event (event.syswm.*) + * + * This event is disabled by default, you can enable it with SDL_EventState() + * + * If you want to use this event, you should include SDL_syswm.h. + */ +typedef struct SDL_SysWMEvent +{ + Uint32 type; /**< SDL_SYSWMEVENT */ + Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ + SDL_SysWMmsg *msg; /**< driver dependent data, defined in SDL_syswm.h */ +} SDL_SysWMEvent; + +/** + * General event structure + * + * The SDL_Event structure is the core of all event handling in SDL. SDL_Event + * is a union of all event structures used in SDL. + */ +typedef union SDL_Event +{ + Uint32 type; /**< Event type, shared with all events */ + SDL_CommonEvent common; /**< Common event data */ + SDL_DisplayEvent display; /**< Display event data */ + SDL_WindowEvent window; /**< Window event data */ + SDL_KeyboardEvent key; /**< Keyboard event data */ + SDL_TextEditingEvent edit; /**< Text editing event data */ + SDL_TextEditingExtEvent editExt; /**< Extended text editing event data */ + SDL_TextInputEvent text; /**< Text input event data */ + SDL_MouseMotionEvent motion; /**< Mouse motion event data */ + SDL_MouseButtonEvent button; /**< Mouse button event data */ + SDL_MouseWheelEvent wheel; /**< Mouse wheel event data */ + SDL_JoyAxisEvent jaxis; /**< Joystick axis event data */ + SDL_JoyBallEvent jball; /**< Joystick ball event data */ + SDL_JoyHatEvent jhat; /**< Joystick hat event data */ + SDL_JoyButtonEvent jbutton; /**< Joystick button event data */ + SDL_JoyDeviceEvent jdevice; /**< Joystick device change event data */ + SDL_JoyBatteryEvent jbattery; /**< Joystick battery event data */ + SDL_ControllerAxisEvent caxis; /**< Game Controller axis event data */ + SDL_ControllerButtonEvent cbutton; /**< Game Controller button event data */ + SDL_ControllerDeviceEvent cdevice; /**< Game Controller device event data */ + SDL_ControllerTouchpadEvent ctouchpad; /**< Game Controller touchpad event data */ + SDL_ControllerSensorEvent csensor; /**< Game Controller sensor event data */ + SDL_AudioDeviceEvent adevice; /**< Audio device event data */ + SDL_SensorEvent sensor; /**< Sensor event data */ + SDL_QuitEvent quit; /**< Quit request event data */ + SDL_UserEvent user; /**< Custom event data */ + SDL_SysWMEvent syswm; /**< System dependent window event data */ + SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ + SDL_MultiGestureEvent mgesture; /**< Gesture event data */ + SDL_DollarGestureEvent dgesture; /**< Gesture event data */ + SDL_DropEvent drop; /**< Drag and drop event data */ + + /* This is necessary for ABI compatibility between Visual C++ and GCC. + Visual C++ will respect the push pack pragma and use 52 bytes (size of + SDL_TextEditingEvent, the largest structure for 32-bit and 64-bit + architectures) for this union, and GCC will use the alignment of the + largest datatype within the union, which is 8 bytes on 64-bit + architectures. + + So... we'll add padding to force the size to be 56 bytes for both. + + On architectures where pointers are 16 bytes, this needs rounding up to + the next multiple of 16, 64, and on architectures where pointers are + even larger the size of SDL_UserEvent will dominate as being 3 pointers. + */ + Uint8 padding[sizeof(void *) <= 8 ? 56 : sizeof(void *) == 16 ? 64 : 3 * sizeof(void *)]; +} SDL_Event; + +/* Make sure we haven't broken binary compatibility */ +SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == sizeof(((SDL_Event *)NULL)->padding)); + + +/* Function prototypes */ + +/** + * Pump the event loop, gathering events from the input devices. + * + * This function updates the event queue and internal input device state. + * + * **WARNING**: This should only be run in the thread that initialized the + * video subsystem, and for extra safety, you should consider only doing those + * things on the main thread in any case. + * + * SDL_PumpEvents() gathers all the pending input information from devices and + * places it in the event queue. Without calls to SDL_PumpEvents() no events + * would ever be placed on the queue. Often the need for calls to + * SDL_PumpEvents() is hidden from the user since SDL_PollEvent() and + * SDL_WaitEvent() implicitly call SDL_PumpEvents(). However, if you are not + * polling or waiting for events (e.g. you are filtering them), then you must + * call SDL_PumpEvents() to force an event queue update. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PollEvent + * \sa SDL_WaitEvent + */ +extern DECLSPEC void SDLCALL SDL_PumpEvents(void); + +/* @{ */ +typedef enum SDL_eventaction +{ + SDL_ADDEVENT, + SDL_PEEKEVENT, + SDL_GETEVENT +} SDL_eventaction; + +/** + * Check the event queue for messages and optionally return them. + * + * `action` may be any of the following: + * + * - `SDL_ADDEVENT`: up to `numevents` events will be added to the back of the + * event queue. + * - `SDL_PEEKEVENT`: `numevents` events at the front of the event queue, + * within the specified minimum and maximum type, will be returned to the + * caller and will _not_ be removed from the queue. + * - `SDL_GETEVENT`: up to `numevents` events at the front of the event queue, + * within the specified minimum and maximum type, will be returned to the + * caller and will be removed from the queue. + * + * You may have to call SDL_PumpEvents() before calling this function. + * Otherwise, the events may not be ready to be filtered when you call + * SDL_PeepEvents(). + * + * This function is thread-safe. + * + * \param events destination buffer for the retrieved events. + * \param numevents if action is SDL_ADDEVENT, the number of events to add + * back to the event queue; if action is SDL_PEEKEVENT or + * SDL_GETEVENT, the maximum number of events to retrieve. + * \param action action to take; see [[#action|Remarks]] for details. + * \param minType minimum value of the event type to be considered; + * SDL_FIRSTEVENT is a safe choice. + * \param maxType maximum value of the event type to be considered; + * SDL_LASTEVENT is a safe choice. + * \returns the number of events actually stored or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PollEvent + * \sa SDL_PumpEvents + * \sa SDL_PushEvent + */ +extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event * events, int numevents, + SDL_eventaction action, + Uint32 minType, Uint32 maxType); +/* @} */ + +/** + * Check for the existence of a certain event type in the event queue. + * + * If you need to check for a range of event types, use SDL_HasEvents() + * instead. + * + * \param type the type of event to be queried; see SDL_EventType for details. + * \returns SDL_TRUE if events matching `type` are present, or SDL_FALSE if + * events matching `type` are not present. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HasEvents + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasEvent(Uint32 type); + + +/** + * Check for the existence of certain event types in the event queue. + * + * If you need to check for a single event type, use SDL_HasEvent() instead. + * + * \param minType the low end of event type to be queried, inclusive; see + * SDL_EventType for details. + * \param maxType the high end of event type to be queried, inclusive; see + * SDL_EventType for details. + * \returns SDL_TRUE if events with type >= `minType` and <= `maxType` are + * present, or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HasEvents + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasEvents(Uint32 minType, Uint32 maxType); + +/** + * Clear events of a specific type from the event queue. + * + * This will unconditionally remove any events from the queue that match + * `type`. If you need to remove a range of event types, use SDL_FlushEvents() + * instead. + * + * It's also normal to just ignore events you don't care about in your event + * loop without calling this function. + * + * This function only affects currently queued events. If you want to make + * sure that all pending OS events are flushed, you can call SDL_PumpEvents() + * on the main thread immediately before the flush call. + * + * \param type the type of event to be cleared; see SDL_EventType for details. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FlushEvents + */ +extern DECLSPEC void SDLCALL SDL_FlushEvent(Uint32 type); + +/** + * Clear events of a range of types from the event queue. + * + * This will unconditionally remove any events from the queue that are in the + * range of `minType` to `maxType`, inclusive. If you need to remove a single + * event type, use SDL_FlushEvent() instead. + * + * It's also normal to just ignore events you don't care about in your event + * loop without calling this function. + * + * This function only affects currently queued events. If you want to make + * sure that all pending OS events are flushed, you can call SDL_PumpEvents() + * on the main thread immediately before the flush call. + * + * \param minType the low end of event type to be cleared, inclusive; see + * SDL_EventType for details. + * \param maxType the high end of event type to be cleared, inclusive; see + * SDL_EventType for details. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FlushEvent + */ +extern DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType); + +/** + * Poll for currently pending events. + * + * If `event` is not NULL, the next event is removed from the queue and stored + * in the SDL_Event structure pointed to by `event`. The 1 returned refers to + * this event, immediately stored in the SDL Event structure -- not an event + * to follow. + * + * If `event` is NULL, it simply returns 1 if there is an event in the queue, + * but will not remove it from the queue. + * + * As this function may implicitly call SDL_PumpEvents(), you can only call + * this function in the thread that set the video mode. + * + * SDL_PollEvent() is the favored way of receiving system events since it can + * be done from the main loop and does not suspend the main loop while waiting + * on an event to be posted. + * + * The common practice is to fully process the event queue once every frame, + * usually as a first step before updating the game's state: + * + * ```c + * while (game_is_still_running) { + * SDL_Event event; + * while (SDL_PollEvent(&event)) { // poll until all events are handled! + * // decide what to do with this event. + * } + * + * // update game state, draw the current frame + * } + * ``` + * + * \param event the SDL_Event structure to be filled with the next event from + * the queue, or NULL. + * \returns 1 if there is a pending event or 0 if there are none available. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetEventFilter + * \sa SDL_PeepEvents + * \sa SDL_PushEvent + * \sa SDL_SetEventFilter + * \sa SDL_WaitEvent + * \sa SDL_WaitEventTimeout + */ +extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event * event); + +/** + * Wait indefinitely for the next available event. + * + * If `event` is not NULL, the next event is removed from the queue and stored + * in the SDL_Event structure pointed to by `event`. + * + * As this function may implicitly call SDL_PumpEvents(), you can only call + * this function in the thread that initialized the video subsystem. + * + * \param event the SDL_Event structure to be filled in with the next event + * from the queue, or NULL. + * \returns 1 on success or 0 if there was an error while waiting for events; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PollEvent + * \sa SDL_PumpEvents + * \sa SDL_WaitEventTimeout + */ +extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event * event); + +/** + * Wait until the specified timeout (in milliseconds) for the next available + * event. + * + * If `event` is not NULL, the next event is removed from the queue and stored + * in the SDL_Event structure pointed to by `event`. + * + * As this function may implicitly call SDL_PumpEvents(), you can only call + * this function in the thread that initialized the video subsystem. + * + * \param event the SDL_Event structure to be filled in with the next event + * from the queue, or NULL. + * \param timeout the maximum number of milliseconds to wait for the next + * available event. + * \returns 1 on success or 0 if there was an error while waiting for events; + * call SDL_GetError() for more information. This also returns 0 if + * the timeout elapsed without an event arriving. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PollEvent + * \sa SDL_PumpEvents + * \sa SDL_WaitEvent + */ +extern DECLSPEC int SDLCALL SDL_WaitEventTimeout(SDL_Event * event, + int timeout); + +/** + * Add an event to the event queue. + * + * The event queue can actually be used as a two way communication channel. + * Not only can events be read from the queue, but the user can also push + * their own events onto it. `event` is a pointer to the event structure you + * wish to push onto the queue. The event is copied into the queue, and the + * caller may dispose of the memory pointed to after SDL_PushEvent() returns. + * + * Note: Pushing device input events onto the queue doesn't modify the state + * of the device within SDL. + * + * This function is thread-safe, and can be called from other threads safely. + * + * Note: Events pushed onto the queue with SDL_PushEvent() get passed through + * the event filter but events added with SDL_PeepEvents() do not. + * + * For pushing application-specific events, please use SDL_RegisterEvents() to + * get an event type that does not conflict with other code that also wants + * its own custom event types. + * + * \param event the SDL_Event to be added to the queue. + * \returns 1 on success, 0 if the event was filtered, or a negative error + * code on failure; call SDL_GetError() for more information. A + * common reason for error is the event queue being full. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PeepEvents + * \sa SDL_PollEvent + * \sa SDL_RegisterEvents + */ +extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event * event); + +/** + * A function pointer used for callbacks that watch the event queue. + * + * \param userdata what was passed as `userdata` to SDL_SetEventFilter() or + * SDL_AddEventWatch, etc. + * \param event the event that triggered the callback. + * \returns 1 to permit event to be added to the queue, and 0 to disallow it. + * When used with SDL_AddEventWatch, the return value is ignored. + * + * \sa SDL_SetEventFilter + * \sa SDL_AddEventWatch + */ +typedef int (SDLCALL * SDL_EventFilter) (void *userdata, SDL_Event * event); + +/** + * Set up a filter to process all events before they change internal state and + * are posted to the internal event queue. + * + * If the filter function returns 1 when called, then the event will be added + * to the internal queue. If it returns 0, then the event will be dropped from + * the queue, but the internal state will still be updated. This allows + * selective filtering of dynamically arriving events. + * + * **WARNING**: Be very careful of what you do in the event filter function, + * as it may run in a different thread! + * + * On platforms that support it, if the quit event is generated by an + * interrupt signal (e.g. pressing Ctrl-C), it will be delivered to the + * application at the next event poll. + * + * There is one caveat when dealing with the SDL_QuitEvent event type. The + * event filter is only called when the window manager desires to close the + * application window. If the event filter returns 1, then the window will be + * closed, otherwise the window will remain open if possible. + * + * Note: Disabled events never make it to the event filter function; see + * SDL_EventState(). + * + * Note: If you just want to inspect events without filtering, you should use + * SDL_AddEventWatch() instead. + * + * Note: Events pushed onto the queue with SDL_PushEvent() get passed through + * the event filter, but events pushed onto the queue with SDL_PeepEvents() do + * not. + * + * \param filter An SDL_EventFilter function to call when an event happens. + * \param userdata a pointer that is passed to `filter`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AddEventWatch + * \sa SDL_EventState + * \sa SDL_GetEventFilter + * \sa SDL_PeepEvents + * \sa SDL_PushEvent + */ +extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter, + void *userdata); + +/** + * Query the current event filter. + * + * This function can be used to "chain" filters, by saving the existing filter + * before replacing it with a function that will call that saved filter. + * + * \param filter the current callback function will be stored here. + * \param userdata the pointer that is passed to the current event filter will + * be stored here. + * \returns SDL_TRUE on success or SDL_FALSE if there is no event filter set. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetEventFilter + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter * filter, + void **userdata); + +/** + * Add a callback to be triggered when an event is added to the event queue. + * + * `filter` will be called when an event happens, and its return value is + * ignored. + * + * **WARNING**: Be very careful of what you do in the event filter function, + * as it may run in a different thread! + * + * If the quit event is generated by a signal (e.g. SIGINT), it will bypass + * the internal queue and be delivered to the watch callback immediately, and + * arrive at the next event poll. + * + * Note: the callback is called for events posted by the user through + * SDL_PushEvent(), but not for disabled events, nor for events by a filter + * callback set with SDL_SetEventFilter(), nor for events posted by the user + * through SDL_PeepEvents(). + * + * \param filter an SDL_EventFilter function to call when an event happens. + * \param userdata a pointer that is passed to `filter`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DelEventWatch + * \sa SDL_SetEventFilter + */ +extern DECLSPEC void SDLCALL SDL_AddEventWatch(SDL_EventFilter filter, + void *userdata); + +/** + * Remove an event watch callback added with SDL_AddEventWatch(). + * + * This function takes the same input as SDL_AddEventWatch() to identify and + * delete the corresponding callback. + * + * \param filter the function originally passed to SDL_AddEventWatch(). + * \param userdata the pointer originally passed to SDL_AddEventWatch(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AddEventWatch + */ +extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter, + void *userdata); + +/** + * Run a specific filter function on the current event queue, removing any + * events for which the filter returns 0. + * + * See SDL_SetEventFilter() for more information. Unlike SDL_SetEventFilter(), + * this function does not change the filter permanently, it only uses the + * supplied filter until this function returns. + * + * \param filter the SDL_EventFilter function to call when an event happens. + * \param userdata a pointer that is passed to `filter`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetEventFilter + * \sa SDL_SetEventFilter + */ +extern DECLSPEC void SDLCALL SDL_FilterEvents(SDL_EventFilter filter, + void *userdata); + +/* @{ */ +#define SDL_QUERY -1 +#define SDL_IGNORE 0 +#define SDL_DISABLE 0 +#define SDL_ENABLE 1 + +/** + * Set the state of processing events by type. + * + * `state` may be any of the following: + * + * - `SDL_QUERY`: returns the current processing state of the specified event + * - `SDL_IGNORE` (aka `SDL_DISABLE`): the event will automatically be dropped + * from the event queue and will not be filtered + * - `SDL_ENABLE`: the event will be processed normally + * + * \param type the type of event; see SDL_EventType for details. + * \param state how to process the event. + * \returns `SDL_DISABLE` or `SDL_ENABLE`, representing the processing state + * of the event before this function makes any changes to it. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetEventState + */ +extern DECLSPEC Uint8 SDLCALL SDL_EventState(Uint32 type, int state); +/* @} */ +#define SDL_GetEventState(type) SDL_EventState(type, SDL_QUERY) + +/** + * Allocate a set of user-defined events, and return the beginning event + * number for that set of events. + * + * Calling this function with `numevents` <= 0 is an error and will return + * (Uint32)-1. + * + * Note, (Uint32)-1 means the maximum unsigned 32-bit integer value (or + * 0xFFFFFFFF), but is clearer to write. + * + * \param numevents the number of events to be allocated. + * \returns the beginning event number, or (Uint32)-1 if there are not enough + * user-defined events left. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PushEvent + */ +extern DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_events_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_filesystem.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_filesystem.h new file mode 100644 index 00000000..c72a6165 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_filesystem.h @@ -0,0 +1,149 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryFilesystem + * + * Include file for filesystem SDL API functions + */ + +#ifndef SDL_filesystem_h_ +#define SDL_filesystem_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get the directory where the application was run from. + * + * This is not necessarily a fast call, so you should call this once near + * startup and save the string if you need it. + * + * **Mac OS X and iOS Specific Functionality**: If the application is in a + * ".app" bundle, this function returns the Resource directory (e.g. + * MyApp.app/Contents/Resources/). This behaviour can be overridden by adding + * a property to the Info.plist file. Adding a string key with the name + * SDL_FILESYSTEM_BASE_DIR_TYPE with a supported value will change the + * behaviour. + * + * Supported values for the SDL_FILESYSTEM_BASE_DIR_TYPE property (Given an + * application in /Applications/SDLApp/MyApp.app): + * + * - `resource`: bundle resource directory (the default). For example: + * `/Applications/SDLApp/MyApp.app/Contents/Resources` + * - `bundle`: the Bundle directory. For example: + * `/Applications/SDLApp/MyApp.app/` + * - `parent`: the containing directory of the bundle. For example: + * `/Applications/SDLApp/` + * + * **Nintendo 3DS Specific Functionality**: This function returns "romfs" + * directory of the application as it is uncommon to store resources outside + * the executable. As such it is not a writable directory. + * + * The returned path is guaranteed to end with a path separator ('\\' on + * Windows, '/' on most other platforms). + * + * The pointer returned is owned by the caller. Please call SDL_free() on the + * pointer when done with it. + * + * \returns an absolute path in UTF-8 encoding to the application data + * directory. NULL will be returned on error or when the platform + * doesn't implement this functionality, call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.1. + * + * \sa SDL_GetPrefPath + */ +extern DECLSPEC char *SDLCALL SDL_GetBasePath(void); + +/** + * Get the user-and-app-specific path where files can be written. + * + * Get the "pref dir". This is meant to be where users can write personal + * files (preferences and save games, etc) that are specific to your + * application. This directory is unique per user, per application. + * + * This function will decide the appropriate location in the native + * filesystem, create the directory if necessary, and return a string of the + * absolute path to the directory in UTF-8 encoding. + * + * On Windows, the string might look like: + * + * `C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name\\` + * + * On Linux, the string might look like: + * + * `/home/bob/.local/share/My Program Name/` + * + * On Mac OS X, the string might look like: + * + * `/Users/bob/Library/Application Support/My Program Name/` + * + * You should assume the path returned by this function is the only safe place + * to write files (and that SDL_GetBasePath(), while it might be writable, or + * even the parent of the returned path, isn't where you should be writing + * things). + * + * Both the org and app strings may become part of a directory name, so please + * follow these rules: + * + * - Try to use the same org string (_including case-sensitivity_) for all + * your applications that use this function. + * - Always use a unique app string for each one, and make sure it never + * changes for an app once you've decided on it. + * - Unicode characters are legal, as long as it's UTF-8 encoded, but... + * - ...only use letters, numbers, and spaces. Avoid punctuation like "Game + * Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient. + * + * The returned path is guaranteed to end with a path separator ('\\' on + * Windows, '/' on most other platforms). + * + * The pointer returned is owned by the caller. Please call SDL_free() on the + * pointer when done with it. + * + * \param org the name of your organization. + * \param app the name of your application. + * \returns a UTF-8 string of the user directory in platform-dependent + * notation. NULL if there's a problem (creating directory failed, + * etc.). + * + * \since This function is available since SDL 2.0.1. + * + * \sa SDL_GetBasePath + */ +extern DECLSPEC char *SDLCALL SDL_GetPrefPath(const char *org, const char *app); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_filesystem_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_gamecontroller.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_gamecontroller.h new file mode 100644 index 00000000..4d8bcce2 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_gamecontroller.h @@ -0,0 +1,1110 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: GameController */ + +/** + * # CategoryGameController + * + * Include file for SDL game controller event handling + */ + +#ifndef SDL_gamecontroller_h_ +#define SDL_gamecontroller_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_rwops.h" +#include "SDL_sensor.h" +#include "SDL_joystick.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file SDL_gamecontroller.h + * + * In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_GAMECONTROLLER flag. This causes SDL to scan the system + * for game controllers, and load appropriate drivers. + * + * If you would like to receive controller updates while the application + * is in the background, you should set the following hint before calling + * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS + */ + +/** + * The gamecontroller structure used to identify an SDL game controller + */ +struct _SDL_GameController; +typedef struct _SDL_GameController SDL_GameController; + +typedef enum SDL_GameControllerType +{ + SDL_CONTROLLER_TYPE_UNKNOWN = 0, + SDL_CONTROLLER_TYPE_XBOX360, + SDL_CONTROLLER_TYPE_XBOXONE, + SDL_CONTROLLER_TYPE_PS3, + SDL_CONTROLLER_TYPE_PS4, + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO, + SDL_CONTROLLER_TYPE_VIRTUAL, + SDL_CONTROLLER_TYPE_PS5, + SDL_CONTROLLER_TYPE_AMAZON_LUNA, + SDL_CONTROLLER_TYPE_GOOGLE_STADIA, + SDL_CONTROLLER_TYPE_NVIDIA_SHIELD, + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT, + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT, + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR, + SDL_CONTROLLER_TYPE_MAX +} SDL_GameControllerType; + +typedef enum SDL_GameControllerBindType +{ + SDL_CONTROLLER_BINDTYPE_NONE = 0, + SDL_CONTROLLER_BINDTYPE_BUTTON, + SDL_CONTROLLER_BINDTYPE_AXIS, + SDL_CONTROLLER_BINDTYPE_HAT +} SDL_GameControllerBindType; + +/** + * Get the SDL joystick layer binding for this controller button/axis mapping + */ +typedef struct SDL_GameControllerButtonBind +{ + SDL_GameControllerBindType bindType; + union + { + int button; + int axis; + struct { + int hat; + int hat_mask; + } hat; + } value; + +} SDL_GameControllerButtonBind; + + +/** + * To count the number of game controllers in the system for the following: + * + * ```c + * int nJoysticks = SDL_NumJoysticks(); + * int nGameControllers = 0; + * for (int i = 0; i < nJoysticks; i++) { + * if (SDL_IsGameController(i)) { + * nGameControllers++; + * } + * } + * ``` + * + * Using the SDL_HINT_GAMECONTROLLERCONFIG hint or the SDL_GameControllerAddMapping() you can add support for controllers SDL is unaware of or cause an existing controller to have a different binding. The format is: + * guid,name,mappings + * + * Where GUID is the string value from SDL_JoystickGetGUIDString(), name is the human readable string for the device and mappings are controller mappings to joystick ones. + * Under Windows there is a reserved GUID of "xinput" that covers any XInput devices. + * The mapping format for joystick is: + * bX - a joystick button, index X + * hX.Y - hat X with value Y + * aX - axis X of the joystick + * Buttons can be used as a controller axis and vice versa. + * + * This string shows an example of a valid mapping for a controller + * + * ```c + * "03000000341a00003608000000000000,PS3 Controller,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7", + * ``` + */ + +/** + * Load a set of Game Controller mappings from a seekable SDL data stream. + * + * You can call this function several times, if needed, to load different + * database files. + * + * If a new mapping is loaded for an already known controller GUID, the later + * version will overwrite the one currently loaded. + * + * If this function is called before SDL_Init, SDL will generate an + * SDL_CONTROLLERDEVICEADDED event for matching controllers that are plugged + * in at the time that SDL_Init is called. + * + * Mappings not belonging to the current platform or with no platform field + * specified will be ignored (i.e. mappings for Linux will be ignored in + * Windows, etc). + * + * This function will load the text database entirely in memory before + * processing it, so take this into consideration if you are in a memory + * constrained environment. + * + * \param rw the data stream for the mappings to be added. + * \param freerw non-zero to close the stream after being read. + * \returns the number of mappings added or -1 on error; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_GameControllerAddMapping + * \sa SDL_GameControllerAddMappingsFromFile + * \sa SDL_GameControllerMappingForGUID + * \sa SDL_CONTROLLERDEVICEADDED + */ +extern DECLSPEC int SDLCALL SDL_GameControllerAddMappingsFromRW(SDL_RWops * rw, int freerw); + +/** + * Load a set of mappings from a file, filtered by the current + * SDL_GetPlatform() + * + * Convenience macro. + */ +#define SDL_GameControllerAddMappingsFromFile(file) SDL_GameControllerAddMappingsFromRW(SDL_RWFromFile(file, "rb"), 1) + +/** + * Add support for controllers that SDL is unaware of or to cause an existing + * controller to have a different binding. + * + * The mapping string has the format "GUID,name,mapping", where GUID is the + * string value from SDL_JoystickGetGUIDString(), name is the human readable + * string for the device and mappings are controller mappings to joystick + * ones. Under Windows there is a reserved GUID of "xinput" that covers all + * XInput devices. The mapping format for joystick is: {| |bX |a joystick + * button, index X |- |hX.Y |hat X with value Y |- |aX |axis X of the joystick + * |} Buttons can be used as a controller axes and vice versa. + * + * This string shows an example of a valid mapping for a controller: + * + * ```c + * "341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7" + * ``` + * + * If this function is called before SDL_Init, SDL will generate an + * SDL_CONTROLLERDEVICEADDED event for matching controllers that are plugged + * in at the time that SDL_Init is called. + * + * \param mappingString the mapping string. + * \returns 1 if a new mapping is added, 0 if an existing mapping is updated, + * -1 on error; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerMapping + * \sa SDL_GameControllerMappingForGUID + * \sa SDL_CONTROLLERDEVICEADDED + */ +extern DECLSPEC int SDLCALL SDL_GameControllerAddMapping(const char* mappingString); + +/** + * Get the number of mappings installed. + * + * \returns the number of mappings. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerNumMappings(void); + +/** + * Get the mapping at a particular index. + * + * \returns the mapping string. Must be freed with SDL_free(). Returns NULL if + * the index is out of range. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC char * SDLCALL SDL_GameControllerMappingForIndex(int mapping_index); + +/** + * Get the game controller mapping string for a given GUID. + * + * The returned string must be freed with SDL_free(). + * + * \param guid a structure containing the GUID for which a mapping is desired. + * \returns a mapping string or NULL on error; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetDeviceGUID + * \sa SDL_JoystickGetGUID + */ +extern DECLSPEC char * SDLCALL SDL_GameControllerMappingForGUID(SDL_JoystickGUID guid); + +/** + * Get the current mapping of a Game Controller. + * + * The returned string must be freed with SDL_free(). + * + * Details about mappings are discussed with SDL_GameControllerAddMapping(). + * + * \param gamecontroller the game controller you want to get the current + * mapping for. + * \returns a string that has the controller's mapping or NULL if no mapping + * is available; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerAddMapping + * \sa SDL_GameControllerMappingForGUID + */ +extern DECLSPEC char * SDLCALL SDL_GameControllerMapping(SDL_GameController *gamecontroller); + +/** + * Check if the given joystick is supported by the game controller interface. + * + * `joystick_index` is the same as the `device_index` passed to + * SDL_JoystickOpen(). + * + * \param joystick_index the device_index of a device, up to + * SDL_NumJoysticks(). + * \returns SDL_TRUE if the given joystick is supported by the game controller + * interface, SDL_FALSE if it isn't or it's an invalid index. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerNameForIndex + * \sa SDL_GameControllerOpen + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index); + +/** + * Get the implementation dependent name for the game controller. + * + * This function can be called before any controllers are opened. + * + * `joystick_index` is the same as the `device_index` passed to + * SDL_JoystickOpen(). + * + * \param joystick_index the device_index of a device, from zero to + * SDL_NumJoysticks()-1. + * \returns the implementation-dependent name for the game controller, or NULL + * if there is no name or the index is invalid. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerName + * \sa SDL_GameControllerOpen + * \sa SDL_IsGameController + */ +extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index); + +/** + * Get the implementation dependent path for the game controller. + * + * This function can be called before any controllers are opened. + * + * `joystick_index` is the same as the `device_index` passed to + * SDL_JoystickOpen(). + * + * \param joystick_index the device_index of a device, from zero to + * SDL_NumJoysticks()-1. + * \returns the implementation-dependent path for the game controller, or NULL + * if there is no path or the index is invalid. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GameControllerPath + */ +extern DECLSPEC const char *SDLCALL SDL_GameControllerPathForIndex(int joystick_index); + +/** + * Get the type of a game controller. + * + * This can be called before any controllers are opened. + * + * \param joystick_index the device_index of a device, from zero to + * SDL_NumJoysticks()-1. + * \returns the controller type. + * + * \since This function is available since SDL 2.0.12. + */ +extern DECLSPEC SDL_GameControllerType SDLCALL SDL_GameControllerTypeForIndex(int joystick_index); + +/** + * Get the mapping of a game controller. + * + * This can be called before any controllers are opened. + * + * \param joystick_index the device_index of a device, from zero to + * SDL_NumJoysticks()-1. + * \returns the mapping string. Must be freed with SDL_free(). Returns NULL if + * no mapping is available. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC char *SDLCALL SDL_GameControllerMappingForDeviceIndex(int joystick_index); + +/** + * Open a game controller for use. + * + * `joystick_index` is the same as the `device_index` passed to + * SDL_JoystickOpen(). + * + * The index passed as an argument refers to the N'th game controller on the + * system. This index is not the value which will identify this controller in + * future controller events. The joystick's instance id (SDL_JoystickID) will + * be used there instead. + * + * \param joystick_index the device_index of a device, up to + * SDL_NumJoysticks(). + * \returns a gamecontroller identifier or NULL if an error occurred; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerClose + * \sa SDL_GameControllerNameForIndex + * \sa SDL_IsGameController + */ +extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerOpen(int joystick_index); + +/** + * Get the SDL_GameController associated with an instance id. + * + * \param joyid the instance id to get the SDL_GameController for. + * \returns an SDL_GameController on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.4. + */ +extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerFromInstanceID(SDL_JoystickID joyid); + +/** + * Get the SDL_GameController associated with a player index. + * + * Please note that the player index is _not_ the device index, nor is it the + * instance id! + * + * \param player_index the player index, which is not the device index or the + * instance id! + * \returns the SDL_GameController associated with a player index. + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_GameControllerGetPlayerIndex + * \sa SDL_GameControllerSetPlayerIndex + */ +extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerFromPlayerIndex(int player_index); + +/** + * Get the implementation-dependent name for an opened game controller. + * + * This is the same name as returned by SDL_GameControllerNameForIndex(), but + * it takes a controller identifier instead of the (unstable) device index. + * + * \param gamecontroller a game controller identifier previously returned by + * SDL_GameControllerOpen(). + * \returns the implementation dependent name for the game controller, or NULL + * if there is no name or the identifier passed is invalid. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerNameForIndex + * \sa SDL_GameControllerOpen + */ +extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller); + +/** + * Get the implementation-dependent path for an opened game controller. + * + * This is the same path as returned by SDL_GameControllerNameForIndex(), but + * it takes a controller identifier instead of the (unstable) device index. + * + * \param gamecontroller a game controller identifier previously returned by + * SDL_GameControllerOpen(). + * \returns the implementation dependent path for the game controller, or NULL + * if there is no path or the identifier passed is invalid. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GameControllerPathForIndex + */ +extern DECLSPEC const char *SDLCALL SDL_GameControllerPath(SDL_GameController *gamecontroller); + +/** + * Get the type of this currently opened controller + * + * This is the same name as returned by SDL_GameControllerTypeForIndex(), but + * it takes a controller identifier instead of the (unstable) device index. + * + * \param gamecontroller the game controller object to query. + * \returns the controller type. + * + * \since This function is available since SDL 2.0.12. + */ +extern DECLSPEC SDL_GameControllerType SDLCALL SDL_GameControllerGetType(SDL_GameController *gamecontroller); + +/** + * Get the player index of an opened game controller. + * + * For XInput controllers this returns the XInput user index. + * + * \param gamecontroller the game controller object to query. + * \returns the player index for controller, or -1 if it's not available. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller); + +/** + * Set the player index of an opened game controller. + * + * \param gamecontroller the game controller object to adjust. + * \param player_index Player index to assign to this controller, or -1 to + * clear the player index and turn off player LEDs. + * + * \since This function is available since SDL 2.0.12. + */ +extern DECLSPEC void SDLCALL SDL_GameControllerSetPlayerIndex(SDL_GameController *gamecontroller, int player_index); + +/** + * Get the USB vendor ID of an opened controller, if available. + * + * If the vendor ID isn't available this function returns 0. + * + * \param gamecontroller the game controller object to query. + * \return the USB vendor ID, or zero if unavailable. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_GameControllerGetVendor(SDL_GameController *gamecontroller); + +/** + * Get the USB product ID of an opened controller, if available. + * + * If the product ID isn't available this function returns 0. + * + * \param gamecontroller the game controller object to query. + * \return the USB product ID, or zero if unavailable. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_GameControllerGetProduct(SDL_GameController *gamecontroller); + +/** + * Get the product version of an opened controller, if available. + * + * If the product version isn't available this function returns 0. + * + * \param gamecontroller the game controller object to query. + * \return the USB product version, or zero if unavailable. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_GameControllerGetProductVersion(SDL_GameController *gamecontroller); + +/** + * Get the firmware version of an opened controller, if available. + * + * If the firmware version isn't available this function returns 0. + * + * \param gamecontroller the game controller object to query. + * \return the controller firmware version, or zero if unavailable. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC Uint16 SDLCALL SDL_GameControllerGetFirmwareVersion(SDL_GameController *gamecontroller); + +/** + * Get the serial number of an opened controller, if available. + * + * Returns the serial number of the controller, or NULL if it is not + * available. + * + * \param gamecontroller the game controller object to query. + * \return the serial number, or NULL if unavailable. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC const char * SDLCALL SDL_GameControllerGetSerial(SDL_GameController *gamecontroller); + +/** + * Get the Steam Input handle of an opened controller, if available. + * + * Returns an InputHandle_t for the controller that can be used with Steam + * Input API: https://partner.steamgames.com/doc/api/ISteamInput + * + * \param gamecontroller the game controller object to query. + * \returns the gamepad handle, or 0 if unavailable. + * + * \since This function is available since SDL 2.30.0. + */ +extern DECLSPEC Uint64 SDLCALL SDL_GameControllerGetSteamHandle(SDL_GameController *gamecontroller); + + +/** + * Check if a controller has been opened and is currently connected. + * + * \param gamecontroller a game controller identifier previously returned by + * SDL_GameControllerOpen(). + * \returns SDL_TRUE if the controller has been opened and is currently + * connected, or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerClose + * \sa SDL_GameControllerOpen + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerGetAttached(SDL_GameController *gamecontroller); + +/** + * Get the Joystick ID from a Game Controller. + * + * This function will give you a SDL_Joystick object, which allows you to use + * the SDL_Joystick functions with a SDL_GameController object. This would be + * useful for getting a joystick's position at any given time, even if it + * hasn't moved (moving it would produce an event, which would have the axis' + * value). + * + * The pointer returned is owned by the SDL_GameController. You should not + * call SDL_JoystickClose() on it, for example, since doing so will likely + * cause SDL to crash. + * + * \param gamecontroller the game controller object that you want to get a + * joystick from. + * \returns a SDL_Joystick object; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_Joystick *SDLCALL SDL_GameControllerGetJoystick(SDL_GameController *gamecontroller); + +/** + * Query or change current state of Game Controller events. + * + * If controller events are disabled, you must call SDL_GameControllerUpdate() + * yourself and check the state of the controller when you want controller + * information. + * + * Any number can be passed to SDL_GameControllerEventState(), but only -1, 0, + * and 1 will have any effect. Other numbers will just be returned. + * + * \param state can be one of `SDL_QUERY`, `SDL_IGNORE`, or `SDL_ENABLE`. + * \returns the same value passed to the function, with exception to -1 + * (SDL_QUERY), which will return the current state. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickEventState + */ +extern DECLSPEC int SDLCALL SDL_GameControllerEventState(int state); + +/** + * Manually pump game controller updates if not using the loop. + * + * This function is called automatically by the event loop if events are + * enabled. Under such circumstances, it will not be necessary to call this + * function. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_GameControllerUpdate(void); + + +/** + * The list of axes available from a controller + * + * Thumbstick axis values range from SDL_JOYSTICK_AXIS_MIN to + * SDL_JOYSTICK_AXIS_MAX, and are centered within ~8000 of zero, though + * advanced UI will allow users to set or autodetect the dead zone, which + * varies between controllers. + * + * Trigger axis values range from 0 (released) to SDL_JOYSTICK_AXIS_MAX (fully + * pressed) when reported by SDL_GameControllerGetAxis(). Note that this is + * not the same range that will be reported by the lower-level + * SDL_GetJoystickAxis(). + */ +typedef enum SDL_GameControllerAxis +{ + SDL_CONTROLLER_AXIS_INVALID = -1, + SDL_CONTROLLER_AXIS_LEFTX, + SDL_CONTROLLER_AXIS_LEFTY, + SDL_CONTROLLER_AXIS_RIGHTX, + SDL_CONTROLLER_AXIS_RIGHTY, + SDL_CONTROLLER_AXIS_TRIGGERLEFT, + SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + SDL_CONTROLLER_AXIS_MAX +} SDL_GameControllerAxis; + +/** + * Convert a string into SDL_GameControllerAxis enum. + * + * This function is called internally to translate SDL_GameController mapping + * strings for the underlying joystick device into the consistent + * SDL_GameController mapping. You do not normally need to call this function + * unless you are parsing SDL_GameController mappings in your own code. + * + * Note specially that "righttrigger" and "lefttrigger" map to + * `SDL_CONTROLLER_AXIS_TRIGGERRIGHT` and `SDL_CONTROLLER_AXIS_TRIGGERLEFT`, + * respectively. + * + * \param str string representing a SDL_GameController axis. + * \returns the SDL_GameControllerAxis enum corresponding to the input string, + * or `SDL_CONTROLLER_AXIS_INVALID` if no match was found. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetStringForAxis + */ +extern DECLSPEC SDL_GameControllerAxis SDLCALL SDL_GameControllerGetAxisFromString(const char *str); + +/** + * Convert from an SDL_GameControllerAxis enum to a string. + * + * The caller should not SDL_free() the returned string. + * + * \param axis an enum value for a given SDL_GameControllerAxis. + * \returns a string for the given axis, or NULL if an invalid axis is + * specified. The string returned is of the format used by + * SDL_GameController mapping strings. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetAxisFromString + */ +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForAxis(SDL_GameControllerAxis axis); + +/** + * Get the SDL joystick layer binding for a controller axis mapping. + * + * \param gamecontroller a game controller. + * \param axis an axis enum value (one of the SDL_GameControllerAxis values). + * \returns a SDL_GameControllerButtonBind describing the bind. On failure + * (like the given Controller axis doesn't exist on the device), its + * `.bindType` will be `SDL_CONTROLLER_BINDTYPE_NONE`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetBindForButton + */ +extern DECLSPEC SDL_GameControllerButtonBind SDLCALL +SDL_GameControllerGetBindForAxis(SDL_GameController *gamecontroller, + SDL_GameControllerAxis axis); + +/** + * Query whether a game controller has a given axis. + * + * This merely reports whether the controller's mapping defined this axis, as + * that is all the information SDL has about the physical device. + * + * \param gamecontroller a game controller. + * \param axis an axis enum value (an SDL_GameControllerAxis value). + * \returns SDL_TRUE if the controller has this axis, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL +SDL_GameControllerHasAxis(SDL_GameController *gamecontroller, SDL_GameControllerAxis axis); + +/** + * Get the current state of an axis control on a game controller. + * + * The axis indices start at index 0. + * + * For thumbsticks, the state is a value ranging from -32768 (up/left) to + * 32767 (down/right). + * + * Triggers range from 0 when released to 32767 when fully pressed, and never + * return a negative value. Note that this differs from the value reported by + * the lower-level SDL_JoystickGetAxis(), which normally uses the full range. + * + * \param gamecontroller a game controller. + * \param axis an axis index (one of the SDL_GameControllerAxis values). + * \returns axis state (including 0) on success or 0 (also) on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetButton + */ +extern DECLSPEC Sint16 SDLCALL +SDL_GameControllerGetAxis(SDL_GameController *gamecontroller, SDL_GameControllerAxis axis); + +/** + * The list of buttons available from a controller + */ +typedef enum SDL_GameControllerButton +{ + SDL_CONTROLLER_BUTTON_INVALID = -1, + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_GUIDE, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_MISC1, /* Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button */ + SDL_CONTROLLER_BUTTON_PADDLE1, /* Xbox Elite paddle P1 (upper left, facing the back) */ + SDL_CONTROLLER_BUTTON_PADDLE2, /* Xbox Elite paddle P3 (upper right, facing the back) */ + SDL_CONTROLLER_BUTTON_PADDLE3, /* Xbox Elite paddle P2 (lower left, facing the back) */ + SDL_CONTROLLER_BUTTON_PADDLE4, /* Xbox Elite paddle P4 (lower right, facing the back) */ + SDL_CONTROLLER_BUTTON_TOUCHPAD, /* PS4/PS5 touchpad button */ + SDL_CONTROLLER_BUTTON_MAX +} SDL_GameControllerButton; + +/** + * Convert a string into an SDL_GameControllerButton enum. + * + * This function is called internally to translate SDL_GameController mapping + * strings for the underlying joystick device into the consistent + * SDL_GameController mapping. You do not normally need to call this function + * unless you are parsing SDL_GameController mappings in your own code. + * + * \param str string representing a SDL_GameController axis. + * \returns the SDL_GameControllerButton enum corresponding to the input + * string, or `SDL_CONTROLLER_AXIS_INVALID` if no match was found. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_GameControllerButton SDLCALL SDL_GameControllerGetButtonFromString(const char *str); + +/** + * Convert from an SDL_GameControllerButton enum to a string. + * + * The caller should not SDL_free() the returned string. + * + * \param button an enum value for a given SDL_GameControllerButton. + * \returns a string for the given button, or NULL if an invalid button is + * specified. The string returned is of the format used by + * SDL_GameController mapping strings. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetButtonFromString + */ +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForButton(SDL_GameControllerButton button); + +/** + * Get the SDL joystick layer binding for a controller button mapping. + * + * \param gamecontroller a game controller. + * \param button an button enum value (an SDL_GameControllerButton value). + * \returns a SDL_GameControllerButtonBind describing the bind. On failure + * (like the given Controller button doesn't exist on the device), + * its `.bindType` will be `SDL_CONTROLLER_BINDTYPE_NONE`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetBindForAxis + */ +extern DECLSPEC SDL_GameControllerButtonBind SDLCALL +SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + +/** + * Query whether a game controller has a given button. + * + * This merely reports whether the controller's mapping defined this button, + * as that is all the information SDL has about the physical device. + * + * \param gamecontroller a game controller. + * \param button a button enum value (an SDL_GameControllerButton value). + * \returns SDL_TRUE if the controller has this button, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + +/** + * Get the current state of a button on a game controller. + * + * \param gamecontroller a game controller. + * \param button a button index (one of the SDL_GameControllerButton values). + * \returns 1 for pressed state or 0 for not pressed state or error; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerGetAxis + */ +extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + +/** + * Get the number of touchpads on a game controller. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerGetNumTouchpads(SDL_GameController *gamecontroller); + +/** + * Get the number of supported simultaneous fingers on a touchpad on a game + * controller. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerGetNumTouchpadFingers(SDL_GameController *gamecontroller, int touchpad); + +/** + * Get the current state of a finger on a touchpad on a game controller. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerGetTouchpadFinger(SDL_GameController *gamecontroller, int touchpad, int finger, Uint8 *state, float *x, float *y, float *pressure); + +/** + * Return whether a game controller has a particular sensor. + * + * \param gamecontroller The controller to query. + * \param type The type of sensor to query. + * \returns SDL_TRUE if the sensor exists, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasSensor(SDL_GameController *gamecontroller, SDL_SensorType type); + +/** + * Set whether data reporting for a game controller sensor is enabled. + * + * \param gamecontroller The controller to update. + * \param type The type of sensor to enable/disable. + * \param enabled Whether data reporting should be enabled. + * \returns 0 or -1 if an error occurred. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerSetSensorEnabled(SDL_GameController *gamecontroller, SDL_SensorType type, SDL_bool enabled); + +/** + * Query whether sensor data reporting is enabled for a game controller. + * + * \param gamecontroller The controller to query. + * \param type The type of sensor to query. + * \returns SDL_TRUE if the sensor is enabled, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerIsSensorEnabled(SDL_GameController *gamecontroller, SDL_SensorType type); + +/** + * Get the data rate (number of events per second) of a game controller + * sensor. + * + * \param gamecontroller The controller to query. + * \param type The type of sensor to query. + * \return the data rate, or 0.0f if the data rate is not available. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC float SDLCALL SDL_GameControllerGetSensorDataRate(SDL_GameController *gamecontroller, SDL_SensorType type); + +/** + * Get the current state of a game controller sensor. + * + * The number of values and interpretation of the data is sensor dependent. + * See SDL_sensor.h for the details for each type of sensor. + * + * \param gamecontroller The controller to query. + * \param type The type of sensor to query. + * \param data A pointer filled with the current sensor state. + * \param num_values The number of values to write to data. + * \return 0 or -1 if an error occurred. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values); + +/** + * Get the current state of a game controller sensor with the timestamp of the + * last update. + * + * The number of values and interpretation of the data is sensor dependent. + * See SDL_sensor.h for the details for each type of sensor. + * + * \param gamecontroller The controller to query. + * \param type The type of sensor to query. + * \param timestamp A pointer filled with the timestamp in microseconds of the + * current sensor reading if available, or 0 if not. + * \param data A pointer filled with the current sensor state. + * \param num_values The number of values to write to data. + * \return 0 or -1 if an error occurred. + * + * \since This function is available since SDL 2.26.0. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorDataWithTimestamp(SDL_GameController *gamecontroller, SDL_SensorType type, Uint64 *timestamp, float *data, int num_values); + +/** + * Start a rumble effect on a game controller. + * + * Each call to this function cancels any previous rumble effect, and calling + * it with 0 intensity stops any rumbling. + * + * \param gamecontroller The controller to vibrate. + * \param low_frequency_rumble The intensity of the low frequency (left) + * rumble motor, from 0 to 0xFFFF. + * \param high_frequency_rumble The intensity of the high frequency (right) + * rumble motor, from 0 to 0xFFFF. + * \param duration_ms The duration of the rumble effect, in milliseconds. + * \returns 0, or -1 if rumble isn't supported on this controller. + * + * \since This function is available since SDL 2.0.9. + * + * \sa SDL_GameControllerHasRumble + */ +extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); + +/** + * Start a rumble effect in the game controller's triggers. + * + * Each call to this function cancels any previous trigger rumble effect, and + * calling it with 0 intensity stops any rumbling. + * + * Note that this is rumbling of the _triggers_ and not the game controller as + * a whole. This is currently only supported on Xbox One controllers. If you + * want the (more common) whole-controller rumble, use + * SDL_GameControllerRumble() instead. + * + * \param gamecontroller The controller to vibrate. + * \param left_rumble The intensity of the left trigger rumble motor, from 0 + * to 0xFFFF. + * \param right_rumble The intensity of the right trigger rumble motor, from 0 + * to 0xFFFF. + * \param duration_ms The duration of the rumble effect, in milliseconds. + * \returns 0, or -1 if trigger rumble isn't supported on this controller. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_GameControllerHasRumbleTriggers + */ +extern DECLSPEC int SDLCALL SDL_GameControllerRumbleTriggers(SDL_GameController *gamecontroller, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms); + +/** + * Query whether a game controller has an LED. + * + * \param gamecontroller The controller to query. + * \returns SDL_TRUE, or SDL_FALSE if this controller does not have a + * modifiable LED. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasLED(SDL_GameController *gamecontroller); + +/** + * Query whether a game controller has rumble support. + * + * \param gamecontroller The controller to query. + * \returns SDL_TRUE, or SDL_FALSE if this controller does not have rumble + * support. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_GameControllerRumble + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasRumble(SDL_GameController *gamecontroller); + +/** + * Query whether a game controller has rumble support on triggers. + * + * \param gamecontroller The controller to query. + * \returns SDL_TRUE, or SDL_FALSE if this controller does not have trigger + * rumble support. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_GameControllerRumbleTriggers + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasRumbleTriggers(SDL_GameController *gamecontroller); + +/** + * Update a game controller's LED color. + * + * \param gamecontroller The controller to update. + * \param red The intensity of the red LED. + * \param green The intensity of the green LED. + * \param blue The intensity of the blue LED. + * \returns 0, or -1 if this controller does not have a modifiable LED. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerSetLED(SDL_GameController *gamecontroller, Uint8 red, Uint8 green, Uint8 blue); + +/** + * Send a controller specific effect packet + * + * \param gamecontroller The controller to affect. + * \param data The data to send to the controller. + * \param size The size of the data to send to the controller. + * \returns 0, or -1 if this controller or driver doesn't support effect + * packets. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerSendEffect(SDL_GameController *gamecontroller, const void *data, int size); + +/** + * Close a game controller previously opened with SDL_GameControllerOpen(). + * + * \param gamecontroller a game controller identifier previously returned by + * SDL_GameControllerOpen(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerOpen + */ +extern DECLSPEC void SDLCALL SDL_GameControllerClose(SDL_GameController *gamecontroller); + +/** + * Return the sfSymbolsName for a given button on a game controller on Apple + * platforms. + * + * \param gamecontroller the controller to query. + * \param button a button on the game controller. + * \returns the sfSymbolsName or NULL if the name can't be found. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_GameControllerGetAppleSFSymbolsNameForAxis + */ +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetAppleSFSymbolsNameForButton(SDL_GameController *gamecontroller, SDL_GameControllerButton button); + +/** + * Return the sfSymbolsName for a given axis on a game controller on Apple + * platforms. + * + * \param gamecontroller the controller to query. + * \param axis an axis on the game controller. + * \returns the sfSymbolsName or NULL if the name can't be found. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_GameControllerGetAppleSFSymbolsNameForButton + */ +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetAppleSFSymbolsNameForAxis(SDL_GameController *gamecontroller, SDL_GameControllerAxis axis); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_gamecontroller_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_gesture.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_gesture.h new file mode 100644 index 00000000..acfa56f3 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_gesture.h @@ -0,0 +1,117 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryGesture + * + * Include file for SDL gesture event handling. + */ + +#ifndef SDL_gesture_h_ +#define SDL_gesture_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "SDL_touch.h" + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef Sint64 SDL_GestureID; + +/* Function prototypes */ + +/** + * Begin recording a gesture on a specified touch device or all touch devices. + * + * If the parameter `touchId` is -1 (i.e., all devices), this function will + * always return 1, regardless of whether there actually are any devices. + * + * \param touchId the touch device id, or -1 for all touch devices. + * \returns 1 on success or 0 if the specified device could not be found. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTouchDevice + */ +extern DECLSPEC int SDLCALL SDL_RecordGesture(SDL_TouchID touchId); + + +/** + * Save all currently loaded Dollar Gesture templates. + * + * \param dst a SDL_RWops to save to. + * \returns the number of saved templates on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadDollarTemplates + * \sa SDL_SaveDollarTemplate + */ +extern DECLSPEC int SDLCALL SDL_SaveAllDollarTemplates(SDL_RWops *dst); + +/** + * Save a currently loaded Dollar Gesture template. + * + * \param gestureId a gesture id. + * \param dst a SDL_RWops to save to. + * \returns 1 on success or 0 on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadDollarTemplates + * \sa SDL_SaveAllDollarTemplates + */ +extern DECLSPEC int SDLCALL SDL_SaveDollarTemplate(SDL_GestureID gestureId,SDL_RWops *dst); + + +/** + * Load Dollar Gesture templates from a file. + * + * \param touchId a touch id. + * \param src a SDL_RWops to load from. + * \returns the number of loaded templates on success or a negative error code + * (or 0) on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SaveAllDollarTemplates + * \sa SDL_SaveDollarTemplate + */ +extern DECLSPEC int SDLCALL SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_gesture_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_guid.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_guid.h new file mode 100644 index 00000000..fd9a50e3 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_guid.h @@ -0,0 +1,107 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: GUID */ + +/** + * # CategoryGUID + * + * A GUID is a 128-bit value that represents something that is uniquely + * identifiable by this value: "globally unique." + */ + +#ifndef SDL_guid_h_ +#define SDL_guid_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * An SDL_GUID is a 128-bit identifier. + * + * This is an acronym for "Globally Unique ID." + * + * While a GUID can be used to assign a unique value to almost anything, in + * SDL these are largely used to identify input devices across runs of SDL + * programs on the same platform.If the device is detached and then + * re-attached to a different port, or if the base system is rebooted, the + * device should still report the same GUID. + * + * GUIDs are as precise as possible but are not guaranteed to distinguish + * physically distinct but equivalent devices. For example, two game + * controllers from the same vendor with the same product ID and revision may + * have the same GUID. + * + * GUIDs may be platform-dependent (i.e., the same device may report different + * GUIDs on different operating systems). + */ +typedef struct SDL_GUID { + Uint8 data[16]; +} SDL_GUID; + +/* Function prototypes */ + +/** + * Get an ASCII string representation for a given SDL_GUID. + * + * You should supply at least 33 bytes for pszGUID. + * + * \param guid the SDL_GUID you wish to convert to string. + * \param pszGUID buffer in which to write the ASCII string. + * \param cbGUID the size of pszGUID. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GUIDFromString + */ +extern DECLSPEC void SDLCALL SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID); + +/** + * Convert a GUID string into a SDL_GUID structure. + * + * Performs no error checking. If this function is given a string containing + * an invalid GUID, the function will silently succeed, but the GUID generated + * will not be useful. + * + * \param pchGUID string containing an ASCII representation of a GUID. + * \returns a SDL_GUID structure. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GUIDToString + */ +extern DECLSPEC SDL_GUID SDLCALL SDL_GUIDFromString(const char *pchGUID); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_guid_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_haptic.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_haptic.h new file mode 100644 index 00000000..f679c573 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_haptic.h @@ -0,0 +1,1354 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryHaptic + * + * SDL haptic subsystem allows you to control haptic (force feedback) devices. + * + * The basic usage is as follows: + * + * - Initialize the subsystem (SDL_INIT_HAPTIC). + * - Open a haptic device. + * - SDL_HapticOpen() to open from index. + * - SDL_HapticOpenFromJoystick() to open from an existing joystick. + * - Create an effect (SDL_HapticEffect). + * - Upload the effect with SDL_HapticNewEffect(). + * - Run the effect with SDL_HapticRunEffect(). + * - (optional) Free the effect with SDL_HapticDestroyEffect(). + * - Close the haptic device with SDL_HapticClose(). + * + * Simple rumble example: + * + * ```c + * SDL_Haptic *haptic; + * + * // Open the device + * haptic = SDL_HapticOpen( 0 ); + * if (haptic == NULL) + * return -1; + * + * // Initialize simple rumble + * if (SDL_HapticRumbleInit( haptic ) != 0) + * return -1; + * + * // Play effect at 50% strength for 2 seconds + * if (SDL_HapticRumblePlay( haptic, 0.5, 2000 ) != 0) + * return -1; + * SDL_Delay( 2000 ); + * + * // Clean up + * SDL_HapticClose( haptic ); + * ``` + * + * Complete example: + * + * ```c + * int test_haptic( SDL_Joystick * joystick ) { + * SDL_Haptic *haptic; + * SDL_HapticEffect effect; + * int effect_id; + * + * // Open the device + * haptic = SDL_HapticOpenFromJoystick( joystick ); + * if (haptic == NULL) return -1; // Most likely joystick isn't haptic + * + * // See if it can do sine waves + * if ((SDL_HapticQuery(haptic) & SDL_HAPTIC_SINE)==0) { + * SDL_HapticClose(haptic); // No sine effect + * return -1; + * } + * + * // Create the effect + * SDL_memset( &effect, 0, sizeof(SDL_HapticEffect) ); // 0 is safe default + * effect.type = SDL_HAPTIC_SINE; + * effect.periodic.direction.type = SDL_HAPTIC_POLAR; // Polar coordinates + * effect.periodic.direction.dir[0] = 18000; // Force comes from south + * effect.periodic.period = 1000; // 1000 ms + * effect.periodic.magnitude = 20000; // 20000/32767 strength + * effect.periodic.length = 5000; // 5 seconds long + * effect.periodic.attack_length = 1000; // Takes 1 second to get max strength + * effect.periodic.fade_length = 1000; // Takes 1 second to fade away + * + * // Upload the effect + * effect_id = SDL_HapticNewEffect( haptic, &effect ); + * + * // Test the effect + * SDL_HapticRunEffect( haptic, effect_id, 1 ); + * SDL_Delay( 5000); // Wait for the effect to finish + * + * // We destroy the effect, although closing the device also does this + * SDL_HapticDestroyEffect( haptic, effect_id ); + * + * // Close the device + * SDL_HapticClose(haptic); + * + * return 0; // Success + * } + * ``` + */ + +#ifndef SDL_haptic_h_ +#define SDL_haptic_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_joystick.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* FIXME: For SDL 2.1, adjust all the magnitude variables to be Uint16 (0xFFFF). + * + * At the moment the magnitude variables are mixed between signed/unsigned, and + * it is also not made clear that ALL of those variables expect a max of 0x7FFF. + * + * Some platforms may have higher precision than that (Linux FF, Windows XInput) + * so we should fix the inconsistency in favor of higher possible precision, + * adjusting for platforms that use different scales. + * -flibit + */ + +/** + * \typedef SDL_Haptic + * + * \brief The haptic structure used to identify an SDL haptic. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticOpenFromJoystick + * \sa SDL_HapticClose + */ +struct _SDL_Haptic; +typedef struct _SDL_Haptic SDL_Haptic; + + +/** + * \name Haptic features + * + * Different haptic features a device can have. + */ +/* @{ */ + +/** + * \name Haptic effects + */ +/* @{ */ + +/** + * Constant effect supported. + * + * Constant haptic effect. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_CONSTANT (1u<<0) + +/** + * Sine wave effect supported. + * + * Periodic haptic effect that simulates sine waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SINE (1u<<1) + +/** + * Left/Right effect supported. + * + * Haptic effect for direct control over high/low frequency motors. + * + * \sa SDL_HapticLeftRight + */ +#define SDL_HAPTIC_LEFTRIGHT (1u<<2) + +/* !!! FIXME: put this back when we have more bits in 2.1 */ +/* #define SDL_HAPTIC_SQUARE (1<<2) */ + +/** + * Triangle wave effect supported. + * + * Periodic haptic effect that simulates triangular waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_TRIANGLE (1u<<3) + +/** + * Sawtoothup wave effect supported. + * + * Periodic haptic effect that simulates saw tooth up waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SAWTOOTHUP (1u<<4) + +/** + * Sawtoothdown wave effect supported. + * + * Periodic haptic effect that simulates saw tooth down waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SAWTOOTHDOWN (1u<<5) + +/** + * Ramp effect supported. + * + * Ramp haptic effect. + * + * \sa SDL_HapticRamp + */ +#define SDL_HAPTIC_RAMP (1u<<6) + +/** + * Spring effect supported - uses axes position. + * + * Condition haptic effect that simulates a spring. Effect is based on the + * axes position. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_SPRING (1u<<7) + +/** + * Damper effect supported - uses axes velocity. + * + * Condition haptic effect that simulates dampening. Effect is based on the + * axes velocity. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_DAMPER (1u<<8) + +/** + * Inertia effect supported - uses axes acceleration. + * + * Condition haptic effect that simulates inertia. Effect is based on the axes + * acceleration. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_INERTIA (1u<<9) + +/** + * Friction effect supported - uses axes movement. + * + * Condition haptic effect that simulates friction. Effect is based on the + * axes movement. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_FRICTION (1u<<10) + +/** + * Custom effect is supported. + * + * User defined custom haptic effect. + */ +#define SDL_HAPTIC_CUSTOM (1u<<11) + +/* @} *//* Haptic effects */ + +/* These last few are features the device has, not effects */ + +/** + * Device can set global gain. + * + * Device supports setting the global gain. + * + * \sa SDL_HapticSetGain + */ +#define SDL_HAPTIC_GAIN (1u<<12) + +/** + * Device can set autocenter. + * + * Device supports setting autocenter. + * + * \sa SDL_HapticSetAutocenter + */ +#define SDL_HAPTIC_AUTOCENTER (1u<<13) + +/** + * Device can be queried for effect status. + * + * Device supports querying effect status. + * + * \sa SDL_HapticGetEffectStatus + */ +#define SDL_HAPTIC_STATUS (1u<<14) + +/** + * Device can be paused. + * + * Devices supports being paused. + * + * \sa SDL_HapticPause + * \sa SDL_HapticUnpause + */ +#define SDL_HAPTIC_PAUSE (1u<<15) + + +/** + * \name Direction encodings + */ +/* @{ */ + +/** + * Uses polar coordinates for the direction. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_POLAR 0 + +/** + * Uses cartesian coordinates for the direction. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_CARTESIAN 1 + +/** + * Uses spherical coordinates for the direction. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_SPHERICAL 2 + +/** + * Use this value to play an effect on the steering wheel axis. + * + * This provides better compatibility across platforms and devices as SDL will + * guess the correct axis. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_STEERING_AXIS 3 + +/* @} *//* Direction encodings */ + +/* @} *//* Haptic features */ + +/* + * Misc defines. + */ + +/** + * Used to play a device an infinite number of times. + * + * \sa SDL_HapticRunEffect + */ +#define SDL_HAPTIC_INFINITY 4294967295U + + +/** + * Structure that represents a haptic direction. + * + * This is the direction where the force comes from, instead of the direction + * in which the force is exerted. + * + * Directions can be specified by: + * + * - SDL_HAPTIC_POLAR : Specified by polar coordinates. + * - SDL_HAPTIC_CARTESIAN : Specified by cartesian coordinates. + * - SDL_HAPTIC_SPHERICAL : Specified by spherical coordinates. + * + * Cardinal directions of the haptic device are relative to the positioning of + * the device. North is considered to be away from the user. + * + * The following diagram represents the cardinal directions: + * + * ``` + * .--. + * |__| .-------. + * |=.| |.-----.| + * |--| || || + * | | |'-----'| + * |__|~')_____(' + * [ COMPUTER ] + * + * + * North (0,-1) + * ^ + * | + * | + * (-1,0) West <----[ HAPTIC ]----> East (1,0) + * | + * | + * v + * South (0,1) + * + * + * [ USER ] + * \|||/ + * (o o) + * ---ooO-(_)-Ooo--- + * ``` + * + * If type is SDL_HAPTIC_POLAR, direction is encoded by hundredths of a degree + * starting north and turning clockwise. SDL_HAPTIC_POLAR only uses the first + * `dir` parameter. The cardinal directions would be: + * + * - North: 0 (0 degrees) + * - East: 9000 (90 degrees) + * - South: 18000 (180 degrees) + * - West: 27000 (270 degrees) + * + * If type is SDL_HAPTIC_CARTESIAN, direction is encoded by three positions (X + * axis, Y axis and Z axis (with 3 axes)). SDL_HAPTIC_CARTESIAN uses the first + * three `dir` parameters. The cardinal directions would be: + * + * - North: 0,-1, 0 + * - East: 1, 0, 0 + * - South: 0, 1, 0 + * - West: -1, 0, 0 + * + * The Z axis represents the height of the effect if supported, otherwise it's + * unused. In cartesian encoding (1, 2) would be the same as (2, 4), you can + * use any multiple you want, only the direction matters. + * + * If type is SDL_HAPTIC_SPHERICAL, direction is encoded by two rotations. The + * first two `dir` parameters are used. The `dir` parameters are as follows + * (all values are in hundredths of degrees): + * + * - Degrees from (1, 0) rotated towards (0, 1). + * - Degrees towards (0, 0, 1) (device needs at least 3 axes). + * + * Example of force coming from the south with all encodings (force coming + * from the south means the user will have to pull the stick to counteract): + * + * ```c + * SDL_HapticDirection direction; + * + * // Cartesian directions + * direction.type = SDL_HAPTIC_CARTESIAN; // Using cartesian direction encoding. + * direction.dir[0] = 0; // X position + * direction.dir[1] = 1; // Y position + * // Assuming the device has 2 axes, we don't need to specify third parameter. + * + * // Polar directions + * direction.type = SDL_HAPTIC_POLAR; // We'll be using polar direction encoding. + * direction.dir[0] = 18000; // Polar only uses first parameter + * + * // Spherical coordinates + * direction.type = SDL_HAPTIC_SPHERICAL; // Spherical encoding + * direction.dir[0] = 9000; // Since we only have two axes we don't need more parameters. + * ``` + * + * \sa SDL_HAPTIC_POLAR + * \sa SDL_HAPTIC_CARTESIAN + * \sa SDL_HAPTIC_SPHERICAL + * \sa SDL_HAPTIC_STEERING_AXIS + * \sa SDL_HapticEffect + * \sa SDL_HapticNumAxes + */ +typedef struct SDL_HapticDirection +{ + Uint8 type; /**< The type of encoding. */ + Sint32 dir[3]; /**< The encoded direction. */ +} SDL_HapticDirection; + + +/** + * A structure containing a template for a Constant effect. + * + * This struct is exclusively for the SDL_HAPTIC_CONSTANT effect. + * + * A constant effect applies a constant force in the specified direction to + * the joystick. + * + * \sa SDL_HAPTIC_CONSTANT + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticConstant +{ + /* Header */ + Uint16 type; /**< SDL_HAPTIC_CONSTANT */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Constant */ + Sint16 level; /**< Strength of the constant effect. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticConstant; + +/** + * A structure containing a template for a Periodic effect. + * + * The struct handles the following effects: + * + * - SDL_HAPTIC_SINE + * - SDL_HAPTIC_SQUARE + * - SDL_HAPTIC_TRIANGLE + * - SDL_HAPTIC_SAWTOOTHUP + * - SDL_HAPTIC_SAWTOOTHDOWN + * + * A periodic effect consists in a wave-shaped effect that repeats itself over + * time. The type determines the shape of the wave and the parameters + * determine the dimensions of the wave. + * + * Phase is given by hundredth of a degree meaning that giving the phase a + * value of 9000 will displace it 25% of its period. Here are sample values: + * + * - 0: No phase displacement. + * - 9000: Displaced 25% of its period. + * - 18000: Displaced 50% of its period. + * - 27000: Displaced 75% of its period. + * - 36000: Displaced 100% of its period, same as 0, but 0 is preferred. + * + * Examples: + * + * ``` + * SDL_HAPTIC_SINE + * __ __ __ __ + * / \ / \ / \ / + * / \__/ \__/ \__/ + * + * SDL_HAPTIC_SQUARE + * __ __ __ __ __ + * | | | | | | | | | | + * | |__| |__| |__| |__| | + * + * SDL_HAPTIC_TRIANGLE + * /\ /\ /\ /\ /\ + * / \ / \ / \ / \ / + * / \/ \/ \/ \/ + * + * SDL_HAPTIC_SAWTOOTHUP + * /| /| /| /| /| /| /| + * / | / | / | / | / | / | / | + * / |/ |/ |/ |/ |/ |/ | + * + * SDL_HAPTIC_SAWTOOTHDOWN + * \ |\ |\ |\ |\ |\ |\ | + * \ | \ | \ | \ | \ | \ | \ | + * \| \| \| \| \| \| \| + * ``` + * + * \sa SDL_HAPTIC_SINE + * \sa SDL_HAPTIC_LEFTRIGHT + * \sa SDL_HAPTIC_TRIANGLE + * \sa SDL_HAPTIC_SAWTOOTHUP + * \sa SDL_HAPTIC_SAWTOOTHDOWN + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticPeriodic +{ + /* Header */ + Uint16 type; /**< SDL_HAPTIC_SINE, SDL_HAPTIC_LEFTRIGHT, + SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP or + SDL_HAPTIC_SAWTOOTHDOWN */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Periodic */ + Uint16 period; /**< Period of the wave. */ + Sint16 magnitude; /**< Peak value; if negative, equivalent to 180 degrees extra phase shift. */ + Sint16 offset; /**< Mean value of the wave. */ + Uint16 phase; /**< Positive phase shift given by hundredth of a degree. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticPeriodic; + +/** + * A structure containing a template for a Condition effect. + * + * The struct handles the following effects: + * + * - SDL_HAPTIC_SPRING: Effect based on axes position. + * - SDL_HAPTIC_DAMPER: Effect based on axes velocity. + * - SDL_HAPTIC_INERTIA: Effect based on axes acceleration. + * - SDL_HAPTIC_FRICTION: Effect based on axes movement. + * + * Direction is handled by condition internals instead of a direction member. + * The condition effect specific members have three parameters. The first + * refers to the X axis, the second refers to the Y axis and the third refers + * to the Z axis. The right terms refer to the positive side of the axis and + * the left terms refer to the negative side of the axis. Please refer to the + * SDL_HapticDirection diagram for which side is positive and which is + * negative. + * + * \sa SDL_HapticDirection + * \sa SDL_HAPTIC_SPRING + * \sa SDL_HAPTIC_DAMPER + * \sa SDL_HAPTIC_INERTIA + * \sa SDL_HAPTIC_FRICTION + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticCondition +{ + /* Header */ + Uint16 type; /**< SDL_HAPTIC_SPRING, SDL_HAPTIC_DAMPER, + SDL_HAPTIC_INERTIA or SDL_HAPTIC_FRICTION */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Condition */ + Uint16 right_sat[3]; /**< Level when joystick is to the positive side; max 0xFFFF. */ + Uint16 left_sat[3]; /**< Level when joystick is to the negative side; max 0xFFFF. */ + Sint16 right_coeff[3]; /**< How fast to increase the force towards the positive side. */ + Sint16 left_coeff[3]; /**< How fast to increase the force towards the negative side. */ + Uint16 deadband[3]; /**< Size of the dead zone; max 0xFFFF: whole axis-range when 0-centered. */ + Sint16 center[3]; /**< Position of the dead zone. */ +} SDL_HapticCondition; + +/** + * A structure containing a template for a Ramp effect. + * + * This struct is exclusively for the SDL_HAPTIC_RAMP effect. + * + * The ramp effect starts at start strength and ends at end strength. It + * augments in linear fashion. If you use attack and fade with a ramp the + * effects get added to the ramp effect making the effect become quadratic + * instead of linear. + * + * \sa SDL_HAPTIC_RAMP + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticRamp +{ + /* Header */ + Uint16 type; /**< SDL_HAPTIC_RAMP */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Ramp */ + Sint16 start; /**< Beginning strength level. */ + Sint16 end; /**< Ending strength level. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticRamp; + +/** + * A structure containing a template for a Left/Right effect. + * + * This struct is exclusively for the SDL_HAPTIC_LEFTRIGHT effect. + * + * The Left/Right effect is used to explicitly control the large and small + * motors, commonly found in modern game controllers. The small (right) motor + * is high frequency, and the large (left) motor is low frequency. + * + * \sa SDL_HAPTIC_LEFTRIGHT + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticLeftRight +{ + /* Header */ + Uint16 type; /**< SDL_HAPTIC_LEFTRIGHT */ + + /* Replay */ + Uint32 length; /**< Duration of the effect in milliseconds. */ + + /* Rumble */ + Uint16 large_magnitude; /**< Control of the large controller motor. */ + Uint16 small_magnitude; /**< Control of the small controller motor. */ +} SDL_HapticLeftRight; + +/** + * A structure containing a template for the SDL_HAPTIC_CUSTOM effect. + * + * This struct is exclusively for the SDL_HAPTIC_CUSTOM effect. + * + * A custom force feedback effect is much like a periodic effect, where the + * application can define its exact shape. You will have to allocate the data + * yourself. Data should consist of channels * samples Uint16 samples. + * + * If channels is one, the effect is rotated using the defined direction. + * Otherwise it uses the samples in data for the different axes. + * + * \sa SDL_HAPTIC_CUSTOM + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticCustom +{ + /* Header */ + Uint16 type; /**< SDL_HAPTIC_CUSTOM */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Custom */ + Uint8 channels; /**< Axes to use, minimum of one. */ + Uint16 period; /**< Sample periods. */ + Uint16 samples; /**< Amount of samples. */ + Uint16 *data; /**< Should contain channels*samples items. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticCustom; + +/** + * The generic template for any haptic effect. + * + * All values max at 32767 (0x7FFF). Signed values also can be negative. Time + * values unless specified otherwise are in milliseconds. + * + * You can also pass SDL_HAPTIC_INFINITY to length instead of a 0-32767 value. + * Neither delay, interval, attack_length nor fade_length support + * SDL_HAPTIC_INFINITY. Fade will also not be used since effect never ends. + * + * Additionally, the SDL_HAPTIC_RAMP effect does not support a duration of + * SDL_HAPTIC_INFINITY. + * + * Button triggers may not be supported on all devices, it is advised to not + * use them if possible. Buttons start at index 1 instead of index 0 like the + * joystick. + * + * If both attack_length and fade_level are 0, the envelope is not used, + * otherwise both values are used. + * + * Common parts: + * + * ```c + * // Replay - All effects have this + * Uint32 length; // Duration of effect (ms). + * Uint16 delay; // Delay before starting effect. + * + * // Trigger - All effects have this + * Uint16 button; // Button that triggers effect. + * Uint16 interval; // How soon before effect can be triggered again. + * + * // Envelope - All effects except condition effects have this + * Uint16 attack_length; // Duration of the attack (ms). + * Uint16 attack_level; // Level at the start of the attack. + * Uint16 fade_length; // Duration of the fade out (ms). + * Uint16 fade_level; // Level at the end of the fade. + * ``` + * + * Here we have an example of a constant effect evolution in time: + * + * ``` + * Strength + * ^ + * | + * | effect level --> _________________ + * | / \ + * | / \ + * | / \ + * | / \ + * | attack_level --> | \ + * | | | <--- fade_level + * | + * +--------------------------------------------------> Time + * [--] [---] + * attack_length fade_length + * + * [------------------][-----------------------] + * delay length + * ``` + * + * Note either the attack_level or the fade_level may be above the actual + * effect level. + * + * \sa SDL_HapticConstant + * \sa SDL_HapticPeriodic + * \sa SDL_HapticCondition + * \sa SDL_HapticRamp + * \sa SDL_HapticLeftRight + * \sa SDL_HapticCustom + */ +typedef union SDL_HapticEffect +{ + /* Common for all force feedback effects */ + Uint16 type; /**< Effect type. */ + SDL_HapticConstant constant; /**< Constant effect. */ + SDL_HapticPeriodic periodic; /**< Periodic effect. */ + SDL_HapticCondition condition; /**< Condition effect. */ + SDL_HapticRamp ramp; /**< Ramp effect. */ + SDL_HapticLeftRight leftright; /**< Left/Right effect. */ + SDL_HapticCustom custom; /**< Custom effect. */ +} SDL_HapticEffect; + + +/* Function prototypes */ + +/** + * Count the number of haptic devices attached to the system. + * + * \returns the number of haptic devices detected on the system or a negative + * error code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticName + */ +extern DECLSPEC int SDLCALL SDL_NumHaptics(void); + +/** + * Get the implementation dependent name of a haptic device. + * + * This can be called before any joysticks are opened. If no name can be + * found, this function returns NULL. + * + * \param device_index index of the device to query. + * \returns the name of the device or NULL on failure; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_NumHaptics + */ +extern DECLSPEC const char *SDLCALL SDL_HapticName(int device_index); + +/** + * Open a haptic device for use. + * + * The index passed as an argument refers to the N'th haptic device on this + * system. + * + * When opening a haptic device, its gain will be set to maximum and + * autocenter will be disabled. To modify these values use SDL_HapticSetGain() + * and SDL_HapticSetAutocenter(). + * + * \param device_index index of the device to open. + * \returns the device identifier or NULL on failure; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticClose + * \sa SDL_HapticIndex + * \sa SDL_HapticOpenFromJoystick + * \sa SDL_HapticOpenFromMouse + * \sa SDL_HapticPause + * \sa SDL_HapticSetAutocenter + * \sa SDL_HapticSetGain + * \sa SDL_HapticStopAll + */ +extern DECLSPEC SDL_Haptic *SDLCALL SDL_HapticOpen(int device_index); + +/** + * Check if the haptic device at the designated index has been opened. + * + * \param device_index the index of the device to query. + * \returns 1 if it has been opened, 0 if it hasn't or on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticIndex + * \sa SDL_HapticOpen + */ +extern DECLSPEC int SDLCALL SDL_HapticOpened(int device_index); + +/** + * Get the index of a haptic device. + * + * \param haptic the SDL_Haptic device to query. + * \returns the index of the specified haptic device or a negative error code + * on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticOpened + */ +extern DECLSPEC int SDLCALL SDL_HapticIndex(SDL_Haptic * haptic); + +/** + * Query whether or not the current mouse has haptic capabilities. + * + * \returns SDL_TRUE if the mouse is haptic or SDL_FALSE if it isn't. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticOpenFromMouse + */ +extern DECLSPEC int SDLCALL SDL_MouseIsHaptic(void); + +/** + * Try to open a haptic device from the current mouse. + * + * \returns the haptic device identifier or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticOpen + * \sa SDL_MouseIsHaptic + */ +extern DECLSPEC SDL_Haptic *SDLCALL SDL_HapticOpenFromMouse(void); + +/** + * Query if a joystick has haptic features. + * + * \param joystick the SDL_Joystick to test for haptic capabilities. + * \returns SDL_TRUE if the joystick is haptic, SDL_FALSE if it isn't, or a + * negative error code on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticOpenFromJoystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickIsHaptic(SDL_Joystick * joystick); + +/** + * Open a haptic device for use from a joystick device. + * + * You must still close the haptic device separately. It will not be closed + * with the joystick. + * + * When opened from a joystick you should first close the haptic device before + * closing the joystick device. If not, on some implementations the haptic + * device will also get unallocated and you'll be unable to use force feedback + * on that device. + * + * \param joystick the SDL_Joystick to create a haptic device from. + * \returns a valid haptic device identifier on success or NULL on failure; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticClose + * \sa SDL_HapticOpen + * \sa SDL_JoystickIsHaptic + */ +extern DECLSPEC SDL_Haptic *SDLCALL SDL_HapticOpenFromJoystick(SDL_Joystick * + joystick); + +/** + * Close a haptic device previously opened with SDL_HapticOpen(). + * + * \param haptic the SDL_Haptic device to close. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticOpen + */ +extern DECLSPEC void SDLCALL SDL_HapticClose(SDL_Haptic * haptic); + +/** + * Get the number of effects a haptic device can store. + * + * On some platforms this isn't fully supported, and therefore is an + * approximation. Always check to see if your created effect was actually + * created and do not rely solely on SDL_HapticNumEffects(). + * + * \param haptic the SDL_Haptic device to query. + * \returns the number of effects the haptic device can store or a negative + * error code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticNumEffectsPlaying + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticNumEffects(SDL_Haptic * haptic); + +/** + * Get the number of effects a haptic device can play at the same time. + * + * This is not supported on all platforms, but will always return a value. + * + * \param haptic the SDL_Haptic device to query maximum playing effects. + * \returns the number of effects the haptic device can play at the same time + * or a negative error code on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticNumEffects + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticNumEffectsPlaying(SDL_Haptic * haptic); + +/** + * Get the haptic device's supported features in bitwise manner. + * + * \param haptic the SDL_Haptic device to query. + * \returns a list of supported haptic features in bitwise manner (OR'd), or 0 + * on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticEffectSupported + * \sa SDL_HapticNumEffects + */ +extern DECLSPEC unsigned int SDLCALL SDL_HapticQuery(SDL_Haptic * haptic); + + +/** + * Get the number of haptic axes the device has. + * + * The number of haptic axes might be useful if working with the + * SDL_HapticDirection effect. + * + * \param haptic the SDL_Haptic device to query. + * \returns the number of axes on success or a negative error code on failure; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_HapticNumAxes(SDL_Haptic * haptic); + +/** + * Check to see if an effect is supported by a haptic device. + * + * \param haptic the SDL_Haptic device to query. + * \param effect the desired effect to query. + * \returns SDL_TRUE if effect is supported, SDL_FALSE if it isn't, or a + * negative error code on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticNewEffect + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticEffectSupported(SDL_Haptic * haptic, + SDL_HapticEffect * + effect); + +/** + * Create a new haptic effect on a specified device. + * + * \param haptic an SDL_Haptic device to create the effect on. + * \param effect an SDL_HapticEffect structure containing the properties of + * the effect to create. + * \returns the ID of the effect on success or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticDestroyEffect + * \sa SDL_HapticRunEffect + * \sa SDL_HapticUpdateEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticNewEffect(SDL_Haptic * haptic, + SDL_HapticEffect * effect); + +/** + * Update the properties of an effect. + * + * Can be used dynamically, although behavior when dynamically changing + * direction may be strange. Specifically the effect may re-upload itself and + * start playing from the start. You also cannot change the type either when + * running SDL_HapticUpdateEffect(). + * + * \param haptic the SDL_Haptic device that has the effect. + * \param effect the identifier of the effect to update. + * \param data an SDL_HapticEffect structure containing the new effect + * properties to use. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticDestroyEffect + * \sa SDL_HapticNewEffect + * \sa SDL_HapticRunEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticUpdateEffect(SDL_Haptic * haptic, + int effect, + SDL_HapticEffect * data); + +/** + * Run the haptic effect on its associated haptic device. + * + * To repeat the effect over and over indefinitely, set `iterations` to + * `SDL_HAPTIC_INFINITY`. (Repeats the envelope - attack and fade.) To make + * one instance of the effect last indefinitely (so the effect does not fade), + * set the effect's `length` in its structure/union to `SDL_HAPTIC_INFINITY` + * instead. + * + * \param haptic the SDL_Haptic device to run the effect on. + * \param effect the ID of the haptic effect to run. + * \param iterations the number of iterations to run the effect; use + * `SDL_HAPTIC_INFINITY` to repeat forever. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticDestroyEffect + * \sa SDL_HapticGetEffectStatus + * \sa SDL_HapticStopEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticRunEffect(SDL_Haptic * haptic, + int effect, + Uint32 iterations); + +/** + * Stop the haptic effect on its associated haptic device. + * + * * + * + * \param haptic the SDL_Haptic device to stop the effect on. + * \param effect the ID of the haptic effect to stop. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticDestroyEffect + * \sa SDL_HapticRunEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticStopEffect(SDL_Haptic * haptic, + int effect); + +/** + * Destroy a haptic effect on the device. + * + * This will stop the effect if it's running. Effects are automatically + * destroyed when the device is closed. + * + * \param haptic the SDL_Haptic device to destroy the effect on. + * \param effect the ID of the haptic effect to destroy. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticNewEffect + */ +extern DECLSPEC void SDLCALL SDL_HapticDestroyEffect(SDL_Haptic * haptic, + int effect); + +/** + * Get the status of the current effect on the specified haptic device. + * + * Device must support the SDL_HAPTIC_STATUS feature. + * + * \param haptic the SDL_Haptic device to query for the effect status on. + * \param effect the ID of the haptic effect to query its status. + * \returns 0 if it isn't playing, 1 if it is playing, or a negative error + * code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticRunEffect + * \sa SDL_HapticStopEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticGetEffectStatus(SDL_Haptic * haptic, + int effect); + +/** + * Set the global gain of the specified haptic device. + * + * Device must support the SDL_HAPTIC_GAIN feature. + * + * The user may specify the maximum gain by setting the environment variable + * `SDL_HAPTIC_GAIN_MAX` which should be between 0 and 100. All calls to + * SDL_HapticSetGain() will scale linearly using `SDL_HAPTIC_GAIN_MAX` as the + * maximum. + * + * \param haptic the SDL_Haptic device to set the gain on. + * \param gain value to set the gain to, should be between 0 and 100 (0 - + * 100). + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticSetGain(SDL_Haptic * haptic, int gain); + +/** + * Set the global autocenter of the device. + * + * Autocenter should be between 0 and 100. Setting it to 0 will disable + * autocentering. + * + * Device must support the SDL_HAPTIC_AUTOCENTER feature. + * + * \param haptic the SDL_Haptic device to set autocentering on. + * \param autocenter value to set autocenter to (0-100). + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticSetAutocenter(SDL_Haptic * haptic, + int autocenter); + +/** + * Pause a haptic device. + * + * Device must support the `SDL_HAPTIC_PAUSE` feature. Call + * SDL_HapticUnpause() to resume playback. + * + * Do not modify the effects nor add new ones while the device is paused. That + * can cause all sorts of weird errors. + * + * \param haptic the SDL_Haptic device to pause. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticUnpause + */ +extern DECLSPEC int SDLCALL SDL_HapticPause(SDL_Haptic * haptic); + +/** + * Unpause a haptic device. + * + * Call to unpause after SDL_HapticPause(). + * + * \param haptic the SDL_Haptic device to unpause. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticPause + */ +extern DECLSPEC int SDLCALL SDL_HapticUnpause(SDL_Haptic * haptic); + +/** + * Stop all the currently playing effects on a haptic device. + * + * \param haptic the SDL_Haptic device to stop. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_HapticStopAll(SDL_Haptic * haptic); + +/** + * Check whether rumble is supported on a haptic device. + * + * \param haptic haptic device to check for rumble support. + * \returns SDL_TRUE if effect is supported, SDL_FALSE if it isn't, or a + * negative error code on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticRumbleInit + * \sa SDL_HapticRumblePlay + * \sa SDL_HapticRumbleStop + */ +extern DECLSPEC int SDLCALL SDL_HapticRumbleSupported(SDL_Haptic * haptic); + +/** + * Initialize a haptic device for simple rumble playback. + * + * \param haptic the haptic device to initialize for simple rumble playback. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticRumblePlay + * \sa SDL_HapticRumbleStop + * \sa SDL_HapticRumbleSupported + */ +extern DECLSPEC int SDLCALL SDL_HapticRumbleInit(SDL_Haptic * haptic); + +/** + * Run a simple rumble effect on a haptic device. + * + * \param haptic the haptic device to play the rumble effect on. + * \param strength strength of the rumble to play as a 0-1 float value. + * \param length length of the rumble to play in milliseconds. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticRumbleInit + * \sa SDL_HapticRumbleStop + * \sa SDL_HapticRumbleSupported + */ +extern DECLSPEC int SDLCALL SDL_HapticRumblePlay(SDL_Haptic * haptic, float strength, Uint32 length ); + +/** + * Stop the simple rumble on a haptic device. + * + * \param haptic the haptic device to stop the rumble effect on. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HapticRumbleInit + * \sa SDL_HapticRumblePlay + * \sa SDL_HapticRumbleSupported + */ +extern DECLSPEC int SDLCALL SDL_HapticRumbleStop(SDL_Haptic * haptic); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_haptic_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_hidapi.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_hidapi.h new file mode 100644 index 00000000..b14442a6 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_hidapi.h @@ -0,0 +1,443 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: HIDAPI */ + +/** + * # CategoryHIDAPI + * + * Header file for SDL HIDAPI functions. + * + * This is an adaptation of the original HIDAPI interface by Alan Ott, and + * includes source code licensed under the following license: + * + * ``` + * HIDAPI - Multi-Platform library for + * communication with HID devices. + * + * Copyright 2009, Alan Ott, Signal 11 Software. + * All Rights Reserved. + * + * This software may be used by anyone for any reason so + * long as the copyright notice in the source files + * remains intact. + * ``` + * + * (Note that this license is the same as item three of SDL's zlib license, so + * it adds no new requirements on the user.) + * + * If you would like a version of SDL without this code, you can build SDL + * with SDL_HIDAPI_DISABLED defined to 1. You might want to do this for + * example on iOS or tvOS to avoid a dependency on the CoreBluetooth + * framework. + */ + +#ifndef SDL_hidapi_h_ +#define SDL_hidapi_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A handle representing an open HID device + */ +struct SDL_hid_device_; +typedef struct SDL_hid_device_ SDL_hid_device; /**< opaque hidapi structure */ + +/** hidapi info structure */ + +/** + * Information about a connected HID device + */ +typedef struct SDL_hid_device_info +{ + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac only). */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac only).*/ + unsigned short usage; + /** The USB interface which this logical device + represents. + + * Valid on both Linux implementations in all cases. + * Valid on the Windows implementation only if the device + contains more than one interface. */ + int interface_number; + + /** Additional information about the USB interface. + Valid on libusb and Android implementations. */ + int interface_class; + int interface_subclass; + int interface_protocol; + + /** Pointer to the next device */ + struct SDL_hid_device_info *next; +} SDL_hid_device_info; + + +/** + * Initialize the HIDAPI library. + * + * This function initializes the HIDAPI library. Calling it is not strictly + * necessary, as it will be called automatically by SDL_hid_enumerate() and + * any of the SDL_hid_open_*() functions if it is needed. This function should + * be called at the beginning of execution however, if there is a chance of + * HIDAPI handles being opened by different threads simultaneously. + * + * Each call to this function should have a matching call to SDL_hid_exit() + * + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_hid_exit + */ +extern DECLSPEC int SDLCALL SDL_hid_init(void); + +/** + * Finalize the HIDAPI library. + * + * This function frees all of the static data associated with HIDAPI. It + * should be called at the end of execution to avoid memory leaks. + * + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_hid_init + */ +extern DECLSPEC int SDLCALL SDL_hid_exit(void); + +/** + * Check to see if devices may have been added or removed. + * + * Enumerating the HID devices is an expensive operation, so you can call this + * to see if there have been any system device changes since the last call to + * this function. A change in the counter returned doesn't necessarily mean + * that anything has changed, but you can call SDL_hid_enumerate() to get an + * updated device list. + * + * Calling this function for the first time may cause a thread or other system + * resource to be allocated to track device change notifications. + * + * \returns a change counter that is incremented with each potential device + * change, or 0 if device change detection isn't available. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_hid_enumerate + */ +extern DECLSPEC Uint32 SDLCALL SDL_hid_device_change_count(void); + +/** + * Enumerate the HID Devices. + * + * This function returns a linked list of all the HID devices attached to the + * system which match vendor_id and product_id. If `vendor_id` is set to 0 + * then any vendor matches. If `product_id` is set to 0 then any product + * matches. If `vendor_id` and `product_id` are both set to 0, then all HID + * devices will be returned. + * + * \param vendor_id The Vendor ID (VID) of the types of device to open. + * \param product_id The Product ID (PID) of the types of device to open. + * \returns a pointer to a linked list of type SDL_hid_device_info, containing + * information about the HID devices attached to the system, or NULL + * in the case of failure. Free this linked list by calling + * SDL_hid_free_enumeration(). + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_hid_device_change_count + */ +extern DECLSPEC SDL_hid_device_info * SDLCALL SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id); + +/** + * Free an enumeration Linked List + * + * This function frees a linked list created by SDL_hid_enumerate(). + * + * \param devs Pointer to a list of struct_device returned from + * SDL_hid_enumerate(). + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC void SDLCALL SDL_hid_free_enumeration(SDL_hid_device_info *devs); + +/** + * Open a HID device using a Vendor ID (VID), Product ID (PID) and optionally + * a serial number. + * + * If `serial_number` is NULL, the first device with the specified VID and PID + * is opened. + * + * \param vendor_id The Vendor ID (VID) of the device to open. + * \param product_id The Product ID (PID) of the device to open. + * \param serial_number The Serial Number of the device to open (Optionally + * NULL). + * \returns a pointer to a SDL_hid_device object on success or NULL on + * failure. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); + +/** + * Open a HID device by its path name. + * + * The path name be determined by calling SDL_hid_enumerate(), or a + * platform-specific path name can be used (eg: /dev/hidraw0 on Linux). + * + * \param path The path name of the device to open. + * \returns a pointer to a SDL_hid_device object on success or NULL on + * failure. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive); + +/** + * Write an Output report to a HID device. + * + * The first byte of `data` must contain the Report ID. For devices which only + * support a single report, this must be set to 0x0. The remaining bytes + * contain the report data. Since the Report ID is mandatory, calls to + * SDL_hid_write() will always contain one more byte than the report contains. + * For example, if a hid report is 16 bytes long, 17 bytes must be passed to + * SDL_hid_write(), the Report ID (or 0x0, for devices with a single report), + * followed by the report data (16 bytes). In this example, the length passed + * in would be 17. + * + * SDL_hid_write() will send the data on the first OUT endpoint, if one + * exists. If it does not, it will send the data through the Control Endpoint + * (Endpoint 0). + * + * \param dev A device handle returned from SDL_hid_open(). + * \param data The data to send, including the report number as the first + * byte. + * \param length The length in bytes of the data to send. + * \returns the actual number of bytes written and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_write(SDL_hid_device *dev, const unsigned char *data, size_t length); + +/** + * Read an Input report from a HID device with timeout. + * + * Input reports are returned to the host through the INTERRUPT IN endpoint. + * The first byte will contain the Report number if the device uses numbered + * reports. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param data A buffer to put the read data into. + * \param length The number of bytes to read. For devices with multiple + * reports, make sure to read an extra byte for the report + * number. + * \param milliseconds timeout in milliseconds or -1 for blocking wait. + * \returns the actual number of bytes read and -1 on error. If no packet was + * available to be read within the timeout period, this function + * returns 0. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_read_timeout(SDL_hid_device *dev, unsigned char *data, size_t length, int milliseconds); + +/** + * Read an Input report from a HID device. + * + * Input reports are returned to the host through the INTERRUPT IN endpoint. + * The first byte will contain the Report number if the device uses numbered + * reports. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param data A buffer to put the read data into. + * \param length The number of bytes to read. For devices with multiple + * reports, make sure to read an extra byte for the report + * number. + * \returns the actual number of bytes read and -1 on error. If no packet was + * available to be read and the handle is in non-blocking mode, this + * function returns 0. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_read(SDL_hid_device *dev, unsigned char *data, size_t length); + +/** + * Set the device handle to be non-blocking. + * + * In non-blocking mode calls to SDL_hid_read() will return immediately with a + * value of 0 if there is no data to be read. In blocking mode, SDL_hid_read() + * will wait (block) until there is data to read before returning. + * + * Nonblocking can be turned on and off at any time. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param nonblock enable or not the nonblocking reads - 1 to enable + * nonblocking - 0 to disable nonblocking. + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_set_nonblocking(SDL_hid_device *dev, int nonblock); + +/** + * Send a Feature report to the device. + * + * Feature reports are sent over the Control endpoint as a Set_Report + * transfer. The first byte of `data` must contain the Report ID. For devices + * which only support a single report, this must be set to 0x0. The remaining + * bytes contain the report data. Since the Report ID is mandatory, calls to + * SDL_hid_send_feature_report() will always contain one more byte than the + * report contains. For example, if a hid report is 16 bytes long, 17 bytes + * must be passed to SDL_hid_send_feature_report(): the Report ID (or 0x0, for + * devices which do not use numbered reports), followed by the report data (16 + * bytes). In this example, the length passed in would be 17. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param data The data to send, including the report number as the first + * byte. + * \param length The length in bytes of the data to send, including the report + * number. + * \returns the actual number of bytes written and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_send_feature_report(SDL_hid_device *dev, const unsigned char *data, size_t length); + +/** + * Get a feature report from a HID device. + * + * Set the first byte of `data` to the Report ID of the report to be read. + * Make sure to allow space for this extra byte in `data`. Upon return, the + * first byte will still contain the Report ID, and the report data will start + * in data[1]. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param data A buffer to put the read data into, including the Report ID. + * Set the first byte of `data` to the Report ID of the report to + * be read, or set it to zero if your device does not use numbered + * reports. + * \param length The number of bytes to read, including an extra byte for the + * report ID. The buffer can be longer than the actual report. + * \returns the number of bytes read plus one for the report ID (which is + * still in the first byte), or -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_get_feature_report(SDL_hid_device *dev, unsigned char *data, size_t length); + +/** + * Close a HID device. + * + * \param dev A device handle returned from SDL_hid_open(). + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC void SDLCALL SDL_hid_close(SDL_hid_device *dev); + +/** + * Get The Manufacturer String from a HID device. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param string A wide string buffer to put the data into. + * \param maxlen The length of the buffer in multiples of wchar_t. + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_get_manufacturer_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen); + +/** + * Get The Product String from a HID device. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param string A wide string buffer to put the data into. + * \param maxlen The length of the buffer in multiples of wchar_t. + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_get_product_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen); + +/** + * Get The Serial Number String from a HID device. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param string A wide string buffer to put the data into. + * \param maxlen The length of the buffer in multiples of wchar_t. + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_get_serial_number_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen); + +/** + * Get a string from a HID device, based on its string index. + * + * \param dev A device handle returned from SDL_hid_open(). + * \param string_index The index of the string to get. + * \param string A wide string buffer to put the data into. + * \param maxlen The length of the buffer in multiples of wchar_t. + * \returns 0 on success and -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_hid_get_indexed_string(SDL_hid_device *dev, int string_index, wchar_t *string, size_t maxlen); + +/** + * Start or stop a BLE scan on iOS and tvOS to pair Steam Controllers + * + * \param active SDL_TRUE to start the scan, SDL_FALSE to stop the scan. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC void SDLCALL SDL_hid_ble_scan(SDL_bool active); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_hidapi_h_ */ + +/* vi: set sts=4 ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_hints.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_hints.h new file mode 100644 index 00000000..6713d01f --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_hints.h @@ -0,0 +1,3303 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryHints + * + * Official documentation for SDL configuration variables + * + * This file contains functions to set and get configuration hints, as well as + * listing each of them alphabetically. + * + * The convention for naming hints is SDL_HINT_X, where "SDL_X" is the + * environment variable that can be used to override the default. + * + * In general these hints are just that - they may or may not be supported or + * applicable on any given platform, but they provide a way for an application + * or user to give the library a hint as to how they would like the library to + * work. + */ + +#ifndef SDL_hints_h_ +#define SDL_hints_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A variable controlling whether the Android / iOS built-in accelerometer + * should be listed as a joystick device. + * + * This variable can be set to the following values: + * + * - "0": The accelerometer is not listed as a joystick + * - "1": The accelerometer is available as a 3 axis joystick (the default). + */ +#define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK" + +/** + * Specify the behavior of Alt+Tab while the keyboard is grabbed. + * + * By default, SDL emulates Alt+Tab functionality while the keyboard is + * grabbed and your window is full-screen. This prevents the user from getting + * stuck in your application if you've enabled keyboard grab. + * + * The variable can be set to the following values: + * + * - "0": SDL will not handle Alt+Tab. Your application is responsible for + * handling Alt+Tab while the keyboard is grabbed. + * - "1": SDL will minimize your window when Alt+Tab is pressed (default) + */ +#define SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED "SDL_ALLOW_ALT_TAB_WHILE_GRABBED" + +/** + * If set to "0" then never set the top most bit on a SDL Window, even if the + * video mode expects it. + * + * This is a debugging aid for developers and not expected to be used by end + * users. The default is "1" + * + * This variable can be set to the following values: + * + * - "0": don't allow topmost + * - "1": allow topmost + */ +#define SDL_HINT_ALLOW_TOPMOST "SDL_ALLOW_TOPMOST" + +/** + * Android APK expansion main file version. + * + * Should be a string number like "1", "2" etc. + * + * Must be set together with + * SDL_HINT_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION. + * + * If both hints were set then SDL_RWFromFile() will look into expansion files + * after a given relative path was not found in the internal storage and + * assets. + * + * By default this hint is not set and the APK expansion files are not + * searched. + */ +#define SDL_HINT_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION "SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION" + +/** + * Android APK expansion patch file version. + * + * Should be a string number like "1", "2" etc. + * + * Must be set together with SDL_HINT_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION. + * + * If both hints were set then SDL_RWFromFile() will look into expansion files + * after a given relative path was not found in the internal storage and + * assets. + * + * By default this hint is not set and the APK expansion files are not + * searched. + */ +#define SDL_HINT_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION "SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION" + +/** + * A variable to control whether the event loop will block itself when the app + * is paused. + * + * The variable can be set to the following values: + * + * - "0": Non blocking. + * - "1": Blocking. (default) + * + * The value should be set before SDL is initialized. + */ +#define SDL_HINT_ANDROID_BLOCK_ON_PAUSE "SDL_ANDROID_BLOCK_ON_PAUSE" + +/** + * A variable to control whether SDL will pause audio in background (Requires + * SDL_ANDROID_BLOCK_ON_PAUSE as "Non blocking") + * + * The variable can be set to the following values: + * + * - "0": Non paused. + * - "1": Paused. (default) + * + * The value should be set before SDL is initialized. + */ +#define SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO "SDL_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO" + +/** + * A variable to control whether we trap the Android back button to handle it + * manually. + * + * This is necessary for the right mouse button to work on some Android + * devices, or to be able to trap the back button for use in your code + * reliably. If set to true, the back button will show up as an SDL_KEYDOWN / + * SDL_KEYUP pair with a keycode of SDL_SCANCODE_AC_BACK. + * + * The variable can be set to the following values: + * + * - "0": Back button will be handled as usual for system. (default) + * - "1": Back button will be trapped, allowing you to handle the key press + * manually. (This will also let right mouse click work on systems where the + * right mouse button functions as back.) + * + * The value of this hint is used at runtime, so it can be changed at any + * time. + */ +#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON" + +/** + * Specify an application name. + * + * This hint lets you specify the application name sent to the OS when + * required. For example, this will often appear in volume control applets for + * audio streams, and in lists of applications which are inhibiting the + * screensaver. You should use a string that describes your program ("My Game + * 2: The Revenge") + * + * Setting this to "" or leaving it unset will have SDL use a reasonable + * default: probably the application's name or "SDL Application" if SDL + * doesn't have any better information. + * + * Note that, for audio streams, this can be overridden with + * SDL_HINT_AUDIO_DEVICE_APP_NAME. + * + * On targets where this is not supported, this hint does nothing. + */ +#define SDL_HINT_APP_NAME "SDL_APP_NAME" + +/** + * A variable controlling whether controllers used with the Apple TV generate + * UI events. + * + * When UI events are generated by controller input, the app will be + * backgrounded when the Apple TV remote's menu button is pressed, and when + * the pause or B buttons on gamepads are pressed. + * + * More information about properly making use of controllers for the Apple TV + * can be found here: + * https://developer.apple.com/tvos/human-interface-guidelines/remote-and-controllers/ + * + * This variable can be set to the following values: + * + * - "0": Controller input does not generate UI events (the default). + * - "1": Controller input generates UI events. + */ +#define SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS "SDL_APPLE_TV_CONTROLLER_UI_EVENTS" + +/** + * A variable controlling whether the Apple TV remote's joystick axes will + * automatically match the rotation of the remote. + * + * This variable can be set to the following values: + * + * - "0": Remote orientation does not affect joystick axes (the default). + * - "1": Joystick axes are based on the orientation of the remote. + */ +#define SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION" + +/** + * A variable controlling the audio category on iOS and Mac OS X + * + * This variable can be set to the following values: + * + * - "ambient": Use the AVAudioSessionCategoryAmbient audio category, will be + * muted by the phone mute switch (default) + * - "playback": Use the AVAudioSessionCategoryPlayback category + * + * For more information, see Apple's documentation: + * https://developer.apple.com/library/content/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionCategoriesandModes/AudioSessionCategoriesandModes.html + */ +#define SDL_HINT_AUDIO_CATEGORY "SDL_AUDIO_CATEGORY" + +/** + * Specify an application name for an audio device. + * + * Some audio backends (such as PulseAudio) allow you to describe your audio + * stream. Among other things, this description might show up in a system + * control panel that lets the user adjust the volume on specific audio + * streams instead of using one giant master volume slider. + * + * This hints lets you transmit that information to the OS. The contents of + * this hint are used while opening an audio device. You should use a string + * that describes your program ("My Game 2: The Revenge") + * + * Setting this to "" or leaving it unset will have SDL use a reasonable + * default: this will be the name set with SDL_HINT_APP_NAME, if that hint is + * set. Otherwise, it'll probably the application's name or "SDL Application" + * if SDL doesn't have any better information. + * + * On targets where this is not supported, this hint does nothing. + */ +#define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME" + +/** + * Specify an application name for an audio device. + * + * Some audio backends (such as PulseAudio) allow you to describe your audio + * stream. Among other things, this description might show up in a system + * control panel that lets the user adjust the volume on specific audio + * streams instead of using one giant master volume slider. + * + * This hints lets you transmit that information to the OS. The contents of + * this hint are used while opening an audio device. You should use a string + * that describes your what your program is playing ("audio stream" is + * probably sufficient in many cases, but this could be useful for something + * like "team chat" if you have a headset playing VoIP audio separately). + * + * Setting this to "" or leaving it unset will have SDL use a reasonable + * default: "audio stream" or something similar. + * + * On targets where this is not supported, this hint does nothing. + */ +#define SDL_HINT_AUDIO_DEVICE_STREAM_NAME "SDL_AUDIO_DEVICE_STREAM_NAME" + +/** + * Specify an application role for an audio device. + * + * Some audio backends (such as Pipewire) allow you to describe the role of + * your audio stream. Among other things, this description might show up in a + * system control panel or software for displaying and manipulating media + * playback/capture graphs. + * + * This hints lets you transmit that information to the OS. The contents of + * this hint are used while opening an audio device. You should use a string + * that describes your what your program is playing (Game, Music, Movie, + * etc...). + * + * Setting this to "" or leaving it unset will have SDL use a reasonable + * default: "Game" or something similar. + * + * On targets where this is not supported, this hint does nothing. + */ +#define SDL_HINT_AUDIO_DEVICE_STREAM_ROLE "SDL_AUDIO_DEVICE_STREAM_ROLE" + +/** + * A variable controlling speed/quality tradeoff of audio resampling. + * + * If available, SDL can use libsamplerate ( http://www.mega-nerd.com/SRC/ ) + * to handle audio resampling. There are different resampling modes available + * that produce different levels of quality, using more CPU. + * + * If this hint isn't specified to a valid setting, or libsamplerate isn't + * available, SDL will use the default, internal resampling algorithm. + * + * As of SDL 2.26, SDL_ConvertAudio() respects this hint when libsamplerate is + * available. + * + * This hint is currently only checked at audio subsystem initialization. + * + * This variable can be set to the following values: + * + * - "0" or "default": Use SDL's internal resampling (Default when not set - + * low quality, fast) + * - "1" or "fast": Use fast, slightly higher quality resampling, if available + * - "2" or "medium": Use medium quality resampling, if available + * - "3" or "best": Use high quality resampling, if available + */ +#define SDL_HINT_AUDIO_RESAMPLING_MODE "SDL_AUDIO_RESAMPLING_MODE" + +/** + * A variable controlling whether SDL updates joystick state when getting + * input events + * + * This variable can be set to the following values: + * + * - "0": You'll call SDL_JoystickUpdate() manually + * - "1": SDL will automatically call SDL_JoystickUpdate() (default) + * + * This hint can be toggled on and off at runtime. + */ +#define SDL_HINT_AUTO_UPDATE_JOYSTICKS "SDL_AUTO_UPDATE_JOYSTICKS" + +/** + * A variable controlling whether SDL updates sensor state when getting input + * events + * + * This variable can be set to the following values: + * + * - "0": You'll call SDL_SensorUpdate() manually + * - "1": SDL will automatically call SDL_SensorUpdate() (default) + * + * This hint can be toggled on and off at runtime. + */ +#define SDL_HINT_AUTO_UPDATE_SENSORS "SDL_AUTO_UPDATE_SENSORS" + +/** + * Prevent SDL from using version 4 of the bitmap header when saving BMPs. + * + * The bitmap header version 4 is required for proper alpha channel support + * and SDL will use it when required. Should this not be desired, this hint + * can force the use of the 40 byte header version which is supported + * everywhere. + * + * The variable can be set to the following values: + * + * - "0": Surfaces with a colorkey or an alpha channel are saved to a 32-bit + * BMP file with an alpha mask. SDL will use the bitmap header version 4 and + * set the alpha mask accordingly. + * - "1": Surfaces with a colorkey or an alpha channel are saved to a 32-bit + * BMP file without an alpha mask. The alpha channel data will be in the + * file, but applications are going to ignore it. + * + * The default value is "0". + */ +#define SDL_HINT_BMP_SAVE_LEGACY_FORMAT "SDL_BMP_SAVE_LEGACY_FORMAT" + +/** + * Override for SDL_GetDisplayUsableBounds() + * + * If set, this hint will override the expected results for + * SDL_GetDisplayUsableBounds() for display index 0. Generally you don't want + * to do this, but this allows an embedded system to request that some of the + * screen be reserved for other uses when paired with a well-behaved + * application. + * + * The contents of this hint must be 4 comma-separated integers, the first is + * the bounds x, then y, width and height, in that order. + */ +#define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS" + +/** + * Disable giving back control to the browser automatically when running with + * asyncify + * + * With -s ASYNCIFY, SDL2 calls emscripten_sleep during operations such as + * refreshing the screen or polling events. + * + * This hint only applies to the emscripten platform + * + * The variable can be set to the following values: + * + * - "0": Disable emscripten_sleep calls (if you give back browser control + * manually or use asyncify for other purposes) + * - "1": Enable emscripten_sleep calls (the default) + */ +#define SDL_HINT_EMSCRIPTEN_ASYNCIFY "SDL_EMSCRIPTEN_ASYNCIFY" + +/** + * override the binding element for keyboard inputs for Emscripten builds + * + * This hint only applies to the emscripten platform. + * + * The variable can be one of: + * + * - "#window": the javascript window object (this is the default) + * - "#document": the javascript document object + * - "#screen": the javascript window.screen object + * - "#canvas": the WebGL canvas element + * + * Any other string without a leading # sign applies to the element on the + * page with that ID. + */ +#define SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT" + +/** + * A variable that controls whether the on-screen keyboard should be shown + * when text input is active + * + * The variable can be set to the following values: + * + * - "0": Do not show the on-screen keyboard + * - "1": Show the on-screen keyboard + * + * The default value is "1". This hint must be set before text input is + * activated. + */ +#define SDL_HINT_ENABLE_SCREEN_KEYBOARD "SDL_ENABLE_SCREEN_KEYBOARD" + +/** + * A variable controlling verbosity of the logging of SDL events pushed onto + * the internal queue. + * + * This variable can be set to the following values, from least to most + * verbose: + * + * - "0": Don't log any events (default) + * - "1": Log most events (other than the really spammy ones). + * - "2": Include mouse and finger motion events. + * - "3": Include SDL_SysWMEvent events. + * + * This is generally meant to be used to debug SDL itself, but can be useful + * for application developers that need better visibility into what is going + * on in the event queue. Logged events are sent through SDL_Log(), which + * means by default they appear on stdout on most platforms or maybe + * OutputDebugString() on Windows, and can be funneled by the app with + * SDL_LogSetOutputFunction(), etc. + * + * This hint can be toggled on and off at runtime, if you only need to log + * events for a small subset of program execution. + */ +#define SDL_HINT_EVENT_LOGGING "SDL_EVENT_LOGGING" + +/** + * A variable controlling whether raising the window should be done more + * forcefully + * + * This variable can be set to the following values: + * + * - "0": No forcing (the default) + * - "1": Extra level of forcing + * + * At present, this is only an issue under MS Windows, which makes it nearly + * impossible to programmatically move a window to the foreground, for + * "security" reasons. See http://stackoverflow.com/a/34414846 for a + * discussion. + */ +#define SDL_HINT_FORCE_RAISEWINDOW "SDL_HINT_FORCE_RAISEWINDOW" + +/** + * A variable controlling how 3D acceleration is used to accelerate the SDL + * screen surface. + * + * SDL can try to accelerate the SDL screen surface by using streaming + * textures with a 3D rendering engine. This variable controls whether and how + * this is done. + * + * This variable can be set to the following values: + * + * - "0": Disable 3D acceleration + * - "1": Enable 3D acceleration, using the default renderer. + * - "X": Enable 3D acceleration, using X where X is one of the valid + * rendering drivers. (e.g. "direct3d", "opengl", etc.) + * + * By default SDL tries to make a best guess for each platform whether to use + * acceleration or not. + */ +#define SDL_HINT_FRAMEBUFFER_ACCELERATION "SDL_FRAMEBUFFER_ACCELERATION" + +/** + * A variable that lets you manually hint extra gamecontroller db entries. + * + * The variable should be newline delimited rows of gamecontroller config + * data, see SDL_gamecontroller.h + * + * This hint must be set before calling SDL_Init(SDL_INIT_GAMECONTROLLER) You + * can update mappings after the system is initialized with + * SDL_GameControllerMappingForGUID() and SDL_GameControllerAddMapping() + */ +#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG" + +/** + * A variable that lets you provide a file with extra gamecontroller db + * entries. + * + * The file should contain lines of gamecontroller config data, see + * SDL_gamecontroller.h + * + * This hint must be set before calling SDL_Init(SDL_INIT_GAMECONTROLLER) You + * can update mappings after the system is initialized with + * SDL_GameControllerMappingForGUID() and SDL_GameControllerAddMapping() + */ +#define SDL_HINT_GAMECONTROLLERCONFIG_FILE "SDL_GAMECONTROLLERCONFIG_FILE" + +/** + * A variable that overrides the automatic controller type detection + * + * The variable should be comma separated entries, in the form: VID/PID=type + * + * The VID and PID should be hexadecimal with exactly 4 digits, e.g. 0x00fd + * + * The type should be one of: Xbox360 XboxOne PS3 PS4 PS5 SwitchPro + * + * This hint affects what driver is used, and must be set before calling + * SDL_Init(SDL_INIT_GAMECONTROLLER) + */ +#define SDL_HINT_GAMECONTROLLERTYPE "SDL_GAMECONTROLLERTYPE" + +/** + * A variable containing a list of devices to skip when scanning for game + * controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES "SDL_GAMECONTROLLER_IGNORE_DEVICES" + +/** + * If set, all devices will be skipped when scanning for game controllers + * except for the ones listed in this variable. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT" + +/** + * If set, game controller face buttons report their values according to their + * labels instead of their positional layout. + * + * For example, on Nintendo Switch controllers, normally you'd get: + * + * ``` + * (Y) + * (X) (B) + * (A) + * ``` + * + * but if this hint is set, you'll get: + * + * ``` + * (X) + * (Y) (A) + * (B) + * ``` + * + * The variable can be set to the following values: + * + * - "0": Report the face buttons by position, as though they were on an Xbox + * controller. + * - "1": Report the face buttons by label instead of position + * + * The default value is "1". This hint may be set at any time. + */ +#define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS" + +/** + * A variable controlling whether grabbing input grabs the keyboard + * + * This variable can be set to the following values: + * + * - "0": Grab will affect only the mouse + * - "1": Grab will affect mouse and keyboard + * + * By default SDL will not grab the keyboard so system shortcuts still work. + */ +#define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD" + +/** + * A variable containing a list of devices to ignore in SDL_hid_enumerate() + * + * For example, to ignore the Shanwan DS3 controller and any Valve controller, + * you might have the string "0x2563/0x0523,0x28de/0x0000" + */ +#define SDL_HINT_HIDAPI_IGNORE_DEVICES "SDL_HIDAPI_IGNORE_DEVICES" + +/** + * A variable controlling whether the idle timer is disabled on iOS. + * + * When an iOS app does not receive touches for some time, the screen is + * dimmed automatically. For games where the accelerometer is the only input + * this is problematic. This functionality can be disabled by setting this + * hint. + * + * As of SDL 2.0.4, SDL_EnableScreenSaver() and SDL_DisableScreenSaver() + * accomplish the same thing on iOS. They should be preferred over this hint. + * + * This variable can be set to the following values: + * + * - "0": Enable idle timer + * - "1": Disable idle timer + */ +#define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED" + +/** + * A variable to control whether certain IMEs should handle text editing + * internally instead of sending SDL_TEXTEDITING events. + * + * The variable can be set to the following values: + * + * - "0": SDL_TEXTEDITING events are sent, and it is the application's + * responsibility to render the text from these events and differentiate it + * somehow from committed text. (default) + * - "1": If supported by the IME then SDL_TEXTEDITING events are not sent, + * and text that is being composed will be rendered in its own UI. + */ +#define SDL_HINT_IME_INTERNAL_EDITING "SDL_IME_INTERNAL_EDITING" + +/** + * A variable to control whether certain IMEs should show native UI components + * (such as the Candidate List) instead of suppressing them. + * + * The variable can be set to the following values: + * + * - "0": Native UI components are not display. (default) + * - "1": Native UI components are displayed. + */ +#define SDL_HINT_IME_SHOW_UI "SDL_IME_SHOW_UI" + +/** + * A variable to control if extended IME text support is enabled. + * + * If enabled then SDL_TextEditingExtEvent will be issued if the text would be + * truncated otherwise. Additionally SDL_TextInputEvent will be dispatched + * multiple times so that it is not truncated. + * + * The variable can be set to the following values: + * + * - "0": Legacy behavior. Text can be truncated, no heap allocations. + * (default) + * - "1": Modern behavior. + */ +#define SDL_HINT_IME_SUPPORT_EXTENDED_TEXT "SDL_IME_SUPPORT_EXTENDED_TEXT" + +/** + * A variable controlling whether the home indicator bar on iPhone X should be + * hidden. + * + * This variable can be set to the following values: + * + * - "0": The indicator bar is not hidden (default for windowed applications) + * - "1": The indicator bar is hidden and is shown when the screen is touched + * (useful for movie playback applications) + * - "2": The indicator bar is dim and the first swipe makes it visible and + * the second swipe performs the "home" action (default for fullscreen + * applications) + */ +#define SDL_HINT_IOS_HIDE_HOME_INDICATOR "SDL_IOS_HIDE_HOME_INDICATOR" + +/** + * A variable that lets you enable joystick (and gamecontroller) events even + * when your app is in the background. + * + * The variable can be set to the following values: + * + * - "0": Disable joystick & gamecontroller input events when the application + * is in the background. + * - "1": Enable joystick & gamecontroller input events when the application + * is in the background. + * + * The default value is "0". This hint may be set at any time. + */ +#define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS" + +/** + * A variable containing a list of arcade stick style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES "SDL_JOYSTICK_ARCADESTICK_DEVICES" + +/** + * A variable containing a list of devices that are not arcade stick style + * controllers. + * + * This will override SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES and the built in + * device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED "SDL_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED" + +/** + * A variable containing a list of devices that should not be considerd + * joysticks. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_BLACKLIST_DEVICES "SDL_JOYSTICK_BLACKLIST_DEVICES" + +/** + * A variable containing a list of devices that should be considered + * joysticks. + * + * This will override SDL_HINT_JOYSTICK_BLACKLIST_DEVICES and the built in + * device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED "SDL_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED" + +/** + * A variable containing a list of flightstick style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES "SDL_JOYSTICK_FLIGHTSTICK_DEVICES" + +/** + * A variable containing a list of devices that are not flightstick style + * controllers. + * + * This will override SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES and the built in + * device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED "SDL_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED" + +/** + * A variable containing a list of devices known to have a GameCube form + * factor. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_GAMECUBE_DEVICES "SDL_JOYSTICK_GAMECUBE_DEVICES" + +/** + * A variable containing a list of devices known not to have a GameCube form + * factor. + * + * This will override SDL_HINT_JOYSTICK_GAMECUBE_DEVICES and the built in + * device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED "SDL_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED" + +/** + * A variable controlling whether the HIDAPI joystick drivers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI drivers are not used + * - "1": HIDAPI drivers are used (the default) + * + * This variable is the default for all drivers, but can be overridden by the + * hints for specific drivers below. + */ +#define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI" + +/** + * A variable controlling whether the HIDAPI driver for Nintendo GameCube + * controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE "SDL_JOYSTICK_HIDAPI_GAMECUBE" + +/** + * A variable controlling whether "low_frequency_rumble" and + * "high_frequency_rumble" is used to implement the GameCube controller's 3 + * rumble modes, Stop(0), Rumble(1), and StopHard(2) this is useful for + * applications that need full compatibility for things like ADSR envelopes. + * + * Stop is implemented by setting "low_frequency_rumble" to "0" and + * "high_frequency_rumble" ">0" Rumble is both at any arbitrary value, + * StopHard is implemented by setting both "low_frequency_rumble" and + * "high_frequency_rumble" to "0" + * + * This variable can be set to the following values: + * + * - "0": Normal rumble behavior is behavior is used (default) + * - "1": Proper GameCube controller rumble behavior is used + */ +#define SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE "SDL_JOYSTICK_GAMECUBE_RUMBLE_BRAKE" + +/** + * A variable controlling whether the HIDAPI driver for Nintendo Switch + * Joy-Cons should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS "SDL_JOYSTICK_HIDAPI_JOY_CONS" + +/** + * A variable controlling whether Nintendo Switch Joy-Con controllers will be + * combined into a single Pro-like controller when using the HIDAPI driver + * + * This variable can be set to the following values: + * + * - "0": Left and right Joy-Con controllers will not be combined and each + * will be a mini-gamepad + * - "1": Left and right Joy-Con controllers will be combined into a single + * controller (the default) + */ +#define SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS" + +/** + * A variable controlling whether Nintendo Switch Joy-Con controllers will be + * in vertical mode when using the HIDAPI driver + * + * This variable can be set to the following values: + * + * - "0": Left and right Joy-Con controllers will not be in vertical mode (the + * default) + * - "1": Left and right Joy-Con controllers will be in vertical mode + * + * This hint must be set before calling SDL_Init(SDL_INIT_GAMECONTROLLER) + */ +#define SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS "SDL_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS" + +/** + * A variable controlling whether the HIDAPI driver for Amazon Luna + * controllers connected via Bluetooth should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_LUNA "SDL_JOYSTICK_HIDAPI_LUNA" + +/** + * A variable controlling whether the HIDAPI driver for Nintendo Online + * classic controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC "SDL_JOYSTICK_HIDAPI_NINTENDO_CLASSIC" + +/** + * A variable controlling whether the HIDAPI driver for NVIDIA SHIELD + * controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SHIELD "SDL_JOYSTICK_HIDAPI_SHIELD" + +/** + * A variable controlling whether the HIDAPI driver for PS3 controllers should + * be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI on macOS, and "0" on + * other platforms. + * + * It is not possible to use this driver on Windows, due to limitations in the + * default drivers installed. See https://github.com/ViGEm/DsHidMini for an + * alternative driver on Windows. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_PS3 "SDL_JOYSTICK_HIDAPI_PS3" + +/** + * A variable controlling whether the HIDAPI driver for PS4 controllers should + * be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4" + +/** + * A variable controlling whether extended input reports should be used for + * PS4 controllers when using the HIDAPI driver. + * + * This variable can be set to the following values: + * + * - "0": extended reports are not enabled (the default) + * - "1": extended reports + * + * Extended input reports allow rumble on Bluetooth PS4 controllers, but break + * DirectInput handling for applications that don't use SDL. + * + * Once extended reports are enabled, they can not be disabled without power + * cycling the controller. + * + * For compatibility with applications written for versions of SDL prior to + * the introduction of PS5 controller support, this value will also control + * the state of extended reports on PS5 controllers when the + * SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE hint is not explicitly set. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE" + +/** + * A variable controlling whether the HIDAPI driver for PS5 controllers should + * be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_PS5 "SDL_JOYSTICK_HIDAPI_PS5" + +/** + * A variable controlling whether the player LEDs should be lit to indicate + * which player is associated with a PS5 controller. + * + * This variable can be set to the following values: + * + * - "0": player LEDs are not enabled + * - "1": player LEDs are enabled (the default) + */ +#define SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED" + +/** + * A variable controlling whether extended input reports should be used for + * PS5 controllers when using the HIDAPI driver. + * + * This variable can be set to the following values: + * + * - "0": extended reports are not enabled (the default) + * - "1": extended reports + * + * Extended input reports allow rumble on Bluetooth PS5 controllers, but break + * DirectInput handling for applications that don't use SDL. + * + * Once extended reports are enabled, they can not be disabled without power + * cycling the controller. + * + * For compatibility with applications written for versions of SDL prior to + * the introduction of PS5 controller support, this value defaults to the + * value of SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE" + +/** + * A variable controlling whether the HIDAPI driver for Google Stadia + * controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_STADIA "SDL_JOYSTICK_HIDAPI_STADIA" + +/** + * A variable controlling whether the HIDAPI driver for Bluetooth Steam + * Controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used for Steam Controllers, which requires + * Bluetooth access and may prompt the user for permission on iOS and + * Android. + * + * The default is "0" + */ +#define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM" + +/** + * A variable controlling whether the HIDAPI driver for the Steam Deck builtin + * controller should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK "SDL_JOYSTICK_HIDAPI_STEAMDECK" + +/** + * A variable controlling whether the HIDAPI driver for Nintendo Switch + * controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH" + +/** + * A variable controlling whether the Home button LED should be turned on when + * a Nintendo Switch Pro controller is opened + * + * This variable can be set to the following values: + * + * - "0": home button LED is turned off + * - "1": home button LED is turned on + * + * By default the Home button LED state is not changed. This hint can also be + * set to a floating point value between 0.0 and 1.0 which controls the + * brightness of the Home button LED. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED "SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED" + +/** + * A variable controlling whether the Home button LED should be turned on when + * a Nintendo Switch Joy-Con controller is opened + * + * This variable can be set to the following values: + * + * - "0": home button LED is turned off + * - "1": home button LED is turned on + * + * By default the Home button LED state is not changed. This hint can also be + * set to a floating point value between 0.0 and 1.0 which controls the + * brightness of the Home button LED. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED "SDL_JOYSTICK_HIDAPI_JOYCON_HOME_LED" + +/** + * A variable controlling whether the player LEDs should be lit to indicate + * which player is associated with a Nintendo Switch controller. + * + * This variable can be set to the following values: + * + * - "0": player LEDs are not enabled + * - "1": player LEDs are enabled (the default) + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED" + +/** + * A variable controlling whether the HIDAPI driver for Nintendo Wii and Wii U + * controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * This driver doesn't work with the dolphinbar, so the default is SDL_FALSE + * for now. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_WII "SDL_JOYSTICK_HIDAPI_WII" + +/** + * A variable controlling whether the player LEDs should be lit to indicate + * which player is associated with a Wii controller. + * + * This variable can be set to the following values: + * + * - "0": player LEDs are not enabled + * - "1": player LEDs are enabled (the default) + */ +#define SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED "SDL_JOYSTICK_HIDAPI_WII_PLAYER_LED" + +/** + * A variable controlling whether the HIDAPI driver for XBox controllers + * should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is "0" on Windows, otherwise the value of + * SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_XBOX "SDL_JOYSTICK_HIDAPI_XBOX" + +/** + * A variable controlling whether the HIDAPI driver for XBox 360 controllers + * should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI_XBOX + */ +#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360 "SDL_JOYSTICK_HIDAPI_XBOX_360" + +/** + * A variable controlling whether the player LEDs should be lit to indicate + * which player is associated with an Xbox 360 controller. + * + * This variable can be set to the following values: + * + * - "0": player LEDs are not enabled + * - "1": player LEDs are enabled (the default) + */ +#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED "SDL_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED" + +/** + * A variable controlling whether the HIDAPI driver for XBox 360 wireless + * controllers should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI_XBOX_360 + */ +#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS "SDL_JOYSTICK_HIDAPI_XBOX_360_WIRELESS" + +/** + * A variable controlling whether the HIDAPI driver for XBox One controllers + * should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI_XBOX + */ +#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE "SDL_JOYSTICK_HIDAPI_XBOX_ONE" + +/** + * A variable controlling whether the Home button LED should be turned on when + * an Xbox One controller is opened + * + * This variable can be set to the following values: + * + * - "0": home button LED is turned off + * - "1": home button LED is turned on + * + * By default the Home button LED state is not changed. This hint can also be + * set to a floating point value between 0.0 and 1.0 which controls the + * brightness of the Home button LED. The default brightness is 0.4. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED" + +/** + * A variable controlling whether IOKit should be used for controller + * handling. + * + * This variable can be set to the following values: + * + * - "0": IOKit is not used + * - "1": IOKit is used (the default) + */ +#define SDL_HINT_JOYSTICK_IOKIT "SDL_JOYSTICK_IOKIT" + +/** + * A variable controlling whether GCController should be used for controller + * handling. + * + * This variable can be set to the following values: + * + * - "0": GCController is not used + * - "1": GCController is used (the default) + */ +#define SDL_HINT_JOYSTICK_MFI "SDL_JOYSTICK_MFI" + +/** + * A variable controlling whether the RAWINPUT joystick drivers should be used + * for better handling XInput-capable devices. + * + * This variable can be set to the following values: + * + * - "0": RAWINPUT drivers are not used + * - "1": RAWINPUT drivers are used (the default) + */ +#define SDL_HINT_JOYSTICK_RAWINPUT "SDL_JOYSTICK_RAWINPUT" + +/** + * A variable controlling whether the RAWINPUT driver should pull correlated + * data from XInput. + * + * This variable can be set to the following values: + * + * - "0": RAWINPUT driver will only use data from raw input APIs + * - "1": RAWINPUT driver will also pull data from XInput, providing better + * trigger axes, guide button presses, and rumble support for Xbox + * controllers + * + * The default is "1". This hint applies to any joysticks opened after setting + * the hint. + */ +#define SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT "SDL_JOYSTICK_RAWINPUT_CORRELATE_XINPUT" + +/** + * A variable controlling whether the ROG Chakram mice should show up as + * joysticks + * + * This variable can be set to the following values: + * + * - "0": ROG Chakram mice do not show up as joysticks (the default) + * - "1": ROG Chakram mice show up as joysticks + */ +#define SDL_HINT_JOYSTICK_ROG_CHAKRAM "SDL_JOYSTICK_ROG_CHAKRAM" + +/** + * A variable controlling whether a separate thread should be used for + * handling joystick detection and raw input messages on Windows + * + * This variable can be set to the following values: + * + * - "0": A separate thread is not used (the default) + * - "1": A separate thread is used for handling raw input messages + */ +#define SDL_HINT_JOYSTICK_THREAD "SDL_JOYSTICK_THREAD" + +/** + * A variable containing a list of throttle style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_THROTTLE_DEVICES "SDL_JOYSTICK_THROTTLE_DEVICES" + +/** + * A variable containing a list of devices that are not throttle style + * controllers. + * + * This will override SDL_HINT_JOYSTICK_THROTTLE_DEVICES and the built in + * device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED "SDL_JOYSTICK_THROTTLE_DEVICES_EXCLUDED" + +/** + * A variable controlling whether Windows.Gaming.Input should be used for + * controller handling. + * + * This variable can be set to the following values: + * + * - "0": WGI is not used + * - "1": WGI is used (the default) + */ +#define SDL_HINT_JOYSTICK_WGI "SDL_JOYSTICK_WGI" + +/** + * A variable containing a list of wheel style controllers. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_WHEEL_DEVICES "SDL_JOYSTICK_WHEEL_DEVICES" + +/** + * A variable containing a list of devices that are not wheel style + * controllers. + * + * This will override SDL_HINT_JOYSTICK_WHEEL_DEVICES and the built in device + * list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED "SDL_JOYSTICK_WHEEL_DEVICES_EXCLUDED" + +/** + * A variable containing a list of devices known to have all axes centered at + * zero. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES" + +/** + * Determines whether SDL enforces that DRM master is required in order to + * initialize the KMSDRM video backend. + * + * The DRM subsystem has a concept of a "DRM master" which is a DRM client + * that has the ability to set planes, set cursor, etc. When SDL is DRM + * master, it can draw to the screen using the SDL rendering APIs. Without DRM + * master, SDL is still able to process input and query attributes of attached + * displays, but it cannot change display state or draw to the screen + * directly. + * + * In some cases, it can be useful to have the KMSDRM backend even if it + * cannot be used for rendering. An app may want to use SDL for input + * processing while using another rendering API (such as an MMAL overlay on + * Raspberry Pi) or using its own code to render to DRM overlays that SDL + * doesn't support. + * + * This hint must be set before initializing the video subsystem. + * + * This variable can be set to the following values: + * + * - "0": SDL will allow usage of the KMSDRM backend without DRM master + * - "1": SDL Will require DRM master to use the KMSDRM backend (default) + */ +#define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER" + +/** + * A comma separated list of devices to open as joysticks + * + * This variable is currently only used by the Linux joystick driver. + */ +#define SDL_HINT_JOYSTICK_DEVICE "SDL_JOYSTICK_DEVICE" + + +/** + * A variable containing a list of devices and their desired number of haptic + * (force feedback) enabled axis. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form plus the number of desired axes, e.g. + * + * `0xAAAA/0xBBBB/1,0xCCCC/0xDDDD/3` + * + * This hint supports a "wildcard" device that will set the number of haptic + * axes on all initialized haptic devices which were not defined explicitly in + * this hint. + * + * `0xFFFF/0xFFFF/1` + * + * This hint should be set before a controller is opened. The number of haptic + * axes won't exceed the number of real axes found on the device. + */ +#define SDL_HINT_JOYSTICK_HAPTIC_AXES "SDL_JOYSTICK_HAPTIC_AXES" + +/** + * A variable controlling whether joysticks on Linux will always treat 'hat' + * axis inputs (ABS_HAT0X - ABS_HAT3Y) as 8-way digital hats without checking + * whether they may be analog. + * + * This variable can be set to the following values: + * + * - "0": Only map hat axis inputs to digital hat outputs if the input axes + * appear to actually be digital (the default) + * - "1": Always handle the input axes numbered ABS_HAT0X to ABS_HAT3Y as + * digital hats + */ +#define SDL_HINT_LINUX_DIGITAL_HATS "SDL_LINUX_DIGITAL_HATS" + +/** + * A variable controlling whether digital hats on Linux will apply deadzones + * to their underlying input axes or use unfiltered values. + * + * This variable can be set to the following values: + * + * - "0": Return digital hat values based on unfiltered input axis values + * - "1": Return digital hat values with deadzones on the input axes taken + * into account (the default) + */ +#define SDL_HINT_LINUX_HAT_DEADZONES "SDL_LINUX_HAT_DEADZONES" + +/** + * A variable controlling whether to use the classic /dev/input/js* joystick + * interface or the newer /dev/input/event* joystick interface on Linux + * + * This variable can be set to the following values: + * + * - "0": Use /dev/input/event* + * - "1": Use /dev/input/js* + * + * By default the /dev/input/event* interfaces are used + */ +#define SDL_HINT_LINUX_JOYSTICK_CLASSIC "SDL_LINUX_JOYSTICK_CLASSIC" + +/** + * A variable controlling whether joysticks on Linux adhere to their + * HID-defined deadzones or return unfiltered values. + * + * This variable can be set to the following values: + * + * - "0": Return unfiltered joystick axis values (the default) + * - "1": Return axis values with deadzones taken into account + */ +#define SDL_HINT_LINUX_JOYSTICK_DEADZONES "SDL_LINUX_JOYSTICK_DEADZONES" + +/** + * A variable controlling the default SDL log levels. + * + * This variable is a comma separated set of category=level tokens that define + * the default logging levels for SDL applications. + * + * The category can be a numeric category, one of "app", "error", "assert", + * "system", "audio", "video", "render", "input", "test", or `*` for any + * unspecified category. + * + * The level can be a numeric level, one of "verbose", "debug", "info", + * "warn", "error", "critical", or "quiet" to disable that category. + * + * You can omit the category if you want to set the logging level for all + * categories. + * + * If this hint isn't set, the default log levels are equivalent to: + * "app=info,assert=warn,test=verbose,*=error" + */ +#define SDL_HINT_LOGGING "SDL_LOGGING" + +/** + * When set don't force the SDL app to become a foreground process + * + * This hint only applies to Mac OS X. + */ +#define SDL_HINT_MAC_BACKGROUND_APP "SDL_MAC_BACKGROUND_APP" + +/** + * A variable that determines whether ctrl+click should generate a right-click + * event on Mac + * + * If present, holding ctrl while left clicking will generate a right click + * event when on Mac. + */ +#define SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK "SDL_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK" + +/** + * A variable controlling whether dispatching OpenGL context updates should + * block the dispatching thread until the main thread finishes processing + * + * This variable can be set to the following values: + * + * - "0": Dispatching OpenGL context updates will block the dispatching thread + * until the main thread finishes processing (default). + * - "1": Dispatching OpenGL context updates will allow the dispatching thread + * to continue execution. + * + * Generally you want the default, but if you have OpenGL code in a background + * thread on a Mac, and the main thread hangs because it's waiting for that + * background thread, but that background thread is also hanging because it's + * waiting for the main thread to do an update, this might fix your issue. + * + * This hint only applies to macOS. + * + * This hint is available since SDL 2.24.0. + */ +#define SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH "SDL_MAC_OPENGL_ASYNC_DISPATCH" + +/** + * A variable setting the double click radius, in pixels. + */ +#define SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS "SDL_MOUSE_DOUBLE_CLICK_RADIUS" + +/** + * A variable setting the double click time, in milliseconds. + */ +#define SDL_HINT_MOUSE_DOUBLE_CLICK_TIME "SDL_MOUSE_DOUBLE_CLICK_TIME" + +/** + * Allow mouse click events when clicking to focus an SDL window + * + * This variable can be set to the following values: + * + * - "0": Ignore mouse clicks that activate a window + * - "1": Generate events for mouse clicks that activate a window + * + * By default SDL will ignore mouse clicks that activate a window + */ +#define SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH "SDL_MOUSE_FOCUS_CLICKTHROUGH" + +/** + * A variable setting the speed scale for mouse motion, in floating point, + * when the mouse is not in relative mode + */ +#define SDL_HINT_MOUSE_NORMAL_SPEED_SCALE "SDL_MOUSE_NORMAL_SPEED_SCALE" + +/** + * A variable controlling whether relative mouse mode constrains the mouse to + * the center of the window + * + * This variable can be set to the following values: + * + * - "0": Relative mouse mode constrains the mouse to the window + * - "1": Relative mouse mode constrains the mouse to the center of the window + * + * Constraining to the center of the window works better for FPS games and + * when the application is running over RDP. Constraining to the whole window + * works better for 2D games and increases the chance that the mouse will be + * in the correct position when using high DPI mice. + * + * By default SDL will constrain the mouse to the center of the window + */ +#define SDL_HINT_MOUSE_RELATIVE_MODE_CENTER "SDL_MOUSE_RELATIVE_MODE_CENTER" + +/** + * A variable controlling whether relative mouse mode is implemented using + * mouse warping + * + * This variable can be set to the following values: + * + * - "0": Relative mouse mode uses raw input + * - "1": Relative mouse mode uses mouse warping + * + * By default SDL will use raw input for relative mouse mode + */ +#define SDL_HINT_MOUSE_RELATIVE_MODE_WARP "SDL_MOUSE_RELATIVE_MODE_WARP" + +/** + * A variable controlling whether relative mouse motion is affected by + * renderer scaling + * + * This variable can be set to the following values: + * + * - "0": Relative motion is unaffected by DPI or renderer's logical size + * - "1": Relative motion is scaled according to DPI scaling and logical size + * + * By default relative mouse deltas are affected by DPI and renderer scaling + */ +#define SDL_HINT_MOUSE_RELATIVE_SCALING "SDL_MOUSE_RELATIVE_SCALING" + +/** + * A variable setting the scale for mouse motion, in floating point, when the + * mouse is in relative mode + */ +#define SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE "SDL_MOUSE_RELATIVE_SPEED_SCALE" + +/** + * A variable controlling whether the system mouse acceleration curve is used + * for relative mouse motion. + * + * This variable can be set to the following values: + * + * - "0": Relative mouse motion will be unscaled (the default) + * - "1": Relative mouse motion will be scaled using the system mouse + * acceleration curve. + * + * If SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE is set, that will override the + * system speed scale. + */ +#define SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE "SDL_MOUSE_RELATIVE_SYSTEM_SCALE" + +/** + * A variable controlling whether a motion event should be generated for mouse + * warping in relative mode. + * + * This variable can be set to the following values: + * + * - "0": Warping the mouse will not generate a motion event in relative mode + * - "1": Warping the mouse will generate a motion event in relative mode + * + * By default warping the mouse will not generate motion events in relative + * mode. This avoids the application having to filter out large relative + * motion due to warping. + */ +#define SDL_HINT_MOUSE_RELATIVE_WARP_MOTION "SDL_MOUSE_RELATIVE_WARP_MOTION" + +/** + * A variable controlling whether the hardware cursor stays visible when + * relative mode is active. + * + * This variable can be set to the following values: "0" - The cursor will be + * hidden while relative mode is active (default) "1" - The cursor will remain + * visible while relative mode is active + * + * Note that for systems without raw hardware inputs, relative mode is + * implemented using warping, so the hardware cursor will visibly warp between + * frames if this is enabled on those systems. + */ +#define SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE "SDL_MOUSE_RELATIVE_CURSOR_VISIBLE" + +/** + * A variable controlling whether mouse events should generate synthetic touch + * events + * + * This variable can be set to the following values: + * + * - "0": Mouse events will not generate touch events (default for desktop + * platforms) + * - "1": Mouse events will generate touch events (default for mobile + * platforms, such as Android and iOS) + */ +#define SDL_HINT_MOUSE_TOUCH_EVENTS "SDL_MOUSE_TOUCH_EVENTS" + +/** + * A variable controlling whether the mouse is captured while mouse buttons + * are pressed + * + * This variable can be set to the following values: + * + * - "0": The mouse is not captured while mouse buttons are pressed + * - "1": The mouse is captured while mouse buttons are pressed + * + * By default the mouse is captured while mouse buttons are pressed so if the + * mouse is dragged outside the window, the application continues to receive + * mouse events until the button is released. + */ +#define SDL_HINT_MOUSE_AUTO_CAPTURE "SDL_MOUSE_AUTO_CAPTURE" + +/** + * Tell SDL not to catch the SIGINT or SIGTERM signals. + * + * This hint only applies to Unix-like platforms, and should set before any + * calls to SDL_Init() + * + * The variable can be set to the following values: + * + * - "0": SDL will install a SIGINT and SIGTERM handler, and when it catches a + * signal, convert it into an SDL_QUIT event. + * - "1": SDL will not install a signal handler at all. + */ +#define SDL_HINT_NO_SIGNAL_HANDLERS "SDL_NO_SIGNAL_HANDLERS" + +/** + * A variable controlling what driver to use for OpenGL ES contexts. + * + * On some platforms, currently Windows and X11, OpenGL drivers may support + * creating contexts with an OpenGL ES profile. By default SDL uses these + * profiles, when available, otherwise it attempts to load an OpenGL ES + * library, e.g. that provided by the ANGLE project. This variable controls + * whether SDL follows this default behaviour or will always load an OpenGL ES + * library. + * + * Circumstances where this is useful include - Testing an app with a + * particular OpenGL ES implementation, e.g ANGLE, or emulator, e.g. those + * from ARM, Imagination or Qualcomm. - Resolving OpenGL ES function addresses + * at link time by linking with the OpenGL ES library instead of querying them + * at run time with SDL_GL_GetProcAddress(). + * + * Caution: for an application to work with the default behaviour across + * different OpenGL drivers it must query the OpenGL ES function addresses at + * run time using SDL_GL_GetProcAddress(). + * + * This variable is ignored on most platforms because OpenGL ES is native or + * not supported. + * + * This variable can be set to the following values: + * + * - "0": Use ES profile of OpenGL, if available. (Default when not set.) + * - "1": Load OpenGL ES library using the default library names. + */ +#define SDL_HINT_OPENGL_ES_DRIVER "SDL_OPENGL_ES_DRIVER" + +/** + * A variable controlling which orientations are allowed on iOS/Android. + * + * In some circumstances it is necessary to be able to explicitly control + * which UI orientations are allowed. + * + * This variable is a space delimited list of the following values: + * + * - "LandscapeLeft" + * - "LandscapeRight" + * - "Portrait" + * - "PortraitUpsideDown" + */ +#define SDL_HINT_ORIENTATIONS "SDL_IOS_ORIENTATIONS" + +/** + * A variable controlling the use of a sentinel event when polling the event + * queue + * + * This variable can be set to the following values: + * + * - "0": Disable poll sentinels + * - "1": Enable poll sentinels + * + * When polling for events, SDL_PumpEvents is used to gather new events from + * devices. If a device keeps producing new events between calls to + * SDL_PumpEvents, a poll loop will become stuck until the new events stop. + * This is most noticeable when moving a high frequency mouse. + * + * By default, poll sentinels are enabled. + */ +#define SDL_HINT_POLL_SENTINEL "SDL_POLL_SENTINEL" + +/** + * Override for SDL_GetPreferredLocales() + * + * If set, this will be favored over anything the OS might report for the + * user's preferred locales. Changing this hint at runtime will not generate a + * SDL_LOCALECHANGED event (but if you can change the hint, you can push your + * own event, if you want). + * + * The format of this hint is a comma-separated list of language and locale, + * combined with an underscore, as is a common format: "en_GB". Locale is + * optional: "en". So you might have a list like this: "en_GB,jp,es_PT" + */ +#define SDL_HINT_PREFERRED_LOCALES "SDL_PREFERRED_LOCALES" + +/** + * A variable describing the content orientation on QtWayland-based platforms. + * + * On QtWayland platforms, windows are rotated client-side to allow for custom + * transitions. In order to correctly position overlays (e.g. volume bar) and + * gestures (e.g. events view, close/minimize gestures), the system needs to + * know in which orientation the application is currently drawing its + * contents. + * + * This does not cause the window to be rotated or resized, the application + * needs to take care of drawing the content in the right orientation (the + * framebuffer is always in portrait mode). + * + * This variable can be one of the following values: + * + * - "primary" (default) + * - "portrait" + * - "landscape" + * - "inverted-portrait" + * - "inverted-landscape" + * + * Since SDL 2.0.22 this variable accepts a comma-separated list of values + * above. + */ +#define SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION "SDL_QTWAYLAND_CONTENT_ORIENTATION" + +/** + * Flags to set on QtWayland windows to integrate with the native window + * manager. + * + * On QtWayland platforms, this hint controls the flags to set on the windows. + * For example, on Sailfish OS "OverridesSystemGestures" disables swipe + * gestures. + * + * This variable is a space-separated list of the following values (empty = no + * flags): + * + * - "OverridesSystemGestures" + * - "StaysOnTop" + * - "BypassWindowManager" + */ +#define SDL_HINT_QTWAYLAND_WINDOW_FLAGS "SDL_QTWAYLAND_WINDOW_FLAGS" + +/** + * A variable controlling whether the 2D render API is compatible or + * efficient. + * + * This variable can be set to the following values: + * + * - "0": Don't use batching to make rendering more efficient. + * - "1": Use batching, but might cause problems if app makes its own direct + * OpenGL calls. + * + * Up to SDL 2.0.9, the render API would draw immediately when requested. Now + * it batches up draw requests and sends them all to the GPU only when forced + * to (during SDL_RenderPresent, when changing render targets, by updating a + * texture that the batch needs, etc). This is significantly more efficient, + * but it can cause problems for apps that expect to render on top of the + * render API's output. As such, SDL will disable batching if a specific + * render backend is requested (since this might indicate that the app is + * planning to use the underlying graphics API directly). This hint can be + * used to explicitly request batching in this instance. It is a contract that + * you will either never use the underlying graphics API directly, or if you + * do, you will call SDL_RenderFlush() before you do so any current batch goes + * to the GPU before your work begins. Not following this contract will result + * in undefined behavior. + */ +#define SDL_HINT_RENDER_BATCHING "SDL_RENDER_BATCHING" + +/** + * A variable controlling how the 2D render API renders lines + * + * This variable can be set to the following values: + * + * - "0": Use the default line drawing method (Bresenham's line algorithm as + * of SDL 2.0.20) + * - "1": Use the driver point API using Bresenham's line algorithm (correct, + * draws many points) + * - "2": Use the driver line API (occasionally misses line endpoints based on + * hardware driver quirks, was the default before 2.0.20) + * - "3": Use the driver geometry API (correct, draws thicker diagonal lines) + * + * This variable should be set when the renderer is created. + */ +#define SDL_HINT_RENDER_LINE_METHOD "SDL_RENDER_LINE_METHOD" + +/** + * A variable controlling whether to enable Direct3D 11+'s Debug Layer. + * + * This variable does not have any effect on the Direct3D 9 based renderer. + * + * This variable can be set to the following values: + * + * - "0": Disable Debug Layer use + * - "1": Enable Debug Layer use + * + * By default, SDL does not use Direct3D Debug Layer. + */ +#define SDL_HINT_RENDER_DIRECT3D11_DEBUG "SDL_RENDER_DIRECT3D11_DEBUG" + +/** + * A variable controlling whether the Direct3D device is initialized for + * thread-safe operations. + * + * This variable can be set to the following values: + * + * - "0": Thread-safety is not enabled (faster) + * - "1": Thread-safety is enabled + * + * By default the Direct3D device is created with thread-safety disabled. + */ +#define SDL_HINT_RENDER_DIRECT3D_THREADSAFE "SDL_RENDER_DIRECT3D_THREADSAFE" + +/** + * A variable specifying which render driver to use. + * + * If the application doesn't pick a specific renderer to use, this variable + * specifies the name of the preferred renderer. If the preferred renderer + * can't be initialized, the normal default renderer is used. + * + * This variable is case insensitive and can be set to the following values: + * + * - "direct3d" + * - "direct3d11" + * - "direct3d12" + * - "opengl" + * - "opengles2" + * - "opengles" + * - "metal" + * - "software" + * + * The default varies by platform, but it's the first one in the list that is + * available on the current platform. + */ +#define SDL_HINT_RENDER_DRIVER "SDL_RENDER_DRIVER" + +/** + * A variable controlling the scaling policy for SDL_RenderSetLogicalSize. + * + * This variable can be set to the following values: + * + * "0" or "letterbox" - Uses letterbox/sidebars to fit the entire rendering on + * screen "1" or "overscan" - Will zoom the rendering so it fills the entire + * screen, allowing edges to be drawn offscreen + * + * By default letterbox is used + */ +#define SDL_HINT_RENDER_LOGICAL_SIZE_MODE "SDL_RENDER_LOGICAL_SIZE_MODE" + +/** + * A variable controlling whether the OpenGL render driver uses shaders if + * they are available. + * + * This variable can be set to the following values: + * + * - "0": Disable shaders + * - "1": Enable shaders + * + * By default shaders are used if OpenGL supports them. + */ +#define SDL_HINT_RENDER_OPENGL_SHADERS "SDL_RENDER_OPENGL_SHADERS" + +/** + * A variable controlling the scaling quality + * + * This variable can be set to the following values: + * + * - "0" or "nearest": Nearest pixel sampling + * - "1" or "linear": Linear filtering (supported by OpenGL and Direct3D) + * - "2" or "best": Currently this is the same as "linear" + * + * By default nearest pixel sampling is used + */ +#define SDL_HINT_RENDER_SCALE_QUALITY "SDL_RENDER_SCALE_QUALITY" + +/** + * A variable controlling whether updates to the SDL screen surface should be + * synchronized with the vertical refresh, to avoid tearing. + * + * This variable can be set to the following values: + * + * - "0": Disable vsync + * - "1": Enable vsync + * + * By default SDL does not sync screen surface updates with vertical refresh. + */ +#define SDL_HINT_RENDER_VSYNC "SDL_RENDER_VSYNC" + +/** + * A variable controlling whether the Metal render driver select low power + * device over default one + * + * This variable can be set to the following values: + * + * - "0": Use the prefered OS device + * - "1": Select a low power one + * + * By default the prefered OS device is used. + */ +#define SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE "SDL_RENDER_METAL_PREFER_LOW_POWER_DEVICE" + +/** + * A variable containing a list of ROG gamepad capable mice. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_ROG_GAMEPAD_MICE "SDL_ROG_GAMEPAD_MICE" + +/** + * A variable containing a list of devices that are not ROG gamepad capable + * mice. + * + * This will override SDL_HINT_ROG_GAMEPAD_MICE and the built in device list. + * + * The format of the string is a comma separated list of USB VID/PID pairs in + * hexadecimal form, e.g. + * + * 0xAAAA/0xBBBB,0xCCCC/0xDDDD + * + * The variable can also take the form of @file, in which case the named file + * will be loaded and interpreted as the value of the variable. + */ +#define SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED "SDL_ROG_GAMEPAD_MICE_EXCLUDED" + +/** + * A variable controlling if VSYNC is automatically disable if doesn't reach + * the enough FPS + * + * This variable can be set to the following values: + * + * - "0": It will be using VSYNC as defined in the main flag. Default + * - "1": If VSYNC was previously enabled, then it will disable VSYNC if + * doesn't reach enough speed + * + * By default SDL does not enable the automatic VSYNC + */ +#define SDL_HINT_PS2_DYNAMIC_VSYNC "SDL_PS2_DYNAMIC_VSYNC" + +/** + * A variable to control whether the return key on the soft keyboard should + * hide the soft keyboard on Android and iOS. + * + * The variable can be set to the following values: + * + * - "0": The return key will be handled as a key event. This is the behaviour + * of SDL <= 2.0.3. (default) + * - "1": The return key will hide the keyboard. + * + * The value of this hint is used at runtime, so it can be changed at any + * time. + */ +#define SDL_HINT_RETURN_KEY_HIDES_IME "SDL_RETURN_KEY_HIDES_IME" + +/** + * Tell SDL which Dispmanx layer to use on a Raspberry PI + * + * Also known as Z-order. The variable can take a negative or positive value. + * The default is 10000. + */ +#define SDL_HINT_RPI_VIDEO_LAYER "SDL_RPI_VIDEO_LAYER" + +/** + * Specify an "activity name" for screensaver inhibition. + * + * Some platforms, notably Linux desktops, list the applications which are + * inhibiting the screensaver or other power-saving features. + * + * This hint lets you specify the "activity name" sent to the OS when + * SDL_DisableScreenSaver() is used (or the screensaver is automatically + * disabled). The contents of this hint are used when the screensaver is + * disabled. You should use a string that describes what your program is doing + * (and, therefore, why the screensaver is disabled). For example, "Playing a + * game" or "Watching a video". + * + * Setting this to "" or leaving it unset will have SDL use a reasonable + * default: "Playing a game" or something similar. + * + * On targets where this is not supported, this hint does nothing. + */ +#define SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME "SDL_SCREENSAVER_INHIBIT_ACTIVITY_NAME" + +/** + * Specifies whether SDL_THREAD_PRIORITY_TIME_CRITICAL should be treated as + * realtime. + * + * On some platforms, like Linux, a realtime priority thread may be subject to + * restrictions that require special handling by the application. This hint + * exists to let SDL know that the app is prepared to handle said + * restrictions. + * + * On Linux, SDL will apply the following configuration to any thread that + * becomes realtime: + * + * - The SCHED_RESET_ON_FORK bit will be set on the scheduling policy. + * - An RLIMIT_RTTIME budget will be configured to the rtkit specified limit. + * - Exceeding this limit will result in the kernel sending SIGKILL to the + * app. + * + * Refer to the man pages for more information. + * + * This variable can be set to the following values: + * + * - "0": default platform specific behaviour + * - "1": Force SDL_THREAD_PRIORITY_TIME_CRITICAL to a realtime scheduling + * policy + */ +#define SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL "SDL_THREAD_FORCE_REALTIME_TIME_CRITICAL" + +/** + * A string specifying additional information to use with + * SDL_SetThreadPriority. + * + * By default SDL_SetThreadPriority will make appropriate system changes in + * order to apply a thread priority. For example on systems using pthreads the + * scheduler policy is changed automatically to a policy that works well with + * a given priority. Code which has specific requirements can override SDL's + * default behavior with this hint. + * + * pthread hint values are "current", "other", "fifo" and "rr". Currently no + * other platform hint values are defined but may be in the future. + */ +#define SDL_HINT_THREAD_PRIORITY_POLICY "SDL_THREAD_PRIORITY_POLICY" + +/** + * A string specifying SDL's threads stack size in bytes or "0" for the + * backend's default size + * + * Use this hint in case you need to set SDL's threads stack size to other + * than the default. This is specially useful if you build SDL against a non + * glibc libc library (such as musl) which provides a relatively small default + * thread stack size (a few kilobytes versus the default 8MB glibc uses). + * Support for this hint is currently available only in the pthread, Windows, + * and PSP backend. + * + * Instead of this hint, in 2.0.9 and later, you can use + * SDL_CreateThreadWithStackSize(). This hint only works with the classic + * SDL_CreateThread(). + */ +#define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE" + +/** + * A variable that controls the timer resolution, in milliseconds. + * + * The higher resolution the timer, the more frequently the CPU services timer + * interrupts, and the more precise delays are, but this takes up power and + * CPU time. This hint is only used on Windows. + * + * See this blog post for more information: + * http://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/ + * + * If this variable is set to "0", the system timer resolution is not set. + * + * The default value is "1". This hint may be set at any time. + */ +#define SDL_HINT_TIMER_RESOLUTION "SDL_TIMER_RESOLUTION" + +/** + * A variable controlling whether touch events should generate synthetic mouse + * events + * + * This variable can be set to the following values: + * + * - "0": Touch events will not generate mouse events + * - "1": Touch events will generate mouse events + * + * By default SDL will generate mouse events for touch events + */ +#define SDL_HINT_TOUCH_MOUSE_EVENTS "SDL_TOUCH_MOUSE_EVENTS" + +/** + * A variable controlling which touchpad should generate synthetic mouse + * events + * + * This variable can be set to the following values: + * + * - "0": Only front touchpad should generate mouse events. Default + * - "1": Only back touchpad should generate mouse events. + * - "2": Both touchpads should generate mouse events. + * + * By default SDL will generate mouse events for all touch devices + */ +#define SDL_HINT_VITA_TOUCH_MOUSE_DEVICE "SDL_HINT_VITA_TOUCH_MOUSE_DEVICE" + +/** + * A variable controlling whether the Android / tvOS remotes should be listed + * as joystick devices, instead of sending keyboard events. + * + * This variable can be set to the following values: + * + * - "0": Remotes send enter/escape/arrow key events + * - "1": Remotes are available as 2 axis, 2 button joysticks (the default). + */ +#define SDL_HINT_TV_REMOTE_AS_JOYSTICK "SDL_TV_REMOTE_AS_JOYSTICK" + +/** + * A variable controlling whether the screensaver is enabled. + * + * This variable can be set to the following values: + * + * - "0": Disable screensaver + * - "1": Enable screensaver + * + * By default SDL will disable the screensaver. + */ +#define SDL_HINT_VIDEO_ALLOW_SCREENSAVER "SDL_VIDEO_ALLOW_SCREENSAVER" + +/** + * Tell the video driver that we only want a double buffer. + * + * By default, most lowlevel 2D APIs will use a triple buffer scheme that + * wastes no CPU time on waiting for vsync after issuing a flip, but + * introduces a frame of latency. On the other hand, using a double buffer + * scheme instead is recommended for cases where low latency is an important + * factor because we save a whole frame of latency. We do so by waiting for + * vsync immediately after issuing a flip, usually just after eglSwapBuffers + * call in the backend's *_SwapWindow function. + * + * Since it's driver-specific, it's only supported where possible and + * implemented. Currently supported the following drivers: + * + * - Wayland (wayland) + * - KMSDRM (kmsdrm) + * - Raspberry Pi (raspberrypi) + */ +#define SDL_HINT_VIDEO_DOUBLE_BUFFER "SDL_VIDEO_DOUBLE_BUFFER" + +/** + * A variable controlling whether the EGL window is allowed to be composited + * as transparent, rather than opaque. + * + * Most window systems will always render windows opaque, even if the surface + * format has an alpha channel. This is not always true, however, so by + * default SDL will try to enforce opaque composition. To override this + * behavior, you can set this hint to "1". + */ +#define SDL_HINT_VIDEO_EGL_ALLOW_TRANSPARENCY "SDL_VIDEO_EGL_ALLOW_TRANSPARENCY" + +/** + * A variable controlling whether the graphics context is externally managed. + * + * This variable can be set to the following values: + * + * - "0": SDL will manage graphics contexts that are attached to windows. + * - "1": Disable graphics context management on windows. + * + * By default SDL will manage OpenGL contexts in certain situations. For + * example, on Android the context will be automatically saved and restored + * when pausing the application. Additionally, some platforms will assume + * usage of OpenGL if Vulkan isn't used. Setting this to "1" will prevent this + * behavior, which is desireable when the application manages the graphics + * context, such as an externally managed OpenGL context or attaching a Vulkan + * surface to the window. + */ +#define SDL_HINT_VIDEO_EXTERNAL_CONTEXT "SDL_VIDEO_EXTERNAL_CONTEXT" + +/** + * If set to 1, then do not allow high-DPI windows. + * + * ("Retina" on Mac and iOS) + */ +#define SDL_HINT_VIDEO_HIGHDPI_DISABLED "SDL_VIDEO_HIGHDPI_DISABLED" + +/** + * A variable that dictates policy for fullscreen Spaces on Mac OS X. + * + * This hint only applies to Mac OS X. + * + * The variable can be set to the following values: + * + * - "0": Disable Spaces support (FULLSCREEN_DESKTOP won't use them and + * SDL_WINDOW_RESIZABLE windows won't offer the "fullscreen" button on their + * titlebars). + * - "1": Enable Spaces support (FULLSCREEN_DESKTOP will use them and + * SDL_WINDOW_RESIZABLE windows will offer the "fullscreen" button on their + * titlebars). + * + * The default value is "1". This hint must be set before any windows are + * created. + */ +#define SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES "SDL_VIDEO_MAC_FULLSCREEN_SPACES" + +/** + * Minimize your SDL_Window if it loses key focus when in fullscreen mode. + * + * Defaults to false. + */ +#define SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS" + +/** + * A variable controlling whether the libdecor Wayland backend is allowed to + * be used. + * + * This variable can be set to the following values: + * + * - "0": libdecor use is disabled. + * - "1": libdecor use is enabled (default). + * + * libdecor is used over xdg-shell when xdg-decoration protocol is + * unavailable. + */ +#define SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR "SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR" + +/** + * A variable controlling whether the libdecor Wayland backend is preferred + * over native decorations. + * + * When this hint is set, libdecor will be used to provide window decorations, + * even if xdg-decoration is available. (Note that, by default, libdecor will + * use xdg-decoration itself if available). + * + * This variable can be set to the following values: + * + * - "0": libdecor is enabled only if server-side decorations are unavailable. + * - "1": libdecor is always enabled if available. + * + * libdecor is used over xdg-shell when xdg-decoration protocol is + * unavailable. + */ +#define SDL_HINT_VIDEO_WAYLAND_PREFER_LIBDECOR "SDL_VIDEO_WAYLAND_PREFER_LIBDECOR" + +/** + * A variable controlling whether video mode emulation is enabled under + * Wayland. + * + * When this hint is set, a standard set of emulated CVT video modes will be + * exposed for use by the application. If it is disabled, the only modes + * exposed will be the logical desktop size and, in the case of a scaled + * desktop, the native display resolution. + * + * This variable can be set to the following values: + * + * - "0": Video mode emulation is disabled. + * - "1": Video mode emulation is enabled. + * + * By default video mode emulation is enabled. + */ +#define SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION "SDL_VIDEO_WAYLAND_MODE_EMULATION" + +/** + * Enable or disable mouse pointer warp emulation, needed by some older games. + * + * When this hint is set, any SDL will emulate mouse warps using relative + * mouse mode. This is required for some older games (such as Source engine + * games), which warp the mouse to the centre of the screen rather than using + * relative mouse motion. Note that relative mouse mode may have different + * mouse acceleration behaviour than pointer warps. + * + * This variable can be set to the following values: + * + * - "0": All mouse warps fail, as mouse warping is not available under + * wayland. + * - "1": Some mouse warps will be emulated by forcing relative mouse mode. + * + * If not set, this is automatically enabled unless an application uses + * relative mouse mode directly. + */ +#define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP" + +/** + * A variable that is the address of another SDL_Window* (as a hex string + * formatted with "%p"). + * + * If this hint is set before SDL_CreateWindowFrom() and the SDL_Window* it is + * set to has SDL_WINDOW_OPENGL set (and running on WGL only, currently), then + * two things will occur on the newly created SDL_Window: + * + * 1. Its pixel format will be set to the same pixel format as this + * SDL_Window. This is needed for example when sharing an OpenGL context + * across multiple windows. + * + * 2. The flag SDL_WINDOW_OPENGL will be set on the new window so it can be + * used for OpenGL rendering. + * + * This variable can be set to the following values: The address (as a string + * "%p") of the SDL_Window* that new windows created with + * SDL_CreateWindowFrom() should share a pixel format with. + */ +#define SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT "SDL_VIDEO_WINDOW_SHARE_PIXEL_FORMAT" + +/** + * When calling SDL_CreateWindowFrom(), make the window compatible with + * OpenGL. + * + * This variable can be set to the following values: + * + * - "0": Don't add any graphics flags to the SDL_WindowFlags + * - "1": Add SDL_WINDOW_OPENGL to the SDL_WindowFlags + * + * By default SDL will not make the foreign window compatible with OpenGL. + */ +#define SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL "SDL_VIDEO_FOREIGN_WINDOW_OPENGL" + +/** + * When calling SDL_CreateWindowFrom(), make the window compatible with + * Vulkan. + * + * This variable can be set to the following values: + * + * - "0": Don't add any graphics flags to the SDL_WindowFlags + * - "1": Add SDL_WINDOW_VULKAN to the SDL_WindowFlags + * + * By default SDL will not make the foreign window compatible with Vulkan. + */ +#define SDL_HINT_VIDEO_FOREIGN_WINDOW_VULKAN "SDL_VIDEO_FOREIGN_WINDOW_VULKAN" + +/** + * A variable specifying which shader compiler to preload when using the + * Chrome ANGLE binaries + * + * SDL has EGL and OpenGL ES2 support on Windows via the ANGLE project. It can + * use two different sets of binaries, those compiled by the user from source + * or those provided by the Chrome browser. In the later case, these binaries + * require that SDL loads a DLL providing the shader compiler. + * + * This variable can be set to the following values: + * + * - "d3dcompiler_46.dll: default, best for Vista or later. + * - "d3dcompiler_43.dll: for XP support. + * - "none": do not load any library, useful if you compiled ANGLE from source + * and included the compiler in your binaries. + */ +#define SDL_HINT_VIDEO_WIN_D3DCOMPILER "SDL_VIDEO_WIN_D3DCOMPILER" + +/** + * A variable controlling whether X11 should use GLX or EGL by default + * + * This variable can be set to the following values: + * + * - "0": Use GLX + * - "1": Use EGL + * + * By default SDL will use GLX when both are present. + */ +#define SDL_HINT_VIDEO_X11_FORCE_EGL "SDL_VIDEO_X11_FORCE_EGL" + +/** + * A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint + * should be used. + * + * This variable can be set to the following values: + * + * - "0": Disable _NET_WM_BYPASS_COMPOSITOR + * - "1": Enable _NET_WM_BYPASS_COMPOSITOR + * + * By default SDL will use _NET_WM_BYPASS_COMPOSITOR + */ +#define SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR" + +/** + * A variable controlling whether the X11 _NET_WM_PING protocol should be + * supported. + * + * This variable can be set to the following values: + * + * - "0": Disable _NET_WM_PING + * - "1": Enable _NET_WM_PING + * + * By default SDL will use _NET_WM_PING, but for applications that know they + * will not always be able to respond to ping requests in a timely manner they + * can turn it off to avoid the window manager thinking the app is hung. The + * hint is checked in CreateWindow. + */ +#define SDL_HINT_VIDEO_X11_NET_WM_PING "SDL_VIDEO_X11_NET_WM_PING" + +/** + * A variable forcing the visual ID chosen for new X11 windows + */ +#define SDL_HINT_VIDEO_X11_WINDOW_VISUALID "SDL_VIDEO_X11_WINDOW_VISUALID" + +/** + * A no-longer-used variable controlling whether the X11 Xinerama extension + * should be used. + * + * Before SDL 2.0.24, this would let apps and users disable Xinerama support + * on X11. Now SDL never uses Xinerama, and does not check for this hint at + * all. The preprocessor define is left here for source compatibility. + */ +#define SDL_HINT_VIDEO_X11_XINERAMA "SDL_VIDEO_X11_XINERAMA" + +/** + * A variable controlling whether the X11 XRandR extension should be used. + * + * This variable can be set to the following values: + * + * - "0": Disable XRandR + * - "1": Enable XRandR + * + * By default SDL will use XRandR. + */ +#define SDL_HINT_VIDEO_X11_XRANDR "SDL_VIDEO_X11_XRANDR" + +/** + * A no-longer-used variable controlling whether the X11 VidMode extension + * should be used. + * + * Before SDL 2.0.24, this would let apps and users disable XVidMode support + * on X11. Now SDL never uses XVidMode, and does not check for this hint at + * all. The preprocessor define is left here for source compatibility. + */ +#define SDL_HINT_VIDEO_X11_XVIDMODE "SDL_VIDEO_X11_XVIDMODE" + +/** + * Controls how the fact chunk affects the loading of a WAVE file. + * + * The fact chunk stores information about the number of samples of a WAVE + * file. The Standards Update from Microsoft notes that this value can be used + * to 'determine the length of the data in seconds'. This is especially useful + * for compressed formats (for which this is a mandatory chunk) if they + * produce multiple sample frames per block and truncating the block is not + * allowed. The fact chunk can exactly specify how many sample frames there + * should be in this case. + * + * Unfortunately, most application seem to ignore the fact chunk and so SDL + * ignores it by default as well. + * + * This variable can be set to the following values: + * + * - "truncate": Use the number of samples to truncate the wave data if the + * fact chunk is present and valid + * - "strict": Like "truncate", but raise an error if the fact chunk is + * invalid, not present for non-PCM formats, or if the data chunk doesn't + * have that many samples + * - "ignorezero": Like "truncate", but ignore fact chunk if the number of + * samples is zero + * - "ignore": Ignore fact chunk entirely (default) + */ +#define SDL_HINT_WAVE_FACT_CHUNK "SDL_WAVE_FACT_CHUNK" + +/** + * Controls how the size of the RIFF chunk affects the loading of a WAVE file. + * + * The size of the RIFF chunk (which includes all the sub-chunks of the WAVE + * file) is not always reliable. In case the size is wrong, it's possible to + * just ignore it and step through the chunks until a fixed limit is reached. + * + * Note that files that have trailing data unrelated to the WAVE file or + * corrupt files may slow down the loading process without a reliable + * boundary. By default, SDL stops after 10000 chunks to prevent wasting time. + * Use the environment variable SDL_WAVE_CHUNK_LIMIT to adjust this value. + * + * This variable can be set to the following values: + * + * - "force": Always use the RIFF chunk size as a boundary for the chunk + * search + * - "ignorezero": Like "force", but a zero size searches up to 4 GiB + * (default) + * - "ignore": Ignore the RIFF chunk size and always search up to 4 GiB + * - "maximum": Search for chunks until the end of file (not recommended) + */ +#define SDL_HINT_WAVE_RIFF_CHUNK_SIZE "SDL_WAVE_RIFF_CHUNK_SIZE" + +/** + * Controls how a truncated WAVE file is handled. + * + * A WAVE file is considered truncated if any of the chunks are incomplete or + * the data chunk size is not a multiple of the block size. By default, SDL + * decodes until the first incomplete block, as most applications seem to do. + * + * This variable can be set to the following values: + * + * - "verystrict": Raise an error if the file is truncated + * - "strict": Like "verystrict", but the size of the RIFF chunk is ignored + * - "dropframe": Decode until the first incomplete sample frame + * - "dropblock": Decode until the first incomplete block (default) + */ +#define SDL_HINT_WAVE_TRUNCATION "SDL_WAVE_TRUNCATION" + +/** + * Tell SDL not to name threads on Windows with the 0x406D1388 Exception. + * + * The 0x406D1388 Exception is a trick used to inform Visual Studio of a + * thread's name, but it tends to cause problems with other debuggers, and the + * .NET runtime. Note that SDL 2.0.6 and later will still use the (safer) + * SetThreadDescription API, introduced in the Windows 10 Creators Update, if + * available. + * + * The variable can be set to the following values: + * + * - "0": SDL will raise the 0x406D1388 Exception to name threads. This is the + * default behavior of SDL <= 2.0.4. + * - "1": SDL will not raise this exception, and threads will be unnamed. + * (default) This is necessary with .NET languages or debuggers that aren't + * Visual Studio. + */ +#define SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING "SDL_WINDOWS_DISABLE_THREAD_NAMING" + +/** + * Controls whether menus can be opened with their keyboard shortcut + * (Alt+mnemonic). + * + * If the mnemonics are enabled, then menus can be opened by pressing the Alt + * key and the corresponding mnemonic (for example, Alt+F opens the File + * menu). However, in case an invalid mnemonic is pressed, Windows makes an + * audible beep to convey that nothing happened. This is true even if the + * window has no menu at all! + * + * Because most SDL applications don't have menus, and some want to use the + * Alt key for other purposes, SDL disables mnemonics (and the beeping) by + * default. + * + * Note: This also affects keyboard events: with mnemonics enabled, when a + * menu is opened from the keyboard, you will not receive a KEYUP event for + * the mnemonic key, and *might* not receive one for Alt. + * + * This variable can be set to the following values: + * + * - "0": Alt+mnemonic does nothing, no beeping. (default) + * - "1": Alt+mnemonic opens menus, invalid mnemonics produce a beep. + */ +#define SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS "SDL_WINDOWS_ENABLE_MENU_MNEMONICS" + +/** + * A variable controlling whether the windows message loop is processed by SDL + * + * This variable can be set to the following values: + * + * - "0": The window message loop is not run + * - "1": The window message loop is processed in SDL_PumpEvents() + * + * By default SDL will process the windows message loop + */ +#define SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP "SDL_WINDOWS_ENABLE_MESSAGELOOP" + +/** + * Force SDL to use Critical Sections for mutexes on Windows. + * + * On Windows 7 and newer, Slim Reader/Writer Locks are available. They offer + * better performance, allocate no kernel resources and use less memory. SDL + * will fall back to Critical Sections on older OS versions or if forced to by + * this hint. + * + * This variable can be set to the following values: + * + * - "0": Use SRW Locks when available. If not, fall back to Critical + * Sections. (default) + * - "1": Force the use of Critical Sections in all cases. + */ +#define SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS "SDL_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS" + +/** + * Force SDL to use Kernel Semaphores on Windows. + * + * Kernel Semaphores are inter-process and require a context switch on every + * interaction. On Windows 8 and newer, the WaitOnAddress API is available. + * Using that and atomics to implement semaphores increases performance. SDL + * will fall back to Kernel Objects on older OS versions or if forced to by + * this hint. + * + * This variable can be set to the following values: + * + * - "0": Use Atomics and WaitOnAddress API when available. If not, fall back + * to Kernel Objects. (default) + * - "1": Force the use of Kernel Objects in all cases. + */ +#define SDL_HINT_WINDOWS_FORCE_SEMAPHORE_KERNEL "SDL_WINDOWS_FORCE_SEMAPHORE_KERNEL" + +/** + * A variable to specify custom icon resource id from RC file on Windows + * platform + */ +#define SDL_HINT_WINDOWS_INTRESOURCE_ICON "SDL_WINDOWS_INTRESOURCE_ICON" + +/** + * A variable to specify custom icon resource id from RC file on Windows + * platform + */ +#define SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL "SDL_WINDOWS_INTRESOURCE_ICON_SMALL" + +/** + * Tell SDL not to generate window-close events for Alt+F4 on Windows. + * + * The variable can be set to the following values: + * + * - "0": SDL will generate a window-close event when it sees Alt+F4. + * - "1": SDL will only do normal key handling for Alt+F4. + */ +#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 "SDL_WINDOWS_NO_CLOSE_ON_ALT_F4" + +/** + * Use the D3D9Ex API introduced in Windows Vista, instead of normal D3D9. + * + * Direct3D 9Ex contains changes to state management that can eliminate device + * loss errors during scenarios like Alt+Tab or UAC prompts. D3D9Ex may + * require some changes to your application to cope with the new behavior, so + * this is disabled by default. + * + * This hint must be set before initializing the video subsystem. + * + * For more information on Direct3D 9Ex, see: - + * https://docs.microsoft.com/en-us/windows/win32/direct3darticles/graphics-apis-in-windows-vista#direct3d-9ex + * - + * https://docs.microsoft.com/en-us/windows/win32/direct3darticles/direct3d-9ex-improvements + * + * This variable can be set to the following values: + * + * - "0": Use the original Direct3D 9 API (default) + * - "1": Use the Direct3D 9Ex API on Vista and later (and fall back if D3D9Ex + * is unavailable) + */ +#define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX" + +/** + * Controls whether SDL will declare the process to be DPI aware. + * + * This hint must be set before initializing the video subsystem. + * + * The main purpose of declaring DPI awareness is to disable OS bitmap scaling + * of SDL windows on monitors with a DPI scale factor. + * + * This hint is equivalent to requesting DPI awareness via external means + * (e.g. calling SetProcessDpiAwarenessContext) and does not cause SDL to use + * a virtualized coordinate system, so it will generally give you 1 SDL + * coordinate = 1 pixel even on high-DPI displays. + * + * For more information, see: + * https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows + * + * This variable can be set to the following values: + * + * - "": Do not change the DPI awareness (default). + * - "unaware": Declare the process as DPI unaware. (Windows 8.1 and later). + * - "system": Request system DPI awareness. (Vista and later). + * - "permonitor": Request per-monitor DPI awareness. (Windows 8.1 and later). + * - "permonitorv2": Request per-monitor V2 DPI awareness. (Windows 10, + * version 1607 and later). The most visible difference from "permonitor" is + * that window title bar will be scaled to the visually correct size when + * dragging between monitors with different scale factors. This is the + * preferred DPI awareness level. + * + * If the requested DPI awareness is not available on the currently running + * OS, SDL will try to request the best available match. + */ +#define SDL_HINT_WINDOWS_DPI_AWARENESS "SDL_WINDOWS_DPI_AWARENESS" + +/** + * Uses DPI-scaled points as the SDL coordinate system on Windows. + * + * This changes the SDL coordinate system units to be DPI-scaled points, + * rather than pixels everywhere. This means windows will be appropriately + * sized, even when created on high-DPI displays with scaling. + * + * e.g. requesting a 640x480 window from SDL, on a display with 125% scaling + * in Windows display settings, will create a window with an 800x600 client + * area (in pixels). + * + * Setting this to "1" implicitly requests process DPI awareness (setting + * SDL_WINDOWS_DPI_AWARENESS is unnecessary), and forces + * SDL_WINDOW_ALLOW_HIGHDPI on all windows. + * + * This variable can be set to the following values: + * + * - "0": SDL coordinates equal Windows coordinates. No automatic window + * resizing when dragging between monitors with different scale factors + * (unless this is performed by Windows itself, which is the case when the + * process is DPI unaware). + * - "1": SDL coordinates are in DPI-scaled points. Automatically resize + * windows as needed on displays with non-100% scale factors. + */ +#define SDL_HINT_WINDOWS_DPI_SCALING "SDL_WINDOWS_DPI_SCALING" + +/** + * A variable controlling whether the window frame and title bar are + * interactive when the cursor is hidden + * + * This variable can be set to the following values: + * + * - "0": The window frame is not interactive when the cursor is hidden (no + * move, resize, etc) + * - "1": The window frame is interactive when the cursor is hidden + * + * By default SDL will allow interaction with the window frame when the cursor + * is hidden + */ +#define SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN "SDL_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN" + +/** + * A variable controlling whether the window is activated when the + * SDL_ShowWindow function is called + * + * This variable can be set to the following values: + * + * - "0": The window is activated when the SDL_ShowWindow function is called + * - "1": The window is not activated when the SDL_ShowWindow function is + * called + * + * By default SDL will activate the window when the SDL_ShowWindow function is + * called + */ +#define SDL_HINT_WINDOW_NO_ACTIVATION_WHEN_SHOWN "SDL_WINDOW_NO_ACTIVATION_WHEN_SHOWN" + +/** Allows back-button-press events on Windows Phone to be marked as handled + * + * Windows Phone devices typically feature a Back button. When pressed, + * the OS will emit back-button-press events, which apps are expected to + * handle in an appropriate manner. If apps do not explicitly mark these + * events as 'Handled', then the OS will invoke its default behavior for + * unhandled back-button-press events, which on Windows Phone 8 and 8.1 is to + * terminate the app (and attempt to switch to the previous app, or to the + * device's home screen). + * + * Setting the SDL_HINT_WINRT_HANDLE_BACK_BUTTON hint to "1" will cause SDL + * to mark back-button-press events as Handled, if and when one is sent to + * the app. + * + * Internally, Windows Phone sends back button events as parameters to + * special back-button-press callback functions. Apps that need to respond + * to back-button-press events are expected to register one or more + * callback functions for such, shortly after being launched (during the + * app's initialization phase). After the back button is pressed, the OS + * will invoke these callbacks. If the app's callback(s) do not explicitly + * mark the event as handled by the time they return, or if the app never + * registers one of these callback, the OS will consider the event + * un-handled, and it will apply its default back button behavior (terminate + * the app). + * + * SDL registers its own back-button-press callback with the Windows Phone + * OS. This callback will emit a pair of SDL key-press events (SDL_KEYDOWN + * and SDL_KEYUP), each with a scancode of SDL_SCANCODE_AC_BACK, after which + * it will check the contents of the hint, SDL_HINT_WINRT_HANDLE_BACK_BUTTON. + * If the hint's value is set to "1", the back button event's Handled + * property will get set to 'true'. If the hint's value is set to something + * else, or if it is unset, SDL will leave the event's Handled property + * alone. (By default, the OS sets this property to 'false', to note.) + * + * SDL apps can either set SDL_HINT_WINRT_HANDLE_BACK_BUTTON well before a + * back button is pressed, or can set it in direct-response to a back button + * being pressed. + * + * In order to get notified when a back button is pressed, SDL apps should + * register a callback function with SDL_AddEventWatch(), and have it listen + * for SDL_KEYDOWN events that have a scancode of SDL_SCANCODE_AC_BACK. + * (Alternatively, SDL_KEYUP events can be listened-for. Listening for + * either event type is suitable.) Any value of SDL_HINT_WINRT_HANDLE_BACK_BUTTON + * set by such a callback, will be applied to the OS' current + * back-button-press event. + * + * More details on back button behavior in Windows Phone apps can be found + * at the following page, on Microsoft's developer site: + * + * http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj247550(v=vs.105).aspx + */ +#define SDL_HINT_WINRT_HANDLE_BACK_BUTTON "SDL_WINRT_HANDLE_BACK_BUTTON" + +/** Label text for a WinRT app's privacy policy link + * + * Network-enabled WinRT apps must include a privacy policy. On Windows 8, 8.1, and RT, + * Microsoft mandates that this policy be available via the Windows Settings charm. + * SDL provides code to add a link there, with its label text being set via the + * optional hint, SDL_HINT_WINRT_PRIVACY_POLICY_LABEL. + * + * Please note that a privacy policy's contents are not set via this hint. A separate + * hint, SDL_HINT_WINRT_PRIVACY_POLICY_URL, is used to link to the actual text of the + * policy. + * + * The contents of this hint should be encoded as a UTF8 string. + * + * The default value is "Privacy Policy". This hint should only be set during app + * initialization, preferably before any calls to SDL_Init(). + * + * For additional information on linking to a privacy policy, see the documentation for + * SDL_HINT_WINRT_PRIVACY_POLICY_URL. + */ +#define SDL_HINT_WINRT_PRIVACY_POLICY_LABEL "SDL_WINRT_PRIVACY_POLICY_LABEL" + +/** + * A URL to a WinRT app's privacy policy + * + * All network-enabled WinRT apps must make a privacy policy available to its + * users. On Windows 8, 8.1, and RT, Microsoft mandates that this policy be be + * available in the Windows Settings charm, as accessed from within the app. + * SDL provides code to add a URL-based link there, which can point to the + * app's privacy policy. + * + * To setup a URL to an app's privacy policy, set + * SDL_HINT_WINRT_PRIVACY_POLICY_URL before calling any SDL_Init() functions. + * The contents of the hint should be a valid URL. For example, + * "http://www.example.com". + * + * The default value is "", which will prevent SDL from adding a privacy + * policy link to the Settings charm. This hint should only be set during app + * init. + * + * The label text of an app's "Privacy Policy" link may be customized via + * another hint, SDL_HINT_WINRT_PRIVACY_POLICY_LABEL. + * + * Please note that on Windows Phone, Microsoft does not provide standard UI + * for displaying a privacy policy link, and as such, + * SDL_HINT_WINRT_PRIVACY_POLICY_URL will not get used on that platform. + * Network-enabled phone apps should display their privacy policy through some + * other, in-app means. + */ +#define SDL_HINT_WINRT_PRIVACY_POLICY_URL "SDL_WINRT_PRIVACY_POLICY_URL" + +/** + * Mark X11 windows as override-redirect. + * + * If set, this _might_ increase framerate at the expense of the desktop not + * working as expected. Override-redirect windows aren't noticed by the window + * manager at all. + * + * You should probably only use this for fullscreen windows, and you probably + * shouldn't even use it for that. But it's here if you want to try! + */ +#define SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT "SDL_X11_FORCE_OVERRIDE_REDIRECT" + +/** + * A variable that lets you disable the detection and use of Xinput gamepad + * devices + * + * The variable can be set to the following values: + * + * - "0": Disable XInput detection (only uses direct input) + * - "1": Enable XInput detection (the default) + */ +#define SDL_HINT_XINPUT_ENABLED "SDL_XINPUT_ENABLED" + +/** + * A variable that lets you disable the detection and use of DirectInput + * gamepad devices + * + * The variable can be set to the following values: + * + * - "0": Disable DirectInput detection (only uses XInput) + * - "1": Enable DirectInput detection (the default) + */ +#define SDL_HINT_DIRECTINPUT_ENABLED "SDL_DIRECTINPUT_ENABLED" + +/** + * A variable that causes SDL to use the old axis and button mapping for + * XInput devices. + * + * This hint is for backwards compatibility only and will be removed in SDL + * 2.1 + * + * The default value is "0". This hint must be set before SDL_Init() + */ +#define SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING "SDL_XINPUT_USE_OLD_JOYSTICK_MAPPING" + +/** + * A variable that causes SDL to not ignore audio "monitors" + * + * This is currently only used for PulseAudio and ignored elsewhere. + * + * By default, SDL ignores audio devices that aren't associated with physical + * hardware. Changing this hint to "1" will expose anything SDL sees that + * appears to be an audio source or sink. This will add "devices" to the list + * that the user probably doesn't want or need, but it can be useful in + * scenarios where you want to hook up SDL to some sort of virtual device, + * etc. + * + * The default value is "0". This hint must be set before SDL_Init(). + * + * This hint is available since SDL 2.0.16. Before then, virtual devices are + * always ignored. + */ +#define SDL_HINT_AUDIO_INCLUDE_MONITORS "SDL_AUDIO_INCLUDE_MONITORS" + +/** + * A variable that forces X11 windows to create as a custom type. + * + * This is currently only used for X11 and ignored elsewhere. + * + * During SDL_CreateWindow, SDL uses the _NET_WM_WINDOW_TYPE X11 property to + * report to the window manager the type of window it wants to create. This + * might be set to various things if SDL_WINDOW_TOOLTIP or + * SDL_WINDOW_POPUP_MENU, etc, were specified. For "normal" windows that + * haven't set a specific type, this hint can be used to specify a custom + * type. For example, a dock window might set this to + * "_NET_WM_WINDOW_TYPE_DOCK". + * + * If not set or set to "", this hint is ignored. This hint must be set before + * the SDL_CreateWindow() call that it is intended to affect. + * + * This hint is available since SDL 2.0.22. + */ +#define SDL_HINT_X11_WINDOW_TYPE "SDL_X11_WINDOW_TYPE" + +/** + * A variable that decides whether to send SDL_QUIT when closing the final + * window. + * + * By default, SDL sends an SDL_QUIT event when there is only one window and + * it receives an SDL_WINDOWEVENT_CLOSE event, under the assumption most apps + * would also take the loss of this window as a signal to terminate the + * program. + * + * However, it's not unreasonable in some cases to have the program continue + * to live on, perhaps to create new windows later. + * + * Changing this hint to "0" will cause SDL to not send an SDL_QUIT event when + * the final window is requesting to close. Note that in this case, there are + * still other legitimate reasons one might get an SDL_QUIT event: choosing + * "Quit" from the macOS menu bar, sending a SIGINT (ctrl-c) on Unix, etc. + * + * The default value is "1". This hint can be changed at any time. + * + * This hint is available since SDL 2.0.22. Before then, you always get an + * SDL_QUIT event when closing the final window. + */ +#define SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE "SDL_QUIT_ON_LAST_WINDOW_CLOSE" + + +/** + * A variable that decides what video backend to use. + * + * By default, SDL will try all available video backends in a reasonable order + * until it finds one that can work, but this hint allows the app or user to + * force a specific target, such as "x11" if, say, you are on Wayland but want + * to try talking to the X server instead. + * + * This functionality has existed since SDL 2.0.0 (indeed, before that) but + * before 2.0.22 this was an environment variable only. In 2.0.22, it was + * upgraded to a full SDL hint, so you can set the environment variable as + * usual or programatically set the hint with SDL_SetHint, which won't + * propagate to child processes. + * + * The default value is unset, in which case SDL will try to figure out the + * best video backend on your behalf. This hint needs to be set before + * SDL_Init() is called to be useful. + * + * This hint is available since SDL 2.0.22. Before then, you could set the + * environment variable to get the same effect. + */ +#define SDL_HINT_VIDEODRIVER "SDL_VIDEODRIVER" + +/** + * A variable that decides what audio backend to use. + * + * By default, SDL will try all available audio backends in a reasonable order + * until it finds one that can work, but this hint allows the app or user to + * force a specific target, such as "alsa" if, say, you are on PulseAudio but + * want to try talking to the lower level instead. + * + * This functionality has existed since SDL 2.0.0 (indeed, before that) but + * before 2.0.22 this was an environment variable only. In 2.0.22, it was + * upgraded to a full SDL hint, so you can set the environment variable as + * usual or programatically set the hint with SDL_SetHint, which won't + * propagate to child processes. + * + * The default value is unset, in which case SDL will try to figure out the + * best audio backend on your behalf. This hint needs to be set before + * SDL_Init() is called to be useful. + * + * This hint is available since SDL 2.0.22. Before then, you could set the + * environment variable to get the same effect. + */ +#define SDL_HINT_AUDIODRIVER "SDL_AUDIODRIVER" + +/** + * A variable that decides what KMSDRM device to use. + * + * Internally, SDL might open something like "/dev/dri/cardNN" to access + * KMSDRM functionality, where "NN" is a device index number. + * + * SDL makes a guess at the best index to use (usually zero), but the app or + * user can set this hint to a number between 0 and 99 to force selection. + * + * This hint is available since SDL 2.24.0. + */ +#define SDL_HINT_KMSDRM_DEVICE_INDEX "SDL_KMSDRM_DEVICE_INDEX" + + +/** + * A variable that treats trackpads as touch devices. + * + * On macOS (and possibly other platforms in the future), SDL will report + * touches on a trackpad as mouse input, which is generally what users expect + * from this device; however, these are often actually full multitouch-capable + * touch devices, so it might be preferable to some apps to treat them as + * such. + * + * Setting this hint to true will make the trackpad input report as a + * multitouch device instead of a mouse. The default is false. + * + * Note that most platforms don't support this hint. As of 2.24.0, it only + * supports MacBooks' trackpads on macOS. Others may follow later. + * + * This hint is checked during SDL_Init and can not be changed after. + * + * This hint is available since SDL 2.24.0. + */ +#define SDL_HINT_TRACKPAD_IS_TOUCH_ONLY "SDL_TRACKPAD_IS_TOUCH_ONLY" + +/** + * Cause SDL to call dbus_shutdown() on quit. + * + * This is useful as a debug tool to validate memory leaks, but shouldn't ever + * be set in production applications, as other libraries used by the + * application might use dbus under the hood and this cause cause crashes if + * they continue after SDL_Quit(). + * + * This variable can be set to the following values: + * + * - "0": SDL will not call dbus_shutdown() on quit (default) + * - "1": SDL will call dbus_shutdown() on quit + * + * This hint is available since SDL 2.30.0. + */ +#define SDL_HINT_SHUTDOWN_DBUS_ON_QUIT "SDL_SHUTDOWN_DBUS_ON_QUIT" + +/** + * Specify if SDL_RWFromFile should use the resource dir on Apple platforms. + * + * SDL2 has always done this on Apple platforms, but it can be surprising to + * try opening a path to discover that SDL adjusts the path to elsewhere, so + * this hint allows that behavior to be disabled. + * + * If running from a App Bundle, this will be MyApp.app/Contents/Resources. If + * running as a normal Unix-like process, this will be the directory where the + * running binary lives. Setting this hint to 0 avoids this and just uses the + * requested path as-is. + * + * This variable can be set to the following values: + * + * - "0": SDL will not use the app resource directory. + * - "1": SDL will use the app's resource directory (default). + * + * This hint is available since SDL 2.32.0. + */ +#define SDL_HINT_APPLE_RWFROMFILE_USE_RESOURCES "SDL_APPLE_RWFROMFILE_USE_RESOURCES" + + +/** + * An enumeration of hint priorities + */ +typedef enum SDL_HintPriority +{ + SDL_HINT_DEFAULT, + SDL_HINT_NORMAL, + SDL_HINT_OVERRIDE +} SDL_HintPriority; + + +/** + * Set a hint with a specific priority. + * + * The priority controls the behavior when setting a hint that already has a + * value. Hints will replace existing hints of their priority and lower. + * Environment variables are considered to have override priority. + * + * \param name the hint to set. + * \param value the value of the hint variable. + * \param priority the SDL_HintPriority level for the hint. + * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetHint + * \sa SDL_SetHint + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name, + const char *value, + SDL_HintPriority priority); + +/** + * Set a hint with normal priority. + * + * Hints will not be set if there is an existing override hint or environment + * variable that takes precedence. You can use SDL_SetHintWithPriority() to + * set the hint with override priority instead. + * + * \param name the hint to set. + * \param value the value of the hint variable. + * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetHint + * \sa SDL_SetHintWithPriority + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetHint(const char *name, + const char *value); + +/** + * Reset a hint to the default value. + * + * This will reset a hint to the value of the environment variable, or NULL if + * the environment isn't set. Callbacks will be called normally with this + * change. + * + * \param name the hint to set. + * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GetHint + * \sa SDL_SetHint + */ +extern DECLSPEC SDL_bool SDLCALL SDL_ResetHint(const char *name); + +/** + * Reset all hints to the default values. + * + * This will reset all hints to the value of the associated environment + * variable, or NULL if the environment isn't set. Callbacks will be called + * normally with this change. + * + * \since This function is available since SDL 2.26.0. + * + * \sa SDL_GetHint + * \sa SDL_SetHint + * \sa SDL_ResetHint + */ +extern DECLSPEC void SDLCALL SDL_ResetHints(void); + +/** + * Get the value of a hint. + * + * \param name the hint to query. + * \returns the string value of a hint or NULL if the hint isn't set. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetHint + * \sa SDL_SetHintWithPriority + */ +extern DECLSPEC const char * SDLCALL SDL_GetHint(const char *name); + +/** + * Get the boolean value of a hint variable. + * + * \param name the name of the hint to get the boolean value from. + * \param default_value the value to return if the hint does not exist. + * \returns the boolean value of a hint or the provided default value if the + * hint does not exist. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_GetHint + * \sa SDL_SetHint + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetHintBoolean(const char *name, SDL_bool default_value); + +/** + * Type definition of the hint callback function. + * + * \param userdata what was passed as `userdata` to SDL_AddHintCallback(). + * \param name what was passed as `name` to SDL_AddHintCallback(). + * \param oldValue the previous hint value. + * \param newValue the new value hint is to be set to. + */ +typedef void (SDLCALL *SDL_HintCallback)(void *userdata, const char *name, const char *oldValue, const char *newValue); + +/** + * Add a function to watch a particular hint. + * + * \param name the hint to watch. + * \param callback An SDL_HintCallback function that will be called when the + * hint value changes. + * \param userdata a pointer to pass to the callback function. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DelHintCallback + */ +extern DECLSPEC void SDLCALL SDL_AddHintCallback(const char *name, + SDL_HintCallback callback, + void *userdata); + +/** + * Remove a function watching a particular hint. + * + * \param name the hint being watched. + * \param callback An SDL_HintCallback function that will be called when the + * hint value changes. + * \param userdata a pointer being passed to the callback function. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AddHintCallback + */ +extern DECLSPEC void SDLCALL SDL_DelHintCallback(const char *name, + SDL_HintCallback callback, + void *userdata); + +/** + * Clear all hints. + * + * This function is automatically called during SDL_Quit(), and deletes all + * callbacks without calling them and frees all memory associated with hints. + * If you're calling this from application code you probably want to call + * SDL_ResetHints() instead. + * + * This function will be removed from the API the next time we rev the ABI. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ResetHints + */ +extern DECLSPEC void SDLCALL SDL_ClearHints(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_hints_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_joystick.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_joystick.h new file mode 100644 index 00000000..668db5e3 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_joystick.h @@ -0,0 +1,1088 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryJoystick + * + * Include file for SDL joystick event handling + * + * The term "device_index" identifies currently plugged in joystick devices + * between 0 and SDL_NumJoysticks(), with the exact joystick behind a + * device_index changing as joysticks are plugged and unplugged. + * + * The term "instance_id" is the current instantiation of a joystick device in + * the system, if the joystick is removed and then re-inserted then it will + * get a new instance_id, instance_id's are monotonically increasing + * identifiers of a joystick plugged in. + * + * The term "player_index" is the number assigned to a player on a specific + * controller. For XInput controllers this returns the XInput user index. Many + * joysticks will not be able to supply this information. + * + * The term JoystickGUID is a stable 128-bit identifier for a joystick device + * that does not change over time, it identifies class of the device (a X360 + * wired controller for example). This identifier is platform dependent. + */ + +#ifndef SDL_joystick_h_ +#define SDL_joystick_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_guid.h" +#include "SDL_mutex.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file SDL_joystick.h + * + * In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_JOYSTICK flag. This causes SDL to scan the system + * for joysticks, and load appropriate drivers. + * + * If you would like to receive joystick updates while the application + * is in the background, you should set the following hint before calling + * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS + */ + +/** + * The joystick structure used to identify an SDL joystick + */ +#ifdef SDL_THREAD_SAFETY_ANALYSIS +extern SDL_mutex *SDL_joystick_lock; +#endif +struct _SDL_Joystick; +typedef struct _SDL_Joystick SDL_Joystick; + +/** + * A structure that encodes the stable unique id for a joystick device. + * + * This is just a standard SDL_GUID by a different name. + */ +typedef SDL_GUID SDL_JoystickGUID; + +/** + * This is a unique ID for a joystick for the time it is connected to the + * system, and is never reused for the lifetime of the application. + * + * If the joystick is disconnected and reconnected, it will get a new ID. + * + * The ID value starts at 0 and increments from there. The value -1 is an + * invalid ID. + */ +typedef Sint32 SDL_JoystickID; + +typedef enum +{ + SDL_JOYSTICK_TYPE_UNKNOWN, + SDL_JOYSTICK_TYPE_GAMECONTROLLER, + SDL_JOYSTICK_TYPE_WHEEL, + SDL_JOYSTICK_TYPE_ARCADE_STICK, + SDL_JOYSTICK_TYPE_FLIGHT_STICK, + SDL_JOYSTICK_TYPE_DANCE_PAD, + SDL_JOYSTICK_TYPE_GUITAR, + SDL_JOYSTICK_TYPE_DRUM_KIT, + SDL_JOYSTICK_TYPE_ARCADE_PAD, + SDL_JOYSTICK_TYPE_THROTTLE +} SDL_JoystickType; + +typedef enum +{ + SDL_JOYSTICK_POWER_UNKNOWN = -1, + SDL_JOYSTICK_POWER_EMPTY, /* <= 5% */ + SDL_JOYSTICK_POWER_LOW, /* <= 20% */ + SDL_JOYSTICK_POWER_MEDIUM, /* <= 70% */ + SDL_JOYSTICK_POWER_FULL, /* <= 100% */ + SDL_JOYSTICK_POWER_WIRED, + SDL_JOYSTICK_POWER_MAX +} SDL_JoystickPowerLevel; + +/* Set max recognized G-force from accelerometer + See src/joystick/uikit/SDL_sysjoystick.m for notes on why this is needed + */ +#define SDL_IPHONE_MAX_GFORCE 5.0 + + +/* Function prototypes */ + +/** + * Locking for multi-threaded access to the joystick API + * + * If you are using the joystick API or handling events from multiple threads + * you should use these locking functions to protect access to the joysticks. + * + * In particular, you are guaranteed that the joystick list won't change, so + * the API functions that take a joystick index will be valid, and joystick + * and game controller events will not be delivered. + * + * As of SDL 2.26.0, you can take the joystick lock around reinitializing the + * joystick subsystem, to prevent other threads from seeing joysticks in an + * uninitialized state. However, all open joysticks will be closed and SDL + * functions called with them will fail. + * + * \since This function is available since SDL 2.0.7. + */ +extern DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystick_lock); + + +/** + * Unlocking for multi-threaded access to the joystick API + * + * If you are using the joystick API or handling events from multiple threads + * you should use these locking functions to protect access to the joysticks. + * + * In particular, you are guaranteed that the joystick list won't change, so + * the API functions that take a joystick index will be valid, and joystick + * and game controller events will not be delivered. + * + * \since This function is available since SDL 2.0.7. + */ +extern DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_lock); + +/** + * Count the number of joysticks attached to the system. + * + * \returns the number of attached joysticks on success or a negative error + * code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickName + * \sa SDL_JoystickPath + * \sa SDL_JoystickOpen + */ +extern DECLSPEC int SDLCALL SDL_NumJoysticks(void); + +/** + * Get the implementation dependent name of a joystick. + * + * This can be called before any joysticks are opened. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system). + * \returns the name of the selected joystick. If no name can be found, this + * function returns NULL; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickName + * \sa SDL_JoystickOpen + */ +extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index); + +/** + * Get the implementation dependent path of a joystick. + * + * This can be called before any joysticks are opened. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system). + * \returns the path of the selected joystick. If no path can be found, this + * function returns NULL; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_JoystickPath + * \sa SDL_JoystickOpen + */ +extern DECLSPEC const char *SDLCALL SDL_JoystickPathForIndex(int device_index); + +/** + * Get the player index of a joystick, or -1 if it's not available This can be + * called before any joysticks are opened. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_JoystickGetDevicePlayerIndex(int device_index); + +/** + * Get the implementation-dependent GUID for the joystick at a given device + * index. + * + * This function can be called before any joysticks are opened. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system. + * \returns the GUID of the selected joystick. If called on an invalid index, + * this function returns a zero GUID. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetGUID + * \sa SDL_JoystickGetGUIDString + */ +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetDeviceGUID(int device_index); + +/** + * Get the USB vendor ID of a joystick, if available. + * + * This can be called before any joysticks are opened. If the vendor ID isn't + * available this function returns 0. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system. + * \returns the USB vendor ID of the selected joystick. If called on an + * invalid index, this function returns zero. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetDeviceVendor(int device_index); + +/** + * Get the USB product ID of a joystick, if available. + * + * This can be called before any joysticks are opened. If the product ID isn't + * available this function returns 0. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system. + * \returns the USB product ID of the selected joystick. If called on an + * invalid index, this function returns zero. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetDeviceProduct(int device_index); + +/** + * Get the product version of a joystick, if available. + * + * This can be called before any joysticks are opened. If the product version + * isn't available this function returns 0. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system. + * \returns the product version of the selected joystick. If called on an + * invalid index, this function returns zero. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetDeviceProductVersion(int device_index); + +/** + * Get the type of a joystick, if available. + * + * This can be called before any joysticks are opened. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system. + * \returns the SDL_JoystickType of the selected joystick. If called on an + * invalid index, this function returns `SDL_JOYSTICK_TYPE_UNKNOWN`. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC SDL_JoystickType SDLCALL SDL_JoystickGetDeviceType(int device_index); + +/** + * Get the instance ID of a joystick. + * + * This can be called before any joysticks are opened. + * + * \param device_index the index of the joystick to query (the N'th joystick + * on the system. + * \returns the instance id of the selected joystick. If called on an invalid + * index, this function returns -1. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC SDL_JoystickID SDLCALL SDL_JoystickGetDeviceInstanceID(int device_index); + +/** + * Open a joystick for use. + * + * The `device_index` argument refers to the N'th joystick presently + * recognized by SDL on the system. It is **NOT** the same as the instance ID + * used to identify the joystick in future events. See + * SDL_JoystickInstanceID() for more details about instance IDs. + * + * The joystick subsystem must be initialized before a joystick can be opened + * for use. + * + * \param device_index the index of the joystick to query. + * \returns a joystick identifier or NULL if an error occurred; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickClose + * \sa SDL_JoystickInstanceID + */ +extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickOpen(int device_index); + +/** + * Get the SDL_Joystick associated with an instance id. + * + * \param instance_id the instance id to get the SDL_Joystick for. + * \returns an SDL_Joystick on success or NULL on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.4. + */ +extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromInstanceID(SDL_JoystickID instance_id); + +/** + * Get the SDL_Joystick associated with a player index. + * + * \param player_index the player index to get the SDL_Joystick for. + * \returns an SDL_Joystick on success or NULL on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.12. + */ +extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromPlayerIndex(int player_index); + +/** + * Attach a new virtual joystick. + * + * \returns the joystick's device index, or -1 if an error occurred. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtual(SDL_JoystickType type, + int naxes, + int nbuttons, + int nhats); + +/** + * The structure that defines an extended virtual joystick description + * + * The caller must zero the structure and then initialize the version with + * `SDL_VIRTUAL_JOYSTICK_DESC_VERSION` before passing it to + * SDL_JoystickAttachVirtualEx() All other elements of this structure are + * optional and can be left 0. + * + * \sa SDL_JoystickAttachVirtualEx + */ +typedef struct SDL_VirtualJoystickDesc +{ + Uint16 version; /**< `SDL_VIRTUAL_JOYSTICK_DESC_VERSION` */ + Uint16 type; /**< `SDL_JoystickType` */ + Uint16 naxes; /**< the number of axes on this joystick */ + Uint16 nbuttons; /**< the number of buttons on this joystick */ + Uint16 nhats; /**< the number of hats on this joystick */ + Uint16 vendor_id; /**< the USB vendor ID of this joystick */ + Uint16 product_id; /**< the USB product ID of this joystick */ + Uint16 padding; /**< unused */ + Uint32 button_mask; /**< A mask of which buttons are valid for this controller + e.g. (1 << SDL_CONTROLLER_BUTTON_A) */ + Uint32 axis_mask; /**< A mask of which axes are valid for this controller + e.g. (1 << SDL_CONTROLLER_AXIS_LEFTX) */ + const char *name; /**< the name of the joystick */ + + void *userdata; /**< User data pointer passed to callbacks */ + void (SDLCALL *Update)(void *userdata); /**< Called when the joystick state should be updated */ + void (SDLCALL *SetPlayerIndex)(void *userdata, int player_index); /**< Called when the player index is set */ + int (SDLCALL *Rumble)(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); /**< Implements SDL_JoystickRumble() */ + int (SDLCALL *RumbleTriggers)(void *userdata, Uint16 left_rumble, Uint16 right_rumble); /**< Implements SDL_JoystickRumbleTriggers() */ + int (SDLCALL *SetLED)(void *userdata, Uint8 red, Uint8 green, Uint8 blue); /**< Implements SDL_JoystickSetLED() */ + int (SDLCALL *SendEffect)(void *userdata, const void *data, int size); /**< Implements SDL_JoystickSendEffect() */ + +} SDL_VirtualJoystickDesc; + +/** + * The current version of the SDL_VirtualJoystickDesc structure + */ +#define SDL_VIRTUAL_JOYSTICK_DESC_VERSION 1 + +/** + * Attach a new virtual joystick with extended properties. + * + * \returns the joystick's device index, or -1 if an error occurred. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtualEx(const SDL_VirtualJoystickDesc *desc); + +/** + * Detach a virtual joystick. + * + * \param device_index a value previously returned from + * SDL_JoystickAttachVirtual(). + * \returns 0 on success, or -1 if an error occurred. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_JoystickDetachVirtual(int device_index); + +/** + * Query whether or not the joystick at a given device index is virtual. + * + * \param device_index a joystick device index. + * \returns SDL_TRUE if the joystick is virtual, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickIsVirtual(int device_index); + +/** + * Set values on an opened, virtual-joystick's axis. + * + * Please note that values set here will not be applied until the next call to + * SDL_JoystickUpdate, which can either be called directly, or can be called + * indirectly through various other SDL APIs, including, but not limited to + * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout, + * SDL_WaitEvent. + * + * Note that when sending trigger axes, you should scale the value to the full + * range of Sint16. For example, a trigger at rest would have the value of + * `SDL_JOYSTICK_AXIS_MIN`. + * + * \param joystick the virtual joystick on which to set state. + * \param axis the specific axis on the virtual joystick to set. + * \param value the new value for the specified axis. + * \returns 0 on success, -1 on error. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value); + +/** + * Set values on an opened, virtual-joystick's button. + * + * Please note that values set here will not be applied until the next call to + * SDL_JoystickUpdate, which can either be called directly, or can be called + * indirectly through various other SDL APIs, including, but not limited to + * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout, + * SDL_WaitEvent. + * + * \param joystick the virtual joystick on which to set state. + * \param button the specific button on the virtual joystick to set. + * \param value the new value for the specified button. + * \returns 0 on success, -1 on error. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualButton(SDL_Joystick *joystick, int button, Uint8 value); + +/** + * Set values on an opened, virtual-joystick's hat. + * + * Please note that values set here will not be applied until the next call to + * SDL_JoystickUpdate, which can either be called directly, or can be called + * indirectly through various other SDL APIs, including, but not limited to + * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout, + * SDL_WaitEvent. + * + * \param joystick the virtual joystick on which to set state. + * \param hat the specific hat on the virtual joystick to set. + * \param value the new value for the specified hat. + * \returns 0 on success, -1 on error. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value); + +/** + * Get the implementation dependent name of a joystick. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the name of the selected joystick. If no name can be found, this + * function returns NULL; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickNameForIndex + * \sa SDL_JoystickOpen + */ +extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick *joystick); + +/** + * Get the implementation dependent path of a joystick. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the path of the selected joystick. If no path can be found, this + * function returns NULL; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_JoystickPathForIndex + */ +extern DECLSPEC const char *SDLCALL SDL_JoystickPath(SDL_Joystick *joystick); + +/** + * Get the player index of an opened joystick. + * + * For XInput controllers this returns the XInput user index. Many joysticks + * will not be able to supply this information. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the player index, or -1 if it's not available. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_JoystickGetPlayerIndex(SDL_Joystick *joystick); + +/** + * Set the player index of an opened joystick. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \param player_index Player index to assign to this joystick, or -1 to clear + * the player index and turn off player LEDs. + * + * \since This function is available since SDL 2.0.12. + */ +extern DECLSPEC void SDLCALL SDL_JoystickSetPlayerIndex(SDL_Joystick *joystick, int player_index); + +/** + * Get the implementation-dependent GUID for the joystick. + * + * This function requires an open joystick. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the GUID of the given joystick. If called on an invalid index, + * this function returns a zero GUID; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetDeviceGUID + * \sa SDL_JoystickGetGUIDString + */ +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetGUID(SDL_Joystick *joystick); + +/** + * Get the USB vendor ID of an opened joystick, if available. + * + * If the vendor ID isn't available this function returns 0. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the USB vendor ID of the selected joystick, or 0 if unavailable. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetVendor(SDL_Joystick *joystick); + +/** + * Get the USB product ID of an opened joystick, if available. + * + * If the product ID isn't available this function returns 0. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the USB product ID of the selected joystick, or 0 if unavailable. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetProduct(SDL_Joystick *joystick); + +/** + * Get the product version of an opened joystick, if available. + * + * If the product version isn't available this function returns 0. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the product version of the selected joystick, or 0 if unavailable. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetProductVersion(SDL_Joystick *joystick); + +/** + * Get the firmware version of an opened joystick, if available. + * + * If the firmware version isn't available this function returns 0. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the firmware version of the selected joystick, or 0 if + * unavailable. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC Uint16 SDLCALL SDL_JoystickGetFirmwareVersion(SDL_Joystick *joystick); + +/** + * Get the serial number of an opened joystick, if available. + * + * Returns the serial number of the joystick, or NULL if it is not available. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the serial number of the selected joystick, or NULL if + * unavailable. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC const char * SDLCALL SDL_JoystickGetSerial(SDL_Joystick *joystick); + +/** + * Get the type of an opened joystick. + * + * \param joystick the SDL_Joystick obtained from SDL_JoystickOpen(). + * \returns the SDL_JoystickType of the selected joystick. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC SDL_JoystickType SDLCALL SDL_JoystickGetType(SDL_Joystick *joystick); + +/** + * Get an ASCII string representation for a given SDL_JoystickGUID. + * + * You should supply at least 33 bytes for pszGUID. + * + * \param guid the SDL_JoystickGUID you wish to convert to string. + * \param pszGUID buffer in which to write the ASCII string. + * \param cbGUID the size of pszGUID. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetDeviceGUID + * \sa SDL_JoystickGetGUID + * \sa SDL_JoystickGetGUIDFromString + */ +extern DECLSPEC void SDLCALL SDL_JoystickGetGUIDString(SDL_JoystickGUID guid, char *pszGUID, int cbGUID); + +/** + * Convert a GUID string into a SDL_JoystickGUID structure. + * + * Performs no error checking. If this function is given a string containing + * an invalid GUID, the function will silently succeed, but the GUID generated + * will not be useful. + * + * \param pchGUID string containing an ASCII representation of a GUID. + * \returns a SDL_JoystickGUID structure. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetGUIDString + */ +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetGUIDFromString(const char *pchGUID); + +/** + * Get the device information encoded in a SDL_JoystickGUID structure + * + * \param guid the SDL_JoystickGUID you wish to get info about. + * \param vendor A pointer filled in with the device VID, or 0 if not + * available. + * \param product A pointer filled in with the device PID, or 0 if not + * available. + * \param version A pointer filled in with the device version, or 0 if not + * available. + * \param crc16 A pointer filled in with a CRC used to distinguish different + * products with the same VID/PID, or 0 if not available. + * + * \since This function is available since SDL 2.26.0. + * + * \sa SDL_JoystickGetDeviceGUID + */ +extern DECLSPEC void SDLCALL SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version, Uint16 *crc16); + +/** + * Get the status of a specified joystick. + * + * \param joystick the joystick to query. + * \returns SDL_TRUE if the joystick has been opened, SDL_FALSE if it has not; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickClose + * \sa SDL_JoystickOpen + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickGetAttached(SDL_Joystick *joystick); + +/** + * Get the instance ID of an opened joystick. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \returns the instance ID of the specified joystick on success or a negative + * error code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickOpen + */ +extern DECLSPEC SDL_JoystickID SDLCALL SDL_JoystickInstanceID(SDL_Joystick *joystick); + +/** + * Get the number of general axis controls on a joystick. + * + * Often, the directional pad on a game controller will either look like 4 + * separate buttons or a POV hat, and not axes, but all of this is up to the + * device and platform. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \returns the number of axis controls/number of axes on success or a + * negative error code on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetAxis + * \sa SDL_JoystickOpen + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumAxes(SDL_Joystick *joystick); + +/** + * Get the number of trackballs on a joystick. + * + * Joystick trackballs have only relative motion events associated with them + * and their state cannot be polled. + * + * Most joysticks do not have trackballs. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \returns the number of trackballs on success or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetBall + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumBalls(SDL_Joystick *joystick); + +/** + * Get the number of POV hats on a joystick. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \returns the number of POV hats on success or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetHat + * \sa SDL_JoystickOpen + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumHats(SDL_Joystick *joystick); + +/** + * Get the number of buttons on a joystick. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \returns the number of buttons on success or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickGetButton + * \sa SDL_JoystickOpen + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumButtons(SDL_Joystick *joystick); + +/** + * Update the current state of the open joysticks. + * + * This is called automatically by the event loop if any joystick events are + * enabled. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickEventState + */ +extern DECLSPEC void SDLCALL SDL_JoystickUpdate(void); + +/** + * Enable/disable joystick event polling. + * + * If joystick events are disabled, you must call SDL_JoystickUpdate() + * yourself and manually check the state of the joystick when you want + * joystick information. + * + * It is recommended that you leave joystick event handling enabled. + * + * **WARNING**: Calling this function may delete all events currently in SDL's + * event queue. + * + * While `param` is meant to be one of `SDL_QUERY`, `SDL_IGNORE`, or + * `SDL_ENABLE`, this function accepts any value, with any non-zero value that + * isn't `SDL_QUERY` being treated as `SDL_ENABLE`. + * + * If SDL was built with events disabled (extremely uncommon!), this will do + * nothing and always return `SDL_IGNORE`. + * + * \param state can be one of `SDL_QUERY`, `SDL_IGNORE`, or `SDL_ENABLE`. + * \returns If `state` is `SDL_QUERY` then the current state is returned, + * otherwise `state` is returned (even if it was not one of the + * allowed values). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GameControllerEventState + */ +extern DECLSPEC int SDLCALL SDL_JoystickEventState(int state); + +/* Limits for joystick axes... */ +#define SDL_JOYSTICK_AXIS_MAX 32767 +#define SDL_JOYSTICK_AXIS_MIN -32768 + +/** + * Get the current state of an axis control on a joystick. + * + * SDL makes no promises about what part of the joystick any given axis refers + * to. Your game should have some sort of configuration UI to let users + * specify what each axis should be bound to. Alternately, SDL's higher-level + * Game Controller API makes a great effort to apply order to this lower-level + * interface, so you know that a specific axis is the "left thumb stick," etc. + * + * The value returned by SDL_JoystickGetAxis() is a signed integer (-32768 to + * 32767) representing the current position of the axis. It may be necessary + * to impose certain tolerances on these values to account for jitter. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \param axis the axis to query; the axis indices start at index 0. + * \returns a 16-bit signed integer representing the current position of the + * axis or 0 on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickNumAxes + */ +extern DECLSPEC Sint16 SDLCALL SDL_JoystickGetAxis(SDL_Joystick *joystick, + int axis); + +/** + * Get the initial state of an axis control on a joystick. + * + * The state is a value ranging from -32768 to 32767. + * + * The axis indices start at index 0. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \param axis the axis to query; the axis indices start at index 0. + * \param state Upon return, the initial value is supplied here. + * \return SDL_TRUE if this axis has any initial value, or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickGetAxisInitialState(SDL_Joystick *joystick, + int axis, Sint16 *state); + +/** + * \name Hat positions + */ +/* @{ */ +#define SDL_HAT_CENTERED 0x00 +#define SDL_HAT_UP 0x01 +#define SDL_HAT_RIGHT 0x02 +#define SDL_HAT_DOWN 0x04 +#define SDL_HAT_LEFT 0x08 +#define SDL_HAT_RIGHTUP (SDL_HAT_RIGHT|SDL_HAT_UP) +#define SDL_HAT_RIGHTDOWN (SDL_HAT_RIGHT|SDL_HAT_DOWN) +#define SDL_HAT_LEFTUP (SDL_HAT_LEFT|SDL_HAT_UP) +#define SDL_HAT_LEFTDOWN (SDL_HAT_LEFT|SDL_HAT_DOWN) +/* @} */ + +/** + * Get the current state of a POV hat on a joystick. + * + * The returned value will be one of the following positions: + * + * - `SDL_HAT_CENTERED` + * - `SDL_HAT_UP` + * - `SDL_HAT_RIGHT` + * - `SDL_HAT_DOWN` + * - `SDL_HAT_LEFT` + * - `SDL_HAT_RIGHTUP` + * - `SDL_HAT_RIGHTDOWN` + * - `SDL_HAT_LEFTUP` + * - `SDL_HAT_LEFTDOWN` + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \param hat the hat index to get the state from; indices start at index 0. + * \returns the current hat position. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickNumHats + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetHat(SDL_Joystick *joystick, + int hat); + +/** + * Get the ball axis change since the last poll. + * + * Trackballs can only return relative motion since the last call to + * SDL_JoystickGetBall(), these motion deltas are placed into `dx` and `dy`. + * + * Most joysticks do not have trackballs. + * + * \param joystick the SDL_Joystick to query. + * \param ball the ball index to query; ball indices start at index 0. + * \param dx stores the difference in the x axis position since the last poll. + * \param dy stores the difference in the y axis position since the last poll. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickNumBalls + */ +extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick *joystick, + int ball, int *dx, int *dy); + +/** + * Get the current state of a button on a joystick. + * + * \param joystick an SDL_Joystick structure containing joystick information. + * \param button the button index to get the state from; indices start at + * index 0. + * \returns 1 if the specified button is pressed, 0 otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickNumButtons + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick *joystick, + int button); + +/** + * Start a rumble effect. + * + * Each call to this function cancels any previous rumble effect, and calling + * it with 0 intensity stops any rumbling. + * + * \param joystick The joystick to vibrate. + * \param low_frequency_rumble The intensity of the low frequency (left) + * rumble motor, from 0 to 0xFFFF. + * \param high_frequency_rumble The intensity of the high frequency (right) + * rumble motor, from 0 to 0xFFFF. + * \param duration_ms The duration of the rumble effect, in milliseconds. + * \returns 0, or -1 if rumble isn't supported on this joystick. + * + * \since This function is available since SDL 2.0.9. + * + * \sa SDL_JoystickHasRumble + */ +extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); + +/** + * Start a rumble effect in the joystick's triggers + * + * Each call to this function cancels any previous trigger rumble effect, and + * calling it with 0 intensity stops any rumbling. + * + * Note that this is rumbling of the _triggers_ and not the game controller as + * a whole. This is currently only supported on Xbox One controllers. If you + * want the (more common) whole-controller rumble, use SDL_JoystickRumble() + * instead. + * + * \param joystick The joystick to vibrate. + * \param left_rumble The intensity of the left trigger rumble motor, from 0 + * to 0xFFFF. + * \param right_rumble The intensity of the right trigger rumble motor, from 0 + * to 0xFFFF. + * \param duration_ms The duration of the rumble effect, in milliseconds. + * \returns 0, or -1 if trigger rumble isn't supported on this joystick. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_JoystickHasRumbleTriggers + */ +extern DECLSPEC int SDLCALL SDL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms); + +/** + * Query whether a joystick has an LED. + * + * An example of a joystick LED is the light on the back of a PlayStation 4's + * DualShock 4 controller. + * + * \param joystick The joystick to query. + * \return SDL_TRUE if the joystick has a modifiable LED, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickHasLED(SDL_Joystick *joystick); + +/** + * Query whether a joystick has rumble support. + * + * \param joystick The joystick to query. + * \return SDL_TRUE if the joystick has rumble, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_JoystickRumble + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickHasRumble(SDL_Joystick *joystick); + +/** + * Query whether a joystick has rumble support on triggers. + * + * \param joystick The joystick to query. + * \return SDL_TRUE if the joystick has trigger rumble, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_JoystickRumbleTriggers + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickHasRumbleTriggers(SDL_Joystick *joystick); + +/** + * Update a joystick's LED color. + * + * An example of a joystick LED is the light on the back of a PlayStation 4's + * DualShock 4 controller. + * + * \param joystick The joystick to update. + * \param red The intensity of the red LED. + * \param green The intensity of the green LED. + * \param blue The intensity of the blue LED. + * \returns 0 on success, -1 if this joystick does not have a modifiable LED. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue); + +/** + * Send a joystick specific effect packet + * + * \param joystick The joystick to affect. + * \param data The data to send to the joystick. + * \param size The size of the data to send to the joystick. + * \returns 0, or -1 if this joystick or driver doesn't support effect + * packets. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC int SDLCALL SDL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size); + +/** + * Close a joystick previously opened with SDL_JoystickOpen(). + * + * \param joystick The joystick device to close. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_JoystickOpen + */ +extern DECLSPEC void SDLCALL SDL_JoystickClose(SDL_Joystick *joystick); + +/** + * Get the battery level of a joystick as SDL_JoystickPowerLevel. + * + * \param joystick the SDL_Joystick to query. + * \returns the current battery level as SDL_JoystickPowerLevel on success or + * `SDL_JOYSTICK_POWER_UNKNOWN` if it is unknown. + * + * \since This function is available since SDL 2.0.4. + */ +extern DECLSPEC SDL_JoystickPowerLevel SDLCALL SDL_JoystickCurrentPowerLevel(SDL_Joystick *joystick); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_joystick_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_keyboard.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_keyboard.h new file mode 100644 index 00000000..eb46db52 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_keyboard.h @@ -0,0 +1,361 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryKeyboard + * + * Include file for SDL keyboard event handling + */ + +#ifndef SDL_keyboard_h_ +#define SDL_keyboard_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_keycode.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The SDL keysym structure, used in key events. + * + * If you are looking for translated character input, see the SDL_TEXTINPUT + * event. + */ +typedef struct SDL_Keysym +{ + SDL_Scancode scancode; /**< SDL physical key code - see SDL_Scancode for details */ + SDL_Keycode sym; /**< SDL virtual key code - see SDL_Keycode for details */ + Uint16 mod; /**< current key modifiers - see SDL_Keymod for details */ + Uint32 unused; +} SDL_Keysym; + +/* Function prototypes */ + +/** + * Query the window which currently has keyboard focus. + * + * \returns the window with keyboard focus. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetKeyboardFocus(void); + +/** + * Get a snapshot of the current state of the keyboard. + * + * The pointer returned is a pointer to an internal SDL array. It will be + * valid for the whole lifetime of the application and should not be freed by + * the caller. + * + * A array element with a value of 1 means that the key is pressed and a value + * of 0 means that it is not. Indexes into this array are obtained by using + * SDL_Scancode values. + * + * Use SDL_PumpEvents() to update the state array. + * + * This function gives you the current state after all events have been + * processed, so if a key or button has been pressed and released before you + * process events, then the pressed state will never show up in the + * SDL_GetKeyboardState() calls. + * + * Note: This function doesn't take into account whether shift has been + * pressed or not. + * + * \param numkeys if non-NULL, receives the length of the returned array. + * \returns a pointer to an array of key states. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PumpEvents + * \sa SDL_ResetKeyboard + */ +extern DECLSPEC const Uint8 *SDLCALL SDL_GetKeyboardState(int *numkeys); + +/** + * Clear the state of the keyboard + * + * This function will generate key up events for all pressed keys. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GetKeyboardState + */ +extern DECLSPEC void SDLCALL SDL_ResetKeyboard(void); + +/** + * Get the current key modifier state for the keyboard. + * + * \returns an OR'd combination of the modifier keys for the keyboard. See + * SDL_Keymod for details. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetKeyboardState + * \sa SDL_SetModState + */ +extern DECLSPEC SDL_Keymod SDLCALL SDL_GetModState(void); + +/** + * Set the current key modifier state for the keyboard. + * + * The inverse of SDL_GetModState(), SDL_SetModState() allows you to impose + * modifier key states on your application. Simply pass your desired modifier + * states into `modstate`. This value may be a bitwise, OR'd combination of + * SDL_Keymod values. + * + * This does not change the keyboard state, only the key modifier flags that + * SDL reports. + * + * \param modstate the desired SDL_Keymod for the keyboard. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetModState + */ +extern DECLSPEC void SDLCALL SDL_SetModState(SDL_Keymod modstate); + +/** + * Get the key code corresponding to the given scancode according to the + * current keyboard layout. + * + * See SDL_Keycode for details. + * + * \param scancode the desired SDL_Scancode to query. + * \returns the SDL_Keycode that corresponds to the given SDL_Scancode. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetKeyName + * \sa SDL_GetScancodeFromKey + */ +extern DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromScancode(SDL_Scancode scancode); + +/** + * Get the scancode corresponding to the given key code according to the + * current keyboard layout. + * + * See SDL_Scancode for details. + * + * \param key the desired SDL_Keycode to query. + * \returns the SDL_Scancode that corresponds to the given SDL_Keycode. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetKeyFromScancode + * \sa SDL_GetScancodeName + */ +extern DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key); + +/** + * Get a human-readable name for a scancode. + * + * See SDL_Scancode for details. + * + * **Warning**: The returned name is by design not stable across platforms, + * e.g. the name for `SDL_SCANCODE_LGUI` is "Left GUI" under Linux but "Left + * Windows" under Microsoft Windows, and some scancodes like + * `SDL_SCANCODE_NONUSBACKSLASH` don't have any name at all. There are even + * scancodes that share names, e.g. `SDL_SCANCODE_RETURN` and + * `SDL_SCANCODE_RETURN2` (both called "Return"). This function is therefore + * unsuitable for creating a stable cross-platform two-way mapping between + * strings and scancodes. + * + * \param scancode the desired SDL_Scancode to query. + * \returns a pointer to the name for the scancode. If the scancode doesn't + * have a name this function returns an empty string (""). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetScancodeFromKey + * \sa SDL_GetScancodeFromName + */ +extern DECLSPEC const char *SDLCALL SDL_GetScancodeName(SDL_Scancode scancode); + +/** + * Get a scancode from a human-readable name. + * + * \param name the human-readable scancode name. + * \returns the SDL_Scancode, or `SDL_SCANCODE_UNKNOWN` if the name wasn't + * recognized; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetKeyFromName + * \sa SDL_GetScancodeFromKey + * \sa SDL_GetScancodeName + */ +extern DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromName(const char *name); + +/** + * Get a human-readable name for a key. + * + * See SDL_Scancode and SDL_Keycode for details. + * + * \param key the desired SDL_Keycode to query. + * \returns a pointer to a UTF-8 string that stays valid at least until the + * next call to this function. If you need it around any longer, you + * must copy it. If the key doesn't have a name, this function + * returns an empty string (""). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetKeyFromName + * \sa SDL_GetKeyFromScancode + * \sa SDL_GetScancodeFromKey + */ +extern DECLSPEC const char *SDLCALL SDL_GetKeyName(SDL_Keycode key); + +/** + * Get a key code from a human-readable name. + * + * \param name the human-readable key name. + * \returns key code, or `SDLK_UNKNOWN` if the name wasn't recognized; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetKeyFromScancode + * \sa SDL_GetKeyName + * \sa SDL_GetScancodeFromName + */ +extern DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromName(const char *name); + +/** + * Start accepting Unicode text input events. + * + * This function will start accepting Unicode text input events in the focused + * SDL window, and start emitting SDL_TextInputEvent (SDL_TEXTINPUT) and + * SDL_TextEditingEvent (SDL_TEXTEDITING) events. Please use this function in + * pair with SDL_StopTextInput(). + * + * On some platforms using this function activates the screen keyboard. + * + * On desktop platforms, SDL_StartTextInput() is implicitly called on SDL + * video subsystem initialization which will cause SDL_TextInputEvent and + * SDL_TextEditingEvent to begin emitting. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetTextInputRect + * \sa SDL_StopTextInput + */ +extern DECLSPEC void SDLCALL SDL_StartTextInput(void); + +/** + * Check whether or not Unicode text input events are enabled. + * + * \returns SDL_TRUE if text input events are enabled else SDL_FALSE. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_StartTextInput + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsTextInputActive(void); + +/** + * Stop receiving any text input events. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_StartTextInput + */ +extern DECLSPEC void SDLCALL SDL_StopTextInput(void); + +/** + * Dismiss the composition window/IME without disabling the subsystem. + * + * \since This function is available since SDL 2.0.22. + * + * \sa SDL_StartTextInput + * \sa SDL_StopTextInput + */ +extern DECLSPEC void SDLCALL SDL_ClearComposition(void); + +/** + * Returns if an IME Composite or Candidate window is currently shown. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsTextInputShown(void); + +/** + * Set the rectangle used to type Unicode text inputs. + * + * Native input methods will place a window with word suggestions near it, + * without covering the text being inputted. + * + * To start text input in a given location, this function is intended to be + * called before SDL_StartTextInput, although some platforms support moving + * the rectangle even while text input (and a composition) is active. + * + * Note: If you want to use the system native IME window, try setting hint + * **SDL_HINT_IME_SHOW_UI** to **1**, otherwise this function won't give you + * any feedback. + * + * \param rect the SDL_Rect structure representing the rectangle to receive + * text (ignored if NULL). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_StartTextInput + */ +extern DECLSPEC void SDLCALL SDL_SetTextInputRect(const SDL_Rect *rect); + +/** + * Check whether the platform has screen keyboard support. + * + * \returns SDL_TRUE if the platform has some screen keyboard support or + * SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_StartTextInput + * \sa SDL_IsScreenKeyboardShown + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasScreenKeyboardSupport(void); + +/** + * Check whether the screen keyboard is shown for given window. + * + * \param window the window for which screen keyboard should be queried. + * \returns SDL_TRUE if screen keyboard is shown or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HasScreenKeyboardSupport + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsScreenKeyboardShown(SDL_Window *window); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_keyboard_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_keycode.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_keycode.h new file mode 100644 index 00000000..eb1678e3 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_keycode.h @@ -0,0 +1,358 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryKeycode + * + * Defines constants which identify keyboard keys and modifiers. + */ + +#ifndef SDL_keycode_h_ +#define SDL_keycode_h_ + +#include "SDL_stdinc.h" +#include "SDL_scancode.h" + +/** + * The SDL virtual key representation. + * + * Values of this type are used to represent keyboard keys using the current + * layout of the keyboard. These values include Unicode values representing + * the unmodified character that would be generated by pressing the key, or an + * SDLK_* constant for those keys that do not generate characters. + * + * A special exception is the number keys at the top of the keyboard which map + * to SDLK_0...SDLK_9 on AZERTY layouts. + */ +typedef Sint32 SDL_Keycode; + +#define SDLK_SCANCODE_MASK (1<<30) +#define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK) + +typedef enum SDL_KeyCode +{ + SDLK_UNKNOWN = 0, + + SDLK_RETURN = '\r', + SDLK_ESCAPE = '\x1B', + SDLK_BACKSPACE = '\b', + SDLK_TAB = '\t', + SDLK_SPACE = ' ', + SDLK_EXCLAIM = '!', + SDLK_QUOTEDBL = '"', + SDLK_HASH = '#', + SDLK_PERCENT = '%', + SDLK_DOLLAR = '$', + SDLK_AMPERSAND = '&', + SDLK_QUOTE = '\'', + SDLK_LEFTPAREN = '(', + SDLK_RIGHTPAREN = ')', + SDLK_ASTERISK = '*', + SDLK_PLUS = '+', + SDLK_COMMA = ',', + SDLK_MINUS = '-', + SDLK_PERIOD = '.', + SDLK_SLASH = '/', + SDLK_0 = '0', + SDLK_1 = '1', + SDLK_2 = '2', + SDLK_3 = '3', + SDLK_4 = '4', + SDLK_5 = '5', + SDLK_6 = '6', + SDLK_7 = '7', + SDLK_8 = '8', + SDLK_9 = '9', + SDLK_COLON = ':', + SDLK_SEMICOLON = ';', + SDLK_LESS = '<', + SDLK_EQUALS = '=', + SDLK_GREATER = '>', + SDLK_QUESTION = '?', + SDLK_AT = '@', + + /* + Skip uppercase letters + */ + + SDLK_LEFTBRACKET = '[', + SDLK_BACKSLASH = '\\', + SDLK_RIGHTBRACKET = ']', + SDLK_CARET = '^', + SDLK_UNDERSCORE = '_', + SDLK_BACKQUOTE = '`', + SDLK_a = 'a', + SDLK_b = 'b', + SDLK_c = 'c', + SDLK_d = 'd', + SDLK_e = 'e', + SDLK_f = 'f', + SDLK_g = 'g', + SDLK_h = 'h', + SDLK_i = 'i', + SDLK_j = 'j', + SDLK_k = 'k', + SDLK_l = 'l', + SDLK_m = 'm', + SDLK_n = 'n', + SDLK_o = 'o', + SDLK_p = 'p', + SDLK_q = 'q', + SDLK_r = 'r', + SDLK_s = 's', + SDLK_t = 't', + SDLK_u = 'u', + SDLK_v = 'v', + SDLK_w = 'w', + SDLK_x = 'x', + SDLK_y = 'y', + SDLK_z = 'z', + + SDLK_CAPSLOCK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CAPSLOCK), + + SDLK_F1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F1), + SDLK_F2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F2), + SDLK_F3 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F3), + SDLK_F4 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F4), + SDLK_F5 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F5), + SDLK_F6 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F6), + SDLK_F7 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F7), + SDLK_F8 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F8), + SDLK_F9 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F9), + SDLK_F10 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F10), + SDLK_F11 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F11), + SDLK_F12 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F12), + + SDLK_PRINTSCREEN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PRINTSCREEN), + SDLK_SCROLLLOCK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SCROLLLOCK), + SDLK_PAUSE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAUSE), + SDLK_INSERT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INSERT), + SDLK_HOME = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HOME), + SDLK_PAGEUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEUP), + SDLK_DELETE = '\x7F', + SDLK_END = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_END), + SDLK_PAGEDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEDOWN), + SDLK_RIGHT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RIGHT), + SDLK_LEFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LEFT), + SDLK_DOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DOWN), + SDLK_UP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_UP), + + SDLK_NUMLOCKCLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_NUMLOCKCLEAR), + SDLK_KP_DIVIDE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DIVIDE), + SDLK_KP_MULTIPLY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MULTIPLY), + SDLK_KP_MINUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MINUS), + SDLK_KP_PLUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PLUS), + SDLK_KP_ENTER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_ENTER), + SDLK_KP_1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_1), + SDLK_KP_2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_2), + SDLK_KP_3 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_3), + SDLK_KP_4 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_4), + SDLK_KP_5 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_5), + SDLK_KP_6 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_6), + SDLK_KP_7 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_7), + SDLK_KP_8 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_8), + SDLK_KP_9 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_9), + SDLK_KP_0 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_0), + SDLK_KP_PERIOD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PERIOD), + + SDLK_APPLICATION = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_APPLICATION), + SDLK_POWER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_POWER), + SDLK_KP_EQUALS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EQUALS), + SDLK_F13 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F13), + SDLK_F14 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F14), + SDLK_F15 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F15), + SDLK_F16 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F16), + SDLK_F17 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F17), + SDLK_F18 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F18), + SDLK_F19 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F19), + SDLK_F20 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F20), + SDLK_F21 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F21), + SDLK_F22 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F22), + SDLK_F23 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F23), + SDLK_F24 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F24), + SDLK_EXECUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EXECUTE), + SDLK_HELP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HELP), + SDLK_MENU = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MENU), + SDLK_SELECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SELECT), + SDLK_STOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_STOP), + SDLK_AGAIN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AGAIN), + SDLK_UNDO = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_UNDO), + SDLK_CUT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CUT), + SDLK_COPY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_COPY), + SDLK_PASTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PASTE), + SDLK_FIND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_FIND), + SDLK_MUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MUTE), + SDLK_VOLUMEUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_VOLUMEUP), + SDLK_VOLUMEDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_VOLUMEDOWN), + SDLK_KP_COMMA = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_COMMA), + SDLK_KP_EQUALSAS400 = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EQUALSAS400), + + SDLK_ALTERASE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_ALTERASE), + SDLK_SYSREQ = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SYSREQ), + SDLK_CANCEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CANCEL), + SDLK_CLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CLEAR), + SDLK_PRIOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PRIOR), + SDLK_RETURN2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RETURN2), + SDLK_SEPARATOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SEPARATOR), + SDLK_OUT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_OUT), + SDLK_OPER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_OPER), + SDLK_CLEARAGAIN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CLEARAGAIN), + SDLK_CRSEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CRSEL), + SDLK_EXSEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EXSEL), + + SDLK_KP_00 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_00), + SDLK_KP_000 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_000), + SDLK_THOUSANDSSEPARATOR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_THOUSANDSSEPARATOR), + SDLK_DECIMALSEPARATOR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DECIMALSEPARATOR), + SDLK_CURRENCYUNIT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CURRENCYUNIT), + SDLK_CURRENCYSUBUNIT = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CURRENCYSUBUNIT), + SDLK_KP_LEFTPAREN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LEFTPAREN), + SDLK_KP_RIGHTPAREN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_RIGHTPAREN), + SDLK_KP_LEFTBRACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LEFTBRACE), + SDLK_KP_RIGHTBRACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_RIGHTBRACE), + SDLK_KP_TAB = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_TAB), + SDLK_KP_BACKSPACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_BACKSPACE), + SDLK_KP_A = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_A), + SDLK_KP_B = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_B), + SDLK_KP_C = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_C), + SDLK_KP_D = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_D), + SDLK_KP_E = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_E), + SDLK_KP_F = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_F), + SDLK_KP_XOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_XOR), + SDLK_KP_POWER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_POWER), + SDLK_KP_PERCENT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PERCENT), + SDLK_KP_LESS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LESS), + SDLK_KP_GREATER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_GREATER), + SDLK_KP_AMPERSAND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_AMPERSAND), + SDLK_KP_DBLAMPERSAND = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DBLAMPERSAND), + SDLK_KP_VERTICALBAR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_VERTICALBAR), + SDLK_KP_DBLVERTICALBAR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DBLVERTICALBAR), + SDLK_KP_COLON = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_COLON), + SDLK_KP_HASH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_HASH), + SDLK_KP_SPACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_SPACE), + SDLK_KP_AT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_AT), + SDLK_KP_EXCLAM = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EXCLAM), + SDLK_KP_MEMSTORE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMSTORE), + SDLK_KP_MEMRECALL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMRECALL), + SDLK_KP_MEMCLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMCLEAR), + SDLK_KP_MEMADD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMADD), + SDLK_KP_MEMSUBTRACT = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMSUBTRACT), + SDLK_KP_MEMMULTIPLY = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMMULTIPLY), + SDLK_KP_MEMDIVIDE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMDIVIDE), + SDLK_KP_PLUSMINUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PLUSMINUS), + SDLK_KP_CLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_CLEAR), + SDLK_KP_CLEARENTRY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_CLEARENTRY), + SDLK_KP_BINARY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_BINARY), + SDLK_KP_OCTAL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_OCTAL), + SDLK_KP_DECIMAL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DECIMAL), + SDLK_KP_HEXADECIMAL = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_HEXADECIMAL), + + SDLK_LCTRL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LCTRL), + SDLK_LSHIFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LSHIFT), + SDLK_LALT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LALT), + SDLK_LGUI = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LGUI), + SDLK_RCTRL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RCTRL), + SDLK_RSHIFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RSHIFT), + SDLK_RALT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RALT), + SDLK_RGUI = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RGUI), + + SDLK_MODE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MODE), + + SDLK_AUDIONEXT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIONEXT), + SDLK_AUDIOPREV = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOPREV), + SDLK_AUDIOSTOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOSTOP), + SDLK_AUDIOPLAY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOPLAY), + SDLK_AUDIOMUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOMUTE), + SDLK_MEDIASELECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MEDIASELECT), + SDLK_WWW = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_WWW), + SDLK_MAIL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MAIL), + SDLK_CALCULATOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CALCULATOR), + SDLK_COMPUTER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_COMPUTER), + SDLK_AC_SEARCH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_SEARCH), + SDLK_AC_HOME = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_HOME), + SDLK_AC_BACK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_BACK), + SDLK_AC_FORWARD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_FORWARD), + SDLK_AC_STOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_STOP), + SDLK_AC_REFRESH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_REFRESH), + SDLK_AC_BOOKMARKS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_BOOKMARKS), + + SDLK_BRIGHTNESSDOWN = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_BRIGHTNESSDOWN), + SDLK_BRIGHTNESSUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_BRIGHTNESSUP), + SDLK_DISPLAYSWITCH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DISPLAYSWITCH), + SDLK_KBDILLUMTOGGLE = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMTOGGLE), + SDLK_KBDILLUMDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMDOWN), + SDLK_KBDILLUMUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMUP), + SDLK_EJECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EJECT), + SDLK_SLEEP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SLEEP), + SDLK_APP1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_APP1), + SDLK_APP2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_APP2), + + SDLK_AUDIOREWIND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOREWIND), + SDLK_AUDIOFASTFORWARD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOFASTFORWARD), + + SDLK_SOFTLEFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SOFTLEFT), + SDLK_SOFTRIGHT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SOFTRIGHT), + SDLK_CALL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CALL), + SDLK_ENDCALL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_ENDCALL) +} SDL_KeyCode; + +/** + * Enumeration of valid key mods (possibly OR'd together). + */ +typedef enum SDL_Keymod +{ + KMOD_NONE = 0x0000, + KMOD_LSHIFT = 0x0001, + KMOD_RSHIFT = 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LGUI = 0x0400, + KMOD_RGUI = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_SCROLL = 0x8000, + + KMOD_CTRL = KMOD_LCTRL | KMOD_RCTRL, + KMOD_SHIFT = KMOD_LSHIFT | KMOD_RSHIFT, + KMOD_ALT = KMOD_LALT | KMOD_RALT, + KMOD_GUI = KMOD_LGUI | KMOD_RGUI, + + KMOD_RESERVED = KMOD_SCROLL /* This is for source-level compatibility with SDL 2.0.0. */ +} SDL_Keymod; + +#endif /* SDL_keycode_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_loadso.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_loadso.h new file mode 100644 index 00000000..1763b528 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_loadso.h @@ -0,0 +1,117 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: LoadSO */ + +/** + * # CategoryLoadSO + * + * System-dependent library loading routines. + * + * Some things to keep in mind: + * + * - These functions only work on C function names. Other languages may have + * name mangling and intrinsic language support that varies from compiler to + * compiler. + * - Make sure you declare your function pointers with the same calling + * convention as the actual library function. Your code will crash + * mysteriously if you do not do this. + * - Avoid namespace collisions. If you load a symbol from the library, it is + * not defined whether or not it goes into the global symbol namespace for + * the application. If it does and it conflicts with symbols in your code or + * other shared libraries, you will not get the results you expect. :) + */ + +#ifndef SDL_loadso_h_ +#define SDL_loadso_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Dynamically load a shared object. + * + * \param sofile a system-dependent name of the object file. + * \returns an opaque pointer to the object handle or NULL if there was an + * error; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadFunction + * \sa SDL_UnloadObject + */ +extern DECLSPEC void *SDLCALL SDL_LoadObject(const char *sofile); + +/** + * Look up the address of the named function in a shared object. + * + * This function pointer is no longer valid after calling SDL_UnloadObject(). + * + * This function can only look up C function names. Other languages may have + * name mangling and intrinsic language support that varies from compiler to + * compiler. + * + * Make sure you declare your function pointers with the same calling + * convention as the actual library function. Your code will crash + * mysteriously if you do not do this. + * + * If the requested function doesn't exist, NULL is returned. + * + * \param handle a valid shared object handle returned by SDL_LoadObject(). + * \param name the name of the function to look up. + * \returns a pointer to the function or NULL if there was an error; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadObject + * \sa SDL_UnloadObject + */ +extern DECLSPEC void *SDLCALL SDL_LoadFunction(void *handle, + const char *name); + +/** + * Unload a shared object from memory. + * + * \param handle a valid shared object handle returned by SDL_LoadObject(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadFunction + * \sa SDL_LoadObject + */ +extern DECLSPEC void SDLCALL SDL_UnloadObject(void *handle); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_loadso_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_locale.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_locale.h new file mode 100644 index 00000000..8126efc7 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_locale.h @@ -0,0 +1,103 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryLocale + * + * Include file for SDL locale services + */ + +#ifndef _SDL_locale_h +#define _SDL_locale_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +typedef struct SDL_Locale +{ + const char *language; /**< A language name, like "en" for English. */ + const char *country; /**< A country, like "US" for America. Can be NULL. */ +} SDL_Locale; + +/** + * Report the user's preferred locale. + * + * This returns an array of SDL_Locale structs, the final item zeroed out. + * When the caller is done with this array, it should call SDL_free() on the + * returned value; all the memory involved is allocated in a single block, so + * a single SDL_free() will suffice. + * + * Returned language strings are in the format xx, where 'xx' is an ISO-639 + * language specifier (such as "en" for English, "de" for German, etc). + * Country strings are in the format YY, where "YY" is an ISO-3166 country + * code (such as "US" for the United States, "CA" for Canada, etc). Country + * might be NULL if there's no specific guidance on them (so you might get { + * "en", "US" } for American English, but { "en", NULL } means "English + * language, generically"). Language strings are never NULL, except to + * terminate the array. + * + * Please note that not all of these strings are 2 characters; some are three + * or more. + * + * The returned list of locales are in the order of the user's preference. For + * example, a German citizen that is fluent in US English and knows enough + * Japanese to navigate around Tokyo might have a list like: { "de", "en_US", + * "jp", NULL }. Someone from England might prefer British English (where + * "color" is spelled "colour", etc), but will settle for anything like it: { + * "en_GB", "en", NULL }. + * + * This function returns NULL on error, including when the platform does not + * supply this information at all. + * + * This might be a "slow" call that has to query the operating system. It's + * best to ask for this once and save the results. However, this list can + * change, usually because the user has changed a system preference outside of + * your program; SDL will send an SDL_LOCALECHANGED event in this case, if + * possible, and you can call this function again to get an updated copy of + * preferred locales. + * + * \return array of locales, terminated with a locale with a NULL language + * field. Will return NULL on error. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_Locale * SDLCALL SDL_GetPreferredLocales(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_locale_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_log.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_log.h new file mode 100644 index 00000000..75833ba3 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_log.h @@ -0,0 +1,405 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryLog + * + * Simple log messages with categories and priorities. + * + * By default logs are quiet, but if you're debugging SDL you might want: + * + * SDL_LogSetAllPriority(SDL_LOG_PRIORITY_WARN); + * + * Here's where the messages go on different platforms: + * + * - Windows: debug output stream + * - Android: log output + * - Others: standard error output (stderr) + */ + +#ifndef SDL_log_h_ +#define SDL_log_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The maximum size of a log message prior to SDL 2.0.24 + * + * As of 2.0.24 there is no limit to the length of SDL log messages. + */ +#define SDL_MAX_LOG_MESSAGE 4096 + +/** + * The predefined log categories + * + * By default the application category is enabled at the INFO level, the + * assert category is enabled at the WARN level, test is enabled at the + * VERBOSE level and all other categories are enabled at the ERROR level. + */ +typedef enum SDL_LogCategory +{ + SDL_LOG_CATEGORY_APPLICATION, + SDL_LOG_CATEGORY_ERROR, + SDL_LOG_CATEGORY_ASSERT, + SDL_LOG_CATEGORY_SYSTEM, + SDL_LOG_CATEGORY_AUDIO, + SDL_LOG_CATEGORY_VIDEO, + SDL_LOG_CATEGORY_RENDER, + SDL_LOG_CATEGORY_INPUT, + SDL_LOG_CATEGORY_TEST, + + /* Reserved for future SDL library use */ + SDL_LOG_CATEGORY_RESERVED1, + SDL_LOG_CATEGORY_RESERVED2, + SDL_LOG_CATEGORY_RESERVED3, + SDL_LOG_CATEGORY_RESERVED4, + SDL_LOG_CATEGORY_RESERVED5, + SDL_LOG_CATEGORY_RESERVED6, + SDL_LOG_CATEGORY_RESERVED7, + SDL_LOG_CATEGORY_RESERVED8, + SDL_LOG_CATEGORY_RESERVED9, + SDL_LOG_CATEGORY_RESERVED10, + + /* Beyond this point is reserved for application use, e.g. + enum { + MYAPP_CATEGORY_AWESOME1 = SDL_LOG_CATEGORY_CUSTOM, + MYAPP_CATEGORY_AWESOME2, + MYAPP_CATEGORY_AWESOME3, + ... + }; + */ + SDL_LOG_CATEGORY_CUSTOM +} SDL_LogCategory; + +/** + * The predefined log priorities + */ +typedef enum SDL_LogPriority +{ + SDL_LOG_PRIORITY_VERBOSE = 1, + SDL_LOG_PRIORITY_DEBUG, + SDL_LOG_PRIORITY_INFO, + SDL_LOG_PRIORITY_WARN, + SDL_LOG_PRIORITY_ERROR, + SDL_LOG_PRIORITY_CRITICAL, + SDL_NUM_LOG_PRIORITIES +} SDL_LogPriority; + + +/** + * Set the priority of all log categories. + * + * \param priority the SDL_LogPriority to assign. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogSetPriority + */ +extern DECLSPEC void SDLCALL SDL_LogSetAllPriority(SDL_LogPriority priority); + +/** + * Set the priority of a particular log category. + * + * \param category the category to assign a priority to. + * \param priority the SDL_LogPriority to assign. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogGetPriority + * \sa SDL_LogSetAllPriority + */ +extern DECLSPEC void SDLCALL SDL_LogSetPriority(int category, + SDL_LogPriority priority); + +/** + * Get the priority of a particular log category. + * + * \param category the category to query. + * \returns the SDL_LogPriority for the requested category. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogSetPriority + */ +extern DECLSPEC SDL_LogPriority SDLCALL SDL_LogGetPriority(int category); + +/** + * Reset all priorities to default. + * + * This is called by SDL_Quit(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogSetAllPriority + * \sa SDL_LogSetPriority + */ +extern DECLSPEC void SDLCALL SDL_LogResetPriorities(void); + +/** + * Log a message with SDL_LOG_CATEGORY_APPLICATION and SDL_LOG_PRIORITY_INFO. + * + * = * \param fmt a printf() style message format string + * + * \param ... additional parameters matching % tokens in the `fmt` string, if + * any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); + +/** + * Log a message with SDL_LOG_PRIORITY_VERBOSE. + * + * \param category the category of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Log a message with SDL_LOG_PRIORITY_DEBUG. + * + * \param category the category of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Log a message with SDL_LOG_PRIORITY_INFO. + * + * \param category the category of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Log a message with SDL_LOG_PRIORITY_WARN. + * + * \param category the category of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + */ +extern DECLSPEC void SDLCALL SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Log a message with SDL_LOG_PRIORITY_ERROR. + * + * \param category the category of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Log a message with SDL_LOG_PRIORITY_CRITICAL. + * + * \param category the category of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * Log a message with the specified category and priority. + * + * \param category the category of the message. + * \param priority the priority of the message. + * \param fmt a printf() style message format string. + * \param ... additional parameters matching % tokens in the **fmt** string, + * if any. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessageV + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogMessage(int category, + SDL_LogPriority priority, + SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(3); + +/** + * Log a message with the specified category and priority. + * + * \param category the category of the message. + * \param priority the priority of the message. + * \param fmt a printf() style message format string. + * \param ap a variable argument list. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Log + * \sa SDL_LogCritical + * \sa SDL_LogDebug + * \sa SDL_LogError + * \sa SDL_LogInfo + * \sa SDL_LogMessage + * \sa SDL_LogVerbose + * \sa SDL_LogWarn + */ +extern DECLSPEC void SDLCALL SDL_LogMessageV(int category, + SDL_LogPriority priority, + SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(3); + +/** + * The prototype for the log output callback function. + * + * This function is called by SDL when there is new text to be logged. + * + * \param userdata what was passed as `userdata` to + * SDL_LogSetOutputFunction(). + * \param category the category of the message. + * \param priority the priority of the message. + * \param message the message being output. + */ +typedef void (SDLCALL *SDL_LogOutputFunction)(void *userdata, int category, SDL_LogPriority priority, const char *message); + +/** + * Get the current log output function. + * + * \param callback an SDL_LogOutputFunction filled in with the current log + * callback. + * \param userdata a pointer filled in with the pointer that is passed to + * `callback`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogSetOutputFunction + */ +extern DECLSPEC void SDLCALL SDL_LogGetOutputFunction(SDL_LogOutputFunction *callback, void **userdata); + +/** + * Replace the default log output function with one of your own. + * + * \param callback an SDL_LogOutputFunction to call instead of the default. + * \param userdata a pointer that is passed to `callback`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LogGetOutputFunction + */ +extern DECLSPEC void SDLCALL SDL_LogSetOutputFunction(SDL_LogOutputFunction callback, void *userdata); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_log_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_main.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_main.h new file mode 100644 index 00000000..a1ef3e74 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_main.h @@ -0,0 +1,282 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_main_h_ +#define SDL_main_h_ + +#include "SDL_stdinc.h" + +/** + * # CategoryMain + * + * Redefine main() on some platforms so that it is called by SDL. + */ + +#ifndef SDL_MAIN_HANDLED +#if defined(__WIN32__) +/* On Windows SDL provides WinMain(), which parses the command line and passes + the arguments to your main function. + + If you provide your own WinMain(), you may define SDL_MAIN_HANDLED + */ +#define SDL_MAIN_AVAILABLE + +#elif defined(__WINRT__) +/* On WinRT, SDL provides a main function that initializes CoreApplication, + creating an instance of IFrameworkView in the process. + + Please note that #include'ing SDL_main.h is not enough to get a main() + function working. In non-XAML apps, the file, + src/main/winrt/SDL_WinRT_main_NonXAML.cpp, or a copy of it, must be compiled + into the app itself. In XAML apps, the function, SDL_WinRTRunApp must be + called, with a pointer to the Direct3D-hosted XAML control passed in. +*/ +#define SDL_MAIN_NEEDED + +#elif defined(__GDK__) +/* On GDK, SDL provides a main function that initializes the game runtime. + + Please note that #include'ing SDL_main.h is not enough to get a main() + function working. You must either link against SDL2main or, if not possible, + call the SDL_GDKRunApp function from your entry point. +*/ +#define SDL_MAIN_NEEDED + +#elif defined(__IPHONEOS__) +/* On iOS SDL provides a main function that creates an application delegate + and starts the iOS application run loop. + + If you link with SDL dynamically on iOS, the main function can't be in a + shared library, so you need to link with libSDLmain.a, which includes a + stub main function that calls into the shared library to start execution. + + See src/video/uikit/SDL_uikitappdelegate.m for more details. + */ +#define SDL_MAIN_NEEDED + +#elif defined(__ANDROID__) +/* On Android SDL provides a Java class in SDLActivity.java that is the + main activity entry point. + + See docs/README-android.md for more details on extending that class. + */ +#define SDL_MAIN_NEEDED + +/* We need to export SDL_main so it can be launched from Java */ +#define SDLMAIN_DECLSPEC DECLSPEC + +#elif defined(__NACL__) +/* On NACL we use ppapi_simple to set up the application helper code, + then wait for the first PSE_INSTANCE_DIDCHANGEVIEW event before + starting the user main function. + All user code is run in a separate thread by ppapi_simple, thus + allowing for blocking io to take place via nacl_io +*/ +#define SDL_MAIN_NEEDED + +#elif defined(__PSP__) +/* On PSP SDL provides a main function that sets the module info, + activates the GPU and starts the thread required to be able to exit + the software. + + If you provide this yourself, you may define SDL_MAIN_HANDLED + */ +#define SDL_MAIN_AVAILABLE + +#elif defined(__PS2__) +#define SDL_MAIN_AVAILABLE + +#define SDL_PS2_SKIP_IOP_RESET() \ + void reset_IOP(); \ + void reset_IOP() {} + +#elif defined(__3DS__) +/* + On N3DS, SDL provides a main function that sets up the screens + and storage. + + If you provide this yourself, you may define SDL_MAIN_HANDLED +*/ +#define SDL_MAIN_AVAILABLE + +#endif +#endif /* SDL_MAIN_HANDLED */ + +#ifndef SDLMAIN_DECLSPEC +#define SDLMAIN_DECLSPEC +#endif + +/** + * \file SDL_main.h + * + * The application's main() function must be called with C linkage, + * and should be declared like this: + * ```c + * #ifdef __cplusplus + * extern "C" + * #endif + * int main(int argc, char *argv[]) + * { + * } + * ``` + */ + +#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) +#define main SDL_main +#endif + +#include "begin_code.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The prototype for the application's main() function + */ +typedef int (*SDL_main_func)(int argc, char *argv[]); +extern SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]); + + +/** + * Circumvent failure of SDL_Init() when not using SDL_main() as an entry + * point. + * + * This function is defined in SDL_main.h, along with the preprocessor rule to + * redefine main() as SDL_main(). Thus to ensure that your main() function + * will not be changed it is necessary to define SDL_MAIN_HANDLED before + * including SDL.h. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_Init + */ +extern DECLSPEC void SDLCALL SDL_SetMainReady(void); + +#if defined(__WIN32__) || defined(__GDK__) + +/** + * Register a win32 window class for SDL's use. + * + * This can be called to set the application window class at startup. It is + * safe to call this multiple times, as long as every call is eventually + * paired with a call to SDL_UnregisterApp, but a second registration attempt + * while a previous registration is still active will be ignored, other than + * to increment a counter. + * + * Most applications do not need to, and should not, call this directly; SDL + * will call it when initializing the video subsystem. + * + * \param name the window class name, in UTF-8 encoding. If NULL, SDL + * currently uses "SDL_app" but this isn't guaranteed. + * \param style the value to use in WNDCLASSEX::style. If `name` is NULL, SDL + * currently uses `(CS_BYTEALIGNCLIENT | CS_OWNDC)` regardless of + * what is specified here. + * \param hInst the HINSTANCE to use in WNDCLASSEX::hInstance. If zero, SDL + * will use `GetModuleHandle(NULL)` instead. + * \returns 0 on success, -1 on error. SDL_GetError() may have details. + * + * \since This function is available since SDL 2.0.2. + */ +extern DECLSPEC int SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst); + +/** + * Deregister the win32 window class from an SDL_RegisterApp call. + * + * This can be called to undo the effects of SDL_RegisterApp. + * + * Most applications do not need to, and should not, call this directly; SDL + * will call it when deinitializing the video subsystem. + * + * It is safe to call this multiple times, as long as every call is eventually + * paired with a prior call to SDL_RegisterApp. The window class will only be + * deregistered when the registration counter in SDL_RegisterApp decrements to + * zero through calls to this function. + * + * \since This function is available since SDL 2.0.2. + */ +extern DECLSPEC void SDLCALL SDL_UnregisterApp(void); + +#endif /* defined(__WIN32__) || defined(__GDK__) */ + + +#ifdef __WINRT__ + +/** + * Initialize and launch an SDL/WinRT application. + * + * \param mainFunction the SDL app's C-style main(), an SDL_main_func. + * \param reserved reserved for future use; should be NULL. + * \returns 0 on success or -1 on failure; call SDL_GetError() to retrieve + * more information on the failure. + * + * \since This function is available since SDL 2.0.3. + */ +extern DECLSPEC int SDLCALL SDL_WinRTRunApp(SDL_main_func mainFunction, void * reserved); + +#endif /* __WINRT__ */ + +#if defined(__IPHONEOS__) + +/** + * Initializes and launches an SDL application. + * + * \param argc The argc parameter from the application's main() function. + * \param argv The argv parameter from the application's main() function. + * \param mainFunction The SDL app's C-style main(), an SDL_main_func. + * \return the return value from mainFunction. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_UIKitRunApp(int argc, char *argv[], SDL_main_func mainFunction); + +#endif /* __IPHONEOS__ */ + +#ifdef __GDK__ + +/** + * Initialize and launch an SDL GDK application. + * + * \param mainFunction the SDL app's C-style main(), an SDL_main_func. + * \param reserved reserved for future use; should be NULL. + * \returns 0 on success or -1 on failure; call SDL_GetError() to retrieve + * more information on the failure. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC int SDLCALL SDL_GDKRunApp(SDL_main_func mainFunction, void *reserved); + +/** + * Callback from the application to let the suspend continue. + * + * \since This function is available since SDL 2.28.0. + */ +extern DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void); + +#endif /* __GDK__ */ + +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_main_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_messagebox.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_messagebox.h new file mode 100644 index 00000000..725d4124 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_messagebox.h @@ -0,0 +1,196 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_messagebox_h_ +#define SDL_messagebox_h_ + +#include "SDL_stdinc.h" +#include "SDL_video.h" /* For SDL_Window */ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * SDL_MessageBox flags. + * + * If supported will display warning icon, etc. + */ +typedef enum SDL_MessageBoxFlags +{ + SDL_MESSAGEBOX_ERROR = 0x00000010, /**< error dialog */ + SDL_MESSAGEBOX_WARNING = 0x00000020, /**< warning dialog */ + SDL_MESSAGEBOX_INFORMATION = 0x00000040, /**< informational dialog */ + SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT = 0x00000080, /**< buttons placed left to right */ + SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT = 0x00000100 /**< buttons placed right to left */ +} SDL_MessageBoxFlags; + +/** + * Flags for SDL_MessageBoxButtonData. + */ +typedef enum SDL_MessageBoxButtonFlags +{ + SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT = 0x00000001, /**< Marks the default button when return is hit */ + SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT = 0x00000002 /**< Marks the default button when escape is hit */ +} SDL_MessageBoxButtonFlags; + +/** + * Individual button data. + */ +typedef struct SDL_MessageBoxButtonData +{ + Uint32 flags; /**< SDL_MessageBoxButtonFlags */ + int buttonid; /**< User defined button id (value returned via SDL_ShowMessageBox) */ + const char * text; /**< The UTF-8 button text */ +} SDL_MessageBoxButtonData; + +/** + * RGB value used in a message box color scheme + */ +typedef struct SDL_MessageBoxColor +{ + Uint8 r, g, b; +} SDL_MessageBoxColor; + +typedef enum SDL_MessageBoxColorType +{ + SDL_MESSAGEBOX_COLOR_BACKGROUND, + SDL_MESSAGEBOX_COLOR_TEXT, + SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, + SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, + SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, + SDL_MESSAGEBOX_COLOR_MAX +} SDL_MessageBoxColorType; + +/** + * A set of colors to use for message box dialogs + */ +typedef struct SDL_MessageBoxColorScheme +{ + SDL_MessageBoxColor colors[SDL_MESSAGEBOX_COLOR_MAX]; +} SDL_MessageBoxColorScheme; + +/** + * MessageBox structure containing title, text, window, etc. + */ +typedef struct SDL_MessageBoxData +{ + Uint32 flags; /**< SDL_MessageBoxFlags */ + SDL_Window *window; /**< Parent window, can be NULL */ + const char *title; /**< UTF-8 title */ + const char *message; /**< UTF-8 message text */ + + int numbuttons; + const SDL_MessageBoxButtonData *buttons; + + const SDL_MessageBoxColorScheme *colorScheme; /**< SDL_MessageBoxColorScheme, can be NULL to use system settings */ +} SDL_MessageBoxData; + +/** + * Create a modal message box. + * + * If your needs aren't complex, it might be easier to use + * SDL_ShowSimpleMessageBox. + * + * This function should be called on the thread that created the parent + * window, or on the main thread if the messagebox has no parent. It will + * block execution of that thread until the user clicks a button or closes the + * messagebox. + * + * This function may be called at any time, even before SDL_Init(). This makes + * it useful for reporting errors like a failure to create a renderer or + * OpenGL context. + * + * On X11, SDL rolls its own dialog box with X11 primitives instead of a + * formal toolkit like GTK+ or Qt. + * + * Note that if SDL_Init() would fail because there isn't any available video + * target, this function is likely to fail for the same reasons. If this is a + * concern, check the return value from this function and fall back to writing + * to stderr if you can. + * + * \param messageboxdata the SDL_MessageBoxData structure with title, text and + * other options. + * \param buttonid the pointer to which user id of hit button should be + * copied. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ShowSimpleMessageBox + */ +extern DECLSPEC int SDLCALL SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid); + +/** + * Display a simple modal message box. + * + * If your needs aren't complex, this function is preferred over + * SDL_ShowMessageBox. + * + * `flags` may be any of the following: + * + * - `SDL_MESSAGEBOX_ERROR`: error dialog + * - `SDL_MESSAGEBOX_WARNING`: warning dialog + * - `SDL_MESSAGEBOX_INFORMATION`: informational dialog + * + * This function should be called on the thread that created the parent + * window, or on the main thread if the messagebox has no parent. It will + * block execution of that thread until the user clicks a button or closes the + * messagebox. + * + * This function may be called at any time, even before SDL_Init(). This makes + * it useful for reporting errors like a failure to create a renderer or + * OpenGL context. + * + * On X11, SDL rolls its own dialog box with X11 primitives instead of a + * formal toolkit like GTK+ or Qt. + * + * Note that if SDL_Init() would fail because there isn't any available video + * target, this function is likely to fail for the same reasons. If this is a + * concern, check the return value from this function and fall back to writing + * to stderr if you can. + * + * \param flags an SDL_MessageBoxFlags value. + * \param title UTF-8 title text. + * \param message UTF-8 message text. + * \param window the parent window, or NULL for no parent. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ShowMessageBox + */ +extern DECLSPEC int SDLCALL SDL_ShowSimpleMessageBox(Uint32 flags, const char *title, const char *message, SDL_Window *window); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_messagebox_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_metal.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_metal.h new file mode 100644 index 00000000..d3f21d5e --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_metal.h @@ -0,0 +1,114 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryMetal + * + * Header file for functions to creating Metal layers and views on SDL + * windows. + */ + +#ifndef SDL_metal_h_ +#define SDL_metal_h_ + +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A handle to a CAMetalLayer-backed NSView (macOS) or UIView (iOS/tvOS). + * + * This can be cast directly to an NSView or UIView. + */ +typedef void *SDL_MetalView; + +/** + * \name Metal support functions + */ +/* @{ */ + +/** + * Create a CAMetalLayer-backed NSView/UIView and attach it to the specified + * window. + * + * On macOS, this does *not* associate a MTLDevice with the CAMetalLayer on + * its own. It is up to user code to do that. + * + * The returned handle can be casted directly to a NSView or UIView. To access + * the backing CAMetalLayer, call SDL_Metal_GetLayer(). + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_Metal_DestroyView + * \sa SDL_Metal_GetLayer + */ +extern DECLSPEC SDL_MetalView SDLCALL SDL_Metal_CreateView(SDL_Window * window); + +/** + * Destroy an existing SDL_MetalView object. + * + * This should be called before SDL_DestroyWindow, if SDL_Metal_CreateView was + * called after SDL_CreateWindow. + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_Metal_CreateView + */ +extern DECLSPEC void SDLCALL SDL_Metal_DestroyView(SDL_MetalView view); + +/** + * Get a pointer to the backing CAMetalLayer for the given view. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_Metal_CreateView + */ +extern DECLSPEC void *SDLCALL SDL_Metal_GetLayer(SDL_MetalView view); + +/** + * Get the size of a window's underlying drawable in pixels (for use with + * setting viewport, scissor & etc). + * + * \param window SDL_Window from which the drawable size should be queried. + * \param w Pointer to variable for storing the width in pixels, may be NULL. + * \param h Pointer to variable for storing the height in pixels, may be NULL. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_GetWindowSize + * \sa SDL_CreateWindow + */ +extern DECLSPEC void SDLCALL SDL_Metal_GetDrawableSize(SDL_Window* window, int *w, + int *h); + +/* @} *//* Metal support functions */ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_metal_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_misc.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_misc.h new file mode 100644 index 00000000..86a82bc5 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_misc.h @@ -0,0 +1,79 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryMisc + * + * Include file for SDL API functions that don't fit elsewhere. + */ + +#ifndef SDL_misc_h_ +#define SDL_misc_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Open a URL/URI in the browser or other appropriate external application. + * + * Open a URL in a separate, system-provided application. How this works will + * vary wildly depending on the platform. This will likely launch what makes + * sense to handle a specific URL's protocol (a web browser for `http://`, + * etc), but it might also be able to launch file managers for directories and + * other things. + * + * What happens when you open a URL varies wildly as well: your game window + * may lose focus (and may or may not lose focus if your game was fullscreen + * or grabbing input at the time). On mobile devices, your app will likely + * move to the background or your process might be paused. Any given platform + * may or may not handle a given URL. + * + * If this is unimplemented (or simply unavailable) for a platform, this will + * fail with an error. A successful result does not mean the URL loaded, just + * that we launched _something_ to handle it (or at least believe we did). + * + * All this to say: this function can be useful, but you should definitely + * test it on every platform you target. + * + * \param url A valid URL/URI to open. Use `file:///full/path/to/file` for + * local files, if supported. + * \returns 0 on success, or -1 on error; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC int SDLCALL SDL_OpenURL(const char *url); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_misc_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_mouse.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_mouse.h new file mode 100644 index 00000000..628b7a2f --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_mouse.h @@ -0,0 +1,464 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryMouse + * + * Include file for SDL mouse event handling. + */ + +#ifndef SDL_mouse_h_ +#define SDL_mouse_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SDL_Cursor SDL_Cursor; /**< Implementation dependent */ + +/** + * Cursor types for SDL_CreateSystemCursor(). + */ +typedef enum SDL_SystemCursor +{ + SDL_SYSTEM_CURSOR_ARROW, /**< Arrow */ + SDL_SYSTEM_CURSOR_IBEAM, /**< I-beam */ + SDL_SYSTEM_CURSOR_WAIT, /**< Wait */ + SDL_SYSTEM_CURSOR_CROSSHAIR, /**< Crosshair */ + SDL_SYSTEM_CURSOR_WAITARROW, /**< Small wait cursor (or Wait if not available) */ + SDL_SYSTEM_CURSOR_SIZENWSE, /**< Double arrow pointing northwest and southeast */ + SDL_SYSTEM_CURSOR_SIZENESW, /**< Double arrow pointing northeast and southwest */ + SDL_SYSTEM_CURSOR_SIZEWE, /**< Double arrow pointing west and east */ + SDL_SYSTEM_CURSOR_SIZENS, /**< Double arrow pointing north and south */ + SDL_SYSTEM_CURSOR_SIZEALL, /**< Four pointed arrow pointing north, south, east, and west */ + SDL_SYSTEM_CURSOR_NO, /**< Slashed circle or crossbones */ + SDL_SYSTEM_CURSOR_HAND, /**< Hand */ + SDL_NUM_SYSTEM_CURSORS +} SDL_SystemCursor; + +/** + * Scroll direction types for the Scroll event + */ +typedef enum SDL_MouseWheelDirection +{ + SDL_MOUSEWHEEL_NORMAL, /**< The scroll direction is normal */ + SDL_MOUSEWHEEL_FLIPPED /**< The scroll direction is flipped / natural */ +} SDL_MouseWheelDirection; + +/* Function prototypes */ + +/** + * Get the window which currently has mouse focus. + * + * \returns the window with mouse focus. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetMouseFocus(void); + +/** + * Retrieve the current state of the mouse. + * + * The current button state is returned as a button bitmask, which can be + * tested using the `SDL_BUTTON(X)` macros (where `X` is generally 1 for the + * left, 2 for middle, 3 for the right button), and `x` and `y` are set to the + * mouse cursor position relative to the focus window. You can pass NULL for + * either `x` or `y`. + * + * \param x the x coordinate of the mouse cursor position relative to the + * focus window. + * \param y the y coordinate of the mouse cursor position relative to the + * focus window. + * \returns a 32-bit button bitmask of the current button state. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetGlobalMouseState + * \sa SDL_GetRelativeMouseState + * \sa SDL_PumpEvents + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetMouseState(int *x, int *y); + +/** + * Get the current state of the mouse in relation to the desktop. + * + * This works similarly to SDL_GetMouseState(), but the coordinates will be + * reported relative to the top-left of the desktop. This can be useful if you + * need to track the mouse outside of a specific window and SDL_CaptureMouse() + * doesn't fit your needs. For example, it could be useful if you need to + * track the mouse while dragging a window, where coordinates relative to a + * window might not be in sync at all times. + * + * Note: SDL_GetMouseState() returns the mouse position as SDL understands it + * from the last pump of the event queue. This function, however, queries the + * OS for the current mouse position, and as such, might be a slightly less + * efficient function. Unless you know what you're doing and have a good + * reason to use this function, you probably want SDL_GetMouseState() instead. + * + * \param x filled in with the current X coord relative to the desktop; can be + * NULL. + * \param y filled in with the current Y coord relative to the desktop; can be + * NULL. + * \returns the current button state as a bitmask which can be tested using + * the SDL_BUTTON(X) macros. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_CaptureMouse + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetGlobalMouseState(int *x, int *y); + +/** + * Retrieve the relative state of the mouse. + * + * The current button state is returned as a button bitmask, which can be + * tested using the `SDL_BUTTON(X)` macros (where `X` is generally 1 for the + * left, 2 for middle, 3 for the right button), and `x` and `y` are set to the + * mouse deltas since the last call to SDL_GetRelativeMouseState() or since + * event initialization. You can pass NULL for either `x` or `y`. + * + * \param x a pointer filled with the last recorded x coordinate of the mouse. + * \param y a pointer filled with the last recorded y coordinate of the mouse. + * \returns a 32-bit button bitmask of the relative button state. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetMouseState + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetRelativeMouseState(int *x, int *y); + +/** + * Move the mouse cursor to the given position within the window. + * + * This function generates a mouse motion event if relative mode is not + * enabled. If relative mode is enabled, you can force mouse events for the + * warp by setting the SDL_HINT_MOUSE_RELATIVE_WARP_MOTION hint. + * + * Note that this function will appear to succeed, but not actually move the + * mouse when used over Microsoft Remote Desktop. + * + * \param window the window to move the mouse into, or NULL for the current + * mouse focus. + * \param x the x coordinate within the window. + * \param y the y coordinate within the window. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WarpMouseGlobal + */ +extern DECLSPEC void SDLCALL SDL_WarpMouseInWindow(SDL_Window * window, + int x, int y); + +/** + * Move the mouse to the given position in global screen space. + * + * This function generates a mouse motion event. + * + * A failure of this function usually means that it is unsupported by a + * platform. + * + * Note that this function will appear to succeed, but not actually move the + * mouse when used over Microsoft Remote Desktop. + * + * \param x the x coordinate. + * \param y the y coordinate. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_WarpMouseInWindow + */ +extern DECLSPEC int SDLCALL SDL_WarpMouseGlobal(int x, int y); + +/** + * Set relative mouse mode. + * + * While the mouse is in relative mode, the cursor is hidden, the mouse + * position is constrained to the window, and SDL will report continuous + * relative mouse motion even if the mouse is at the edge of the window. + * + * This function will flush any pending mouse motion. + * + * \param enabled SDL_TRUE to enable relative mode, SDL_FALSE to disable. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * If relative mode is not supported, this returns -1. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRelativeMouseMode + */ +extern DECLSPEC int SDLCALL SDL_SetRelativeMouseMode(SDL_bool enabled); + +/** + * Capture the mouse and to track input outside an SDL window. + * + * Capturing enables your app to obtain mouse events globally, instead of just + * within your window. Not all video targets support this function. When + * capturing is enabled, the current window will get all mouse events, but + * unlike relative mode, no change is made to the cursor and it is not + * restrained to your window. + * + * This function may also deny mouse input to other windows--both those in + * your application and others on the system--so you should use this function + * sparingly, and in small bursts. For example, you might want to track the + * mouse while the user is dragging something, until the user releases a mouse + * button. It is not recommended that you capture the mouse for long periods + * of time, such as the entire time your app is running. For that, you should + * probably use SDL_SetRelativeMouseMode() or SDL_SetWindowGrab(), depending + * on your goals. + * + * While captured, mouse events still report coordinates relative to the + * current (foreground) window, but those coordinates may be outside the + * bounds of the window (including negative values). Capturing is only allowed + * for the foreground window. If the window loses focus while capturing, the + * capture will be disabled automatically. + * + * While capturing is enabled, the current window will have the + * `SDL_WINDOW_MOUSE_CAPTURE` flag set. + * + * Please note that as of SDL 2.0.22, SDL will attempt to "auto capture" the + * mouse while the user is pressing a button; this is to try and make mouse + * behavior more consistent between platforms, and deal with the common case + * of a user dragging the mouse outside of the window. This means that if you + * are calling SDL_CaptureMouse() only to deal with this situation, you no + * longer have to (although it is safe to do so). If this causes problems for + * your app, you can disable auto capture by setting the + * `SDL_HINT_MOUSE_AUTO_CAPTURE` hint to zero. + * + * \param enabled SDL_TRUE to enable capturing, SDL_FALSE to disable. + * \returns 0 on success or -1 if not supported; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_GetGlobalMouseState + */ +extern DECLSPEC int SDLCALL SDL_CaptureMouse(SDL_bool enabled); + +/** + * Query whether relative mouse mode is enabled. + * + * \returns SDL_TRUE if relative mode is enabled or SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetRelativeMouseMode + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetRelativeMouseMode(void); + +/** + * Create a cursor using the specified bitmap data and mask (in MSB format). + * + * `mask` has to be in MSB (Most Significant Bit) format. + * + * The cursor width (`w`) must be a multiple of 8 bits. + * + * The cursor is created in black and white according to the following: + * + * - data=0, mask=1: white + * - data=1, mask=1: black + * - data=0, mask=0: transparent + * - data=1, mask=0: inverted color if possible, black if not. + * + * Cursors created with this function must be freed with SDL_FreeCursor(). + * + * If you want to have a color cursor, or create your cursor from an + * SDL_Surface, you should use SDL_CreateColorCursor(). Alternately, you can + * hide the cursor and draw your own as part of your game's rendering, but it + * will be bound to the framerate. + * + * Also, since SDL 2.0.0, SDL_CreateSystemCursor() is available, which + * provides twelve readily available system cursors to pick from. + * + * \param data the color value for each pixel of the cursor. + * \param mask the mask value for each pixel of the cursor. + * \param w the width of the cursor. + * \param h the height of the cursor. + * \param hot_x the X-axis location of the upper left corner of the cursor + * relative to the actual mouse position. + * \param hot_y the Y-axis location of the upper left corner of the cursor + * relative to the actual mouse position. + * \returns a new cursor with the specified parameters on success or NULL on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreeCursor + * \sa SDL_SetCursor + * \sa SDL_ShowCursor + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateCursor(const Uint8 * data, + const Uint8 * mask, + int w, int h, int hot_x, + int hot_y); + +/** + * Create a color cursor. + * + * \param surface an SDL_Surface structure representing the cursor image. + * \param hot_x the x position of the cursor hot spot. + * \param hot_y the y position of the cursor hot spot. + * \returns the new cursor on success or NULL on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateCursor + * \sa SDL_FreeCursor + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateColorCursor(SDL_Surface *surface, + int hot_x, + int hot_y); + +/** + * Create a system cursor. + * + * \param id an SDL_SystemCursor enum value. + * \returns a cursor on success or NULL on failure; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreeCursor + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateSystemCursor(SDL_SystemCursor id); + +/** + * Set the active cursor. + * + * This function sets the currently active cursor to the specified one. If the + * cursor is currently visible, the change will be immediately represented on + * the display. SDL_SetCursor(NULL) can be used to force cursor redraw, if + * this is desired for any reason. + * + * \param cursor a cursor to make active. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateCursor + * \sa SDL_GetCursor + * \sa SDL_ShowCursor + */ +extern DECLSPEC void SDLCALL SDL_SetCursor(SDL_Cursor * cursor); + +/** + * Get the active cursor. + * + * This function returns a pointer to the current cursor which is owned by the + * library. It is not necessary to free the cursor with SDL_FreeCursor(). + * + * \returns the active cursor or NULL if there is no mouse. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetCursor + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_GetCursor(void); + +/** + * Get the default cursor. + * + * You do not have to call SDL_FreeCursor() on the return value, but it is + * safe to do so. + * + * \returns the default cursor on success or NULL on failure. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSystemCursor + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_GetDefaultCursor(void); + +/** + * Free a previously-created cursor. + * + * Use this function to free cursor resources created with SDL_CreateCursor(), + * SDL_CreateColorCursor() or SDL_CreateSystemCursor(). + * + * \param cursor the cursor to free. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateColorCursor + * \sa SDL_CreateCursor + * \sa SDL_CreateSystemCursor + */ +extern DECLSPEC void SDLCALL SDL_FreeCursor(SDL_Cursor * cursor); + +/** + * Toggle whether or not the cursor is shown. + * + * The cursor starts off displayed but can be turned off. Passing `SDL_ENABLE` + * displays the cursor and passing `SDL_DISABLE` hides it. + * + * The current state of the mouse cursor can be queried by passing + * `SDL_QUERY`; either `SDL_DISABLE` or `SDL_ENABLE` will be returned. + * + * \param toggle `SDL_ENABLE` to show the cursor, `SDL_DISABLE` to hide it, + * `SDL_QUERY` to query the current state without changing it. + * \returns `SDL_ENABLE` if the cursor is shown, or `SDL_DISABLE` if the + * cursor is hidden, or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateCursor + * \sa SDL_SetCursor + */ +extern DECLSPEC int SDLCALL SDL_ShowCursor(int toggle); + +/** + * Used as a mask when testing buttons in buttonstate. + * + * - Button 1: Left mouse button + * - Button 2: Middle mouse button + * - Button 3: Right mouse button + */ +#define SDL_BUTTON(X) (1 << ((X)-1)) +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_X1 4 +#define SDL_BUTTON_X2 5 +#define SDL_BUTTON_LMASK SDL_BUTTON(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON(SDL_BUTTON_RIGHT) +#define SDL_BUTTON_X1MASK SDL_BUTTON(SDL_BUTTON_X1) +#define SDL_BUTTON_X2MASK SDL_BUTTON(SDL_BUTTON_X2) + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_mouse_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_mutex.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_mutex.h new file mode 100644 index 00000000..0fe3eb5a --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_mutex.h @@ -0,0 +1,545 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_mutex_h_ +#define SDL_mutex_h_ + +/** + * # CategoryMutex + * + * Functions to provide thread synchronization primitives. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/******************************************************************************/ +/* Enable thread safety attributes only with clang. + * The attributes can be safely erased when compiling with other compilers. + */ +#if defined(SDL_THREAD_SAFETY_ANALYSIS) && \ + defined(__clang__) && (!defined(SWIG)) +#define SDL_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define SDL_THREAD_ANNOTATION_ATTRIBUTE__(x) /* no-op */ +#endif + +#define SDL_CAPABILITY(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SDL_SCOPED_CAPABILITY \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define SDL_GUARDED_BY(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define SDL_PT_GUARDED_BY(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define SDL_ACQUIRED_BEFORE(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(x)) + +#define SDL_ACQUIRED_AFTER(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(x)) + +#define SDL_REQUIRES(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(x)) + +#define SDL_REQUIRES_SHARED(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(x)) + +#define SDL_ACQUIRE(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(x)) + +#define SDL_ACQUIRE_SHARED(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(x)) + +#define SDL_RELEASE(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(release_capability(x)) + +#define SDL_RELEASE_SHARED(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(x)) + +#define SDL_RELEASE_GENERIC(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(x)) + +#define SDL_TRY_ACQUIRE(x, y) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(x, y)) + +#define SDL_TRY_ACQUIRE_SHARED(x, y) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(x, y)) + +#define SDL_EXCLUDES(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(x)) + +#define SDL_ASSERT_CAPABILITY(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define SDL_ASSERT_SHARED_CAPABILITY(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define SDL_RETURN_CAPABILITY(x) \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define SDL_NO_THREAD_SAFETY_ANALYSIS \ + SDL_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + +/******************************************************************************/ + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Synchronization functions which can time out return this value if they time + * out. + */ +#define SDL_MUTEX_TIMEDOUT 1 + +/** + * This is the timeout value which corresponds to never time out. + */ +#define SDL_MUTEX_MAXWAIT (~(Uint32)0) + + +/** + * \name Mutex functions + */ +/* @{ */ + +/* The SDL mutex structure, defined in SDL_sysmutex.c */ +struct SDL_mutex; +typedef struct SDL_mutex SDL_mutex; + +/** + * Create a new mutex. + * + * All newly-created mutexes begin in the _unlocked_ state. + * + * Calls to SDL_LockMutex() will not return while the mutex is locked by + * another thread. See SDL_TryLockMutex() to attempt to lock without blocking. + * + * SDL mutexes are reentrant. + * + * \returns the initialized and unlocked mutex or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DestroyMutex + * \sa SDL_LockMutex + * \sa SDL_TryLockMutex + * \sa SDL_UnlockMutex + */ +extern DECLSPEC SDL_mutex *SDLCALL SDL_CreateMutex(void); + +/** + * Lock the mutex. + * + * This will block until the mutex is available, which is to say it is in the + * unlocked state and the OS has chosen the caller as the next thread to lock + * it. Of all threads waiting to lock the mutex, only one may do so at a time. + * + * It is legal for the owning thread to lock an already-locked mutex. It must + * unlock it the same number of times before it is actually made available for + * other threads in the system (this is known as a "recursive mutex"). + * + * \param mutex the mutex to lock. + * \return 0, or -1 on error. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_LockMutex(SDL_mutex * mutex) SDL_ACQUIRE(mutex); +#define SDL_mutexP(m) SDL_LockMutex(m) + +/** + * Try to lock a mutex without blocking. + * + * This works just like SDL_LockMutex(), but if the mutex is not available, + * this function returns `SDL_MUTEX_TIMEOUT` immediately. + * + * This technique is useful if you need exclusive access to a resource but + * don't want to wait for it, and will return to it to try again later. + * + * \param mutex the mutex to try to lock. + * \returns 0, `SDL_MUTEX_TIMEDOUT`, or -1 on error; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateMutex + * \sa SDL_DestroyMutex + * \sa SDL_LockMutex + * \sa SDL_UnlockMutex + */ +extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_mutex * mutex) SDL_TRY_ACQUIRE(0, mutex); + +/** + * Unlock the mutex. + * + * It is legal for the owning thread to lock an already-locked mutex. It must + * unlock it the same number of times before it is actually made available for + * other threads in the system (this is known as a "recursive mutex"). + * + * It is an error to unlock a mutex that has not been locked by the current + * thread, and doing so results in undefined behavior. + * + * It is also an error to unlock a mutex that isn't locked at all. + * + * \param mutex the mutex to unlock. + * \returns 0, or -1 on error. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_UnlockMutex(SDL_mutex * mutex) SDL_RELEASE(mutex); +#define SDL_mutexV(m) SDL_UnlockMutex(m) + +/** + * Destroy a mutex created with SDL_CreateMutex(). + * + * This function must be called on any mutex that is no longer needed. Failure + * to destroy a mutex will result in a system memory or resource leak. While + * it is safe to destroy a mutex that is _unlocked_, it is not safe to attempt + * to destroy a locked mutex, and may result in undefined behavior depending + * on the platform. + * + * \param mutex the mutex to destroy. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateMutex + * \sa SDL_LockMutex + * \sa SDL_TryLockMutex + * \sa SDL_UnlockMutex + */ +extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex * mutex); + +/* @} *//* Mutex functions */ + + +/** + * \name Semaphore functions + */ +/* @{ */ + +/* The SDL semaphore structure, defined in SDL_syssem.c */ +struct SDL_semaphore; +typedef struct SDL_semaphore SDL_sem; + +/** + * Create a semaphore. + * + * This function creates a new semaphore and initializes it with the value + * `initial_value`. Each wait operation on the semaphore will atomically + * decrement the semaphore value and potentially block if the semaphore value + * is 0. Each post operation will atomically increment the semaphore value and + * wake waiting threads and allow them to retry the wait operation. + * + * \param initial_value the starting value of the semaphore. + * \returns a new semaphore or NULL on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DestroySemaphore + * \sa SDL_SemPost + * \sa SDL_SemTryWait + * \sa SDL_SemValue + * \sa SDL_SemWait + * \sa SDL_SemWaitTimeout + */ +extern DECLSPEC SDL_sem *SDLCALL SDL_CreateSemaphore(Uint32 initial_value); + +/** + * Destroy a semaphore. + * + * It is not safe to destroy a semaphore if there are threads currently + * waiting on it. + * + * \param sem the semaphore to destroy. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSemaphore + * \sa SDL_SemPost + * \sa SDL_SemTryWait + * \sa SDL_SemValue + * \sa SDL_SemWait + * \sa SDL_SemWaitTimeout + */ +extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem * sem); + +/** + * Wait until a semaphore has a positive value and then decrements it. + * + * This function suspends the calling thread until either the semaphore + * pointed to by `sem` has a positive value or the call is interrupted by a + * signal or error. If the call is successful it will atomically decrement the + * semaphore value. + * + * This function is the equivalent of calling SDL_SemWaitTimeout() with a time + * length of `SDL_MUTEX_MAXWAIT`. + * + * \param sem the semaphore wait on. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSemaphore + * \sa SDL_DestroySemaphore + * \sa SDL_SemPost + * \sa SDL_SemTryWait + * \sa SDL_SemValue + * \sa SDL_SemWait + * \sa SDL_SemWaitTimeout + */ +extern DECLSPEC int SDLCALL SDL_SemWait(SDL_sem * sem); + +/** + * See if a semaphore has a positive value and decrement it if it does. + * + * This function checks to see if the semaphore pointed to by `sem` has a + * positive value and atomically decrements the semaphore value if it does. If + * the semaphore doesn't have a positive value, the function immediately + * returns SDL_MUTEX_TIMEDOUT. + * + * \param sem the semaphore to wait on. + * \returns 0 if the wait succeeds, `SDL_MUTEX_TIMEDOUT` if the wait would + * block, or a negative error code on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSemaphore + * \sa SDL_DestroySemaphore + * \sa SDL_SemPost + * \sa SDL_SemValue + * \sa SDL_SemWait + * \sa SDL_SemWaitTimeout + */ +extern DECLSPEC int SDLCALL SDL_SemTryWait(SDL_sem * sem); + +/** + * Wait until a semaphore has a positive value and then decrements it. + * + * This function suspends the calling thread until either the semaphore + * pointed to by `sem` has a positive value, the call is interrupted by a + * signal or error, or the specified time has elapsed. If the call is + * successful it will atomically decrement the semaphore value. + * + * \param sem the semaphore to wait on. + * \param timeout the length of the timeout, in milliseconds. + * \returns 0 if the wait succeeds, `SDL_MUTEX_TIMEDOUT` if the wait does not + * succeed in the allotted time, or a negative error code on failure; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSemaphore + * \sa SDL_DestroySemaphore + * \sa SDL_SemPost + * \sa SDL_SemTryWait + * \sa SDL_SemValue + * \sa SDL_SemWait + */ +extern DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem *sem, Uint32 timeout); + +/** + * Atomically increment a semaphore's value and wake waiting threads. + * + * \param sem the semaphore to increment. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSemaphore + * \sa SDL_DestroySemaphore + * \sa SDL_SemTryWait + * \sa SDL_SemValue + * \sa SDL_SemWait + * \sa SDL_SemWaitTimeout + */ +extern DECLSPEC int SDLCALL SDL_SemPost(SDL_sem * sem); + +/** + * Get the current value of a semaphore. + * + * \param sem the semaphore to query. + * \returns the current value of the semaphore. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSemaphore + */ +extern DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem * sem); + +/* @} *//* Semaphore functions */ + + +/** + * \name Condition variable functions + */ +/* @{ */ + +/* The SDL condition variable structure, defined in SDL_syscond.c */ +struct SDL_cond; +typedef struct SDL_cond SDL_cond; + +/** + * Create a condition variable. + * + * \returns a new condition variable or NULL on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CondBroadcast + * \sa SDL_CondSignal + * \sa SDL_CondWait + * \sa SDL_CondWaitTimeout + * \sa SDL_DestroyCond + */ +extern DECLSPEC SDL_cond *SDLCALL SDL_CreateCond(void); + +/** + * Destroy a condition variable. + * + * \param cond the condition variable to destroy. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CondBroadcast + * \sa SDL_CondSignal + * \sa SDL_CondWait + * \sa SDL_CondWaitTimeout + * \sa SDL_CreateCond + */ +extern DECLSPEC void SDLCALL SDL_DestroyCond(SDL_cond * cond); + +/** + * Restart one of the threads that are waiting on the condition variable. + * + * \param cond the condition variable to signal. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CondBroadcast + * \sa SDL_CondWait + * \sa SDL_CondWaitTimeout + * \sa SDL_CreateCond + * \sa SDL_DestroyCond + */ +extern DECLSPEC int SDLCALL SDL_CondSignal(SDL_cond * cond); + +/** + * Restart all threads that are waiting on the condition variable. + * + * \param cond the condition variable to signal. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CondSignal + * \sa SDL_CondWait + * \sa SDL_CondWaitTimeout + * \sa SDL_CreateCond + * \sa SDL_DestroyCond + */ +extern DECLSPEC int SDLCALL SDL_CondBroadcast(SDL_cond * cond); + +/** + * Wait until a condition variable is signaled. + * + * This function unlocks the specified `mutex` and waits for another thread to + * call SDL_CondSignal() or SDL_CondBroadcast() on the condition variable + * `cond`. Once the condition variable is signaled, the mutex is re-locked and + * the function returns. + * + * The mutex must be locked before calling this function. + * + * This function is the equivalent of calling SDL_CondWaitTimeout() with a + * time length of `SDL_MUTEX_MAXWAIT`. + * + * \param cond the condition variable to wait on. + * \param mutex the mutex used to coordinate thread access. + * \returns 0 when it is signaled or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CondBroadcast + * \sa SDL_CondSignal + * \sa SDL_CondWaitTimeout + * \sa SDL_CreateCond + * \sa SDL_DestroyCond + */ +extern DECLSPEC int SDLCALL SDL_CondWait(SDL_cond * cond, SDL_mutex * mutex); + +/** + * Wait until a condition variable is signaled or a certain time has passed. + * + * This function unlocks the specified `mutex` and waits for another thread to + * call SDL_CondSignal() or SDL_CondBroadcast() on the condition variable + * `cond`, or for the specified time to elapse. Once the condition variable is + * signaled or the time elapsed, the mutex is re-locked and the function + * returns. + * + * The mutex must be locked before calling this function. + * + * \param cond the condition variable to wait on. + * \param mutex the mutex used to coordinate thread access. + * \param ms the maximum time to wait, in milliseconds, or `SDL_MUTEX_MAXWAIT` + * to wait indefinitely. + * \returns 0 if the condition variable is signaled, `SDL_MUTEX_TIMEDOUT` if + * the condition is not signaled in the allotted time, or a negative + * error code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CondBroadcast + * \sa SDL_CondSignal + * \sa SDL_CondWait + * \sa SDL_CreateCond + * \sa SDL_DestroyCond + */ +extern DECLSPEC int SDLCALL SDL_CondWaitTimeout(SDL_cond * cond, + SDL_mutex * mutex, Uint32 ms); + +/* @} *//* Condition variable functions */ + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_mutex_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_name.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_name.h new file mode 100644 index 00000000..0c48bcf3 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_name.h @@ -0,0 +1,33 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDLname_h_ +#define SDLname_h_ + +#if defined(__STDC__) || defined(__cplusplus) +#define NeedFunctionPrototypes 1 +#endif + +#define SDL_NAME(X) SDL_##X + +#endif /* SDLname_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengl.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengl.h new file mode 100644 index 00000000..c6250d13 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengl.h @@ -0,0 +1,2126 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * This is a simple file to encapsulate the OpenGL API headers. + * + * Define NO_SDL_GLEXT if you have your own version of glext.h and want + * to disable the version included in SDL_opengl.h. + */ + +#ifndef SDL_opengl_h_ +#define SDL_opengl_h_ + +#include "SDL_config.h" + +#ifndef __IPHONEOS__ /* No OpenGL on iOS. */ + +/* + * Mesa 3-D graphics library + * + * Copyright (C) 1999-2006 Brian Paul All Rights Reserved. + * Copyright (C) 2009 VMware, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +#ifndef __gl_h_ +#define __gl_h_ + +#if defined(USE_MGL_NAMESPACE) +#include "gl_mangle.h" +#endif + + +/********************************************************************** + * Begin system-specific stuff. + */ + +#if defined(_WIN32) && !defined(__WIN32__) && !defined(__CYGWIN__) +#define __WIN32__ +#endif + +#if defined(__WIN32__) && !defined(__CYGWIN__) +# if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(BUILD_GL32) /* tag specify we're building mesa as a DLL */ +# define GLAPI __declspec(dllexport) +# elif (defined(_MSC_VER) || defined(__MINGW32__)) && defined(_DLL) /* tag specifying we're building for DLL runtime support */ +# define GLAPI __declspec(dllimport) +# else /* for use with static link lib build of Win32 edition only */ +# define GLAPI extern +# endif /* _STATIC_MESA support */ +# if defined(__MINGW32__) && defined(GL_NO_STDCALL) || defined(UNDER_CE) /* The generated DLLs by MingW with STDCALL are not compatible with the ones done by Microsoft's compilers */ +# define GLAPIENTRY +# else +# define GLAPIENTRY __stdcall +# endif +#elif defined(__CYGWIN__) && defined(USE_OPENGL32) /* use native windows opengl32 */ +# define GLAPI extern +# define GLAPIENTRY __stdcall +#elif defined(__OS2__) || defined(__EMX__) /* native os/2 opengl */ +# define GLAPI extern +# define GLAPIENTRY _System +# define APIENTRY _System +# if defined(__GNUC__) && !defined(_System) +# define _System +# endif +#elif (defined(__GNUC__) && __GNUC__ >= 4) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)) +# define GLAPI __attribute__((visibility("default"))) +# define GLAPIENTRY +#endif /* WIN32 && !CYGWIN */ + +/* + * WINDOWS: Include windows.h here to define APIENTRY. + * It is also useful when applications include this file by + * including only glut.h, since glut.h depends on windows.h. + * Applications needing to include windows.h with parms other + * than "WIN32_LEAN_AND_MEAN" may include windows.h before + * glut.h or gl.h. + */ +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef NOMINMAX /* don't define min() and max(). */ +#define NOMINMAX +#endif +#include +#endif + +#ifndef GLAPI +#define GLAPI extern +#endif + +#ifndef GLAPIENTRY +#define GLAPIENTRY +#endif + +#ifndef APIENTRY +#define APIENTRY GLAPIENTRY +#endif + +/* "P" suffix to be used for a pointer to a function */ +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif + +#ifndef GLAPIENTRYP +#define GLAPIENTRYP GLAPIENTRY * +#endif + +#if defined(PRAGMA_EXPORT_SUPPORTED) +#pragma export on +#endif + +/* + * End system-specific stuff. + **********************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + + + +#define GL_VERSION_1_1 1 +#define GL_VERSION_1_2 1 +#define GL_VERSION_1_3 1 +#define GL_ARB_imaging 1 + + +/* + * Datatypes + */ +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; /* 1-byte signed */ +typedef short GLshort; /* 2-byte signed */ +typedef int GLint; /* 4-byte signed */ +typedef unsigned char GLubyte; /* 1-byte unsigned */ +typedef unsigned short GLushort; /* 2-byte unsigned */ +typedef unsigned int GLuint; /* 4-byte unsigned */ +typedef int GLsizei; /* 4-byte signed */ +typedef float GLfloat; /* single precision float */ +typedef float GLclampf; /* single precision float in [0,1] */ +typedef double GLdouble; /* double precision float */ +typedef double GLclampd; /* double precision float in [0,1] */ + + + +/* + * Constants + */ + +/* Boolean values */ +#define GL_FALSE 0 +#define GL_TRUE 1 + +/* Data types */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A + +/* Primitives */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +/* Vertex Arrays */ +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +/* Matrix Mode */ +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 + +/* Points */ +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_POINT_SIZE_RANGE 0x0B12 + +/* Lines */ +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_WIDTH_RANGE 0x0B22 + +/* Polygons */ +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +/* Display Lists */ +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_LIST_MODE 0x0B30 + +/* Depth buffer */ +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_BITS 0x0D56 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_COMPONENT 0x1902 + +/* Lighting */ +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_SHININESS 0x1601 +#define GL_EMISSION 0x1600 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_SHADE_MODEL 0x0B54 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_NORMALIZE 0x0BA1 + +/* User clipping planes */ +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +/* Accumulation buffer */ +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_ACCUM 0x0100 +#define GL_ADD 0x0104 +#define GL_LOAD 0x0101 +#define GL_MULT 0x0103 +#define GL_RETURN 0x0102 + +/* Alpha testing */ +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_ALPHA_TEST_FUNC 0x0BC1 + +/* Blending */ +#define GL_BLEND 0x0BE2 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND_DST 0x0BE0 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 + +/* Render Mode */ +#define GL_FEEDBACK 0x1C01 +#define GL_RENDER 0x1C00 +#define GL_SELECT 0x1C02 + +/* Feedback */ +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 + +/* Selection */ +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 + +/* Fog */ +#define GL_FOG 0x0B60 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_COLOR 0x0B66 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_LINEAR 0x2601 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +/* Logic Ops */ +#define GL_LOGIC_OP 0x0BF1 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_CLEAR 0x1500 +#define GL_SET 0x150F +#define GL_COPY 0x1503 +#define GL_COPY_INVERTED 0x150C +#define GL_NOOP 0x1505 +#define GL_INVERT 0x150A +#define GL_AND 0x1501 +#define GL_NAND 0x150E +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_XOR 0x1506 +#define GL_EQUIV 0x1509 +#define GL_AND_REVERSE 0x1502 +#define GL_AND_INVERTED 0x1504 +#define GL_OR_REVERSE 0x150B +#define GL_OR_INVERTED 0x150D + +/* Stencil */ +#define GL_STENCIL_BITS 0x0D57 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_STENCIL_INDEX 0x1901 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +/* Buffers, Pixel Drawing/Reading */ +#define GL_NONE 0 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +/*GL_FRONT 0x0404 */ +/*GL_BACK 0x0405 */ +/*GL_FRONT_AND_BACK 0x0408 */ +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_COLOR_INDEX 0x1900 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_ALPHA_BITS 0x0D55 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_INDEX_BITS 0x0D51 +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_READ_BUFFER 0x0C02 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_BITMAP 0x1A00 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_DITHER 0x0BD0 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +/* Implementation limits */ +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B + +/* Gets */ +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_MODE 0x0C30 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_RENDER_MODE 0x0C40 +#define GL_RGBA_MODE 0x0C31 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_VIEWPORT 0x0BA2 + +/* Evaluators */ +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 + +/* Hints */ +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* Scissor box */ +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 + +/* Pixel Mode / Transfer */ +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 + +/* Texture mapping */ +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_TEXTURE_COMPONENTS 0x1003 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_LINEAR 0x2400 +#define GL_EYE_PLANE 0x2502 +#define GL_SPHERE_MAP 0x2402 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 +#define GL_NEAREST 0x2600 +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +/* Utility */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* Errors */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* glPush/PopAttrib bits */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000FFFFF + + +/* OpenGL 1.1 */ +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_ALL_CLIENT_ATTRIB_BITS 0xFFFFFFFF +#define GL_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF + + + +/* + * Miscellaneous + */ + +GLAPI void GLAPIENTRY glClearIndex( GLfloat c ); + +GLAPI void GLAPIENTRY glClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ); + +GLAPI void GLAPIENTRY glClear( GLbitfield mask ); + +GLAPI void GLAPIENTRY glIndexMask( GLuint mask ); + +GLAPI void GLAPIENTRY glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha ); + +GLAPI void GLAPIENTRY glAlphaFunc( GLenum func, GLclampf ref ); + +GLAPI void GLAPIENTRY glBlendFunc( GLenum sfactor, GLenum dfactor ); + +GLAPI void GLAPIENTRY glLogicOp( GLenum opcode ); + +GLAPI void GLAPIENTRY glCullFace( GLenum mode ); + +GLAPI void GLAPIENTRY glFrontFace( GLenum mode ); + +GLAPI void GLAPIENTRY glPointSize( GLfloat size ); + +GLAPI void GLAPIENTRY glLineWidth( GLfloat width ); + +GLAPI void GLAPIENTRY glLineStipple( GLint factor, GLushort pattern ); + +GLAPI void GLAPIENTRY glPolygonMode( GLenum face, GLenum mode ); + +GLAPI void GLAPIENTRY glPolygonOffset( GLfloat factor, GLfloat units ); + +GLAPI void GLAPIENTRY glPolygonStipple( const GLubyte *mask ); + +GLAPI void GLAPIENTRY glGetPolygonStipple( GLubyte *mask ); + +GLAPI void GLAPIENTRY glEdgeFlag( GLboolean flag ); + +GLAPI void GLAPIENTRY glEdgeFlagv( const GLboolean *flag ); + +GLAPI void GLAPIENTRY glScissor( GLint x, GLint y, GLsizei width, GLsizei height); + +GLAPI void GLAPIENTRY glClipPlane( GLenum plane, const GLdouble *equation ); + +GLAPI void GLAPIENTRY glGetClipPlane( GLenum plane, GLdouble *equation ); + +GLAPI void GLAPIENTRY glDrawBuffer( GLenum mode ); + +GLAPI void GLAPIENTRY glReadBuffer( GLenum mode ); + +GLAPI void GLAPIENTRY glEnable( GLenum cap ); + +GLAPI void GLAPIENTRY glDisable( GLenum cap ); + +GLAPI GLboolean GLAPIENTRY glIsEnabled( GLenum cap ); + + +GLAPI void GLAPIENTRY glEnableClientState( GLenum cap ); /* 1.1 */ + +GLAPI void GLAPIENTRY glDisableClientState( GLenum cap ); /* 1.1 */ + + +GLAPI void GLAPIENTRY glGetBooleanv( GLenum pname, GLboolean *params ); + +GLAPI void GLAPIENTRY glGetDoublev( GLenum pname, GLdouble *params ); + +GLAPI void GLAPIENTRY glGetFloatv( GLenum pname, GLfloat *params ); + +GLAPI void GLAPIENTRY glGetIntegerv( GLenum pname, GLint *params ); + + +GLAPI void GLAPIENTRY glPushAttrib( GLbitfield mask ); + +GLAPI void GLAPIENTRY glPopAttrib( void ); + + +GLAPI void GLAPIENTRY glPushClientAttrib( GLbitfield mask ); /* 1.1 */ + +GLAPI void GLAPIENTRY glPopClientAttrib( void ); /* 1.1 */ + + +GLAPI GLint GLAPIENTRY glRenderMode( GLenum mode ); + +GLAPI GLenum GLAPIENTRY glGetError( void ); + +GLAPI const GLubyte * GLAPIENTRY glGetString( GLenum name ); + +GLAPI void GLAPIENTRY glFinish( void ); + +GLAPI void GLAPIENTRY glFlush( void ); + +GLAPI void GLAPIENTRY glHint( GLenum target, GLenum mode ); + + +/* + * Depth Buffer + */ + +GLAPI void GLAPIENTRY glClearDepth( GLclampd depth ); + +GLAPI void GLAPIENTRY glDepthFunc( GLenum func ); + +GLAPI void GLAPIENTRY glDepthMask( GLboolean flag ); + +GLAPI void GLAPIENTRY glDepthRange( GLclampd near_val, GLclampd far_val ); + + +/* + * Accumulation Buffer + */ + +GLAPI void GLAPIENTRY glClearAccum( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ); + +GLAPI void GLAPIENTRY glAccum( GLenum op, GLfloat value ); + + +/* + * Transformation + */ + +GLAPI void GLAPIENTRY glMatrixMode( GLenum mode ); + +GLAPI void GLAPIENTRY glOrtho( GLdouble left, GLdouble right, + GLdouble bottom, GLdouble top, + GLdouble near_val, GLdouble far_val ); + +GLAPI void GLAPIENTRY glFrustum( GLdouble left, GLdouble right, + GLdouble bottom, GLdouble top, + GLdouble near_val, GLdouble far_val ); + +GLAPI void GLAPIENTRY glViewport( GLint x, GLint y, + GLsizei width, GLsizei height ); + +GLAPI void GLAPIENTRY glPushMatrix( void ); + +GLAPI void GLAPIENTRY glPopMatrix( void ); + +GLAPI void GLAPIENTRY glLoadIdentity( void ); + +GLAPI void GLAPIENTRY glLoadMatrixd( const GLdouble *m ); +GLAPI void GLAPIENTRY glLoadMatrixf( const GLfloat *m ); + +GLAPI void GLAPIENTRY glMultMatrixd( const GLdouble *m ); +GLAPI void GLAPIENTRY glMultMatrixf( const GLfloat *m ); + +GLAPI void GLAPIENTRY glRotated( GLdouble angle, + GLdouble x, GLdouble y, GLdouble z ); +GLAPI void GLAPIENTRY glRotatef( GLfloat angle, + GLfloat x, GLfloat y, GLfloat z ); + +GLAPI void GLAPIENTRY glScaled( GLdouble x, GLdouble y, GLdouble z ); +GLAPI void GLAPIENTRY glScalef( GLfloat x, GLfloat y, GLfloat z ); + +GLAPI void GLAPIENTRY glTranslated( GLdouble x, GLdouble y, GLdouble z ); +GLAPI void GLAPIENTRY glTranslatef( GLfloat x, GLfloat y, GLfloat z ); + + +/* + * Display Lists + */ + +GLAPI GLboolean GLAPIENTRY glIsList( GLuint list ); + +GLAPI void GLAPIENTRY glDeleteLists( GLuint list, GLsizei range ); + +GLAPI GLuint GLAPIENTRY glGenLists( GLsizei range ); + +GLAPI void GLAPIENTRY glNewList( GLuint list, GLenum mode ); + +GLAPI void GLAPIENTRY glEndList( void ); + +GLAPI void GLAPIENTRY glCallList( GLuint list ); + +GLAPI void GLAPIENTRY glCallLists( GLsizei n, GLenum type, + const GLvoid *lists ); + +GLAPI void GLAPIENTRY glListBase( GLuint base ); + + +/* + * Drawing Functions + */ + +GLAPI void GLAPIENTRY glBegin( GLenum mode ); + +GLAPI void GLAPIENTRY glEnd( void ); + + +GLAPI void GLAPIENTRY glVertex2d( GLdouble x, GLdouble y ); +GLAPI void GLAPIENTRY glVertex2f( GLfloat x, GLfloat y ); +GLAPI void GLAPIENTRY glVertex2i( GLint x, GLint y ); +GLAPI void GLAPIENTRY glVertex2s( GLshort x, GLshort y ); + +GLAPI void GLAPIENTRY glVertex3d( GLdouble x, GLdouble y, GLdouble z ); +GLAPI void GLAPIENTRY glVertex3f( GLfloat x, GLfloat y, GLfloat z ); +GLAPI void GLAPIENTRY glVertex3i( GLint x, GLint y, GLint z ); +GLAPI void GLAPIENTRY glVertex3s( GLshort x, GLshort y, GLshort z ); + +GLAPI void GLAPIENTRY glVertex4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w ); +GLAPI void GLAPIENTRY glVertex4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w ); +GLAPI void GLAPIENTRY glVertex4i( GLint x, GLint y, GLint z, GLint w ); +GLAPI void GLAPIENTRY glVertex4s( GLshort x, GLshort y, GLshort z, GLshort w ); + +GLAPI void GLAPIENTRY glVertex2dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glVertex2fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glVertex2iv( const GLint *v ); +GLAPI void GLAPIENTRY glVertex2sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glVertex3dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glVertex3fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glVertex3iv( const GLint *v ); +GLAPI void GLAPIENTRY glVertex3sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glVertex4dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glVertex4fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glVertex4iv( const GLint *v ); +GLAPI void GLAPIENTRY glVertex4sv( const GLshort *v ); + + +GLAPI void GLAPIENTRY glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz ); +GLAPI void GLAPIENTRY glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz ); +GLAPI void GLAPIENTRY glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz ); +GLAPI void GLAPIENTRY glNormal3i( GLint nx, GLint ny, GLint nz ); +GLAPI void GLAPIENTRY glNormal3s( GLshort nx, GLshort ny, GLshort nz ); + +GLAPI void GLAPIENTRY glNormal3bv( const GLbyte *v ); +GLAPI void GLAPIENTRY glNormal3dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glNormal3fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glNormal3iv( const GLint *v ); +GLAPI void GLAPIENTRY glNormal3sv( const GLshort *v ); + + +GLAPI void GLAPIENTRY glIndexd( GLdouble c ); +GLAPI void GLAPIENTRY glIndexf( GLfloat c ); +GLAPI void GLAPIENTRY glIndexi( GLint c ); +GLAPI void GLAPIENTRY glIndexs( GLshort c ); +GLAPI void GLAPIENTRY glIndexub( GLubyte c ); /* 1.1 */ + +GLAPI void GLAPIENTRY glIndexdv( const GLdouble *c ); +GLAPI void GLAPIENTRY glIndexfv( const GLfloat *c ); +GLAPI void GLAPIENTRY glIndexiv( const GLint *c ); +GLAPI void GLAPIENTRY glIndexsv( const GLshort *c ); +GLAPI void GLAPIENTRY glIndexubv( const GLubyte *c ); /* 1.1 */ + +GLAPI void GLAPIENTRY glColor3b( GLbyte red, GLbyte green, GLbyte blue ); +GLAPI void GLAPIENTRY glColor3d( GLdouble red, GLdouble green, GLdouble blue ); +GLAPI void GLAPIENTRY glColor3f( GLfloat red, GLfloat green, GLfloat blue ); +GLAPI void GLAPIENTRY glColor3i( GLint red, GLint green, GLint blue ); +GLAPI void GLAPIENTRY glColor3s( GLshort red, GLshort green, GLshort blue ); +GLAPI void GLAPIENTRY glColor3ub( GLubyte red, GLubyte green, GLubyte blue ); +GLAPI void GLAPIENTRY glColor3ui( GLuint red, GLuint green, GLuint blue ); +GLAPI void GLAPIENTRY glColor3us( GLushort red, GLushort green, GLushort blue ); + +GLAPI void GLAPIENTRY glColor4b( GLbyte red, GLbyte green, + GLbyte blue, GLbyte alpha ); +GLAPI void GLAPIENTRY glColor4d( GLdouble red, GLdouble green, + GLdouble blue, GLdouble alpha ); +GLAPI void GLAPIENTRY glColor4f( GLfloat red, GLfloat green, + GLfloat blue, GLfloat alpha ); +GLAPI void GLAPIENTRY glColor4i( GLint red, GLint green, + GLint blue, GLint alpha ); +GLAPI void GLAPIENTRY glColor4s( GLshort red, GLshort green, + GLshort blue, GLshort alpha ); +GLAPI void GLAPIENTRY glColor4ub( GLubyte red, GLubyte green, + GLubyte blue, GLubyte alpha ); +GLAPI void GLAPIENTRY glColor4ui( GLuint red, GLuint green, + GLuint blue, GLuint alpha ); +GLAPI void GLAPIENTRY glColor4us( GLushort red, GLushort green, + GLushort blue, GLushort alpha ); + + +GLAPI void GLAPIENTRY glColor3bv( const GLbyte *v ); +GLAPI void GLAPIENTRY glColor3dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glColor3fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glColor3iv( const GLint *v ); +GLAPI void GLAPIENTRY glColor3sv( const GLshort *v ); +GLAPI void GLAPIENTRY glColor3ubv( const GLubyte *v ); +GLAPI void GLAPIENTRY glColor3uiv( const GLuint *v ); +GLAPI void GLAPIENTRY glColor3usv( const GLushort *v ); + +GLAPI void GLAPIENTRY glColor4bv( const GLbyte *v ); +GLAPI void GLAPIENTRY glColor4dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glColor4fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glColor4iv( const GLint *v ); +GLAPI void GLAPIENTRY glColor4sv( const GLshort *v ); +GLAPI void GLAPIENTRY glColor4ubv( const GLubyte *v ); +GLAPI void GLAPIENTRY glColor4uiv( const GLuint *v ); +GLAPI void GLAPIENTRY glColor4usv( const GLushort *v ); + + +GLAPI void GLAPIENTRY glTexCoord1d( GLdouble s ); +GLAPI void GLAPIENTRY glTexCoord1f( GLfloat s ); +GLAPI void GLAPIENTRY glTexCoord1i( GLint s ); +GLAPI void GLAPIENTRY glTexCoord1s( GLshort s ); + +GLAPI void GLAPIENTRY glTexCoord2d( GLdouble s, GLdouble t ); +GLAPI void GLAPIENTRY glTexCoord2f( GLfloat s, GLfloat t ); +GLAPI void GLAPIENTRY glTexCoord2i( GLint s, GLint t ); +GLAPI void GLAPIENTRY glTexCoord2s( GLshort s, GLshort t ); + +GLAPI void GLAPIENTRY glTexCoord3d( GLdouble s, GLdouble t, GLdouble r ); +GLAPI void GLAPIENTRY glTexCoord3f( GLfloat s, GLfloat t, GLfloat r ); +GLAPI void GLAPIENTRY glTexCoord3i( GLint s, GLint t, GLint r ); +GLAPI void GLAPIENTRY glTexCoord3s( GLshort s, GLshort t, GLshort r ); + +GLAPI void GLAPIENTRY glTexCoord4d( GLdouble s, GLdouble t, GLdouble r, GLdouble q ); +GLAPI void GLAPIENTRY glTexCoord4f( GLfloat s, GLfloat t, GLfloat r, GLfloat q ); +GLAPI void GLAPIENTRY glTexCoord4i( GLint s, GLint t, GLint r, GLint q ); +GLAPI void GLAPIENTRY glTexCoord4s( GLshort s, GLshort t, GLshort r, GLshort q ); + +GLAPI void GLAPIENTRY glTexCoord1dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glTexCoord1fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glTexCoord1iv( const GLint *v ); +GLAPI void GLAPIENTRY glTexCoord1sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glTexCoord2dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glTexCoord2fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glTexCoord2iv( const GLint *v ); +GLAPI void GLAPIENTRY glTexCoord2sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glTexCoord3dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glTexCoord3fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glTexCoord3iv( const GLint *v ); +GLAPI void GLAPIENTRY glTexCoord3sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glTexCoord4dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glTexCoord4fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glTexCoord4iv( const GLint *v ); +GLAPI void GLAPIENTRY glTexCoord4sv( const GLshort *v ); + + +GLAPI void GLAPIENTRY glRasterPos2d( GLdouble x, GLdouble y ); +GLAPI void GLAPIENTRY glRasterPos2f( GLfloat x, GLfloat y ); +GLAPI void GLAPIENTRY glRasterPos2i( GLint x, GLint y ); +GLAPI void GLAPIENTRY glRasterPos2s( GLshort x, GLshort y ); + +GLAPI void GLAPIENTRY glRasterPos3d( GLdouble x, GLdouble y, GLdouble z ); +GLAPI void GLAPIENTRY glRasterPos3f( GLfloat x, GLfloat y, GLfloat z ); +GLAPI void GLAPIENTRY glRasterPos3i( GLint x, GLint y, GLint z ); +GLAPI void GLAPIENTRY glRasterPos3s( GLshort x, GLshort y, GLshort z ); + +GLAPI void GLAPIENTRY glRasterPos4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w ); +GLAPI void GLAPIENTRY glRasterPos4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w ); +GLAPI void GLAPIENTRY glRasterPos4i( GLint x, GLint y, GLint z, GLint w ); +GLAPI void GLAPIENTRY glRasterPos4s( GLshort x, GLshort y, GLshort z, GLshort w ); + +GLAPI void GLAPIENTRY glRasterPos2dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glRasterPos2fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glRasterPos2iv( const GLint *v ); +GLAPI void GLAPIENTRY glRasterPos2sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glRasterPos3dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glRasterPos3fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glRasterPos3iv( const GLint *v ); +GLAPI void GLAPIENTRY glRasterPos3sv( const GLshort *v ); + +GLAPI void GLAPIENTRY glRasterPos4dv( const GLdouble *v ); +GLAPI void GLAPIENTRY glRasterPos4fv( const GLfloat *v ); +GLAPI void GLAPIENTRY glRasterPos4iv( const GLint *v ); +GLAPI void GLAPIENTRY glRasterPos4sv( const GLshort *v ); + + +GLAPI void GLAPIENTRY glRectd( GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2 ); +GLAPI void GLAPIENTRY glRectf( GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2 ); +GLAPI void GLAPIENTRY glRecti( GLint x1, GLint y1, GLint x2, GLint y2 ); +GLAPI void GLAPIENTRY glRects( GLshort x1, GLshort y1, GLshort x2, GLshort y2 ); + + +GLAPI void GLAPIENTRY glRectdv( const GLdouble *v1, const GLdouble *v2 ); +GLAPI void GLAPIENTRY glRectfv( const GLfloat *v1, const GLfloat *v2 ); +GLAPI void GLAPIENTRY glRectiv( const GLint *v1, const GLint *v2 ); +GLAPI void GLAPIENTRY glRectsv( const GLshort *v1, const GLshort *v2 ); + + +/* + * Vertex Arrays (1.1) + */ + +GLAPI void GLAPIENTRY glVertexPointer( GLint size, GLenum type, + GLsizei stride, const GLvoid *ptr ); + +GLAPI void GLAPIENTRY glNormalPointer( GLenum type, GLsizei stride, + const GLvoid *ptr ); + +GLAPI void GLAPIENTRY glColorPointer( GLint size, GLenum type, + GLsizei stride, const GLvoid *ptr ); + +GLAPI void GLAPIENTRY glIndexPointer( GLenum type, GLsizei stride, + const GLvoid *ptr ); + +GLAPI void GLAPIENTRY glTexCoordPointer( GLint size, GLenum type, + GLsizei stride, const GLvoid *ptr ); + +GLAPI void GLAPIENTRY glEdgeFlagPointer( GLsizei stride, const GLvoid *ptr ); + +GLAPI void GLAPIENTRY glGetPointerv( GLenum pname, GLvoid **params ); + +GLAPI void GLAPIENTRY glArrayElement( GLint i ); + +GLAPI void GLAPIENTRY glDrawArrays( GLenum mode, GLint first, GLsizei count ); + +GLAPI void GLAPIENTRY glDrawElements( GLenum mode, GLsizei count, + GLenum type, const GLvoid *indices ); + +GLAPI void GLAPIENTRY glInterleavedArrays( GLenum format, GLsizei stride, + const GLvoid *pointer ); + +/* + * Lighting + */ + +GLAPI void GLAPIENTRY glShadeModel( GLenum mode ); + +GLAPI void GLAPIENTRY glLightf( GLenum light, GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glLighti( GLenum light, GLenum pname, GLint param ); +GLAPI void GLAPIENTRY glLightfv( GLenum light, GLenum pname, + const GLfloat *params ); +GLAPI void GLAPIENTRY glLightiv( GLenum light, GLenum pname, + const GLint *params ); + +GLAPI void GLAPIENTRY glGetLightfv( GLenum light, GLenum pname, + GLfloat *params ); +GLAPI void GLAPIENTRY glGetLightiv( GLenum light, GLenum pname, + GLint *params ); + +GLAPI void GLAPIENTRY glLightModelf( GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glLightModeli( GLenum pname, GLint param ); +GLAPI void GLAPIENTRY glLightModelfv( GLenum pname, const GLfloat *params ); +GLAPI void GLAPIENTRY glLightModeliv( GLenum pname, const GLint *params ); + +GLAPI void GLAPIENTRY glMaterialf( GLenum face, GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glMateriali( GLenum face, GLenum pname, GLint param ); +GLAPI void GLAPIENTRY glMaterialfv( GLenum face, GLenum pname, const GLfloat *params ); +GLAPI void GLAPIENTRY glMaterialiv( GLenum face, GLenum pname, const GLint *params ); + +GLAPI void GLAPIENTRY glGetMaterialfv( GLenum face, GLenum pname, GLfloat *params ); +GLAPI void GLAPIENTRY glGetMaterialiv( GLenum face, GLenum pname, GLint *params ); + +GLAPI void GLAPIENTRY glColorMaterial( GLenum face, GLenum mode ); + + +/* + * Raster functions + */ + +GLAPI void GLAPIENTRY glPixelZoom( GLfloat xfactor, GLfloat yfactor ); + +GLAPI void GLAPIENTRY glPixelStoref( GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glPixelStorei( GLenum pname, GLint param ); + +GLAPI void GLAPIENTRY glPixelTransferf( GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glPixelTransferi( GLenum pname, GLint param ); + +GLAPI void GLAPIENTRY glPixelMapfv( GLenum map, GLsizei mapsize, + const GLfloat *values ); +GLAPI void GLAPIENTRY glPixelMapuiv( GLenum map, GLsizei mapsize, + const GLuint *values ); +GLAPI void GLAPIENTRY glPixelMapusv( GLenum map, GLsizei mapsize, + const GLushort *values ); + +GLAPI void GLAPIENTRY glGetPixelMapfv( GLenum map, GLfloat *values ); +GLAPI void GLAPIENTRY glGetPixelMapuiv( GLenum map, GLuint *values ); +GLAPI void GLAPIENTRY glGetPixelMapusv( GLenum map, GLushort *values ); + +GLAPI void GLAPIENTRY glBitmap( GLsizei width, GLsizei height, + GLfloat xorig, GLfloat yorig, + GLfloat xmove, GLfloat ymove, + const GLubyte *bitmap ); + +GLAPI void GLAPIENTRY glReadPixels( GLint x, GLint y, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + GLvoid *pixels ); + +GLAPI void GLAPIENTRY glDrawPixels( GLsizei width, GLsizei height, + GLenum format, GLenum type, + const GLvoid *pixels ); + +GLAPI void GLAPIENTRY glCopyPixels( GLint x, GLint y, + GLsizei width, GLsizei height, + GLenum type ); + +/* + * Stenciling + */ + +GLAPI void GLAPIENTRY glStencilFunc( GLenum func, GLint ref, GLuint mask ); + +GLAPI void GLAPIENTRY glStencilMask( GLuint mask ); + +GLAPI void GLAPIENTRY glStencilOp( GLenum fail, GLenum zfail, GLenum zpass ); + +GLAPI void GLAPIENTRY glClearStencil( GLint s ); + + + +/* + * Texture mapping + */ + +GLAPI void GLAPIENTRY glTexGend( GLenum coord, GLenum pname, GLdouble param ); +GLAPI void GLAPIENTRY glTexGenf( GLenum coord, GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glTexGeni( GLenum coord, GLenum pname, GLint param ); + +GLAPI void GLAPIENTRY glTexGendv( GLenum coord, GLenum pname, const GLdouble *params ); +GLAPI void GLAPIENTRY glTexGenfv( GLenum coord, GLenum pname, const GLfloat *params ); +GLAPI void GLAPIENTRY glTexGeniv( GLenum coord, GLenum pname, const GLint *params ); + +GLAPI void GLAPIENTRY glGetTexGendv( GLenum coord, GLenum pname, GLdouble *params ); +GLAPI void GLAPIENTRY glGetTexGenfv( GLenum coord, GLenum pname, GLfloat *params ); +GLAPI void GLAPIENTRY glGetTexGeniv( GLenum coord, GLenum pname, GLint *params ); + + +GLAPI void GLAPIENTRY glTexEnvf( GLenum target, GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glTexEnvi( GLenum target, GLenum pname, GLint param ); + +GLAPI void GLAPIENTRY glTexEnvfv( GLenum target, GLenum pname, const GLfloat *params ); +GLAPI void GLAPIENTRY glTexEnviv( GLenum target, GLenum pname, const GLint *params ); + +GLAPI void GLAPIENTRY glGetTexEnvfv( GLenum target, GLenum pname, GLfloat *params ); +GLAPI void GLAPIENTRY glGetTexEnviv( GLenum target, GLenum pname, GLint *params ); + + +GLAPI void GLAPIENTRY glTexParameterf( GLenum target, GLenum pname, GLfloat param ); +GLAPI void GLAPIENTRY glTexParameteri( GLenum target, GLenum pname, GLint param ); + +GLAPI void GLAPIENTRY glTexParameterfv( GLenum target, GLenum pname, + const GLfloat *params ); +GLAPI void GLAPIENTRY glTexParameteriv( GLenum target, GLenum pname, + const GLint *params ); + +GLAPI void GLAPIENTRY glGetTexParameterfv( GLenum target, + GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetTexParameteriv( GLenum target, + GLenum pname, GLint *params ); + +GLAPI void GLAPIENTRY glGetTexLevelParameterfv( GLenum target, GLint level, + GLenum pname, GLfloat *params ); +GLAPI void GLAPIENTRY glGetTexLevelParameteriv( GLenum target, GLint level, + GLenum pname, GLint *params ); + + +GLAPI void GLAPIENTRY glTexImage1D( GLenum target, GLint level, + GLint internalFormat, + GLsizei width, GLint border, + GLenum format, GLenum type, + const GLvoid *pixels ); + +GLAPI void GLAPIENTRY glTexImage2D( GLenum target, GLint level, + GLint internalFormat, + GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, + const GLvoid *pixels ); + +GLAPI void GLAPIENTRY glGetTexImage( GLenum target, GLint level, + GLenum format, GLenum type, + GLvoid *pixels ); + + +/* 1.1 functions */ + +GLAPI void GLAPIENTRY glGenTextures( GLsizei n, GLuint *textures ); + +GLAPI void GLAPIENTRY glDeleteTextures( GLsizei n, const GLuint *textures); + +GLAPI void GLAPIENTRY glBindTexture( GLenum target, GLuint texture ); + +GLAPI void GLAPIENTRY glPrioritizeTextures( GLsizei n, + const GLuint *textures, + const GLclampf *priorities ); + +GLAPI GLboolean GLAPIENTRY glAreTexturesResident( GLsizei n, + const GLuint *textures, + GLboolean *residences ); + +GLAPI GLboolean GLAPIENTRY glIsTexture( GLuint texture ); + + +GLAPI void GLAPIENTRY glTexSubImage1D( GLenum target, GLint level, + GLint xoffset, + GLsizei width, GLenum format, + GLenum type, const GLvoid *pixels ); + + +GLAPI void GLAPIENTRY glTexSubImage2D( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + const GLvoid *pixels ); + + +GLAPI void GLAPIENTRY glCopyTexImage1D( GLenum target, GLint level, + GLenum internalformat, + GLint x, GLint y, + GLsizei width, GLint border ); + + +GLAPI void GLAPIENTRY glCopyTexImage2D( GLenum target, GLint level, + GLenum internalformat, + GLint x, GLint y, + GLsizei width, GLsizei height, + GLint border ); + + +GLAPI void GLAPIENTRY glCopyTexSubImage1D( GLenum target, GLint level, + GLint xoffset, GLint x, GLint y, + GLsizei width ); + + +GLAPI void GLAPIENTRY glCopyTexSubImage2D( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLint x, GLint y, + GLsizei width, GLsizei height ); + + +/* + * Evaluators + */ + +GLAPI void GLAPIENTRY glMap1d( GLenum target, GLdouble u1, GLdouble u2, + GLint stride, + GLint order, const GLdouble *points ); +GLAPI void GLAPIENTRY glMap1f( GLenum target, GLfloat u1, GLfloat u2, + GLint stride, + GLint order, const GLfloat *points ); + +GLAPI void GLAPIENTRY glMap2d( GLenum target, + GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, + GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, + const GLdouble *points ); +GLAPI void GLAPIENTRY glMap2f( GLenum target, + GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, + GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, + const GLfloat *points ); + +GLAPI void GLAPIENTRY glGetMapdv( GLenum target, GLenum query, GLdouble *v ); +GLAPI void GLAPIENTRY glGetMapfv( GLenum target, GLenum query, GLfloat *v ); +GLAPI void GLAPIENTRY glGetMapiv( GLenum target, GLenum query, GLint *v ); + +GLAPI void GLAPIENTRY glEvalCoord1d( GLdouble u ); +GLAPI void GLAPIENTRY glEvalCoord1f( GLfloat u ); + +GLAPI void GLAPIENTRY glEvalCoord1dv( const GLdouble *u ); +GLAPI void GLAPIENTRY glEvalCoord1fv( const GLfloat *u ); + +GLAPI void GLAPIENTRY glEvalCoord2d( GLdouble u, GLdouble v ); +GLAPI void GLAPIENTRY glEvalCoord2f( GLfloat u, GLfloat v ); + +GLAPI void GLAPIENTRY glEvalCoord2dv( const GLdouble *u ); +GLAPI void GLAPIENTRY glEvalCoord2fv( const GLfloat *u ); + +GLAPI void GLAPIENTRY glMapGrid1d( GLint un, GLdouble u1, GLdouble u2 ); +GLAPI void GLAPIENTRY glMapGrid1f( GLint un, GLfloat u1, GLfloat u2 ); + +GLAPI void GLAPIENTRY glMapGrid2d( GLint un, GLdouble u1, GLdouble u2, + GLint vn, GLdouble v1, GLdouble v2 ); +GLAPI void GLAPIENTRY glMapGrid2f( GLint un, GLfloat u1, GLfloat u2, + GLint vn, GLfloat v1, GLfloat v2 ); + +GLAPI void GLAPIENTRY glEvalPoint1( GLint i ); + +GLAPI void GLAPIENTRY glEvalPoint2( GLint i, GLint j ); + +GLAPI void GLAPIENTRY glEvalMesh1( GLenum mode, GLint i1, GLint i2 ); + +GLAPI void GLAPIENTRY glEvalMesh2( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 ); + + +/* + * Fog + */ + +GLAPI void GLAPIENTRY glFogf( GLenum pname, GLfloat param ); + +GLAPI void GLAPIENTRY glFogi( GLenum pname, GLint param ); + +GLAPI void GLAPIENTRY glFogfv( GLenum pname, const GLfloat *params ); + +GLAPI void GLAPIENTRY glFogiv( GLenum pname, const GLint *params ); + + +/* + * Selection and Feedback + */ + +GLAPI void GLAPIENTRY glFeedbackBuffer( GLsizei size, GLenum type, GLfloat *buffer ); + +GLAPI void GLAPIENTRY glPassThrough( GLfloat token ); + +GLAPI void GLAPIENTRY glSelectBuffer( GLsizei size, GLuint *buffer ); + +GLAPI void GLAPIENTRY glInitNames( void ); + +GLAPI void GLAPIENTRY glLoadName( GLuint name ); + +GLAPI void GLAPIENTRY glPushName( GLuint name ); + +GLAPI void GLAPIENTRY glPopName( void ); + + + +/* + * OpenGL 1.2 + */ + +#define GL_RESCALE_NORMAL 0x803A +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A + +GLAPI void GLAPIENTRY glDrawRangeElements( GLenum mode, GLuint start, + GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); + +GLAPI void GLAPIENTRY glTexImage3D( GLenum target, GLint level, + GLint internalFormat, + GLsizei width, GLsizei height, + GLsizei depth, GLint border, + GLenum format, GLenum type, + const GLvoid *pixels ); + +GLAPI void GLAPIENTRY glTexSubImage3D( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, + GLenum format, + GLenum type, const GLvoid *pixels); + +GLAPI void GLAPIENTRY glCopyTexSubImage3D( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLint zoffset, GLint x, + GLint y, GLsizei width, + GLsizei height ); + +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); + + +/* + * GL_ARB_imaging + */ + +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_BLEND_EQUATION 0x8009 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_BLEND_COLOR 0x8005 + + +GLAPI void GLAPIENTRY glColorTable( GLenum target, GLenum internalformat, + GLsizei width, GLenum format, + GLenum type, const GLvoid *table ); + +GLAPI void GLAPIENTRY glColorSubTable( GLenum target, + GLsizei start, GLsizei count, + GLenum format, GLenum type, + const GLvoid *data ); + +GLAPI void GLAPIENTRY glColorTableParameteriv(GLenum target, GLenum pname, + const GLint *params); + +GLAPI void GLAPIENTRY glColorTableParameterfv(GLenum target, GLenum pname, + const GLfloat *params); + +GLAPI void GLAPIENTRY glCopyColorSubTable( GLenum target, GLsizei start, + GLint x, GLint y, GLsizei width ); + +GLAPI void GLAPIENTRY glCopyColorTable( GLenum target, GLenum internalformat, + GLint x, GLint y, GLsizei width ); + +GLAPI void GLAPIENTRY glGetColorTable( GLenum target, GLenum format, + GLenum type, GLvoid *table ); + +GLAPI void GLAPIENTRY glGetColorTableParameterfv( GLenum target, GLenum pname, + GLfloat *params ); + +GLAPI void GLAPIENTRY glGetColorTableParameteriv( GLenum target, GLenum pname, + GLint *params ); + +GLAPI void GLAPIENTRY glBlendEquation( GLenum mode ); + +GLAPI void GLAPIENTRY glBlendColor( GLclampf red, GLclampf green, + GLclampf blue, GLclampf alpha ); + +GLAPI void GLAPIENTRY glHistogram( GLenum target, GLsizei width, + GLenum internalformat, GLboolean sink ); + +GLAPI void GLAPIENTRY glResetHistogram( GLenum target ); + +GLAPI void GLAPIENTRY glGetHistogram( GLenum target, GLboolean reset, + GLenum format, GLenum type, + GLvoid *values ); + +GLAPI void GLAPIENTRY glGetHistogramParameterfv( GLenum target, GLenum pname, + GLfloat *params ); + +GLAPI void GLAPIENTRY glGetHistogramParameteriv( GLenum target, GLenum pname, + GLint *params ); + +GLAPI void GLAPIENTRY glMinmax( GLenum target, GLenum internalformat, + GLboolean sink ); + +GLAPI void GLAPIENTRY glResetMinmax( GLenum target ); + +GLAPI void GLAPIENTRY glGetMinmax( GLenum target, GLboolean reset, + GLenum format, GLenum types, + GLvoid *values ); + +GLAPI void GLAPIENTRY glGetMinmaxParameterfv( GLenum target, GLenum pname, + GLfloat *params ); + +GLAPI void GLAPIENTRY glGetMinmaxParameteriv( GLenum target, GLenum pname, + GLint *params ); + +GLAPI void GLAPIENTRY glConvolutionFilter1D( GLenum target, + GLenum internalformat, GLsizei width, GLenum format, GLenum type, + const GLvoid *image ); + +GLAPI void GLAPIENTRY glConvolutionFilter2D( GLenum target, + GLenum internalformat, GLsizei width, GLsizei height, GLenum format, + GLenum type, const GLvoid *image ); + +GLAPI void GLAPIENTRY glConvolutionParameterf( GLenum target, GLenum pname, + GLfloat params ); + +GLAPI void GLAPIENTRY glConvolutionParameterfv( GLenum target, GLenum pname, + const GLfloat *params ); + +GLAPI void GLAPIENTRY glConvolutionParameteri( GLenum target, GLenum pname, + GLint params ); + +GLAPI void GLAPIENTRY glConvolutionParameteriv( GLenum target, GLenum pname, + const GLint *params ); + +GLAPI void GLAPIENTRY glCopyConvolutionFilter1D( GLenum target, + GLenum internalformat, GLint x, GLint y, GLsizei width ); + +GLAPI void GLAPIENTRY glCopyConvolutionFilter2D( GLenum target, + GLenum internalformat, GLint x, GLint y, GLsizei width, + GLsizei height); + +GLAPI void GLAPIENTRY glGetConvolutionFilter( GLenum target, GLenum format, + GLenum type, GLvoid *image ); + +GLAPI void GLAPIENTRY glGetConvolutionParameterfv( GLenum target, GLenum pname, + GLfloat *params ); + +GLAPI void GLAPIENTRY glGetConvolutionParameteriv( GLenum target, GLenum pname, + GLint *params ); + +GLAPI void GLAPIENTRY glSeparableFilter2D( GLenum target, + GLenum internalformat, GLsizei width, GLsizei height, GLenum format, + GLenum type, const GLvoid *row, const GLvoid *column ); + +GLAPI void GLAPIENTRY glGetSeparableFilter( GLenum target, GLenum format, + GLenum type, GLvoid *row, GLvoid *column, GLvoid *span ); + + + + +/* + * OpenGL 1.3 + */ + +/* multitexture */ +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +/* texture_cube_map */ +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +/* texture_compression */ +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +/* multisample */ +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_MULTISAMPLE_BIT 0x20000000 +/* transpose_matrix */ +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +/* texture_env_combine */ +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +/* texture_env_dot3 */ +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +/* texture_border_clamp */ +#define GL_CLAMP_TO_BORDER 0x812D + +GLAPI void GLAPIENTRY glActiveTexture( GLenum texture ); + +GLAPI void GLAPIENTRY glClientActiveTexture( GLenum texture ); + +GLAPI void GLAPIENTRY glCompressedTexImage1D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data ); + +GLAPI void GLAPIENTRY glCompressedTexImage2D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data ); + +GLAPI void GLAPIENTRY glCompressedTexImage3D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data ); + +GLAPI void GLAPIENTRY glCompressedTexSubImage1D( GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data ); + +GLAPI void GLAPIENTRY glCompressedTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data ); + +GLAPI void GLAPIENTRY glCompressedTexSubImage3D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data ); + +GLAPI void GLAPIENTRY glGetCompressedTexImage( GLenum target, GLint lod, GLvoid *img ); + +GLAPI void GLAPIENTRY glMultiTexCoord1d( GLenum target, GLdouble s ); + +GLAPI void GLAPIENTRY glMultiTexCoord1dv( GLenum target, const GLdouble *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord1f( GLenum target, GLfloat s ); + +GLAPI void GLAPIENTRY glMultiTexCoord1fv( GLenum target, const GLfloat *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord1i( GLenum target, GLint s ); + +GLAPI void GLAPIENTRY glMultiTexCoord1iv( GLenum target, const GLint *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord1s( GLenum target, GLshort s ); + +GLAPI void GLAPIENTRY glMultiTexCoord1sv( GLenum target, const GLshort *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord2d( GLenum target, GLdouble s, GLdouble t ); + +GLAPI void GLAPIENTRY glMultiTexCoord2dv( GLenum target, const GLdouble *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord2f( GLenum target, GLfloat s, GLfloat t ); + +GLAPI void GLAPIENTRY glMultiTexCoord2fv( GLenum target, const GLfloat *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord2i( GLenum target, GLint s, GLint t ); + +GLAPI void GLAPIENTRY glMultiTexCoord2iv( GLenum target, const GLint *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord2s( GLenum target, GLshort s, GLshort t ); + +GLAPI void GLAPIENTRY glMultiTexCoord2sv( GLenum target, const GLshort *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord3d( GLenum target, GLdouble s, GLdouble t, GLdouble r ); + +GLAPI void GLAPIENTRY glMultiTexCoord3dv( GLenum target, const GLdouble *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord3f( GLenum target, GLfloat s, GLfloat t, GLfloat r ); + +GLAPI void GLAPIENTRY glMultiTexCoord3fv( GLenum target, const GLfloat *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord3i( GLenum target, GLint s, GLint t, GLint r ); + +GLAPI void GLAPIENTRY glMultiTexCoord3iv( GLenum target, const GLint *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord3s( GLenum target, GLshort s, GLshort t, GLshort r ); + +GLAPI void GLAPIENTRY glMultiTexCoord3sv( GLenum target, const GLshort *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord4d( GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q ); + +GLAPI void GLAPIENTRY glMultiTexCoord4dv( GLenum target, const GLdouble *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord4f( GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q ); + +GLAPI void GLAPIENTRY glMultiTexCoord4fv( GLenum target, const GLfloat *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord4i( GLenum target, GLint s, GLint t, GLint r, GLint q ); + +GLAPI void GLAPIENTRY glMultiTexCoord4iv( GLenum target, const GLint *v ); + +GLAPI void GLAPIENTRY glMultiTexCoord4s( GLenum target, GLshort s, GLshort t, GLshort r, GLshort q ); + +GLAPI void GLAPIENTRY glMultiTexCoord4sv( GLenum target, const GLshort *v ); + + +GLAPI void GLAPIENTRY glLoadTransposeMatrixd( const GLdouble m[16] ); + +GLAPI void GLAPIENTRY glLoadTransposeMatrixf( const GLfloat m[16] ); + +GLAPI void GLAPIENTRY glMultTransposeMatrixd( const GLdouble m[16] ); + +GLAPI void GLAPIENTRY glMultTransposeMatrixf( const GLfloat m[16] ); + +GLAPI void GLAPIENTRY glSampleCoverage( GLclampf value, GLboolean invert ); + + +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); + + + +/* + * GL_ARB_multitexture (ARB extension 1 and OpenGL 1.2.1) + */ +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +GLAPI void GLAPIENTRY glActiveTextureARB(GLenum texture); +GLAPI void GLAPIENTRY glClientActiveTextureARB(GLenum texture); +GLAPI void GLAPIENTRY glMultiTexCoord1dARB(GLenum target, GLdouble s); +GLAPI void GLAPIENTRY glMultiTexCoord1dvARB(GLenum target, const GLdouble *v); +GLAPI void GLAPIENTRY glMultiTexCoord1fARB(GLenum target, GLfloat s); +GLAPI void GLAPIENTRY glMultiTexCoord1fvARB(GLenum target, const GLfloat *v); +GLAPI void GLAPIENTRY glMultiTexCoord1iARB(GLenum target, GLint s); +GLAPI void GLAPIENTRY glMultiTexCoord1ivARB(GLenum target, const GLint *v); +GLAPI void GLAPIENTRY glMultiTexCoord1sARB(GLenum target, GLshort s); +GLAPI void GLAPIENTRY glMultiTexCoord1svARB(GLenum target, const GLshort *v); +GLAPI void GLAPIENTRY glMultiTexCoord2dARB(GLenum target, GLdouble s, GLdouble t); +GLAPI void GLAPIENTRY glMultiTexCoord2dvARB(GLenum target, const GLdouble *v); +GLAPI void GLAPIENTRY glMultiTexCoord2fARB(GLenum target, GLfloat s, GLfloat t); +GLAPI void GLAPIENTRY glMultiTexCoord2fvARB(GLenum target, const GLfloat *v); +GLAPI void GLAPIENTRY glMultiTexCoord2iARB(GLenum target, GLint s, GLint t); +GLAPI void GLAPIENTRY glMultiTexCoord2ivARB(GLenum target, const GLint *v); +GLAPI void GLAPIENTRY glMultiTexCoord2sARB(GLenum target, GLshort s, GLshort t); +GLAPI void GLAPIENTRY glMultiTexCoord2svARB(GLenum target, const GLshort *v); +GLAPI void GLAPIENTRY glMultiTexCoord3dARB(GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void GLAPIENTRY glMultiTexCoord3dvARB(GLenum target, const GLdouble *v); +GLAPI void GLAPIENTRY glMultiTexCoord3fARB(GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void GLAPIENTRY glMultiTexCoord3fvARB(GLenum target, const GLfloat *v); +GLAPI void GLAPIENTRY glMultiTexCoord3iARB(GLenum target, GLint s, GLint t, GLint r); +GLAPI void GLAPIENTRY glMultiTexCoord3ivARB(GLenum target, const GLint *v); +GLAPI void GLAPIENTRY glMultiTexCoord3sARB(GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void GLAPIENTRY glMultiTexCoord3svARB(GLenum target, const GLshort *v); +GLAPI void GLAPIENTRY glMultiTexCoord4dARB(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void GLAPIENTRY glMultiTexCoord4dvARB(GLenum target, const GLdouble *v); +GLAPI void GLAPIENTRY glMultiTexCoord4fARB(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void GLAPIENTRY glMultiTexCoord4fvARB(GLenum target, const GLfloat *v); +GLAPI void GLAPIENTRY glMultiTexCoord4iARB(GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void GLAPIENTRY glMultiTexCoord4ivARB(GLenum target, const GLint *v); +GLAPI void GLAPIENTRY glMultiTexCoord4sARB(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void GLAPIENTRY glMultiTexCoord4svARB(GLenum target, const GLshort *v); + +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); + +#endif /* GL_ARB_multitexture */ + + + +/* + * Define this token if you want "old-style" header file behaviour (extensions + * defined in gl.h). Otherwise, extensions will be included from glext.h. + */ +#if !defined(NO_SDL_GLEXT) && !defined(GL_GLEXT_LEGACY) +#include "SDL_opengl_glext.h" +#endif /* GL_GLEXT_LEGACY */ + + + +/********************************************************************** + * Begin system-specific stuff + */ +#if defined(PRAGMA_EXPORT_SUPPORTED) +#pragma export off +#endif + +/* + * End system-specific stuff + **********************************************************************/ + + +#ifdef __cplusplus +} +#endif + +#endif /* __gl_h_ */ + +#endif /* !__IPHONEOS__ */ + +#endif /* SDL_opengl_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengl_glext.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengl_glext.h new file mode 100644 index 00000000..ff6ad12c --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengl_glext.h @@ -0,0 +1,13213 @@ +/* SDL modified the include guard to be compatible with Mesa and Apple include guards: + * - Mesa uses: __gl_glext_h_ + * - Apple uses: __glext_h_ */ +#if !defined(__glext_h_) && !defined(__gl_glext_h_) +#define __glext_h_ 1 +#define __gl_glext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +#define GL_GLEXT_VERSION 20220530 + +/*#include */ +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Khronos platform-specific types and definitions. + * + * The master copy of khrplatform.h is maintained in the Khronos EGL + * Registry repository at https://github.com/KhronosGroup/EGL-Registry + * The last semantic modification to khrplatform.h was at commit ID: + * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 + * + * Adopters may modify this file to suit their platform. Adopters are + * encouraged to submit platform specific modifications to the Khronos + * group so that they can be included in future versions of this file. + * Please submit changes by filing pull requests or issues on + * the EGL Registry repository linked above. + * + * + * See the Implementer's Guidelines for information about where this file + * should be located on your system and for more details of its use: + * http://www.khronos.org/registry/implementers_guide.pdf + * + * This file should be included as + * #include + * by Khronos client API header files that use its types and defines. + * + * The types in khrplatform.h should only be used to define API-specific types. + * + * Types defined in khrplatform.h: + * khronos_int8_t signed 8 bit + * khronos_uint8_t unsigned 8 bit + * khronos_int16_t signed 16 bit + * khronos_uint16_t unsigned 16 bit + * khronos_int32_t signed 32 bit + * khronos_uint32_t unsigned 32 bit + * khronos_int64_t signed 64 bit + * khronos_uint64_t unsigned 64 bit + * khronos_intptr_t signed same number of bits as a pointer + * khronos_uintptr_t unsigned same number of bits as a pointer + * khronos_ssize_t signed size + * khronos_usize_t unsigned size + * khronos_float_t signed 32 bit floating point + * khronos_time_ns_t unsigned 64 bit time in nanoseconds + * khronos_utime_nanoseconds_t unsigned time interval or absolute time in + * nanoseconds + * khronos_stime_nanoseconds_t signed time interval in nanoseconds + * khronos_boolean_enum_t enumerated boolean type. This should + * only be used as a base type when a client API's boolean type is + * an enum. Client APIs which use an integer or other type for + * booleans cannot use this as the base type for their boolean. + * + * Tokens defined in khrplatform.h: + * + * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. + * + * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. + * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. + * + * Calling convention macros defined in this file: + * KHRONOS_APICALL + * KHRONOS_APIENTRY + * KHRONOS_APIATTRIBUTES + * + * These may be used in function prototypes as: + * + * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( + * int arg1, + * int arg2) KHRONOS_APIATTRIBUTES; + */ + +#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) +# define KHRONOS_STATIC 1 +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APICALL + *------------------------------------------------------------------------- + * This precedes the return type of the function in the function prototype. + */ +#if defined(KHRONOS_STATIC) + /* If the preprocessor constant KHRONOS_STATIC is defined, make the + * header compatible with static linking. */ +# define KHRONOS_APICALL +#elif defined(_WIN32) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIENTRY + *------------------------------------------------------------------------- + * This follows the return type of the function and precedes the function + * name in the function prototype. + */ +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) + /* Win32 but not WinCE */ +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIATTRIBUTES + *------------------------------------------------------------------------- + * This follows the closing parenthesis of the function prototype arguments. + */ +#if defined (__ARMCC_2__) +#define KHRONOS_APIATTRIBUTES __softfp +#else +#define KHRONOS_APIATTRIBUTES +#endif + +/*------------------------------------------------------------------------- + * basic type definitions + *-----------------------------------------------------------------------*/ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) + + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 +/* + * To support platform where unsigned long cannot be used interchangeably with + * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. + * Ideally, we could just use (u)intptr_t everywhere, but this could result in + * ABI breakage if khronos_uintptr_t is changed from unsigned long to + * unsigned long long or similar (this results in different C++ name mangling). + * To avoid changes for existing platforms, we restrict usage of intptr_t to + * platforms where the size of a pointer is larger than the size of long. + */ +#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) +#if __SIZEOF_POINTER__ > __SIZEOF_LONG__ +#define KHRONOS_USE_INTPTR_T +#endif +#endif + +#elif defined(__VMS ) || defined(__sgi) + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) + +/* + * Win32 + */ +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) + +/* + * Sun or Digital + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif /* __arch64__ */ +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 + +/* + * Hypothetical platform with no float or int64 support + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#define KHRONOS_SUPPORT_INT64 0 +#define KHRONOS_SUPPORT_FLOAT 0 + +#else + +/* + * Generic fallback + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#endif + + +/* + * Types that are (so far) the same on all platforms + */ +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +/* + * Types that differ between LLP64 and LP64 architectures - in LLP64, + * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears + * to be the only LLP64 architecture in current use. + */ +#ifdef KHRONOS_USE_INTPTR_T +typedef intptr_t khronos_intptr_t; +typedef uintptr_t khronos_uintptr_t; +#elif defined(_WIN64) +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +#endif + +#if defined(_WIN64) +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + +#if KHRONOS_SUPPORT_FLOAT +/* + * Float type + */ +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +/* Time types + * + * These types can be used to represent a time interval in nanoseconds or + * an absolute Unadjusted System Time. Unadjusted System Time is the number + * of nanoseconds since some arbitrary system event (e.g. since the last + * time the system booted). The Unadjusted System Time is an unsigned + * 64 bit value that wraps back to 0 every 584 years. Time intervals + * may be either signed or unsigned. + */ +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +/* + * Dummy value used to pad enum types to 32 bits. + */ +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +/* + * Enumerated boolean type + * + * Values other than zero should be considered to be true. Therefore + * comparisons should not be made against KHRONOS_TRUE. + */ +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +#endif /* __khrplatform_h_ */ + +/* Generated C header for: + * API: gl + * Profile: compatibility + * Versions considered: .* + * Versions emitted: 1\.[2-9]|[234]\.[0-9] + * Default extensions included: gl + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_RESCALE_NORMAL 0x803A +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_VERSION_1_2 */ + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLfloat value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, void *img); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +GLAPI void APIENTRY glSampleCoverage (GLfloat value, GLboolean invert); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum target, GLint level, void *img); +GLAPI void APIENTRY glClientActiveTexture (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *m); +#endif +#endif /* GL_VERSION_1_3 */ + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#define GL_BLEND_COLOR 0x8005 +#define GL_BLEND_EQUATION 0x8009 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_FUNC_SUBTRACT 0x800A +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glMultiDrawArrays (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +GLAPI void APIENTRY glMultiDrawElements (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +GLAPI void APIENTRY glPointParameterf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glPointParameteri (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameteriv (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFogCoordf (GLfloat coord); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *coord); +GLAPI void APIENTRY glFogCoordd (GLdouble coord); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointer (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3i (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glWindowPos2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2i (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2iv (const GLint *v); +GLAPI void APIENTRY glWindowPos2s (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *v); +GLAPI void APIENTRY glWindowPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3iv (const GLint *v); +GLAPI void APIENTRY glWindowPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *v); +GLAPI void APIENTRY glBlendColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +#endif +#endif /* GL_VERSION_1_4 */ + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_SRC1_ALPHA 0x8589 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_FOG_COORD_SRC 0x8450 +#define GL_FOG_COORD 0x8451 +#define GL_CURRENT_FOG_COORD 0x8453 +#define GL_FOG_COORD_ARRAY_TYPE 0x8454 +#define GL_FOG_COORD_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORD_ARRAY_POINTER 0x8456 +#define GL_FOG_COORD_ARRAY 0x8457 +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D +#define GL_SRC0_RGB 0x8580 +#define GL_SRC1_RGB 0x8581 +#define GL_SRC2_RGB 0x8582 +#define GL_SRC0_ALPHA 0x8588 +#define GL_SRC2_ALPHA 0x858A +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, void *data); +typedef void *(APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQuery (GLuint id); +GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQuery (GLenum target); +GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint buffer); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glGetBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void *APIENTRY glMapBuffer (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum target); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointerv (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_VERSION_1_5 */ + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +typedef char GLchar; +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_TEXTURE_COORDS 0x8871 +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glDrawBuffers (GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI GLboolean APIENTRY glIsShader (GLuint shader); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glValidateProgram (GLuint program); +GLAPI void APIENTRY glVertexAttrib1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1s (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2s (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3s (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4s (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#endif +#endif /* GL_VERSION_2_0 */ + +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif +#endif /* GL_VERSION_2_1 */ + +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 +typedef khronos_uint16_t GLhalf; +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_COLOR_ATTACHMENT16 0x8CF0 +#define GL_COLOR_ATTACHMENT17 0x8CF1 +#define GL_COLOR_ATTACHMENT18 0x8CF2 +#define GL_COLOR_ATTACHMENT19 0x8CF3 +#define GL_COLOR_ATTACHMENT20 0x8CF4 +#define GL_COLOR_ATTACHMENT21 0x8CF5 +#define GL_COLOR_ATTACHMENT22 0x8CF6 +#define GL_COLOR_ATTACHMENT23 0x8CF7 +#define GL_COLOR_ATTACHMENT24 0x8CF8 +#define GL_COLOR_ATTACHMENT25 0x8CF9 +#define GL_COLOR_ATTACHMENT26 0x8CFA +#define GL_COLOR_ATTACHMENT27 0x8CFB +#define GL_COLOR_ATTACHMENT28 0x8CFC +#define GL_COLOR_ATTACHMENT29 0x8CFD +#define GL_COLOR_ATTACHMENT30 0x8CFE +#define GL_COLOR_ATTACHMENT31 0x8CFF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#define GL_INDEX 0x8222 +#define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE 0x8C15 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_HALF_FLOAT 0x140B +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#define GL_CLAMP_VERTEX_COLOR 0x891A +#define GL_CLAMP_FRAGMENT_COLOR 0x891B +#define GL_ALPHA_INTEGER 0x8D97 +typedef void (APIENTRYP PFNGLCOLORMASKIPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEIPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLCLAMPCOLORPROC) (GLenum target, GLenum clamp); +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint index, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void *(APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaski (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleani_v (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegeri_v (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnablei (GLenum target, GLuint index); +GLAPI void APIENTRY glDisablei (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledi (GLenum target, GLuint index); +GLAPI void APIENTRY glBeginTransformFeedback (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedback (void); +GLAPI void APIENTRY glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferBase (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryings (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVarying (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glClampColor (GLenum target, GLenum clamp); +GLAPI void APIENTRY glBeginConditionalRender (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRender (void); +GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribIiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuiv (GLuint index, GLenum pname, GLuint *params); +GLAPI void APIENTRY glVertexAttribI1i (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2i (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3i (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4i (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1ui (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2ui (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3ui (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4ui (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glGetUniformuiv (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocation (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1ui (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glTexParameterIiv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuiv (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuiv (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearBufferiv (GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearBufferuiv (GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearBufferfv (GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearBufferfi (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index); +GLAPI GLboolean APIENTRY glIsRenderbuffer (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebuffer (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatus (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmap (GLenum target); +GLAPI void APIENTRY glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glRenderbufferStorageMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glFramebufferTextureLayer (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void *APIENTRY glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArray (GLuint array); +#endif +#endif /* GL_VERSION_3_0 */ + +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFFu +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +typedef void (APIENTRYP PFNGLTEXBUFFERPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC) (GLuint index); +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +GLAPI void APIENTRY glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +GLAPI void APIENTRY glTexBuffer (GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glPrimitiveRestartIndex (GLuint index); +GLAPI void APIENTRY glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glGetUniformIndices (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +GLAPI void APIENTRY glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformName (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +GLAPI GLuint APIENTRY glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName); +GLAPI void APIENTRY glGetActiveUniformBlockiv (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformBlockName (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +GLAPI void APIENTRY glUniformBlockBinding (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif +#endif /* GL_VERSION_3_1 */ + +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +typedef struct __GLsync *GLsync; +typedef khronos_uint64_t GLuint64; +typedef khronos_int64_t GLint64; +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +#define GL_DEPTH_CLAMP 0x864F +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC) (GLenum mode); +typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags); +typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync); +typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync); +typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *data); +typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC) (GLenum target, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC) (GLuint maskNumber, GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI void APIENTRY glDrawRangeElementsBaseVertex (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +GLAPI void APIENTRY glMultiDrawElementsBaseVertex (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +GLAPI void APIENTRY glProvokingVertex (GLenum mode); +GLAPI GLsync APIENTRY glFenceSync (GLenum condition, GLbitfield flags); +GLAPI GLboolean APIENTRY glIsSync (GLsync sync); +GLAPI void APIENTRY glDeleteSync (GLsync sync); +GLAPI GLenum APIENTRY glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glGetInteger64v (GLenum pname, GLint64 *data); +GLAPI void APIENTRY glGetSynciv (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +GLAPI void APIENTRY glGetInteger64i_v (GLenum target, GLuint index, GLint64 *data); +GLAPI void APIENTRY glGetBufferParameteri64v (GLenum target, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glFramebufferTexture (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glTexImage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexImage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glGetMultisamplefv (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaski (GLuint maskNumber, GLbitfield mask); +#endif +#endif /* GL_VERSION_3_2 */ + +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#define GL_SAMPLER_BINDING 0x8919 +#define GL_RGB10_A2UI 0x906F +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#define GL_INT_2_10_10_10_REV 0x8D9F +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGENSAMPLERSPROC) (GLsizei count, GLuint *samplers); +typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC) (GLsizei count, const GLuint *samplers); +typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC) (GLuint sampler); +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC) (GLuint sampler, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC) (GLuint sampler, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64 *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC) (GLuint index, GLuint divisor); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP2UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP3UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP4UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLNORMALP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLNORMALP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLCOLORP4UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP4UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC) (GLenum type, const GLuint *color); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindFragDataLocationIndexed (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataIndex (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGenSamplers (GLsizei count, GLuint *samplers); +GLAPI void APIENTRY glDeleteSamplers (GLsizei count, const GLuint *samplers); +GLAPI GLboolean APIENTRY glIsSampler (GLuint sampler); +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +GLAPI void APIENTRY glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); +GLAPI void APIENTRY glSamplerParameteriv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); +GLAPI void APIENTRY glSamplerParameterfv (GLuint sampler, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glSamplerParameterIiv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterIuiv (GLuint sampler, GLenum pname, const GLuint *param); +GLAPI void APIENTRY glGetSamplerParameteriv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterIiv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterfv (GLuint sampler, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetSamplerParameterIuiv (GLuint sampler, GLenum pname, GLuint *params); +GLAPI void APIENTRY glQueryCounter (GLuint id, GLenum target); +GLAPI void APIENTRY glGetQueryObjecti64v (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64v (GLuint id, GLenum pname, GLuint64 *params); +GLAPI void APIENTRY glVertexAttribDivisor (GLuint index, GLuint divisor); +GLAPI void APIENTRY glVertexAttribP1ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP1uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP2ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP2uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP3ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP3uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP4ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP4uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexP2ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP2uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP3ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP3uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP4ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP4uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glTexCoordP1ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP1uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP2ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP2uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP4ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP4uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP1ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP1uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP2ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP2uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP3ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP3uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP4ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP4uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glNormalP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glNormalP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glColorP4ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP4uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glSecondaryColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glSecondaryColorP3uiv (GLenum type, const GLuint *color); +#endif +#endif /* GL_VERSION_3_3 */ + +#ifndef GL_VERSION_4_0 +#define GL_VERSION_4_0 1 +#define GL_SAMPLE_SHADING 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F +#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C +#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D +#define GL_MAX_VERTEX_STREAMS 0x8E71 +#define GL_DOUBLE_VEC2 0x8FFC +#define GL_DOUBLE_VEC3 0x8FFD +#define GL_DOUBLE_VEC4 0x8FFE +#define GL_DOUBLE_MAT2 0x8F46 +#define GL_DOUBLE_MAT3 0x8F47 +#define GL_DOUBLE_MAT4 0x8F48 +#define GL_DOUBLE_MAT2x3 0x8F49 +#define GL_DOUBLE_MAT2x4 0x8F4A +#define GL_DOUBLE_MAT3x2 0x8F4B +#define GL_DOUBLE_MAT3x4 0x8F4C +#define GL_DOUBLE_MAT4x2 0x8F4D +#define GL_DOUBLE_MAT4x3 0x8F4E +#define GL_ACTIVE_SUBROUTINES 0x8DE5 +#define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 +#define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 +#define GL_MAX_SUBROUTINES 0x8DE7 +#define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 +#define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A +#define GL_COMPATIBLE_SUBROUTINES 0x8E4B +#define GL_PATCHES 0x000E +#define GL_PATCH_VERTICES 0x8E72 +#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 +#define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 +#define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 +#define GL_TESS_GEN_MODE 0x8E76 +#define GL_TESS_GEN_SPACING 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER 0x8E78 +#define GL_TESS_GEN_POINT_MODE 0x8E79 +#define GL_ISOLINES 0x8E7A +#define GL_FRACTIONAL_ODD 0x8E7B +#define GL_FRACTIONAL_EVEN 0x8E7C +#define GL_MAX_PATCH_VERTICES 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 +#define GL_TESS_EVALUATION_SHADER 0x8E87 +#define GL_TESS_CONTROL_SHADER 0x8E88 +#define GL_TRANSFORM_FEEDBACK 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 +#define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGPROC) (GLfloat value); +typedef void (APIENTRYP PFNGLBLENDEQUATIONIPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect); +typedef void (APIENTRYP PFNGLUNIFORM1DPROC) (GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLUNIFORM2DPROC) (GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLUNIFORM3DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLUNIFORM4DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLUNIFORM1DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM2DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM3DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM4DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLGETUNIFORMDVPROC) (GLuint program, GLint location, GLdouble *params); +typedef GLint (APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef GLuint (APIENTRYP PFNGLGETSUBROUTINEINDEXPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC) (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC) (GLenum shadertype, GLsizei count, const GLuint *indices); +typedef void (APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC) (GLenum shadertype, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC) (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLPATCHPARAMETERIPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATCHPARAMETERFVPROC) (GLenum pname, const GLfloat *values); +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC) (GLenum mode, GLuint id); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC) (GLenum mode, GLuint id, GLuint stream); +typedef void (APIENTRYP PFNGLBEGINQUERYINDEXEDPROC) (GLenum target, GLuint index, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYINDEXEDPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETQUERYINDEXEDIVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShading (GLfloat value); +GLAPI void APIENTRY glBlendEquationi (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparatei (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunci (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparatei (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glDrawArraysIndirect (GLenum mode, const void *indirect); +GLAPI void APIENTRY glDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect); +GLAPI void APIENTRY glUniform1d (GLint location, GLdouble x); +GLAPI void APIENTRY glUniform2d (GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glUniform3d (GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glUniform4d (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glUniform1dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform2dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform3dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform4dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glGetUniformdv (GLuint program, GLint location, GLdouble *params); +GLAPI GLint APIENTRY glGetSubroutineUniformLocation (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI GLuint APIENTRY glGetSubroutineIndex (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineUniformiv (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +GLAPI void APIENTRY glGetActiveSubroutineUniformName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glUniformSubroutinesuiv (GLenum shadertype, GLsizei count, const GLuint *indices); +GLAPI void APIENTRY glGetUniformSubroutineuiv (GLenum shadertype, GLint location, GLuint *params); +GLAPI void APIENTRY glGetProgramStageiv (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +GLAPI void APIENTRY glPatchParameteri (GLenum pname, GLint value); +GLAPI void APIENTRY glPatchParameterfv (GLenum pname, const GLfloat *values); +GLAPI void APIENTRY glBindTransformFeedback (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacks (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedback (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedback (void); +GLAPI void APIENTRY glResumeTransformFeedback (void); +GLAPI void APIENTRY glDrawTransformFeedback (GLenum mode, GLuint id); +GLAPI void APIENTRY glDrawTransformFeedbackStream (GLenum mode, GLuint id, GLuint stream); +GLAPI void APIENTRY glBeginQueryIndexed (GLenum target, GLuint index, GLuint id); +GLAPI void APIENTRY glEndQueryIndexed (GLenum target, GLuint index); +GLAPI void APIENTRY glGetQueryIndexediv (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif +#endif /* GL_VERSION_4_0 */ + +#ifndef GL_VERSION_4_1 +#define GL_VERSION_4_1 1 +#define GL_FIXED 0x140C +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_SHADER_COMPILER 0x8DFA +#define GL_SHADER_BINARY_FORMATS 0x8DF8 +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#define GL_RGB565 0x8D62 +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#define GL_VERTEX_SHADER_BIT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT 0x00000002 +#define GL_GEOMETRY_SHADER_BIT 0x00000004 +#define GL_TESS_CONTROL_SHADER_BIT 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 +#define GL_ALL_SHADER_BITS 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE 0x8258 +#define GL_ACTIVE_PROGRAM 0x8259 +#define GL_PROGRAM_PIPELINE_BINDING 0x825A +#define GL_MAX_VIEWPORTS 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE 0x825D +#define GL_LAYER_PROVOKING_VERTEX 0x825E +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F +#define GL_UNDEFINED_VERTEX 0x8260 +typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); +typedef void (APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC) (GLfloat n, GLfloat f); +typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC) (GLfloat d); +typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC) (GLuint pipeline, GLbitfield stages, GLuint program); +typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC) (GLuint pipeline, GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC) (GLenum type, GLsizei count, const GLchar *const*strings); +typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC) (GLsizei n, const GLuint *pipelines); +typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC) (GLuint pipeline, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DPROC) (GLuint program, GLint location, GLdouble v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLVIEWPORTARRAYVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLSCISSORARRAYVPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYVPROC) (GLuint first, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC) (GLuint index, GLdouble n, GLdouble f); +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReleaseShaderCompiler (void); +GLAPI void APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI void APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GLAPI void APIENTRY glDepthRangef (GLfloat n, GLfloat f); +GLAPI void APIENTRY glClearDepthf (GLfloat d); +GLAPI void APIENTRY glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +GLAPI void APIENTRY glProgramBinary (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI void APIENTRY glProgramParameteri (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glUseProgramStages (GLuint pipeline, GLbitfield stages, GLuint program); +GLAPI void APIENTRY glActiveShaderProgram (GLuint pipeline, GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramv (GLenum type, GLsizei count, const GLchar *const*strings); +GLAPI void APIENTRY glBindProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glDeleteProgramPipelines (GLsizei n, const GLuint *pipelines); +GLAPI void APIENTRY glGenProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI GLboolean APIENTRY glIsProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineiv (GLuint pipeline, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1i (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform1iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform1f (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform1fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1d (GLuint program, GLint location, GLdouble v0); +GLAPI void APIENTRY glProgramUniform1dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform1ui (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform1uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2i (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform2iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2f (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform2fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2d (GLuint program, GLint location, GLdouble v0, GLdouble v1); +GLAPI void APIENTRY glProgramUniform2dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2ui (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform2uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform3iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform3fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +GLAPI void APIENTRY glProgramUniform3dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform3uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform4iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform4fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +GLAPI void APIENTRY glProgramUniform4dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform4uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glValidateProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineInfoLog (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glVertexAttribL1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribLdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glViewportArrayv (GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glViewportIndexedf (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GLAPI void APIENTRY glViewportIndexedfv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glScissorArrayv (GLuint first, GLsizei count, const GLint *v); +GLAPI void APIENTRY glScissorIndexed (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorIndexedv (GLuint index, const GLint *v); +GLAPI void APIENTRY glDepthRangeArrayv (GLuint first, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glDepthRangeIndexed (GLuint index, GLdouble n, GLdouble f); +GLAPI void APIENTRY glGetFloati_v (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoublei_v (GLenum target, GLuint index, GLdouble *data); +#endif +#endif /* GL_VERSION_4_1 */ + +#ifndef GL_VERSION_4_2 +#define GL_VERSION_4_2 1 +#define GL_COPY_READ_BUFFER_BINDING 0x8F36 +#define GL_COPY_WRITE_BUFFER_BINDING 0x8F37 +#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23 +#define GL_UNPACK_COMPRESSED_BLOCK_WIDTH 0x9127 +#define GL_UNPACK_COMPRESSED_BLOCK_HEIGHT 0x9128 +#define GL_UNPACK_COMPRESSED_BLOCK_DEPTH 0x9129 +#define GL_UNPACK_COMPRESSED_BLOCK_SIZE 0x912A +#define GL_PACK_COMPRESSED_BLOCK_WIDTH 0x912B +#define GL_PACK_COMPRESSED_BLOCK_HEIGHT 0x912C +#define GL_PACK_COMPRESSED_BLOCK_DEPTH 0x912D +#define GL_PACK_COMPRESSED_BLOCK_SIZE 0x912E +#define GL_NUM_SAMPLE_COUNTS 0x9380 +#define GL_MIN_MAP_BUFFER_ALIGNMENT 0x90BC +#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 +#define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 +#define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 +#define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 +#define GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE 0x92C4 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS 0x92C5 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES 0x92C6 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER 0x92C7 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER 0x92C8 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER 0x92C9 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER 0x92CA +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER 0x92CB +#define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS 0x92CD +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS 0x92CE +#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS 0x92CF +#define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 +#define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 +#define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS 0x92D3 +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS 0x92D4 +#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS 0x92D5 +#define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 +#define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC +#define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 +#define GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX 0x92DA +#define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 +#define GL_COMMAND_BARRIER_BIT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 +#define GL_ALL_BARRIER_BITS 0xFFFFFFFF +#define GL_MAX_IMAGE_UNITS 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS 0x8F39 +#define GL_IMAGE_BINDING_NAME 0x8F3A +#define GL_IMAGE_BINDING_LEVEL 0x8F3B +#define GL_IMAGE_BINDING_LAYERED 0x8F3C +#define GL_IMAGE_BINDING_LAYER 0x8F3D +#define GL_IMAGE_BINDING_ACCESS 0x8F3E +#define GL_IMAGE_1D 0x904C +#define GL_IMAGE_2D 0x904D +#define GL_IMAGE_3D 0x904E +#define GL_IMAGE_2D_RECT 0x904F +#define GL_IMAGE_CUBE 0x9050 +#define GL_IMAGE_BUFFER 0x9051 +#define GL_IMAGE_1D_ARRAY 0x9052 +#define GL_IMAGE_2D_ARRAY 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY 0x9056 +#define GL_INT_IMAGE_1D 0x9057 +#define GL_INT_IMAGE_2D 0x9058 +#define GL_INT_IMAGE_3D 0x9059 +#define GL_INT_IMAGE_2D_RECT 0x905A +#define GL_INT_IMAGE_CUBE 0x905B +#define GL_INT_IMAGE_BUFFER 0x905C +#define GL_INT_IMAGE_1D_ARRAY 0x905D +#define GL_INT_IMAGE_2D_ARRAY 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x906C +#define GL_MAX_IMAGE_SAMPLES 0x906D +#define GL_IMAGE_BINDING_FORMAT 0x906E +#define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 +#define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA +#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS 0x90CB +#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS 0x90CC +#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS 0x90CD +#define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE +#define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF +#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC) (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +typedef void (APIENTRYP PFNGLMEMORYBARRIERPROC) (GLbitfield barriers); +typedef void (APIENTRYP PFNGLTEXSTORAGE1DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC) (GLenum mode, GLuint id, GLsizei instancecount); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC) (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedBaseInstance (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertexBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +GLAPI void APIENTRY glGetInternalformativ (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); +GLAPI void APIENTRY glGetActiveAtomicCounterBufferiv (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glBindImageTexture (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +GLAPI void APIENTRY glMemoryBarrier (GLbitfield barriers); +GLAPI void APIENTRY glTexStorage1D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTexStorage2D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexStorage3D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glDrawTransformFeedbackInstanced (GLenum mode, GLuint id, GLsizei instancecount); +GLAPI void APIENTRY glDrawTransformFeedbackStreamInstanced (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); +#endif +#endif /* GL_VERSION_4_2 */ + +#ifndef GL_VERSION_4_3 +#define GL_VERSION_4_3 1 +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_NUM_SHADING_LANGUAGE_VERSIONS 0x82E9 +#define GL_VERTEX_ATTRIB_ARRAY_LONG 0x874E +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 +#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A +#define GL_MAX_ELEMENT_INDEX 0x8D6B +#define GL_COMPUTE_SHADER 0x91B9 +#define GL_MAX_COMPUTE_UNIFORM_BLOCKS 0x91BB +#define GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS 0x91BC +#define GL_MAX_COMPUTE_IMAGE_UNIFORMS 0x91BD +#define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262 +#define GL_MAX_COMPUTE_UNIFORM_COMPONENTS 0x8263 +#define GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS 0x8264 +#define GL_MAX_COMPUTE_ATOMIC_COUNTERS 0x8265 +#define GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS 0x8266 +#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB +#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE +#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF +#define GL_COMPUTE_WORK_GROUP_SIZE 0x8267 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER 0x90EC +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER 0x90ED +#define GL_DISPATCH_INDIRECT_BUFFER 0x90EE +#define GL_DISPATCH_INDIRECT_BUFFER_BINDING 0x90EF +#define GL_COMPUTE_SHADER_BIT 0x00000020 +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 +#define GL_DEBUG_SOURCE_API 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION 0x824A +#define GL_DEBUG_SOURCE_OTHER 0x824B +#define GL_DEBUG_TYPE_ERROR 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +#define GL_DEBUG_TYPE_PORTABILITY 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE 0x8250 +#define GL_DEBUG_TYPE_OTHER 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES 0x9145 +#define GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM 0x9147 +#define GL_DEBUG_SEVERITY_LOW 0x9148 +#define GL_DEBUG_TYPE_MARKER 0x8268 +#define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 +#define GL_DEBUG_TYPE_POP_GROUP 0x826A +#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B +#define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C +#define GL_DEBUG_GROUP_STACK_DEPTH 0x826D +#define GL_BUFFER 0x82E0 +#define GL_SHADER 0x82E1 +#define GL_PROGRAM 0x82E2 +#define GL_QUERY 0x82E3 +#define GL_PROGRAM_PIPELINE 0x82E4 +#define GL_SAMPLER 0x82E6 +#define GL_MAX_LABEL_LENGTH 0x82E8 +#define GL_DEBUG_OUTPUT 0x92E0 +#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 +#define GL_MAX_UNIFORM_LOCATIONS 0x826E +#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 +#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 +#define GL_FRAMEBUFFER_DEFAULT_LAYERS 0x9312 +#define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 +#define GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 0x9314 +#define GL_MAX_FRAMEBUFFER_WIDTH 0x9315 +#define GL_MAX_FRAMEBUFFER_HEIGHT 0x9316 +#define GL_MAX_FRAMEBUFFER_LAYERS 0x9317 +#define GL_MAX_FRAMEBUFFER_SAMPLES 0x9318 +#define GL_INTERNALFORMAT_SUPPORTED 0x826F +#define GL_INTERNALFORMAT_PREFERRED 0x8270 +#define GL_INTERNALFORMAT_RED_SIZE 0x8271 +#define GL_INTERNALFORMAT_GREEN_SIZE 0x8272 +#define GL_INTERNALFORMAT_BLUE_SIZE 0x8273 +#define GL_INTERNALFORMAT_ALPHA_SIZE 0x8274 +#define GL_INTERNALFORMAT_DEPTH_SIZE 0x8275 +#define GL_INTERNALFORMAT_STENCIL_SIZE 0x8276 +#define GL_INTERNALFORMAT_SHARED_SIZE 0x8277 +#define GL_INTERNALFORMAT_RED_TYPE 0x8278 +#define GL_INTERNALFORMAT_GREEN_TYPE 0x8279 +#define GL_INTERNALFORMAT_BLUE_TYPE 0x827A +#define GL_INTERNALFORMAT_ALPHA_TYPE 0x827B +#define GL_INTERNALFORMAT_DEPTH_TYPE 0x827C +#define GL_INTERNALFORMAT_STENCIL_TYPE 0x827D +#define GL_MAX_WIDTH 0x827E +#define GL_MAX_HEIGHT 0x827F +#define GL_MAX_DEPTH 0x8280 +#define GL_MAX_LAYERS 0x8281 +#define GL_MAX_COMBINED_DIMENSIONS 0x8282 +#define GL_COLOR_COMPONENTS 0x8283 +#define GL_DEPTH_COMPONENTS 0x8284 +#define GL_STENCIL_COMPONENTS 0x8285 +#define GL_COLOR_RENDERABLE 0x8286 +#define GL_DEPTH_RENDERABLE 0x8287 +#define GL_STENCIL_RENDERABLE 0x8288 +#define GL_FRAMEBUFFER_RENDERABLE 0x8289 +#define GL_FRAMEBUFFER_RENDERABLE_LAYERED 0x828A +#define GL_FRAMEBUFFER_BLEND 0x828B +#define GL_READ_PIXELS 0x828C +#define GL_READ_PIXELS_FORMAT 0x828D +#define GL_READ_PIXELS_TYPE 0x828E +#define GL_TEXTURE_IMAGE_FORMAT 0x828F +#define GL_TEXTURE_IMAGE_TYPE 0x8290 +#define GL_GET_TEXTURE_IMAGE_FORMAT 0x8291 +#define GL_GET_TEXTURE_IMAGE_TYPE 0x8292 +#define GL_MIPMAP 0x8293 +#define GL_MANUAL_GENERATE_MIPMAP 0x8294 +#define GL_AUTO_GENERATE_MIPMAP 0x8295 +#define GL_COLOR_ENCODING 0x8296 +#define GL_SRGB_READ 0x8297 +#define GL_SRGB_WRITE 0x8298 +#define GL_FILTER 0x829A +#define GL_VERTEX_TEXTURE 0x829B +#define GL_TESS_CONTROL_TEXTURE 0x829C +#define GL_TESS_EVALUATION_TEXTURE 0x829D +#define GL_GEOMETRY_TEXTURE 0x829E +#define GL_FRAGMENT_TEXTURE 0x829F +#define GL_COMPUTE_TEXTURE 0x82A0 +#define GL_TEXTURE_SHADOW 0x82A1 +#define GL_TEXTURE_GATHER 0x82A2 +#define GL_TEXTURE_GATHER_SHADOW 0x82A3 +#define GL_SHADER_IMAGE_LOAD 0x82A4 +#define GL_SHADER_IMAGE_STORE 0x82A5 +#define GL_SHADER_IMAGE_ATOMIC 0x82A6 +#define GL_IMAGE_TEXEL_SIZE 0x82A7 +#define GL_IMAGE_COMPATIBILITY_CLASS 0x82A8 +#define GL_IMAGE_PIXEL_FORMAT 0x82A9 +#define GL_IMAGE_PIXEL_TYPE 0x82AA +#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_TEST 0x82AC +#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_TEST 0x82AD +#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_WRITE 0x82AE +#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_WRITE 0x82AF +#define GL_TEXTURE_COMPRESSED_BLOCK_WIDTH 0x82B1 +#define GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT 0x82B2 +#define GL_TEXTURE_COMPRESSED_BLOCK_SIZE 0x82B3 +#define GL_CLEAR_BUFFER 0x82B4 +#define GL_TEXTURE_VIEW 0x82B5 +#define GL_VIEW_COMPATIBILITY_CLASS 0x82B6 +#define GL_FULL_SUPPORT 0x82B7 +#define GL_CAVEAT_SUPPORT 0x82B8 +#define GL_IMAGE_CLASS_4_X_32 0x82B9 +#define GL_IMAGE_CLASS_2_X_32 0x82BA +#define GL_IMAGE_CLASS_1_X_32 0x82BB +#define GL_IMAGE_CLASS_4_X_16 0x82BC +#define GL_IMAGE_CLASS_2_X_16 0x82BD +#define GL_IMAGE_CLASS_1_X_16 0x82BE +#define GL_IMAGE_CLASS_4_X_8 0x82BF +#define GL_IMAGE_CLASS_2_X_8 0x82C0 +#define GL_IMAGE_CLASS_1_X_8 0x82C1 +#define GL_IMAGE_CLASS_11_11_10 0x82C2 +#define GL_IMAGE_CLASS_10_10_10_2 0x82C3 +#define GL_VIEW_CLASS_128_BITS 0x82C4 +#define GL_VIEW_CLASS_96_BITS 0x82C5 +#define GL_VIEW_CLASS_64_BITS 0x82C6 +#define GL_VIEW_CLASS_48_BITS 0x82C7 +#define GL_VIEW_CLASS_32_BITS 0x82C8 +#define GL_VIEW_CLASS_24_BITS 0x82C9 +#define GL_VIEW_CLASS_16_BITS 0x82CA +#define GL_VIEW_CLASS_8_BITS 0x82CB +#define GL_VIEW_CLASS_S3TC_DXT1_RGB 0x82CC +#define GL_VIEW_CLASS_S3TC_DXT1_RGBA 0x82CD +#define GL_VIEW_CLASS_S3TC_DXT3_RGBA 0x82CE +#define GL_VIEW_CLASS_S3TC_DXT5_RGBA 0x82CF +#define GL_VIEW_CLASS_RGTC1_RED 0x82D0 +#define GL_VIEW_CLASS_RGTC2_RG 0x82D1 +#define GL_VIEW_CLASS_BPTC_UNORM 0x82D2 +#define GL_VIEW_CLASS_BPTC_FLOAT 0x82D3 +#define GL_UNIFORM 0x92E1 +#define GL_UNIFORM_BLOCK 0x92E2 +#define GL_PROGRAM_INPUT 0x92E3 +#define GL_PROGRAM_OUTPUT 0x92E4 +#define GL_BUFFER_VARIABLE 0x92E5 +#define GL_SHADER_STORAGE_BLOCK 0x92E6 +#define GL_VERTEX_SUBROUTINE 0x92E8 +#define GL_TESS_CONTROL_SUBROUTINE 0x92E9 +#define GL_TESS_EVALUATION_SUBROUTINE 0x92EA +#define GL_GEOMETRY_SUBROUTINE 0x92EB +#define GL_FRAGMENT_SUBROUTINE 0x92EC +#define GL_COMPUTE_SUBROUTINE 0x92ED +#define GL_VERTEX_SUBROUTINE_UNIFORM 0x92EE +#define GL_TESS_CONTROL_SUBROUTINE_UNIFORM 0x92EF +#define GL_TESS_EVALUATION_SUBROUTINE_UNIFORM 0x92F0 +#define GL_GEOMETRY_SUBROUTINE_UNIFORM 0x92F1 +#define GL_FRAGMENT_SUBROUTINE_UNIFORM 0x92F2 +#define GL_COMPUTE_SUBROUTINE_UNIFORM 0x92F3 +#define GL_TRANSFORM_FEEDBACK_VARYING 0x92F4 +#define GL_ACTIVE_RESOURCES 0x92F5 +#define GL_MAX_NAME_LENGTH 0x92F6 +#define GL_MAX_NUM_ACTIVE_VARIABLES 0x92F7 +#define GL_MAX_NUM_COMPATIBLE_SUBROUTINES 0x92F8 +#define GL_NAME_LENGTH 0x92F9 +#define GL_TYPE 0x92FA +#define GL_ARRAY_SIZE 0x92FB +#define GL_OFFSET 0x92FC +#define GL_BLOCK_INDEX 0x92FD +#define GL_ARRAY_STRIDE 0x92FE +#define GL_MATRIX_STRIDE 0x92FF +#define GL_IS_ROW_MAJOR 0x9300 +#define GL_ATOMIC_COUNTER_BUFFER_INDEX 0x9301 +#define GL_BUFFER_BINDING 0x9302 +#define GL_BUFFER_DATA_SIZE 0x9303 +#define GL_NUM_ACTIVE_VARIABLES 0x9304 +#define GL_ACTIVE_VARIABLES 0x9305 +#define GL_REFERENCED_BY_VERTEX_SHADER 0x9306 +#define GL_REFERENCED_BY_TESS_CONTROL_SHADER 0x9307 +#define GL_REFERENCED_BY_TESS_EVALUATION_SHADER 0x9308 +#define GL_REFERENCED_BY_GEOMETRY_SHADER 0x9309 +#define GL_REFERENCED_BY_FRAGMENT_SHADER 0x930A +#define GL_REFERENCED_BY_COMPUTE_SHADER 0x930B +#define GL_TOP_LEVEL_ARRAY_SIZE 0x930C +#define GL_TOP_LEVEL_ARRAY_STRIDE 0x930D +#define GL_LOCATION 0x930E +#define GL_LOCATION_INDEX 0x930F +#define GL_IS_PER_PATCH 0x92E7 +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 +#define GL_SHADER_STORAGE_BUFFER_START 0x90D4 +#define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 +#define GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 +#define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS 0x90D7 +#define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS 0x90D8 +#define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS 0x90D9 +#define GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA +#define GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB +#define GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC +#define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD +#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE +#define GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT 0x90DF +#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 +#define GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES 0x8F39 +#define GL_DEPTH_STENCIL_TEXTURE_MODE 0x90EA +#define GL_TEXTURE_BUFFER_OFFSET 0x919D +#define GL_TEXTURE_BUFFER_SIZE 0x919E +#define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT 0x919F +#define GL_TEXTURE_VIEW_MIN_LEVEL 0x82DB +#define GL_TEXTURE_VIEW_NUM_LEVELS 0x82DC +#define GL_TEXTURE_VIEW_MIN_LAYER 0x82DD +#define GL_TEXTURE_VIEW_NUM_LAYERS 0x82DE +#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF +#define GL_VERTEX_ATTRIB_BINDING 0x82D4 +#define GL_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D5 +#define GL_VERTEX_BINDING_DIVISOR 0x82D6 +#define GL_VERTEX_BINDING_OFFSET 0x82D7 +#define GL_VERTEX_BINDING_STRIDE 0x82D8 +#define GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D9 +#define GL_MAX_VERTEX_ATTRIB_BINDINGS 0x82DA +#define GL_VERTEX_BINDING_BUFFER 0x8F4F +#define GL_DISPLAY_LIST 0x82E7 +typedef void (APIENTRYP PFNGLCLEARBUFFERDATAPROC) (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARBUFFERSUBDATAPROC) (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEINDIRECTPROC) (GLintptr indirect); +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATAPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINTERNALFORMATI64VPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64 *params); +typedef void (APIENTRYP PFNGLINVALIDATETEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLINVALIDATETEXIMAGEPROC) (GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLINVALIDATEBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLINVALIDATEFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments); +typedef void (APIENTRYP PFNGLINVALIDATESUBFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLGETPROGRAMINTERFACEIVPROC) (GLuint program, GLenum programInterface, GLenum pname, GLint *params); +typedef GLuint (APIENTRYP PFNGLGETPROGRAMRESOURCEINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef void (APIENTRYP PFNGLSHADERSTORAGEBLOCKBINDINGPROC) (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); +typedef void (APIENTRYP PFNGLTEXBUFFERRANGEPROC) (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTUREVIEWPROC) (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERPROC) (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBBINDINGPROC) (GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXBINDINGDIVISORPROC) (GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKPROC) (GLDEBUGPROC callback, const void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message); +typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC) (void); +typedef void (APIENTRYP PFNGLOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +typedef void (APIENTRYP PFNGLOBJECTPTRLABELPROC) (const void *ptr, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELPROC) (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClearBufferData (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearBufferSubData (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glDispatchCompute (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +GLAPI void APIENTRY glDispatchComputeIndirect (GLintptr indirect); +GLAPI void APIENTRY glCopyImageSubData (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +GLAPI void APIENTRY glFramebufferParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glGetFramebufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInternalformati64v (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64 *params); +GLAPI void APIENTRY glInvalidateTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glInvalidateTexImage (GLuint texture, GLint level); +GLAPI void APIENTRY glInvalidateBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glInvalidateBufferData (GLuint buffer); +GLAPI void APIENTRY glInvalidateFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments); +GLAPI void APIENTRY glInvalidateSubFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glMultiDrawArraysIndirect (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glGetProgramInterfaceiv (GLuint program, GLenum programInterface, GLenum pname, GLint *params); +GLAPI GLuint APIENTRY glGetProgramResourceIndex (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI void APIENTRY glGetProgramResourceName (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetProgramResourceiv (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); +GLAPI GLint APIENTRY glGetProgramResourceLocation (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI GLint APIENTRY glGetProgramResourceLocationIndex (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI void APIENTRY glShaderStorageBlockBinding (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); +GLAPI void APIENTRY glTexBufferRange (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTexStorage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexStorage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureView (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +GLAPI void APIENTRY glBindVertexBuffer (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormat (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribIFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribLFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribBinding (GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexBindingDivisor (GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glDebugMessageControl (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsert (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallback (GLDEBUGPROC callback, const void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLog (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +GLAPI void APIENTRY glPushDebugGroup (GLenum source, GLuint id, GLsizei length, const GLchar *message); +GLAPI void APIENTRY glPopDebugGroup (void); +GLAPI void APIENTRY glObjectLabel (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectLabel (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI void APIENTRY glObjectPtrLabel (const void *ptr, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectPtrLabel (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_VERSION_4_3 */ + +#ifndef GL_VERSION_4_4 +#define GL_VERSION_4_4 1 +#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 +#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221 +#define GL_TEXTURE_BUFFER_BINDING 0x8C2A +#define GL_MAP_PERSISTENT_BIT 0x0040 +#define GL_MAP_COHERENT_BIT 0x0080 +#define GL_DYNAMIC_STORAGE_BIT 0x0100 +#define GL_CLIENT_STORAGE_BIT 0x0200 +#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 +#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F +#define GL_BUFFER_STORAGE_FLAGS 0x8220 +#define GL_CLEAR_TEXTURE 0x9365 +#define GL_LOCATION_COMPONENT 0x934A +#define GL_TRANSFORM_FEEDBACK_BUFFER_INDEX 0x934B +#define GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE 0x934C +#define GL_QUERY_BUFFER 0x9192 +#define GL_QUERY_BUFFER_BARRIER_BIT 0x00008000 +#define GL_QUERY_BUFFER_BINDING 0x9193 +#define GL_QUERY_RESULT_NO_WAIT 0x9194 +#define GL_MIRROR_CLAMP_TO_EDGE 0x8743 +typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLBINDBUFFERSBASEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); +typedef void (APIENTRYP PFNGLBINDBUFFERSRANGEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); +typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); +typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC) (GLuint first, GLsizei count, const GLuint *samplers); +typedef void (APIENTRYP PFNGLBINDIMAGETEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERSPROC) (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferStorage (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glClearTexImage (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glBindBuffersBase (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); +GLAPI void APIENTRY glBindBuffersRange (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); +GLAPI void APIENTRY glBindTextures (GLuint first, GLsizei count, const GLuint *textures); +GLAPI void APIENTRY glBindSamplers (GLuint first, GLsizei count, const GLuint *samplers); +GLAPI void APIENTRY glBindImageTextures (GLuint first, GLsizei count, const GLuint *textures); +GLAPI void APIENTRY glBindVertexBuffers (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +#endif +#endif /* GL_VERSION_4_4 */ + +#ifndef GL_VERSION_4_5 +#define GL_VERSION_4_5 1 +#define GL_CONTEXT_LOST 0x0507 +#define GL_NEGATIVE_ONE_TO_ONE 0x935E +#define GL_ZERO_TO_ONE 0x935F +#define GL_CLIP_ORIGIN 0x935C +#define GL_CLIP_DEPTH_MODE 0x935D +#define GL_QUERY_WAIT_INVERTED 0x8E17 +#define GL_QUERY_NO_WAIT_INVERTED 0x8E18 +#define GL_QUERY_BY_REGION_WAIT_INVERTED 0x8E19 +#define GL_QUERY_BY_REGION_NO_WAIT_INVERTED 0x8E1A +#define GL_MAX_CULL_DISTANCES 0x82F9 +#define GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES 0x82FA +#define GL_TEXTURE_TARGET 0x1006 +#define GL_QUERY_TARGET 0x82EA +#define GL_GUILTY_CONTEXT_RESET 0x8253 +#define GL_INNOCENT_CONTEXT_RESET 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY 0x8256 +#define GL_LOSE_CONTEXT_ON_RESET 0x8252 +#define GL_NO_RESET_NOTIFICATION 0x8261 +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004 +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_MINMAX 0x802E +#define GL_CONTEXT_RELEASE_BEHAVIOR 0x82FB +#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82FC +typedef void (APIENTRYP PFNGLCLIPCONTROLPROC) (GLenum origin, GLenum depth); +typedef void (APIENTRYP PFNGLCREATETRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC) (GLuint xfb, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC) (GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKIVPROC) (GLuint xfb, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); +typedef void (APIENTRYP PFNGLCREATEBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLCOPYNAMEDBUFFERSUBDATAPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERPROC) (GLuint buffer, GLenum access); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERI64VPROC) (GLuint buffer, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVPROC) (GLuint buffer, GLenum pname, void **params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +typedef void (APIENTRYP PFNGLCREATEFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC) (GLuint framebuffer, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC) (GLuint framebuffer, GLenum buf); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC) (GLuint framebuffer, GLenum src); +typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC) (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments); +typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC) (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERIVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFIPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef void (APIENTRYP PFNGLBLITNAMEDFRAMEBUFFERPROC) (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC) (GLuint framebuffer, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCREATERENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCREATETEXTURESPROC) (GLenum target, GLsizei n, GLuint *textures); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERPROC) (GLuint texture, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEPROC) (GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC) (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC) (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFPROC) (GLuint texture, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVPROC) (GLuint texture, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIPROC) (GLuint texture, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVPROC) (GLuint texture, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVPROC) (GLuint texture, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVPROC) (GLuint texture, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLBINDTEXTUREUNITPROC) (GLuint unit, GLuint texture); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC) (GLuint texture, GLint level, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVPROC) (GLuint texture, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVPROC) (GLuint texture, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVPROC) (GLuint texture, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVPROC) (GLuint texture, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVPROC) (GLuint texture, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVPROC) (GLuint texture, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCREATEVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLVERTEXARRAYELEMENTBUFFERPROC) (GLuint vaobj, GLuint buffer); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERSPROC) (GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBBINDINGPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBIFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBLFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYBINDINGDIVISORPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYIVPROC) (GLuint vaobj, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXEDIVPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXED64IVPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint64 *param); +typedef void (APIENTRYP PFNGLCREATESAMPLERSPROC) (GLsizei n, GLuint *samplers); +typedef void (APIENTRYP PFNGLCREATEPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef void (APIENTRYP PFNGLCREATEQUERIESPROC) (GLenum target, GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTI64VPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTIVPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUI64VPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUIVPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC) (GLbitfield barriers); +typedef void (APIENTRYP PFNGLGETTEXTURESUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels); +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSPROC) (void); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint lod, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLREADNPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (APIENTRYP PFNGLGETNMAPDVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLGETNMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLTEXTUREBARRIERPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClipControl (GLenum origin, GLenum depth); +GLAPI void APIENTRY glCreateTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glTransformFeedbackBufferBase (GLuint xfb, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackBufferRange (GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glGetTransformFeedbackiv (GLuint xfb, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTransformFeedbacki_v (GLuint xfb, GLenum pname, GLuint index, GLint *param); +GLAPI void APIENTRY glGetTransformFeedbacki64_v (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); +GLAPI void APIENTRY glCreateBuffers (GLsizei n, GLuint *buffers); +GLAPI void APIENTRY glNamedBufferStorage (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glNamedBufferData (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glCopyNamedBufferSubData (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glClearNamedBufferData (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearNamedBufferSubData (GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void *APIENTRY glMapNamedBuffer (GLuint buffer, GLenum access); +GLAPI void *APIENTRY glMapNamedBufferRange (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI GLboolean APIENTRY glUnmapNamedBuffer (GLuint buffer); +GLAPI void APIENTRY glFlushMappedNamedBufferRange (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glGetNamedBufferParameteriv (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferParameteri64v (GLuint buffer, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetNamedBufferPointerv (GLuint buffer, GLenum pname, void **params); +GLAPI void APIENTRY glGetNamedBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void APIENTRY glCreateFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI void APIENTRY glNamedFramebufferRenderbuffer (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glNamedFramebufferParameteri (GLuint framebuffer, GLenum pname, GLint param); +GLAPI void APIENTRY glNamedFramebufferTexture (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayer (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferDrawBuffer (GLuint framebuffer, GLenum buf); +GLAPI void APIENTRY glNamedFramebufferDrawBuffers (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glNamedFramebufferReadBuffer (GLuint framebuffer, GLenum src); +GLAPI void APIENTRY glInvalidateNamedFramebufferData (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments); +GLAPI void APIENTRY glInvalidateNamedFramebufferSubData (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glClearNamedFramebufferiv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearNamedFramebufferuiv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearNamedFramebufferfv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearNamedFramebufferfi (GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI void APIENTRY glBlitNamedFramebuffer (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatus (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glGetNamedFramebufferParameteriv (GLuint framebuffer, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameteriv (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glCreateRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glNamedRenderbufferStorage (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisample (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameteriv (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glCreateTextures (GLenum target, GLsizei n, GLuint *textures); +GLAPI void APIENTRY glTextureBuffer (GLuint texture, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glTextureBufferRange (GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTextureStorage1D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTextureStorage2D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureStorage3D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glTextureStorage2DMultisample (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureStorage3DMultisample (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCompressedTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCopyTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureParameterf (GLuint texture, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfv (GLuint texture, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glTextureParameteri (GLuint texture, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterIiv (GLuint texture, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuiv (GLuint texture, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glTextureParameteriv (GLuint texture, GLenum pname, const GLint *param); +GLAPI void APIENTRY glGenerateTextureMipmap (GLuint texture); +GLAPI void APIENTRY glBindTextureUnit (GLuint unit, GLuint texture); +GLAPI void APIENTRY glGetTextureImage (GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetCompressedTextureImage (GLuint texture, GLint level, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetTextureLevelParameterfv (GLuint texture, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameteriv (GLuint texture, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterfv (GLuint texture, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterIiv (GLuint texture, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuiv (GLuint texture, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetTextureParameteriv (GLuint texture, GLenum pname, GLint *params); +GLAPI void APIENTRY glCreateVertexArrays (GLsizei n, GLuint *arrays); +GLAPI void APIENTRY glDisableVertexArrayAttrib (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glEnableVertexArrayAttrib (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glVertexArrayElementBuffer (GLuint vaobj, GLuint buffer); +GLAPI void APIENTRY glVertexArrayVertexBuffer (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexArrayVertexBuffers (GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +GLAPI void APIENTRY glVertexArrayAttribBinding (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexArrayAttribFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayAttribIFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayAttribLFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayBindingDivisor (GLuint vaobj, GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glGetVertexArrayiv (GLuint vaobj, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayIndexediv (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayIndexed64iv (GLuint vaobj, GLuint index, GLenum pname, GLint64 *param); +GLAPI void APIENTRY glCreateSamplers (GLsizei n, GLuint *samplers); +GLAPI void APIENTRY glCreateProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI void APIENTRY glCreateQueries (GLenum target, GLsizei n, GLuint *ids); +GLAPI void APIENTRY glGetQueryBufferObjecti64v (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glGetQueryBufferObjectiv (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glGetQueryBufferObjectui64v (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glGetQueryBufferObjectuiv (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glMemoryBarrierByRegion (GLbitfield barriers); +GLAPI void APIENTRY glGetTextureSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetCompressedTextureSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels); +GLAPI GLenum APIENTRY glGetGraphicsResetStatus (void); +GLAPI void APIENTRY glGetnCompressedTexImage (GLenum target, GLint lod, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetnTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetnUniformdv (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +GLAPI void APIENTRY glGetnUniformfv (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformiv (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuiv (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glReadnPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GLAPI void APIENTRY glGetnMapdv (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfv (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapiv (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfv (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuiv (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusv (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStipple (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTable (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +GLAPI void APIENTRY glGetnConvolutionFilter (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +GLAPI void APIENTRY glGetnSeparableFilter (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +GLAPI void APIENTRY glGetnHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glGetnMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glTextureBarrier (void); +#endif +#endif /* GL_VERSION_4_5 */ + +#ifndef GL_VERSION_4_6 +#define GL_VERSION_4_6 1 +#define GL_SHADER_BINARY_FORMAT_SPIR_V 0x9551 +#define GL_SPIR_V_BINARY 0x9552 +#define GL_PARAMETER_BUFFER 0x80EE +#define GL_PARAMETER_BUFFER_BINDING 0x80EF +#define GL_CONTEXT_FLAG_NO_ERROR_BIT 0x00000008 +#define GL_VERTICES_SUBMITTED 0x82EE +#define GL_PRIMITIVES_SUBMITTED 0x82EF +#define GL_VERTEX_SHADER_INVOCATIONS 0x82F0 +#define GL_TESS_CONTROL_SHADER_PATCHES 0x82F1 +#define GL_TESS_EVALUATION_SHADER_INVOCATIONS 0x82F2 +#define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED 0x82F3 +#define GL_FRAGMENT_SHADER_INVOCATIONS 0x82F4 +#define GL_COMPUTE_SHADER_INVOCATIONS 0x82F5 +#define GL_CLIPPING_INPUT_PRIMITIVES 0x82F6 +#define GL_CLIPPING_OUTPUT_PRIMITIVES 0x82F7 +#define GL_POLYGON_OFFSET_CLAMP 0x8E1B +#define GL_SPIR_V_EXTENSIONS 0x9553 +#define GL_NUM_SPIR_V_EXTENSIONS 0x9554 +#define GL_TEXTURE_MAX_ANISOTROPY 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF +#define GL_TRANSFORM_FEEDBACK_OVERFLOW 0x82EC +#define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW 0x82ED +typedef void (APIENTRYP PFNGLSPECIALIZESHADERPROC) (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC) (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC) (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPPROC) (GLfloat factor, GLfloat units, GLfloat clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpecializeShader (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +GLAPI void APIENTRY glMultiDrawArraysIndirectCount (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectCount (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glPolygonOffsetClamp (GLfloat factor, GLfloat units, GLfloat clamp); +#endif +#endif /* GL_VERSION_4_6 */ + +#ifndef GL_ARB_ES2_compatibility +#define GL_ARB_ES2_compatibility 1 +#endif /* GL_ARB_ES2_compatibility */ + +#ifndef GL_ARB_ES3_1_compatibility +#define GL_ARB_ES3_1_compatibility 1 +#endif /* GL_ARB_ES3_1_compatibility */ + +#ifndef GL_ARB_ES3_2_compatibility +#define GL_ARB_ES3_2_compatibility 1 +#define GL_PRIMITIVE_BOUNDING_BOX_ARB 0x92BE +#define GL_MULTISAMPLE_LINE_WIDTH_RANGE_ARB 0x9381 +#define GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY_ARB 0x9382 +typedef void (APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXARBPROC) (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveBoundingBoxARB (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#endif +#endif /* GL_ARB_ES3_2_compatibility */ + +#ifndef GL_ARB_ES3_compatibility +#define GL_ARB_ES3_compatibility 1 +#endif /* GL_ARB_ES3_compatibility */ + +#ifndef GL_ARB_arrays_of_arrays +#define GL_ARB_arrays_of_arrays 1 +#endif /* GL_ARB_arrays_of_arrays */ + +#ifndef GL_ARB_base_instance +#define GL_ARB_base_instance 1 +#endif /* GL_ARB_base_instance */ + +#ifndef GL_ARB_bindless_texture +#define GL_ARB_bindless_texture 1 +typedef khronos_uint64_t GLuint64EXT; +#define GL_UNSIGNED_INT64_ARB 0x140F +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLEARBPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEARBPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLEARBPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64ARBPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64ARBPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VARBPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VARBPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleARB (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleARB (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentARB (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentARB (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleARB (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentARB (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentARB (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64ARB (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64ARB (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentARB (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentARB (GLuint64 handle); +GLAPI void APIENTRY glVertexAttribL1ui64ARB (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL1ui64vARB (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLui64vARB (GLuint index, GLenum pname, GLuint64EXT *params); +#endif +#endif /* GL_ARB_bindless_texture */ + +#ifndef GL_ARB_blend_func_extended +#define GL_ARB_blend_func_extended 1 +#endif /* GL_ARB_blend_func_extended */ + +#ifndef GL_ARB_buffer_storage +#define GL_ARB_buffer_storage 1 +#endif /* GL_ARB_buffer_storage */ + +#ifndef GL_ARB_cl_event +#define GL_ARB_cl_event 1 +struct _cl_context; +struct _cl_event; +#define GL_SYNC_CL_EVENT_ARB 0x8240 +#define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 +typedef GLsync (APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC) (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); +#endif +#endif /* GL_ARB_cl_event */ + +#ifndef GL_ARB_clear_buffer_object +#define GL_ARB_clear_buffer_object 1 +#endif /* GL_ARB_clear_buffer_object */ + +#ifndef GL_ARB_clear_texture +#define GL_ARB_clear_texture 1 +#endif /* GL_ARB_clear_texture */ + +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +#endif /* GL_ARB_clip_control */ + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum target, GLenum clamp); +#endif +#endif /* GL_ARB_color_buffer_float */ + +#ifndef GL_ARB_compatibility +#define GL_ARB_compatibility 1 +#endif /* GL_ARB_compatibility */ + +#ifndef GL_ARB_compressed_texture_pixel_storage +#define GL_ARB_compressed_texture_pixel_storage 1 +#endif /* GL_ARB_compressed_texture_pixel_storage */ + +#ifndef GL_ARB_compute_shader +#define GL_ARB_compute_shader 1 +#endif /* GL_ARB_compute_shader */ + +#ifndef GL_ARB_compute_variable_group_size +#define GL_ARB_compute_variable_group_size 1 +#define GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB 0x9344 +#define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS_ARB 0x90EB +#define GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB 0x9345 +#define GL_MAX_COMPUTE_FIXED_GROUP_SIZE_ARB 0x91BF +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDispatchComputeGroupSizeARB (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); +#endif +#endif /* GL_ARB_compute_variable_group_size */ + +#ifndef GL_ARB_conditional_render_inverted +#define GL_ARB_conditional_render_inverted 1 +#endif /* GL_ARB_conditional_render_inverted */ + +#ifndef GL_ARB_conservative_depth +#define GL_ARB_conservative_depth 1 +#endif /* GL_ARB_conservative_depth */ + +#ifndef GL_ARB_copy_buffer +#define GL_ARB_copy_buffer 1 +#endif /* GL_ARB_copy_buffer */ + +#ifndef GL_ARB_copy_image +#define GL_ARB_copy_image 1 +#endif /* GL_ARB_copy_image */ + +#ifndef GL_ARB_cull_distance +#define GL_ARB_cull_distance 1 +#endif /* GL_ARB_cull_distance */ + +#ifndef GL_ARB_debug_output +#define GL_ARB_debug_output 1 +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback, const void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageControlARB (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertARB (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackARB (GLDEBUGPROCARB callback, const void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogARB (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif +#endif /* GL_ARB_debug_output */ + +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 +#endif /* GL_ARB_depth_buffer_float */ + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp 1 +#endif /* GL_ARB_depth_clamp */ + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif /* GL_ARB_depth_texture */ + +#ifndef GL_ARB_derivative_control +#define GL_ARB_derivative_control 1 +#endif /* GL_ARB_derivative_control */ + +#ifndef GL_ARB_direct_state_access +#define GL_ARB_direct_state_access 1 +#endif /* GL_ARB_direct_state_access */ + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_ARB_draw_buffers */ + +#ifndef GL_ARB_draw_buffers_blend +#define GL_ARB_draw_buffers_blend 1 +typedef void (APIENTRYP PFNGLBLENDEQUATIONIARBPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIARBPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationiARB (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateiARB (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunciARB (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateiARB (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif +#endif /* GL_ARB_draw_buffers_blend */ + +#ifndef GL_ARB_draw_elements_base_vertex +#define GL_ARB_draw_elements_base_vertex 1 +#endif /* GL_ARB_draw_elements_base_vertex */ + +#ifndef GL_ARB_draw_indirect +#define GL_ARB_draw_indirect 1 +#endif /* GL_ARB_draw_indirect */ + +#ifndef GL_ARB_draw_instanced +#define GL_ARB_draw_instanced 1 +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedARB (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedARB (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_ARB_draw_instanced */ + +#ifndef GL_ARB_enhanced_layouts +#define GL_ARB_enhanced_layouts 1 +#endif /* GL_ARB_enhanced_layouts */ + +#ifndef GL_ARB_explicit_attrib_location +#define GL_ARB_explicit_attrib_location 1 +#endif /* GL_ARB_explicit_attrib_location */ + +#ifndef GL_ARB_explicit_uniform_location +#define GL_ARB_explicit_uniform_location 1 +#endif /* GL_ARB_explicit_uniform_location */ + +#ifndef GL_ARB_fragment_coord_conventions +#define GL_ARB_fragment_coord_conventions 1 +#endif /* GL_ARB_fragment_coord_conventions */ + +#ifndef GL_ARB_fragment_layer_viewport +#define GL_ARB_fragment_layer_viewport 1 +#endif /* GL_ARB_fragment_layer_viewport */ + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const void *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, void *string); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramStringARB (GLenum target, GLenum format, GLsizei len, const void *string); +GLAPI void APIENTRY glBindProgramARB (GLenum target, GLuint program); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glGenProgramsARB (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringARB (GLenum target, GLenum pname, void *string); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint program); +#endif +#endif /* GL_ARB_fragment_program */ + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif /* GL_ARB_fragment_program_shadow */ + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif /* GL_ARB_fragment_shader */ + +#ifndef GL_ARB_fragment_shader_interlock +#define GL_ARB_fragment_shader_interlock 1 +#endif /* GL_ARB_fragment_shader_interlock */ + +#ifndef GL_ARB_framebuffer_no_attachments +#define GL_ARB_framebuffer_no_attachments 1 +#endif /* GL_ARB_framebuffer_no_attachments */ + +#ifndef GL_ARB_framebuffer_object +#define GL_ARB_framebuffer_object 1 +#endif /* GL_ARB_framebuffer_object */ + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_ARB_framebuffer_sRGB 1 +#endif /* GL_ARB_framebuffer_sRGB */ + +#ifndef GL_ARB_geometry_shader4 +#define GL_ARB_geometry_shader4 1 +#define GL_LINES_ADJACENCY_ARB 0x000A +#define GL_LINE_STRIP_ADJACENCY_ARB 0x000B +#define GL_TRIANGLES_ADJACENCY_ARB 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D +#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 +#define GL_GEOMETRY_SHADER_ARB 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriARB (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glFramebufferTextureARB (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif +#endif /* GL_ARB_geometry_shader4 */ + +#ifndef GL_ARB_get_program_binary +#define GL_ARB_get_program_binary 1 +#endif /* GL_ARB_get_program_binary */ + +#ifndef GL_ARB_get_texture_sub_image +#define GL_ARB_get_texture_sub_image 1 +#endif /* GL_ARB_get_texture_sub_image */ + +#ifndef GL_ARB_gl_spirv +#define GL_ARB_gl_spirv 1 +#define GL_SHADER_BINARY_FORMAT_SPIR_V_ARB 0x9551 +#define GL_SPIR_V_BINARY_ARB 0x9552 +typedef void (APIENTRYP PFNGLSPECIALIZESHADERARBPROC) (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpecializeShaderARB (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +#endif +#endif /* GL_ARB_gl_spirv */ + +#ifndef GL_ARB_gpu_shader5 +#define GL_ARB_gpu_shader5 1 +#endif /* GL_ARB_gpu_shader5 */ + +#ifndef GL_ARB_gpu_shader_fp64 +#define GL_ARB_gpu_shader_fp64 1 +#endif /* GL_ARB_gpu_shader_fp64 */ + +#ifndef GL_ARB_gpu_shader_int64 +#define GL_ARB_gpu_shader_int64 1 +#define GL_INT64_ARB 0x140E +#define GL_INT64_VEC2_ARB 0x8FE9 +#define GL_INT64_VEC3_ARB 0x8FEA +#define GL_INT64_VEC4_ARB 0x8FEB +#define GL_UNSIGNED_INT64_VEC2_ARB 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_ARB 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_ARB 0x8FF7 +typedef void (APIENTRYP PFNGLUNIFORM1I64ARBPROC) (GLint location, GLint64 x); +typedef void (APIENTRYP PFNGLUNIFORM2I64ARBPROC) (GLint location, GLint64 x, GLint64 y); +typedef void (APIENTRYP PFNGLUNIFORM3I64ARBPROC) (GLint location, GLint64 x, GLint64 y, GLint64 z); +typedef void (APIENTRYP PFNGLUNIFORM4I64ARBPROC) (GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64ARBPROC) (GLint location, GLuint64 x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VARBPROC) (GLuint program, GLint location, GLint64 *params); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VARBPROC) (GLuint program, GLint location, GLuint64 *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMI64VARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint64 *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUI64VARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint64 *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64ARBPROC) (GLuint program, GLint location, GLint64 x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64ARBPROC) (GLuint program, GLint location, GLuint64 x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64ARB (GLint location, GLint64 x); +GLAPI void APIENTRY glUniform2i64ARB (GLint location, GLint64 x, GLint64 y); +GLAPI void APIENTRY glUniform3i64ARB (GLint location, GLint64 x, GLint64 y, GLint64 z); +GLAPI void APIENTRY glUniform4i64ARB (GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +GLAPI void APIENTRY glUniform1i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform2i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform3i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform4i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform1ui64ARB (GLint location, GLuint64 x); +GLAPI void APIENTRY glUniform2ui64ARB (GLint location, GLuint64 x, GLuint64 y); +GLAPI void APIENTRY glUniform3ui64ARB (GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +GLAPI void APIENTRY glUniform4ui64ARB (GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +GLAPI void APIENTRY glUniform1ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glUniform2ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glUniform3ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glUniform4ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glGetUniformi64vARB (GLuint program, GLint location, GLint64 *params); +GLAPI void APIENTRY glGetUniformui64vARB (GLuint program, GLint location, GLuint64 *params); +GLAPI void APIENTRY glGetnUniformi64vARB (GLuint program, GLint location, GLsizei bufSize, GLint64 *params); +GLAPI void APIENTRY glGetnUniformui64vARB (GLuint program, GLint location, GLsizei bufSize, GLuint64 *params); +GLAPI void APIENTRY glProgramUniform1i64ARB (GLuint program, GLint location, GLint64 x); +GLAPI void APIENTRY glProgramUniform2i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y); +GLAPI void APIENTRY glProgramUniform3i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); +GLAPI void APIENTRY glProgramUniform4i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +GLAPI void APIENTRY glProgramUniform1i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform2i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform3i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform4i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform1ui64ARB (GLuint program, GLint location, GLuint64 x); +GLAPI void APIENTRY glProgramUniform2ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y); +GLAPI void APIENTRY glProgramUniform3ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +GLAPI void APIENTRY glProgramUniform4ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +GLAPI void APIENTRY glProgramUniform1ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniform2ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniform3ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniform4ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +#endif +#endif /* GL_ARB_gpu_shader_int64 */ + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +typedef khronos_uint16_t GLhalfARB; +#define GL_HALF_FLOAT_ARB 0x140B +#endif /* GL_ARB_half_float_pixel */ + +#ifndef GL_ARB_half_float_vertex +#define GL_ARB_half_float_vertex 1 +#endif /* GL_ARB_half_float_vertex */ + +#ifndef GL_ARB_imaging +#define GL_ARB_imaging 1 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, void *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTable (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glColorTableParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTable (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTable (GLenum target, GLenum format, GLenum type, void *table); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glColorSubTable (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glCopyColorSubTable (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionParameterf (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteri (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum target, GLenum format, GLenum type, void *image); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilter (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +GLAPI void APIENTRY glSeparableFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +GLAPI void APIENTRY glGetHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogram (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmax (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogram (GLenum target); +GLAPI void APIENTRY glResetMinmax (GLenum target); +#endif +#endif /* GL_ARB_imaging */ + +#ifndef GL_ARB_indirect_parameters +#define GL_ARB_indirect_parameters 1 +#define GL_PARAMETER_BUFFER_ARB 0x80EE +#define GL_PARAMETER_BUFFER_BINDING_ARB 0x80EF +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC) (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC) (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectCountARB (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectCountARB (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_ARB_indirect_parameters */ + +#ifndef GL_ARB_instanced_arrays +#define GL_ARB_instanced_arrays 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisorARB (GLuint index, GLuint divisor); +#endif +#endif /* GL_ARB_instanced_arrays */ + +#ifndef GL_ARB_internalformat_query +#define GL_ARB_internalformat_query 1 +#endif /* GL_ARB_internalformat_query */ + +#ifndef GL_ARB_internalformat_query2 +#define GL_ARB_internalformat_query2 1 +#define GL_SRGB_DECODE_ARB 0x8299 +#define GL_VIEW_CLASS_EAC_R11 0x9383 +#define GL_VIEW_CLASS_EAC_RG11 0x9384 +#define GL_VIEW_CLASS_ETC2_RGB 0x9385 +#define GL_VIEW_CLASS_ETC2_RGBA 0x9386 +#define GL_VIEW_CLASS_ETC2_EAC_RGBA 0x9387 +#define GL_VIEW_CLASS_ASTC_4x4_RGBA 0x9388 +#define GL_VIEW_CLASS_ASTC_5x4_RGBA 0x9389 +#define GL_VIEW_CLASS_ASTC_5x5_RGBA 0x938A +#define GL_VIEW_CLASS_ASTC_6x5_RGBA 0x938B +#define GL_VIEW_CLASS_ASTC_6x6_RGBA 0x938C +#define GL_VIEW_CLASS_ASTC_8x5_RGBA 0x938D +#define GL_VIEW_CLASS_ASTC_8x6_RGBA 0x938E +#define GL_VIEW_CLASS_ASTC_8x8_RGBA 0x938F +#define GL_VIEW_CLASS_ASTC_10x5_RGBA 0x9390 +#define GL_VIEW_CLASS_ASTC_10x6_RGBA 0x9391 +#define GL_VIEW_CLASS_ASTC_10x8_RGBA 0x9392 +#define GL_VIEW_CLASS_ASTC_10x10_RGBA 0x9393 +#define GL_VIEW_CLASS_ASTC_12x10_RGBA 0x9394 +#define GL_VIEW_CLASS_ASTC_12x12_RGBA 0x9395 +#endif /* GL_ARB_internalformat_query2 */ + +#ifndef GL_ARB_invalidate_subdata +#define GL_ARB_invalidate_subdata 1 +#endif /* GL_ARB_invalidate_subdata */ + +#ifndef GL_ARB_map_buffer_alignment +#define GL_ARB_map_buffer_alignment 1 +#endif /* GL_ARB_map_buffer_alignment */ + +#ifndef GL_ARB_map_buffer_range +#define GL_ARB_map_buffer_range 1 +#endif /* GL_ARB_map_buffer_range */ + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint index); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint size, const GLubyte *indices); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint size, const GLushort *indices); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint size, const GLuint *indices); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_ARB_matrix_palette */ + +#ifndef GL_ARB_multi_bind +#define GL_ARB_multi_bind 1 +#endif /* GL_ARB_multi_bind */ + +#ifndef GL_ARB_multi_draw_indirect +#define GL_ARB_multi_draw_indirect 1 +#endif /* GL_ARB_multi_draw_indirect */ + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLfloat value, GLboolean invert); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLfloat value, GLboolean invert); +#endif +#endif /* GL_ARB_multisample */ + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum target, const GLshort *v); +#endif +#endif /* GL_ARB_multitexture */ + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id); +GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQueryARB (GLenum target); +GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params); +#endif +#endif /* GL_ARB_occlusion_query */ + +#ifndef GL_ARB_occlusion_query2 +#define GL_ARB_occlusion_query2 1 +#endif /* GL_ARB_occlusion_query2 */ + +#ifndef GL_ARB_parallel_shader_compile +#define GL_ARB_parallel_shader_compile 1 +#define GL_MAX_SHADER_COMPILER_THREADS_ARB 0x91B0 +#define GL_COMPLETION_STATUS_ARB 0x91B1 +typedef void (APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSARBPROC) (GLuint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMaxShaderCompilerThreadsARB (GLuint count); +#endif +#endif /* GL_ARB_parallel_shader_compile */ + +#ifndef GL_ARB_pipeline_statistics_query +#define GL_ARB_pipeline_statistics_query 1 +#define GL_VERTICES_SUBMITTED_ARB 0x82EE +#define GL_PRIMITIVES_SUBMITTED_ARB 0x82EF +#define GL_VERTEX_SHADER_INVOCATIONS_ARB 0x82F0 +#define GL_TESS_CONTROL_SHADER_PATCHES_ARB 0x82F1 +#define GL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB 0x82F2 +#define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB 0x82F3 +#define GL_FRAGMENT_SHADER_INVOCATIONS_ARB 0x82F4 +#define GL_COMPUTE_SHADER_INVOCATIONS_ARB 0x82F5 +#define GL_CLIPPING_INPUT_PRIMITIVES_ARB 0x82F6 +#define GL_CLIPPING_OUTPUT_PRIMITIVES_ARB 0x82F7 +#endif /* GL_ARB_pipeline_statistics_query */ + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif /* GL_ARB_pixel_buffer_object */ + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvARB (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_ARB_point_parameters */ + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif /* GL_ARB_point_sprite */ + +#ifndef GL_ARB_polygon_offset_clamp +#define GL_ARB_polygon_offset_clamp 1 +#endif /* GL_ARB_polygon_offset_clamp */ + +#ifndef GL_ARB_post_depth_coverage +#define GL_ARB_post_depth_coverage 1 +#endif /* GL_ARB_post_depth_coverage */ + +#ifndef GL_ARB_program_interface_query +#define GL_ARB_program_interface_query 1 +#endif /* GL_ARB_program_interface_query */ + +#ifndef GL_ARB_provoking_vertex +#define GL_ARB_provoking_vertex 1 +#endif /* GL_ARB_provoking_vertex */ + +#ifndef GL_ARB_query_buffer_object +#define GL_ARB_query_buffer_object 1 +#endif /* GL_ARB_query_buffer_object */ + +#ifndef GL_ARB_robust_buffer_access_behavior +#define GL_ARB_robust_buffer_access_behavior 1 +#endif /* GL_ARB_robust_buffer_access_behavior */ + +#ifndef GL_ARB_robustness +#define GL_ARB_robustness 1 +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GL_NO_RESET_NOTIFICATION_ARB 0x8261 +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC) (void); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEARBPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); +typedef void (APIENTRYP PFNGLREADNPIXELSARBPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, GLsizei bufSize, void *img); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNMAPDVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVARBPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVARBPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVARBPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEARBPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLGETNMINMAXARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB (void); +GLAPI void APIENTRY glGetnTexImageARB (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); +GLAPI void APIENTRY glReadnPixelsARB (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GLAPI void APIENTRY glGetnCompressedTexImageARB (GLenum target, GLint lod, GLsizei bufSize, void *img); +GLAPI void APIENTRY glGetnUniformfvARB (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformivARB (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuivARB (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glGetnUniformdvARB (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +GLAPI void APIENTRY glGetnMapdvARB (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfvARB (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapivARB (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfvARB (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuivARB (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusvARB (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStippleARB (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTableARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +GLAPI void APIENTRY glGetnConvolutionFilterARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +GLAPI void APIENTRY glGetnSeparableFilterARB (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +GLAPI void APIENTRY glGetnHistogramARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glGetnMinmaxARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +#endif +#endif /* GL_ARB_robustness */ + +#ifndef GL_ARB_robustness_isolation +#define GL_ARB_robustness_isolation 1 +#endif /* GL_ARB_robustness_isolation */ + +#ifndef GL_ARB_sample_locations +#define GL_ARB_sample_locations 1 +#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_ARB 0x933D +#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_ARB 0x933E +#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_ARB 0x933F +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_ARB 0x9340 +#define GL_SAMPLE_LOCATION_ARB 0x8E50 +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_ARB 0x9341 +#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_ARB 0x9342 +#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_ARB 0x9343 +typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVARBPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVARBPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLEVALUATEDEPTHVALUESARBPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferSampleLocationsfvARB (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvARB (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glEvaluateDepthValuesARB (void); +#endif +#endif /* GL_ARB_sample_locations */ + +#ifndef GL_ARB_sample_shading +#define GL_ARB_sample_shading 1 +#define GL_SAMPLE_SHADING_ARB 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGARBPROC) (GLfloat value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShadingARB (GLfloat value); +#endif +#endif /* GL_ARB_sample_shading */ + +#ifndef GL_ARB_sampler_objects +#define GL_ARB_sampler_objects 1 +#endif /* GL_ARB_sampler_objects */ + +#ifndef GL_ARB_seamless_cube_map +#define GL_ARB_seamless_cube_map 1 +#endif /* GL_ARB_seamless_cube_map */ + +#ifndef GL_ARB_seamless_cubemap_per_texture +#define GL_ARB_seamless_cubemap_per_texture 1 +#endif /* GL_ARB_seamless_cubemap_per_texture */ + +#ifndef GL_ARB_separate_shader_objects +#define GL_ARB_separate_shader_objects 1 +#endif /* GL_ARB_separate_shader_objects */ + +#ifndef GL_ARB_shader_atomic_counter_ops +#define GL_ARB_shader_atomic_counter_ops 1 +#endif /* GL_ARB_shader_atomic_counter_ops */ + +#ifndef GL_ARB_shader_atomic_counters +#define GL_ARB_shader_atomic_counters 1 +#endif /* GL_ARB_shader_atomic_counters */ + +#ifndef GL_ARB_shader_ballot +#define GL_ARB_shader_ballot 1 +#endif /* GL_ARB_shader_ballot */ + +#ifndef GL_ARB_shader_bit_encoding +#define GL_ARB_shader_bit_encoding 1 +#endif /* GL_ARB_shader_bit_encoding */ + +#ifndef GL_ARB_shader_clock +#define GL_ARB_shader_clock 1 +#endif /* GL_ARB_shader_clock */ + +#ifndef GL_ARB_shader_draw_parameters +#define GL_ARB_shader_draw_parameters 1 +#endif /* GL_ARB_shader_draw_parameters */ + +#ifndef GL_ARB_shader_group_vote +#define GL_ARB_shader_group_vote 1 +#endif /* GL_ARB_shader_group_vote */ + +#ifndef GL_ARB_shader_image_load_store +#define GL_ARB_shader_image_load_store 1 +#endif /* GL_ARB_shader_image_load_store */ + +#ifndef GL_ARB_shader_image_size +#define GL_ARB_shader_image_size 1 +#endif /* GL_ARB_shader_image_size */ + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef __APPLE__ +typedef void *GLhandleARB; +#else +typedef unsigned int GLhandleARB; +#endif +typedef char GLcharARB; +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB obj); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum pname); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB containerObj, GLhandleARB attachedObj); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum shaderType); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB shaderObj); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB containerObj, GLhandleARB obj); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB programObj); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUniform1fARB (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2fARB (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1iARB (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2iARB (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3iARB (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4iARB (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB obj, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB obj, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB programObj, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB programObj, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB programObj, GLint location, GLint *params); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif +#endif /* GL_ARB_shader_objects */ + +#ifndef GL_ARB_shader_precision +#define GL_ARB_shader_precision 1 +#endif /* GL_ARB_shader_precision */ + +#ifndef GL_ARB_shader_stencil_export +#define GL_ARB_shader_stencil_export 1 +#endif /* GL_ARB_shader_stencil_export */ + +#ifndef GL_ARB_shader_storage_buffer_object +#define GL_ARB_shader_storage_buffer_object 1 +#endif /* GL_ARB_shader_storage_buffer_object */ + +#ifndef GL_ARB_shader_subroutine +#define GL_ARB_shader_subroutine 1 +#endif /* GL_ARB_shader_subroutine */ + +#ifndef GL_ARB_shader_texture_image_samples +#define GL_ARB_shader_texture_image_samples 1 +#endif /* GL_ARB_shader_texture_image_samples */ + +#ifndef GL_ARB_shader_texture_lod +#define GL_ARB_shader_texture_lod 1 +#endif /* GL_ARB_shader_texture_lod */ + +#ifndef GL_ARB_shader_viewport_layer_array +#define GL_ARB_shader_viewport_layer_array 1 +#endif /* GL_ARB_shader_viewport_layer_array */ + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif /* GL_ARB_shading_language_100 */ + +#ifndef GL_ARB_shading_language_420pack +#define GL_ARB_shading_language_420pack 1 +#endif /* GL_ARB_shading_language_420pack */ + +#ifndef GL_ARB_shading_language_include +#define GL_ARB_shading_language_include 1 +#define GL_SHADER_INCLUDE_ARB 0x8DAE +#define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 +#define GL_NAMED_STRING_TYPE_ARB 0x8DEA +typedef void (APIENTRYP PFNGLNAMEDSTRINGARBPROC) (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +typedef void (APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC) (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); +typedef GLboolean (APIENTRYP PFNGLISNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC) (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glNamedStringARB (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +GLAPI void APIENTRY glDeleteNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glCompileShaderIncludeARB (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); +GLAPI GLboolean APIENTRY glIsNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glGetNamedStringARB (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +GLAPI void APIENTRY glGetNamedStringivARB (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif +#endif /* GL_ARB_shading_language_include */ + +#ifndef GL_ARB_shading_language_packing +#define GL_ARB_shading_language_packing 1 +#endif /* GL_ARB_shading_language_packing */ + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif /* GL_ARB_shadow */ + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif /* GL_ARB_shadow_ambient */ + +#ifndef GL_ARB_sparse_buffer +#define GL_ARB_sparse_buffer 1 +#define GL_SPARSE_STORAGE_BIT_ARB 0x0400 +#define GL_SPARSE_BUFFER_PAGE_SIZE_ARB 0x82F8 +typedef void (APIENTRYP PFNGLBUFFERPAGECOMMITMENTARBPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); +typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTARBPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferPageCommitmentARB (GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); +GLAPI void APIENTRY glNamedBufferPageCommitmentEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +GLAPI void APIENTRY glNamedBufferPageCommitmentARB (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +#endif +#endif /* GL_ARB_sparse_buffer */ + +#ifndef GL_ARB_sparse_texture +#define GL_ARB_sparse_texture 1 +#define GL_TEXTURE_SPARSE_ARB 0x91A6 +#define GL_VIRTUAL_PAGE_SIZE_INDEX_ARB 0x91A7 +#define GL_NUM_SPARSE_LEVELS_ARB 0x91AA +#define GL_NUM_VIRTUAL_PAGE_SIZES_ARB 0x91A8 +#define GL_VIRTUAL_PAGE_SIZE_X_ARB 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_ARB 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_ARB 0x9197 +#define GL_MAX_SPARSE_TEXTURE_SIZE_ARB 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_ARB 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS_ARB 0x919A +#define GL_SPARSE_TEXTURE_FULL_ARRAY_CUBE_MIPMAPS_ARB 0x91A9 +typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexPageCommitmentARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +#endif +#endif /* GL_ARB_sparse_texture */ + +#ifndef GL_ARB_sparse_texture2 +#define GL_ARB_sparse_texture2 1 +#endif /* GL_ARB_sparse_texture2 */ + +#ifndef GL_ARB_sparse_texture_clamp +#define GL_ARB_sparse_texture_clamp 1 +#endif /* GL_ARB_sparse_texture_clamp */ + +#ifndef GL_ARB_spirv_extensions +#define GL_ARB_spirv_extensions 1 +#endif /* GL_ARB_spirv_extensions */ + +#ifndef GL_ARB_stencil_texturing +#define GL_ARB_stencil_texturing 1 +#endif /* GL_ARB_stencil_texturing */ + +#ifndef GL_ARB_sync +#define GL_ARB_sync 1 +#endif /* GL_ARB_sync */ + +#ifndef GL_ARB_tessellation_shader +#define GL_ARB_tessellation_shader 1 +#endif /* GL_ARB_tessellation_shader */ + +#ifndef GL_ARB_texture_barrier +#define GL_ARB_texture_barrier 1 +#endif /* GL_ARB_texture_barrier */ + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif /* GL_ARB_texture_border_clamp */ + +#ifndef GL_ARB_texture_buffer_object +#define GL_ARB_texture_buffer_object 1 +#define GL_TEXTURE_BUFFER_ARB 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E +typedef void (APIENTRYP PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferARB (GLenum target, GLenum internalformat, GLuint buffer); +#endif +#endif /* GL_ARB_texture_buffer_object */ + +#ifndef GL_ARB_texture_buffer_object_rgb32 +#define GL_ARB_texture_buffer_object_rgb32 1 +#endif /* GL_ARB_texture_buffer_object_rgb32 */ + +#ifndef GL_ARB_texture_buffer_range +#define GL_ARB_texture_buffer_range 1 +#endif /* GL_ARB_texture_buffer_range */ + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, void *img); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum target, GLint level, void *img); +#endif +#endif /* GL_ARB_texture_compression */ + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc 1 +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif /* GL_ARB_texture_compression_bptc */ + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc 1 +#endif /* GL_ARB_texture_compression_rgtc */ + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif /* GL_ARB_texture_cube_map */ + +#ifndef GL_ARB_texture_cube_map_array +#define GL_ARB_texture_cube_map_array 1 +#define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F +#endif /* GL_ARB_texture_cube_map_array */ + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif /* GL_ARB_texture_env_add */ + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif /* GL_ARB_texture_env_combine */ + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif /* GL_ARB_texture_env_crossbar */ + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif /* GL_ARB_texture_env_dot3 */ + +#ifndef GL_ARB_texture_filter_anisotropic +#define GL_ARB_texture_filter_anisotropic 1 +#endif /* GL_ARB_texture_filter_anisotropic */ + +#ifndef GL_ARB_texture_filter_minmax +#define GL_ARB_texture_filter_minmax 1 +#define GL_TEXTURE_REDUCTION_MODE_ARB 0x9366 +#define GL_WEIGHTED_AVERAGE_ARB 0x9367 +#endif /* GL_ARB_texture_filter_minmax */ + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif /* GL_ARB_texture_float */ + +#ifndef GL_ARB_texture_gather +#define GL_ARB_texture_gather 1 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F +#define GL_MAX_PROGRAM_TEXTURE_GATHER_COMPONENTS_ARB 0x8F9F +#endif /* GL_ARB_texture_gather */ + +#ifndef GL_ARB_texture_mirror_clamp_to_edge +#define GL_ARB_texture_mirror_clamp_to_edge 1 +#endif /* GL_ARB_texture_mirror_clamp_to_edge */ + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif /* GL_ARB_texture_mirrored_repeat */ + +#ifndef GL_ARB_texture_multisample +#define GL_ARB_texture_multisample 1 +#endif /* GL_ARB_texture_multisample */ + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif /* GL_ARB_texture_non_power_of_two */ + +#ifndef GL_ARB_texture_query_levels +#define GL_ARB_texture_query_levels 1 +#endif /* GL_ARB_texture_query_levels */ + +#ifndef GL_ARB_texture_query_lod +#define GL_ARB_texture_query_lod 1 +#endif /* GL_ARB_texture_query_lod */ + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif /* GL_ARB_texture_rectangle */ + +#ifndef GL_ARB_texture_rg +#define GL_ARB_texture_rg 1 +#endif /* GL_ARB_texture_rg */ + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_ARB_texture_rgb10_a2ui 1 +#endif /* GL_ARB_texture_rgb10_a2ui */ + +#ifndef GL_ARB_texture_stencil8 +#define GL_ARB_texture_stencil8 1 +#endif /* GL_ARB_texture_stencil8 */ + +#ifndef GL_ARB_texture_storage +#define GL_ARB_texture_storage 1 +#endif /* GL_ARB_texture_storage */ + +#ifndef GL_ARB_texture_storage_multisample +#define GL_ARB_texture_storage_multisample 1 +#endif /* GL_ARB_texture_storage_multisample */ + +#ifndef GL_ARB_texture_swizzle +#define GL_ARB_texture_swizzle 1 +#endif /* GL_ARB_texture_swizzle */ + +#ifndef GL_ARB_texture_view +#define GL_ARB_texture_view 1 +#endif /* GL_ARB_texture_view */ + +#ifndef GL_ARB_timer_query +#define GL_ARB_timer_query 1 +#endif /* GL_ARB_timer_query */ + +#ifndef GL_ARB_transform_feedback2 +#define GL_ARB_transform_feedback2 1 +#endif /* GL_ARB_transform_feedback2 */ + +#ifndef GL_ARB_transform_feedback3 +#define GL_ARB_transform_feedback3 1 +#endif /* GL_ARB_transform_feedback3 */ + +#ifndef GL_ARB_transform_feedback_instanced +#define GL_ARB_transform_feedback_instanced 1 +#endif /* GL_ARB_transform_feedback_instanced */ + +#ifndef GL_ARB_transform_feedback_overflow_query +#define GL_ARB_transform_feedback_overflow_query 1 +#define GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB 0x82EC +#define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB 0x82ED +#endif /* GL_ARB_transform_feedback_overflow_query */ + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *m); +#endif +#endif /* GL_ARB_transpose_matrix */ + +#ifndef GL_ARB_uniform_buffer_object +#define GL_ARB_uniform_buffer_object 1 +#endif /* GL_ARB_uniform_buffer_object */ + +#ifndef GL_ARB_vertex_array_bgra +#define GL_ARB_vertex_array_bgra 1 +#endif /* GL_ARB_vertex_array_bgra */ + +#ifndef GL_ARB_vertex_array_object +#define GL_ARB_vertex_array_object 1 +#endif /* GL_ARB_vertex_array_object */ + +#ifndef GL_ARB_vertex_attrib_64bit +#define GL_ARB_vertex_attrib_64bit 1 +#endif /* GL_ARB_vertex_attrib_64bit */ + +#ifndef GL_ARB_vertex_attrib_binding +#define GL_ARB_vertex_attrib_binding 1 +#endif /* GL_ARB_vertex_attrib_binding */ + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint size, const GLbyte *weights); +GLAPI void APIENTRY glWeightsvARB (GLint size, const GLshort *weights); +GLAPI void APIENTRY glWeightivARB (GLint size, const GLint *weights); +GLAPI void APIENTRY glWeightfvARB (GLint size, const GLfloat *weights); +GLAPI void APIENTRY glWeightdvARB (GLint size, const GLdouble *weights); +GLAPI void APIENTRY glWeightubvARB (GLint size, const GLubyte *weights); +GLAPI void APIENTRY glWeightusvARB (GLint size, const GLushort *weights); +GLAPI void APIENTRY glWeightuivARB (GLint size, const GLuint *weights); +GLAPI void APIENTRY glWeightPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexBlendARB (GLint count); +#endif +#endif /* GL_ARB_vertex_blend */ + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +typedef khronos_ssize_t GLsizeiptrARB; +typedef khronos_intptr_t GLintptrARB; +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); +typedef void *(APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffersARB (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint buffer); +GLAPI void APIENTRY glBufferDataARB (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); +GLAPI void *APIENTRY glMapBufferARB (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum target); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_ARB_vertex_buffer_object */ + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint index, GLenum pname, void **pointer); +#endif +#endif /* GL_ARB_vertex_program */ + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB programObj, GLuint index, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB programObj, const GLcharARB *name); +#endif +#endif /* GL_ARB_vertex_shader */ + +#ifndef GL_ARB_vertex_type_10f_11f_11f_rev +#define GL_ARB_vertex_type_10f_11f_11f_rev 1 +#endif /* GL_ARB_vertex_type_10f_11f_11f_rev */ + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +#define GL_ARB_vertex_type_2_10_10_10_rev 1 +#endif /* GL_ARB_vertex_type_2_10_10_10_rev */ + +#ifndef GL_ARB_viewport_array +#define GL_ARB_viewport_array 1 +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYDVNVPROC) (GLuint first, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDDNVPROC) (GLuint index, GLdouble n, GLdouble f); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangeArraydvNV (GLuint first, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glDepthRangeIndexeddNV (GLuint index, GLdouble n, GLdouble f); +#endif +#endif /* GL_ARB_viewport_array */ + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iARB (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos2sARB (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iARB (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos3sARB (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *v); +#endif +#endif /* GL_ARB_window_pos */ + +#ifndef GL_KHR_blend_equation_advanced +#define GL_KHR_blend_equation_advanced 1 +#define GL_MULTIPLY_KHR 0x9294 +#define GL_SCREEN_KHR 0x9295 +#define GL_OVERLAY_KHR 0x9296 +#define GL_DARKEN_KHR 0x9297 +#define GL_LIGHTEN_KHR 0x9298 +#define GL_COLORDODGE_KHR 0x9299 +#define GL_COLORBURN_KHR 0x929A +#define GL_HARDLIGHT_KHR 0x929B +#define GL_SOFTLIGHT_KHR 0x929C +#define GL_DIFFERENCE_KHR 0x929E +#define GL_EXCLUSION_KHR 0x92A0 +#define GL_HSL_HUE_KHR 0x92AD +#define GL_HSL_SATURATION_KHR 0x92AE +#define GL_HSL_COLOR_KHR 0x92AF +#define GL_HSL_LUMINOSITY_KHR 0x92B0 +typedef void (APIENTRYP PFNGLBLENDBARRIERKHRPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendBarrierKHR (void); +#endif +#endif /* GL_KHR_blend_equation_advanced */ + +#ifndef GL_KHR_blend_equation_advanced_coherent +#define GL_KHR_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285 +#endif /* GL_KHR_blend_equation_advanced_coherent */ + +#ifndef GL_KHR_context_flush_control +#define GL_KHR_context_flush_control 1 +#endif /* GL_KHR_context_flush_control */ + +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +#endif /* GL_KHR_debug */ + +#ifndef GL_KHR_no_error +#define GL_KHR_no_error 1 +#define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 +#endif /* GL_KHR_no_error */ + +#ifndef GL_KHR_parallel_shader_compile +#define GL_KHR_parallel_shader_compile 1 +#define GL_MAX_SHADER_COMPILER_THREADS_KHR 0x91B0 +#define GL_COMPLETION_STATUS_KHR 0x91B1 +typedef void (APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSKHRPROC) (GLuint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMaxShaderCompilerThreadsKHR (GLuint count); +#endif +#endif /* GL_KHR_parallel_shader_compile */ + +#ifndef GL_KHR_robust_buffer_access_behavior +#define GL_KHR_robust_buffer_access_behavior 1 +#endif /* GL_KHR_robust_buffer_access_behavior */ + +#ifndef GL_KHR_robustness +#define GL_KHR_robustness 1 +#define GL_CONTEXT_ROBUST_ACCESS 0x90F3 +#endif /* GL_KHR_robustness */ + +#ifndef GL_KHR_shader_subgroup +#define GL_KHR_shader_subgroup 1 +#define GL_SUBGROUP_SIZE_KHR 0x9532 +#define GL_SUBGROUP_SUPPORTED_STAGES_KHR 0x9533 +#define GL_SUBGROUP_SUPPORTED_FEATURES_KHR 0x9534 +#define GL_SUBGROUP_QUAD_ALL_STAGES_KHR 0x9535 +#define GL_SUBGROUP_FEATURE_BASIC_BIT_KHR 0x00000001 +#define GL_SUBGROUP_FEATURE_VOTE_BIT_KHR 0x00000002 +#define GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR 0x00000004 +#define GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR 0x00000008 +#define GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR 0x00000010 +#define GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR 0x00000020 +#define GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR 0x00000040 +#define GL_SUBGROUP_FEATURE_QUAD_BIT_KHR 0x00000080 +#endif /* GL_KHR_shader_subgroup */ + +#ifndef GL_KHR_texture_compression_astc_hdr +#define GL_KHR_texture_compression_astc_hdr 1 +#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 +#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 +#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 +#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 +#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 +#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 +#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 +#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 +#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 +#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 +#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA +#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB +#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC +#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD +#endif /* GL_KHR_texture_compression_astc_hdr */ + +#ifndef GL_KHR_texture_compression_astc_ldr +#define GL_KHR_texture_compression_astc_ldr 1 +#endif /* GL_KHR_texture_compression_astc_ldr */ + +#ifndef GL_KHR_texture_compression_astc_sliced_3d +#define GL_KHR_texture_compression_astc_sliced_3d 1 +#endif /* GL_KHR_texture_compression_astc_sliced_3d */ + +#ifndef GL_OES_byte_coordinates +#define GL_OES_byte_coordinates 1 +typedef void (APIENTRYP PFNGLMULTITEXCOORD1BOESPROC) (GLenum texture, GLbyte s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2BOESPROC) (GLenum texture, GLbyte s, GLbyte t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD1BOESPROC) (GLbyte s); +typedef void (APIENTRYP PFNGLTEXCOORD1BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD2BOESPROC) (GLbyte s, GLbyte t); +typedef void (APIENTRYP PFNGLTEXCOORD2BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD3BOESPROC) (GLbyte s, GLbyte t, GLbyte r); +typedef void (APIENTRYP PFNGLTEXCOORD3BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD4BOESPROC) (GLbyte s, GLbyte t, GLbyte r, GLbyte q); +typedef void (APIENTRYP PFNGLTEXCOORD4BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX2BOESPROC) (GLbyte x, GLbyte y); +typedef void (APIENTRYP PFNGLVERTEX2BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX3BOESPROC) (GLbyte x, GLbyte y, GLbyte z); +typedef void (APIENTRYP PFNGLVERTEX3BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX4BOESPROC) (GLbyte x, GLbyte y, GLbyte z, GLbyte w); +typedef void (APIENTRYP PFNGLVERTEX4BVOESPROC) (const GLbyte *coords); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiTexCoord1bOES (GLenum texture, GLbyte s); +GLAPI void APIENTRY glMultiTexCoord1bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord2bOES (GLenum texture, GLbyte s, GLbyte t); +GLAPI void APIENTRY glMultiTexCoord2bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord3bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r); +GLAPI void APIENTRY glMultiTexCoord3bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord4bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); +GLAPI void APIENTRY glMultiTexCoord4bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glTexCoord1bOES (GLbyte s); +GLAPI void APIENTRY glTexCoord1bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord2bOES (GLbyte s, GLbyte t); +GLAPI void APIENTRY glTexCoord2bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord3bOES (GLbyte s, GLbyte t, GLbyte r); +GLAPI void APIENTRY glTexCoord3bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord4bOES (GLbyte s, GLbyte t, GLbyte r, GLbyte q); +GLAPI void APIENTRY glTexCoord4bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex2bOES (GLbyte x, GLbyte y); +GLAPI void APIENTRY glVertex2bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex3bOES (GLbyte x, GLbyte y, GLbyte z); +GLAPI void APIENTRY glVertex3bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex4bOES (GLbyte x, GLbyte y, GLbyte z, GLbyte w); +GLAPI void APIENTRY glVertex4bvOES (const GLbyte *coords); +#endif +#endif /* GL_OES_byte_coordinates */ + +#ifndef GL_OES_compressed_paletted_texture +#define GL_OES_compressed_paletted_texture 1 +#define GL_PALETTE4_RGB8_OES 0x8B90 +#define GL_PALETTE4_RGBA8_OES 0x8B91 +#define GL_PALETTE4_R5_G6_B5_OES 0x8B92 +#define GL_PALETTE4_RGBA4_OES 0x8B93 +#define GL_PALETTE4_RGB5_A1_OES 0x8B94 +#define GL_PALETTE8_RGB8_OES 0x8B95 +#define GL_PALETTE8_RGBA8_OES 0x8B96 +#define GL_PALETTE8_R5_G6_B5_OES 0x8B97 +#define GL_PALETTE8_RGBA4_OES 0x8B98 +#define GL_PALETTE8_RGB5_A1_OES 0x8B99 +#endif /* GL_OES_compressed_paletted_texture */ + +#ifndef GL_OES_fixed_point +#define GL_OES_fixed_point 1 +typedef khronos_int32_t GLfixed; +#define GL_FIXED_OES 0x140C +typedef void (APIENTRYP PFNGLALPHAFUNCXOESPROC) (GLenum func, GLfixed ref); +typedef void (APIENTRYP PFNGLCLEARCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCLEARDEPTHXOESPROC) (GLfixed depth); +typedef void (APIENTRYP PFNGLCLIPPLANEXOESPROC) (GLenum plane, const GLfixed *equation); +typedef void (APIENTRYP PFNGLCOLOR4XOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLDEPTHRANGEXOESPROC) (GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLFOGXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLFOGXVOESPROC) (GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLFRUSTUMXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLGETCLIPPLANEXOESPROC) (GLenum plane, GLfixed *equation); +typedef void (APIENTRYP PFNGLGETFIXEDVOESPROC) (GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXENVXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLLIGHTMODELXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLLIGHTMODELXVOESPROC) (GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLLIGHTXVOESPROC) (GLenum light, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLLINEWIDTHXOESPROC) (GLfixed width); +typedef void (APIENTRYP PFNGLLOADMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLMATERIALXVOESPROC) (GLenum face, GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLMULTMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +typedef void (APIENTRYP PFNGLNORMAL3XOESPROC) (GLfixed nx, GLfixed ny, GLfixed nz); +typedef void (APIENTRYP PFNGLORTHOXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLPOINTPARAMETERXVOESPROC) (GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLPOINTSIZEXOESPROC) (GLfixed size); +typedef void (APIENTRYP PFNGLPOLYGONOFFSETXOESPROC) (GLfixed factor, GLfixed units); +typedef void (APIENTRYP PFNGLROTATEXOESPROC) (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLSCALEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLTEXENVXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXENVXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLTRANSLATEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLACCUMXOESPROC) (GLenum op, GLfixed value); +typedef void (APIENTRYP PFNGLBITMAPXOESPROC) (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); +typedef void (APIENTRYP PFNGLBLENDCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCLEARACCUMXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCOLOR3XOESPROC) (GLfixed red, GLfixed green, GLfixed blue); +typedef void (APIENTRYP PFNGLCOLOR3XVOESPROC) (const GLfixed *components); +typedef void (APIENTRYP PFNGLCOLOR4XVOESPROC) (const GLfixed *components); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLEVALCOORD1XOESPROC) (GLfixed u); +typedef void (APIENTRYP PFNGLEVALCOORD1XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLEVALCOORD2XOESPROC) (GLfixed u, GLfixed v); +typedef void (APIENTRYP PFNGLEVALCOORD2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLFEEDBACKBUFFERXOESPROC) (GLsizei n, GLenum type, const GLfixed *buffer); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETMAPXVOESPROC) (GLenum target, GLenum query, GLfixed *v); +typedef void (APIENTRYP PFNGLGETMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLGETPIXELMAPXVPROC) (GLenum map, GLint size, GLfixed *values); +typedef void (APIENTRYP PFNGLGETTEXGENXVOESPROC) (GLenum coord, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERXVOESPROC) (GLenum target, GLint level, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLINDEXXOESPROC) (GLfixed component); +typedef void (APIENTRYP PFNGLINDEXXVOESPROC) (const GLfixed *component); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMAP1XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); +typedef void (APIENTRYP PFNGLMAP2XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); +typedef void (APIENTRYP PFNGLMAPGRID1XOESPROC) (GLint n, GLfixed u1, GLfixed u2); +typedef void (APIENTRYP PFNGLMAPGRID2XOESPROC) (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1XOESPROC) (GLenum texture, GLfixed s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2XOESPROC) (GLenum texture, GLfixed s, GLfixed t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLNORMAL3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLPASSTHROUGHXOESPROC) (GLfixed token); +typedef void (APIENTRYP PFNGLPIXELMAPXPROC) (GLenum map, GLint size, const GLfixed *values); +typedef void (APIENTRYP PFNGLPIXELSTOREXPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLPIXELTRANSFERXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLPIXELZOOMXOESPROC) (GLfixed xfactor, GLfixed yfactor); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESXOESPROC) (GLsizei n, const GLuint *textures, const GLfixed *priorities); +typedef void (APIENTRYP PFNGLRASTERPOS2XOESPROC) (GLfixed x, GLfixed y); +typedef void (APIENTRYP PFNGLRASTERPOS2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRASTERPOS3XOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLRASTERPOS3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRASTERPOS4XOESPROC) (GLfixed x, GLfixed y, GLfixed z, GLfixed w); +typedef void (APIENTRYP PFNGLRASTERPOS4XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRECTXOESPROC) (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); +typedef void (APIENTRYP PFNGLRECTXVOESPROC) (const GLfixed *v1, const GLfixed *v2); +typedef void (APIENTRYP PFNGLTEXCOORD1XOESPROC) (GLfixed s); +typedef void (APIENTRYP PFNGLTEXCOORD1XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD2XOESPROC) (GLfixed s, GLfixed t); +typedef void (APIENTRYP PFNGLTEXCOORD2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD3XOESPROC) (GLfixed s, GLfixed t, GLfixed r); +typedef void (APIENTRYP PFNGLTEXCOORD3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD4XOESPROC) (GLfixed s, GLfixed t, GLfixed r, GLfixed q); +typedef void (APIENTRYP PFNGLTEXCOORD4XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXGENXOESPROC) (GLenum coord, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXGENXVOESPROC) (GLenum coord, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLVERTEX2XOESPROC) (GLfixed x); +typedef void (APIENTRYP PFNGLVERTEX2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLVERTEX3XOESPROC) (GLfixed x, GLfixed y); +typedef void (APIENTRYP PFNGLVERTEX3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLVERTEX4XOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLVERTEX4XVOESPROC) (const GLfixed *coords); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAlphaFuncxOES (GLenum func, GLfixed ref); +GLAPI void APIENTRY glClearColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glClearDepthxOES (GLfixed depth); +GLAPI void APIENTRY glClipPlanexOES (GLenum plane, const GLfixed *equation); +GLAPI void APIENTRY glColor4xOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glDepthRangexOES (GLfixed n, GLfixed f); +GLAPI void APIENTRY glFogxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glFogxvOES (GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glFrustumxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI void APIENTRY glGetClipPlanexOES (GLenum plane, GLfixed *equation); +GLAPI void APIENTRY glGetFixedvOES (GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexEnvxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glLightModelxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glLightModelxvOES (GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glLightxOES (GLenum light, GLenum pname, GLfixed param); +GLAPI void APIENTRY glLightxvOES (GLenum light, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glLineWidthxOES (GLfixed width); +GLAPI void APIENTRY glLoadMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMaterialxOES (GLenum face, GLenum pname, GLfixed param); +GLAPI void APIENTRY glMaterialxvOES (GLenum face, GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glMultMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMultiTexCoord4xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI void APIENTRY glNormal3xOES (GLfixed nx, GLfixed ny, GLfixed nz); +GLAPI void APIENTRY glOrthoxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI void APIENTRY glPointParameterxvOES (GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glPointSizexOES (GLfixed size); +GLAPI void APIENTRY glPolygonOffsetxOES (GLfixed factor, GLfixed units); +GLAPI void APIENTRY glRotatexOES (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glScalexOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glTexEnvxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexEnvxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glTexParameterxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glTranslatexOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glAccumxOES (GLenum op, GLfixed value); +GLAPI void APIENTRY glBitmapxOES (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); +GLAPI void APIENTRY glBlendColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glClearAccumxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glColor3xOES (GLfixed red, GLfixed green, GLfixed blue); +GLAPI void APIENTRY glColor3xvOES (const GLfixed *components); +GLAPI void APIENTRY glColor4xvOES (const GLfixed *components); +GLAPI void APIENTRY glConvolutionParameterxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glConvolutionParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glEvalCoord1xOES (GLfixed u); +GLAPI void APIENTRY glEvalCoord1xvOES (const GLfixed *coords); +GLAPI void APIENTRY glEvalCoord2xOES (GLfixed u, GLfixed v); +GLAPI void APIENTRY glEvalCoord2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glFeedbackBufferxOES (GLsizei n, GLenum type, const GLfixed *buffer); +GLAPI void APIENTRY glGetConvolutionParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetHistogramParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetLightxOES (GLenum light, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetMapxvOES (GLenum target, GLenum query, GLfixed *v); +GLAPI void APIENTRY glGetMaterialxOES (GLenum face, GLenum pname, GLfixed param); +GLAPI void APIENTRY glGetPixelMapxv (GLenum map, GLint size, GLfixed *values); +GLAPI void APIENTRY glGetTexGenxvOES (GLenum coord, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexLevelParameterxvOES (GLenum target, GLint level, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glIndexxOES (GLfixed component); +GLAPI void APIENTRY glIndexxvOES (const GLfixed *component); +GLAPI void APIENTRY glLoadTransposeMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMap1xOES (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); +GLAPI void APIENTRY glMap2xOES (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); +GLAPI void APIENTRY glMapGrid1xOES (GLint n, GLfixed u1, GLfixed u2); +GLAPI void APIENTRY glMapGrid2xOES (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); +GLAPI void APIENTRY glMultTransposeMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMultiTexCoord1xOES (GLenum texture, GLfixed s); +GLAPI void APIENTRY glMultiTexCoord1xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord2xOES (GLenum texture, GLfixed s, GLfixed t); +GLAPI void APIENTRY glMultiTexCoord2xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord3xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r); +GLAPI void APIENTRY glMultiTexCoord3xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord4xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glNormal3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glPassThroughxOES (GLfixed token); +GLAPI void APIENTRY glPixelMapx (GLenum map, GLint size, const GLfixed *values); +GLAPI void APIENTRY glPixelStorex (GLenum pname, GLfixed param); +GLAPI void APIENTRY glPixelTransferxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glPixelZoomxOES (GLfixed xfactor, GLfixed yfactor); +GLAPI void APIENTRY glPrioritizeTexturesxOES (GLsizei n, const GLuint *textures, const GLfixed *priorities); +GLAPI void APIENTRY glRasterPos2xOES (GLfixed x, GLfixed y); +GLAPI void APIENTRY glRasterPos2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRasterPos3xOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glRasterPos3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRasterPos4xOES (GLfixed x, GLfixed y, GLfixed z, GLfixed w); +GLAPI void APIENTRY glRasterPos4xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRectxOES (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); +GLAPI void APIENTRY glRectxvOES (const GLfixed *v1, const GLfixed *v2); +GLAPI void APIENTRY glTexCoord1xOES (GLfixed s); +GLAPI void APIENTRY glTexCoord1xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord2xOES (GLfixed s, GLfixed t); +GLAPI void APIENTRY glTexCoord2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord3xOES (GLfixed s, GLfixed t, GLfixed r); +GLAPI void APIENTRY glTexCoord3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord4xOES (GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI void APIENTRY glTexCoord4xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexGenxOES (GLenum coord, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexGenxvOES (GLenum coord, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glVertex2xOES (GLfixed x); +GLAPI void APIENTRY glVertex2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glVertex3xOES (GLfixed x, GLfixed y); +GLAPI void APIENTRY glVertex3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glVertex4xOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glVertex4xvOES (const GLfixed *coords); +#endif +#endif /* GL_OES_fixed_point */ + +#ifndef GL_OES_query_matrix +#define GL_OES_query_matrix 1 +typedef GLbitfield (APIENTRYP PFNGLQUERYMATRIXXOESPROC) (GLfixed *mantissa, GLint *exponent); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLbitfield APIENTRY glQueryMatrixxOES (GLfixed *mantissa, GLint *exponent); +#endif +#endif /* GL_OES_query_matrix */ + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif /* GL_OES_read_format */ + +#ifndef GL_OES_single_precision +#define GL_OES_single_precision 1 +typedef void (APIENTRYP PFNGLCLEARDEPTHFOESPROC) (GLclampf depth); +typedef void (APIENTRYP PFNGLCLIPPLANEFOESPROC) (GLenum plane, const GLfloat *equation); +typedef void (APIENTRYP PFNGLDEPTHRANGEFOESPROC) (GLclampf n, GLclampf f); +typedef void (APIENTRYP PFNGLFRUSTUMFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +typedef void (APIENTRYP PFNGLGETCLIPPLANEFOESPROC) (GLenum plane, GLfloat *equation); +typedef void (APIENTRYP PFNGLORTHOFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClearDepthfOES (GLclampf depth); +GLAPI void APIENTRY glClipPlanefOES (GLenum plane, const GLfloat *equation); +GLAPI void APIENTRY glDepthRangefOES (GLclampf n, GLclampf f); +GLAPI void APIENTRY glFrustumfOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +GLAPI void APIENTRY glGetClipPlanefOES (GLenum plane, GLfloat *equation); +GLAPI void APIENTRY glOrthofOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +#endif +#endif /* GL_OES_single_precision */ + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif /* GL_3DFX_multisample */ + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint mask); +#endif +#endif /* GL_3DFX_tbuffer */ + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif /* GL_3DFX_texture_compression_FXT1 */ + +#ifndef GL_AMD_blend_minmax_factor +#define GL_AMD_blend_minmax_factor 1 +#define GL_FACTOR_MIN_AMD 0x901C +#define GL_FACTOR_MAX_AMD 0x901D +#endif /* GL_AMD_blend_minmax_factor */ + +#ifndef GL_AMD_conservative_depth +#define GL_AMD_conservative_depth 1 +#endif /* GL_AMD_conservative_depth */ + +#ifndef GL_AMD_debug_output +#define GL_AMD_debug_output 1 +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); +#define GL_MAX_DEBUG_MESSAGE_LENGTH_AMD 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_AMD 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_AMD 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_AMD 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_AMD 0x9147 +#define GL_DEBUG_SEVERITY_LOW_AMD 0x9148 +#define GL_DEBUG_CATEGORY_API_ERROR_AMD 0x9149 +#define GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD 0x914A +#define GL_DEBUG_CATEGORY_DEPRECATION_AMD 0x914B +#define GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD 0x914C +#define GL_DEBUG_CATEGORY_PERFORMANCE_AMD 0x914D +#define GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD 0x914E +#define GL_DEBUG_CATEGORY_APPLICATION_AMD 0x914F +#define GL_DEBUG_CATEGORY_OTHER_AMD 0x9150 +typedef void (APIENTRYP PFNGLDEBUGMESSAGEENABLEAMDPROC) (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTAMDPROC) (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKAMDPROC) (GLDEBUGPROCAMD callback, void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGAMDPROC) (GLuint count, GLsizei bufSize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageEnableAMD (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertAMD (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackAMD (GLDEBUGPROCAMD callback, void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogAMD (GLuint count, GLsizei bufSize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif +#endif /* GL_AMD_debug_output */ + +#ifndef GL_AMD_depth_clamp_separate +#define GL_AMD_depth_clamp_separate 1 +#define GL_DEPTH_CLAMP_NEAR_AMD 0x901E +#define GL_DEPTH_CLAMP_FAR_AMD 0x901F +#endif /* GL_AMD_depth_clamp_separate */ + +#ifndef GL_AMD_draw_buffers_blend +#define GL_AMD_draw_buffers_blend 1 +typedef void (APIENTRYP PFNGLBLENDFUNCINDEXEDAMDPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONINDEXEDAMDPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncIndexedAMD (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateIndexedAMD (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glBlendEquationIndexedAMD (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateIndexedAMD (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif +#endif /* GL_AMD_draw_buffers_blend */ + +#ifndef GL_AMD_framebuffer_multisample_advanced +#define GL_AMD_framebuffer_multisample_advanced 1 +#define GL_RENDERBUFFER_STORAGE_SAMPLES_AMD 0x91B2 +#define GL_MAX_COLOR_FRAMEBUFFER_SAMPLES_AMD 0x91B3 +#define GL_MAX_COLOR_FRAMEBUFFER_STORAGE_SAMPLES_AMD 0x91B4 +#define GL_MAX_DEPTH_STENCIL_FRAMEBUFFER_SAMPLES_AMD 0x91B5 +#define GL_NUM_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B6 +#define GL_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B7 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleAdvancedAMD (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleAdvancedAMD (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_AMD_framebuffer_multisample_advanced */ + +#ifndef GL_AMD_framebuffer_sample_positions +#define GL_AMD_framebuffer_sample_positions 1 +#define GL_SUBSAMPLE_DISTANCE_AMD 0x883F +#define GL_PIXELS_PER_SAMPLE_PATTERN_X_AMD 0x91AE +#define GL_PIXELS_PER_SAMPLE_PATTERN_Y_AMD 0x91AF +#define GL_ALL_PIXELS_AMD 0xFFFFFFFF +typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC) (GLenum target, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC) (GLuint framebuffer, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERFVAMDPROC) (GLenum target, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERFVAMDPROC) (GLuint framebuffer, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferSamplePositionsfvAMD (GLenum target, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +GLAPI void APIENTRY glNamedFramebufferSamplePositionsfvAMD (GLuint framebuffer, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +GLAPI void APIENTRY glGetFramebufferParameterfvAMD (GLenum target, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +GLAPI void APIENTRY glGetNamedFramebufferParameterfvAMD (GLuint framebuffer, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +#endif +#endif /* GL_AMD_framebuffer_sample_positions */ + +#ifndef GL_AMD_gcn_shader +#define GL_AMD_gcn_shader 1 +#endif /* GL_AMD_gcn_shader */ + +#ifndef GL_AMD_gpu_shader_half_float +#define GL_AMD_gpu_shader_half_float 1 +#define GL_FLOAT16_NV 0x8FF8 +#define GL_FLOAT16_VEC2_NV 0x8FF9 +#define GL_FLOAT16_VEC3_NV 0x8FFA +#define GL_FLOAT16_VEC4_NV 0x8FFB +#define GL_FLOAT16_MAT2_AMD 0x91C5 +#define GL_FLOAT16_MAT3_AMD 0x91C6 +#define GL_FLOAT16_MAT4_AMD 0x91C7 +#define GL_FLOAT16_MAT2x3_AMD 0x91C8 +#define GL_FLOAT16_MAT2x4_AMD 0x91C9 +#define GL_FLOAT16_MAT3x2_AMD 0x91CA +#define GL_FLOAT16_MAT3x4_AMD 0x91CB +#define GL_FLOAT16_MAT4x2_AMD 0x91CC +#define GL_FLOAT16_MAT4x3_AMD 0x91CD +#endif /* GL_AMD_gpu_shader_half_float */ + +#ifndef GL_AMD_gpu_shader_int16 +#define GL_AMD_gpu_shader_int16 1 +#endif /* GL_AMD_gpu_shader_int16 */ + +#ifndef GL_AMD_gpu_shader_int64 +#define GL_AMD_gpu_shader_int64 1 +typedef khronos_int64_t GLint64EXT; +#define GL_INT64_NV 0x140E +#define GL_UNSIGNED_INT64_NV 0x140F +#define GL_INT8_NV 0x8FE0 +#define GL_INT8_VEC2_NV 0x8FE1 +#define GL_INT8_VEC3_NV 0x8FE2 +#define GL_INT8_VEC4_NV 0x8FE3 +#define GL_INT16_NV 0x8FE4 +#define GL_INT16_VEC2_NV 0x8FE5 +#define GL_INT16_VEC3_NV 0x8FE6 +#define GL_INT16_VEC4_NV 0x8FE7 +#define GL_INT64_VEC2_NV 0x8FE9 +#define GL_INT64_VEC3_NV 0x8FEA +#define GL_INT64_VEC4_NV 0x8FEB +#define GL_UNSIGNED_INT8_NV 0x8FEC +#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED +#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE +#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF +#define GL_UNSIGNED_INT16_NV 0x8FF0 +#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 +#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 +#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 +#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 +typedef void (APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); +GLAPI void APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); +GLAPI void APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); +GLAPI void APIENTRY glGetUniformui64vNV (GLuint program, GLint location, GLuint64EXT *params); +GLAPI void APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); +GLAPI void APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); +GLAPI void APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_AMD_gpu_shader_int64 */ + +#ifndef GL_AMD_interleaved_elements +#define GL_AMD_interleaved_elements 1 +#define GL_VERTEX_ELEMENT_SWIZZLE_AMD 0x91A4 +#define GL_VERTEX_ID_SWIZZLE_AMD 0x91A5 +typedef void (APIENTRYP PFNGLVERTEXATTRIBPARAMETERIAMDPROC) (GLuint index, GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribParameteriAMD (GLuint index, GLenum pname, GLint param); +#endif +#endif /* GL_AMD_interleaved_elements */ + +#ifndef GL_AMD_multi_draw_indirect +#define GL_AMD_multi_draw_indirect 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTAMDPROC) (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectAMD (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectAMD (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); +#endif +#endif /* GL_AMD_multi_draw_indirect */ + +#ifndef GL_AMD_name_gen_delete +#define GL_AMD_name_gen_delete 1 +#define GL_DATA_BUFFER_AMD 0x9151 +#define GL_PERFORMANCE_MONITOR_AMD 0x9152 +#define GL_QUERY_OBJECT_AMD 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_AMD 0x9154 +#define GL_SAMPLER_OBJECT_AMD 0x9155 +typedef void (APIENTRYP PFNGLGENNAMESAMDPROC) (GLenum identifier, GLuint num, GLuint *names); +typedef void (APIENTRYP PFNGLDELETENAMESAMDPROC) (GLenum identifier, GLuint num, const GLuint *names); +typedef GLboolean (APIENTRYP PFNGLISNAMEAMDPROC) (GLenum identifier, GLuint name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenNamesAMD (GLenum identifier, GLuint num, GLuint *names); +GLAPI void APIENTRY glDeleteNamesAMD (GLenum identifier, GLuint num, const GLuint *names); +GLAPI GLboolean APIENTRY glIsNameAMD (GLenum identifier, GLuint name); +#endif +#endif /* GL_AMD_name_gen_delete */ + +#ifndef GL_AMD_occlusion_query_event +#define GL_AMD_occlusion_query_event 1 +#define GL_OCCLUSION_QUERY_EVENT_MASK_AMD 0x874F +#define GL_QUERY_DEPTH_PASS_EVENT_BIT_AMD 0x00000001 +#define GL_QUERY_DEPTH_FAIL_EVENT_BIT_AMD 0x00000002 +#define GL_QUERY_STENCIL_FAIL_EVENT_BIT_AMD 0x00000004 +#define GL_QUERY_DEPTH_BOUNDS_FAIL_EVENT_BIT_AMD 0x00000008 +#define GL_QUERY_ALL_EVENT_BITS_AMD 0xFFFFFFFF +typedef void (APIENTRYP PFNGLQUERYOBJECTPARAMETERUIAMDPROC) (GLenum target, GLuint id, GLenum pname, GLuint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glQueryObjectParameteruiAMD (GLenum target, GLuint id, GLenum pname, GLuint param); +#endif +#endif /* GL_AMD_occlusion_query_event */ + +#ifndef GL_AMD_performance_monitor +#define GL_AMD_performance_monitor 1 +#define GL_COUNTER_TYPE_AMD 0x8BC0 +#define GL_COUNTER_RANGE_AMD 0x8BC1 +#define GL_UNSIGNED_INT64_AMD 0x8BC2 +#define GL_PERCENTAGE_AMD 0x8BC3 +#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 +#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 +#define GL_PERFMON_RESULT_AMD 0x8BC6 +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, void *data); +typedef void (APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +typedef void (APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +GLAPI void APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, void *data); +GLAPI void APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +GLAPI void APIENTRY glBeginPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glEndPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif +#endif /* GL_AMD_performance_monitor */ + +#ifndef GL_AMD_pinned_memory +#define GL_AMD_pinned_memory 1 +#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160 +#endif /* GL_AMD_pinned_memory */ + +#ifndef GL_AMD_query_buffer_object +#define GL_AMD_query_buffer_object 1 +#define GL_QUERY_BUFFER_AMD 0x9192 +#define GL_QUERY_BUFFER_BINDING_AMD 0x9193 +#define GL_QUERY_RESULT_NO_WAIT_AMD 0x9194 +#endif /* GL_AMD_query_buffer_object */ + +#ifndef GL_AMD_sample_positions +#define GL_AMD_sample_positions 1 +typedef void (APIENTRYP PFNGLSETMULTISAMPLEFVAMDPROC) (GLenum pname, GLuint index, const GLfloat *val); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSetMultisamplefvAMD (GLenum pname, GLuint index, const GLfloat *val); +#endif +#endif /* GL_AMD_sample_positions */ + +#ifndef GL_AMD_seamless_cubemap_per_texture +#define GL_AMD_seamless_cubemap_per_texture 1 +#endif /* GL_AMD_seamless_cubemap_per_texture */ + +#ifndef GL_AMD_shader_atomic_counter_ops +#define GL_AMD_shader_atomic_counter_ops 1 +#endif /* GL_AMD_shader_atomic_counter_ops */ + +#ifndef GL_AMD_shader_ballot +#define GL_AMD_shader_ballot 1 +#endif /* GL_AMD_shader_ballot */ + +#ifndef GL_AMD_shader_explicit_vertex_parameter +#define GL_AMD_shader_explicit_vertex_parameter 1 +#endif /* GL_AMD_shader_explicit_vertex_parameter */ + +#ifndef GL_AMD_shader_gpu_shader_half_float_fetch +#define GL_AMD_shader_gpu_shader_half_float_fetch 1 +#endif /* GL_AMD_shader_gpu_shader_half_float_fetch */ + +#ifndef GL_AMD_shader_image_load_store_lod +#define GL_AMD_shader_image_load_store_lod 1 +#endif /* GL_AMD_shader_image_load_store_lod */ + +#ifndef GL_AMD_shader_stencil_export +#define GL_AMD_shader_stencil_export 1 +#endif /* GL_AMD_shader_stencil_export */ + +#ifndef GL_AMD_shader_trinary_minmax +#define GL_AMD_shader_trinary_minmax 1 +#endif /* GL_AMD_shader_trinary_minmax */ + +#ifndef GL_AMD_sparse_texture +#define GL_AMD_sparse_texture 1 +#define GL_VIRTUAL_PAGE_SIZE_X_AMD 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_AMD 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_AMD 0x9197 +#define GL_MAX_SPARSE_TEXTURE_SIZE_AMD 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_AMD 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS 0x919A +#define GL_MIN_SPARSE_LEVEL_AMD 0x919B +#define GL_MIN_LOD_WARNING_AMD 0x919C +#define GL_TEXTURE_STORAGE_SPARSE_BIT_AMD 0x00000001 +typedef void (APIENTRYP PFNGLTEXSTORAGESPARSEAMDPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +typedef void (APIENTRYP PFNGLTEXTURESTORAGESPARSEAMDPROC) (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexStorageSparseAMD (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +GLAPI void APIENTRY glTextureStorageSparseAMD (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +#endif +#endif /* GL_AMD_sparse_texture */ + +#ifndef GL_AMD_stencil_operation_extended +#define GL_AMD_stencil_operation_extended 1 +#define GL_SET_AMD 0x874A +#define GL_REPLACE_VALUE_AMD 0x874B +#define GL_STENCIL_OP_VALUE_AMD 0x874C +#define GL_STENCIL_BACK_OP_VALUE_AMD 0x874D +typedef void (APIENTRYP PFNGLSTENCILOPVALUEAMDPROC) (GLenum face, GLuint value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpValueAMD (GLenum face, GLuint value); +#endif +#endif /* GL_AMD_stencil_operation_extended */ + +#ifndef GL_AMD_texture_gather_bias_lod +#define GL_AMD_texture_gather_bias_lod 1 +#endif /* GL_AMD_texture_gather_bias_lod */ + +#ifndef GL_AMD_texture_texture4 +#define GL_AMD_texture_texture4 1 +#endif /* GL_AMD_texture_texture4 */ + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#define GL_AMD_transform_feedback3_lines_triangles 1 +#endif /* GL_AMD_transform_feedback3_lines_triangles */ + +#ifndef GL_AMD_transform_feedback4 +#define GL_AMD_transform_feedback4 1 +#define GL_STREAM_RASTERIZATION_AMD 0x91A0 +#endif /* GL_AMD_transform_feedback4 */ + +#ifndef GL_AMD_vertex_shader_layer +#define GL_AMD_vertex_shader_layer 1 +#endif /* GL_AMD_vertex_shader_layer */ + +#ifndef GL_AMD_vertex_shader_tessellator +#define GL_AMD_vertex_shader_tessellator 1 +#define GL_SAMPLER_BUFFER_AMD 0x9001 +#define GL_INT_SAMPLER_BUFFER_AMD 0x9002 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_AMD 0x9003 +#define GL_TESSELLATION_MODE_AMD 0x9004 +#define GL_TESSELLATION_FACTOR_AMD 0x9005 +#define GL_DISCRETE_AMD 0x9006 +#define GL_CONTINUOUS_AMD 0x9007 +typedef void (APIENTRYP PFNGLTESSELLATIONFACTORAMDPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLTESSELLATIONMODEAMDPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTessellationFactorAMD (GLfloat factor); +GLAPI void APIENTRY glTessellationModeAMD (GLenum mode); +#endif +#endif /* GL_AMD_vertex_shader_tessellator */ + +#ifndef GL_AMD_vertex_shader_viewport_index +#define GL_AMD_vertex_shader_viewport_index 1 +#endif /* GL_AMD_vertex_shader_viewport_index */ + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_APPLE_aux_depth_stencil 1 +#define GL_AUX_DEPTH_STENCIL_APPLE 0x8A14 +#endif /* GL_APPLE_aux_depth_stencil */ + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif /* GL_APPLE_client_storage */ + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#define GL_ELEMENT_ARRAY_APPLE 0x8A0C +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8A0D +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x8A0E +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum type, const void *pointer); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif +#endif /* GL_APPLE_element_array */ + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei n, GLuint *fences); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint fence); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum object, GLuint name); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum object, GLint name); +#endif +#endif /* GL_APPLE_fence */ + +#ifndef GL_APPLE_float_pixels +#define GL_APPLE_float_pixels 1 +#define GL_HALF_APPLE 0x140B +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#define GL_COLOR_FLOAT_APPLE 0x8A0F +#endif /* GL_APPLE_float_pixels */ + +#ifndef GL_APPLE_flush_buffer_range +#define GL_APPLE_flush_buffer_range 1 +#define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 +#define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 +typedef void (APIENTRYP PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferParameteriAPPLE (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glFlushMappedBufferRangeAPPLE (GLenum target, GLintptr offset, GLsizeiptr size); +#endif +#endif /* GL_APPLE_flush_buffer_range */ + +#ifndef GL_APPLE_object_purgeable +#define GL_APPLE_object_purgeable 1 +#define GL_BUFFER_OBJECT_APPLE 0x85B3 +#define GL_RELEASED_APPLE 0x8A19 +#define GL_VOLATILE_APPLE 0x8A1A +#define GL_RETAINED_APPLE 0x8A1B +#define GL_UNDEFINED_APPLE 0x8A1C +#define GL_PURGEABLE_APPLE 0x8A1D +typedef GLenum (APIENTRYP PFNGLOBJECTPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef GLenum (APIENTRYP PFNGLOBJECTUNPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVAPPLEPROC) (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glObjectPurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI GLenum APIENTRY glObjectUnpurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI void APIENTRY glGetObjectParameterivAPPLE (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif +#endif /* GL_APPLE_object_purgeable */ + +#ifndef GL_APPLE_rgb_422 +#define GL_APPLE_rgb_422 1 +#define GL_RGB_422_APPLE 0x8A1F +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#define GL_RGB_RAW_422_APPLE 0x8A51 +#endif /* GL_APPLE_rgb_422 */ + +#ifndef GL_APPLE_row_bytes +#define GL_APPLE_row_bytes 1 +#define GL_PACK_ROW_BYTES_APPLE 0x8A15 +#define GL_UNPACK_ROW_BYTES_APPLE 0x8A16 +#endif /* GL_APPLE_row_bytes */ + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif /* GL_APPLE_specular_vector */ + +#ifndef GL_APPLE_texture_range +#define GL_APPLE_texture_range 1 +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +typedef void (APIENTRYP PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, const void *pointer); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureRangeAPPLE (GLenum target, GLsizei length, const void *pointer); +GLAPI void APIENTRY glGetTexParameterPointervAPPLE (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_APPLE_texture_range */ + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif /* GL_APPLE_transform_hint */ + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint array); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint array); +#endif +#endif /* GL_APPLE_vertex_array_object */ + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CLIENT_APPLE 0x85B4 +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei length, void *pointer); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei length, void *pointer); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum pname, GLint param); +#endif +#endif /* GL_APPLE_vertex_array_range */ + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_APPLE_vertex_program_evaluators 1 +#define GL_VERTEX_ATTRIB_MAP1_APPLE 0x8A00 +#define GL_VERTEX_ATTRIB_MAP2_APPLE 0x8A01 +#define GL_VERTEX_ATTRIB_MAP1_SIZE_APPLE 0x8A02 +#define GL_VERTEX_ATTRIB_MAP1_COEFF_APPLE 0x8A03 +#define GL_VERTEX_ATTRIB_MAP1_ORDER_APPLE 0x8A04 +#define GL_VERTEX_ATTRIB_MAP1_DOMAIN_APPLE 0x8A05 +#define GL_VERTEX_ATTRIB_MAP2_SIZE_APPLE 0x8A06 +#define GL_VERTEX_ATTRIB_MAP2_COEFF_APPLE 0x8A07 +#define GL_VERTEX_ATTRIB_MAP2_ORDER_APPLE 0x8A08 +#define GL_VERTEX_ATTRIB_MAP2_DOMAIN_APPLE 0x8A09 +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef GLboolean (APIENTRYP PFNGLISVERTEXATTRIBENABLEDAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEnableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glDisableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI GLboolean APIENTRY glIsVertexAttribEnabledAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glMapVertexAttrib1dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib1fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void APIENTRY glMapVertexAttrib2dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib2fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif +#endif /* GL_APPLE_vertex_program_evaluators */ + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#define GL_YCBCR_422_APPLE 0x85B9 +#endif /* GL_APPLE_ycbcr_422 */ + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_ATI_draw_buffers */ + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum type, const void *pointer); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum mode, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif +#endif /* GL_ATI_element_array */ + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum pname, const GLint *param); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum pname, GLfloat *param); +#endif +#endif /* GL_ATI_envmap_bumpmap */ + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint range); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint dst, GLuint coord, GLenum swizzle); +GLAPI void APIENTRY glSampleMapATI (GLuint dst, GLuint interp, GLenum swizzle); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint dst, const GLfloat *value); +#endif +#endif /* GL_ATI_fragment_shader */ + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +typedef void *(APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void *APIENTRY glMapObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint buffer); +#endif +#endif /* GL_ATI_map_object_buffer */ + +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo 1 +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif /* GL_ATI_meminfo */ + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +#define GL_RGBA_FLOAT_MODE_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif /* GL_ATI_pixel_format_float */ + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum pname, GLint param); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum pname, GLfloat param); +#endif +#endif /* GL_ATI_pn_triangles */ + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif +#endif /* GL_ATI_separate_stencil */ + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif /* GL_ATI_text_fragment_shader */ + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif /* GL_ATI_texture_env_combine3 */ + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif /* GL_ATI_texture_float */ + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif /* GL_ATI_texture_mirror_once */ + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const void *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei size, const void *pointer, GLenum usage); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint buffer, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glArrayObjectATI (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum array, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum array, GLenum pname, GLint *params); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint id, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint id, GLenum pname, GLint *params); +#endif +#endif /* GL_ATI_vertex_array_object */ + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint index, GLenum pname, GLint *params); +#endif +#endif /* GL_ATI_vertex_attrib_array_object */ + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum stream, GLshort x); +GLAPI void APIENTRY glVertexStream1svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream1iATI (GLenum stream, GLint x); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream1fATI (GLenum stream, GLfloat x); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream1dATI (GLenum stream, GLdouble x); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream2sATI (GLenum stream, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexStream2svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream2iATI (GLenum stream, GLint x, GLint y); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream2fATI (GLenum stream, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream2dATI (GLenum stream, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream3sATI (GLenum stream, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream3iATI (GLenum stream, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream3fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream3dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream4sATI (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexStream4svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream4iATI (GLenum stream, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream4fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream4dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glNormalStream3bATI (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum stream, const GLbyte *coords); +GLAPI void APIENTRY glNormalStream3sATI (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +GLAPI void APIENTRY glNormalStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glNormalStream3iATI (GLenum stream, GLint nx, GLint ny, GLint nz); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glNormalStream3fATI (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glNormalStream3dATI (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum stream); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum pname, GLint param); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum pname, GLfloat param); +#endif +#endif /* GL_ATI_vertex_streams */ + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif /* GL_EXT_422_pixels */ + +#ifndef GL_EXT_EGL_image_storage +#define GL_EXT_EGL_image_storage 1 +typedef void *GLeglImageOES; +typedef void (APIENTRYP PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC) (GLenum target, GLeglImageOES image, const GLint* attrib_list); +typedef void (APIENTRYP PFNGLEGLIMAGETARGETTEXTURESTORAGEEXTPROC) (GLuint texture, GLeglImageOES image, const GLint* attrib_list); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEGLImageTargetTexStorageEXT (GLenum target, GLeglImageOES image, const GLint* attrib_list); +GLAPI void APIENTRY glEGLImageTargetTextureStorageEXT (GLuint texture, GLeglImageOES image, const GLint* attrib_list); +#endif +#endif /* GL_EXT_EGL_image_storage */ + +#ifndef GL_EXT_EGL_sync +#define GL_EXT_EGL_sync 1 +#endif /* GL_EXT_EGL_sync */ + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#define GL_ABGR_EXT 0x8000 +#endif /* GL_EXT_abgr */ + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif /* GL_EXT_bgra */ + +#ifndef GL_EXT_bindable_uniform +#define GL_EXT_bindable_uniform 1 +#define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 +#define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 +#define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 +#define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED +#define GL_UNIFORM_BUFFER_EXT 0x8DEE +#define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF +typedef void (APIENTRYP PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); +typedef GLint (APIENTRYP PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); +typedef GLintptr (APIENTRYP PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformBufferEXT (GLuint program, GLint location, GLuint buffer); +GLAPI GLint APIENTRY glGetUniformBufferSizeEXT (GLuint program, GLint location); +GLAPI GLintptr APIENTRY glGetUniformOffsetEXT (GLuint program, GLint location); +#endif +#endif /* GL_EXT_bindable_uniform */ + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +#endif +#endif /* GL_EXT_blend_color */ + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum modeRGB, GLenum modeAlpha); +#endif +#endif /* GL_EXT_blend_equation_separate */ + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif +#endif /* GL_EXT_blend_func_separate */ + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif /* GL_EXT_blend_logic_op */ + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_BLEND_EQUATION_EXT 0x8009 +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum mode); +#endif +#endif /* GL_EXT_blend_minmax */ + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif /* GL_EXT_blend_subtract */ + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif /* GL_EXT_clip_volume_hint */ + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif /* GL_EXT_cmyka */ + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif +#endif /* GL_EXT_color_subtable */ + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint first, GLsizei count); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif +#endif /* GL_EXT_compiled_vertex_array */ + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum target, GLenum format, GLenum type, void *image); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +#endif +#endif /* GL_EXT_convolution */ + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte tx, GLbyte ty, GLbyte tz); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glTangent3dEXT (GLdouble tx, GLdouble ty, GLdouble tz); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glTangent3fEXT (GLfloat tx, GLfloat ty, GLfloat tz); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glTangent3iEXT (GLint tx, GLint ty, GLint tz); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *v); +GLAPI void APIENTRY glTangent3sEXT (GLshort tx, GLshort ty, GLshort tz); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *v); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte bx, GLbyte by, GLbyte bz); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble bx, GLdouble by, GLdouble bz); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat bx, GLfloat by, GLfloat bz); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glBinormal3iEXT (GLint bx, GLint by, GLint bz); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *v); +GLAPI void APIENTRY glBinormal3sEXT (GLshort bx, GLshort by, GLshort bz); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *v); +GLAPI void APIENTRY glTangentPointerEXT (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_coordinate_frame */ + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_EXT_copy_texture */ + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum pname, GLdouble *params); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_cull_vertex */ + +#ifndef GL_EXT_debug_label +#define GL_EXT_debug_label 1 +#define GL_PROGRAM_PIPELINE_OBJECT_EXT 0x8A4F +#define GL_PROGRAM_OBJECT_EXT 0x8B40 +#define GL_SHADER_OBJECT_EXT 0x8B48 +#define GL_BUFFER_OBJECT_EXT 0x9151 +#define GL_QUERY_OBJECT_EXT 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_EXT 0x9154 +typedef void (APIENTRYP PFNGLLABELOBJECTEXTPROC) (GLenum type, GLuint object, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLabelObjectEXT (GLenum type, GLuint object, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectLabelEXT (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_EXT_debug_label */ + +#ifndef GL_EXT_debug_marker +#define GL_EXT_debug_marker 1 +typedef void (APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (APIENTRYP PFNGLPOPGROUPMARKEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glInsertEventMarkerEXT (GLsizei length, const GLchar *marker); +GLAPI void APIENTRY glPushGroupMarkerEXT (GLsizei length, const GLchar *marker); +GLAPI void APIENTRY glPopGroupMarkerEXT (void); +#endif +#endif /* GL_EXT_debug_marker */ + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd zmin, GLclampd zmax); +#endif +#endif /* GL_EXT_depth_bounds_test */ + +#ifndef GL_EXT_direct_state_access +#define GL_EXT_direct_state_access 1 +#define GL_PROGRAM_MATRIX_EXT 0x8E2D +#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E +#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F +typedef void (APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +typedef void (APIENTRYP PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +typedef void (APIENTRYP PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum target, GLuint index, GLdouble *data); +typedef void (APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum target, GLuint index, void **data); +typedef void (APIENTRYP PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint lod, void *img); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint lod, void *img); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, void **params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, void *string); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); +typedef void (APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYINDEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYNORMALOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERVEXTPROC) (GLuint vaobj, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERVEXTPROC) (GLuint vaobj, GLenum pname, void **param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC) (GLuint framebuffer, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC) (GLuint program, GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEEXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTEXTPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC) (GLuint vaobj, GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixLoadIdentityEXT (GLenum mode); +GLAPI void APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixPopEXT (GLenum mode); +GLAPI void APIENTRY glMatrixPushEXT (GLenum mode); +GLAPI void APIENTRY glClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glPushClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glTextureParameterfEXT (GLuint texture, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTextureParameteriEXT (GLuint texture, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetTextureImageEXT (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureLevelParameterfvEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameterivEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glBindMultiTextureEXT (GLenum texunit, GLenum target, GLuint texture); +GLAPI void APIENTRY glMultiTexCoordPointerEXT (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glMultiTexEnvfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexEnviEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexGendEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +GLAPI void APIENTRY glMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void APIENTRY glMultiTexGenfEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexGeniEXT (GLenum texunit, GLenum coord, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexParameteriEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetMultiTexImageEXT (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glEnableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glGetFloatIndexedvEXT (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoubleIndexedvEXT (GLenum target, GLuint index, GLdouble *data); +GLAPI void APIENTRY glGetPointerIndexedvEXT (GLenum target, GLuint index, void **data); +GLAPI void APIENTRY glEnableIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glDisableIndexedEXT (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glGetIntegerIndexedvEXT (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glGetBooleanIndexedvEXT (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glCompressedTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glGetCompressedTextureImageEXT (GLuint texture, GLenum target, GLint lod, void *img); +GLAPI void APIENTRY glCompressedMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glGetCompressedMultiTexImageEXT (GLenum texunit, GLenum target, GLint lod, void *img); +GLAPI void APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glNamedBufferDataEXT (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void *APIENTRY glMapNamedBufferEXT (GLuint buffer, GLenum access); +GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT (GLuint buffer); +GLAPI void APIENTRY glGetNamedBufferParameterivEXT (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferPointervEXT (GLuint buffer, GLenum pname, void **params); +GLAPI void APIENTRY glGetNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glTextureBufferEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glMultiTexBufferEXT (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT (GLuint program, GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT (GLuint program, GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT (GLuint program, GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT (GLuint program, GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glEnableClientStateiEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateiEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glGetFloati_vEXT (GLenum pname, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetDoublei_vEXT (GLenum pname, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetPointeri_vEXT (GLenum pname, GLuint index, void **params); +GLAPI void APIENTRY glNamedProgramStringEXT (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); +GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT (GLuint program, GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT (GLuint program, GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT (GLuint program, GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT (GLuint program, GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramivEXT (GLuint program, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedProgramStringEXT (GLuint program, GLenum target, GLenum pname, void *string); +GLAPI void APIENTRY glNamedRenderbufferStorageEXT (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glNamedFramebufferTexture1DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture2DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture3DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateTextureMipmapEXT (GLuint texture, GLenum target); +GLAPI void APIENTRY glGenerateMultiTexMipmapEXT (GLenum texunit, GLenum target); +GLAPI void APIENTRY glFramebufferDrawBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glFramebufferDrawBuffersEXT (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glFramebufferReadBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glGetFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedCopyBufferSubDataEXT (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glNamedFramebufferTextureEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +GLAPI void APIENTRY glTextureRenderbufferEXT (GLuint texture, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glMultiTexRenderbufferEXT (GLenum texunit, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glVertexArrayVertexOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayEdgeFlagOffsetEXT (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayIndexOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayNormalOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayMultiTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayFogCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArraySecondaryColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayVertexAttribOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayVertexAttribIOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glEnableVertexArrayEXT (GLuint vaobj, GLenum array); +GLAPI void APIENTRY glDisableVertexArrayEXT (GLuint vaobj, GLenum array); +GLAPI void APIENTRY glEnableVertexArrayAttribEXT (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glDisableVertexArrayAttribEXT (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glGetVertexArrayIntegervEXT (GLuint vaobj, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayPointervEXT (GLuint vaobj, GLenum pname, void **param); +GLAPI void APIENTRY glGetVertexArrayIntegeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayPointeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, void **param); +GLAPI void *APIENTRY glMapNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glNamedBufferStorageEXT (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glClearNamedBufferDataEXT (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearNamedBufferSubDataEXT (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glNamedFramebufferParameteriEXT (GLuint framebuffer, GLenum pname, GLint param); +GLAPI void APIENTRY glGetNamedFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1dEXT (GLuint program, GLint location, GLdouble x); +GLAPI void APIENTRY glProgramUniform2dEXT (GLuint program, GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glProgramUniform3dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glProgramUniform4dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramUniform1dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glTextureBufferRangeEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glTextureStorage2DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureStorage3DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glVertexArrayBindVertexBufferEXT (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexArrayVertexAttribFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribIFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribLFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribBindingEXT (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexArrayVertexBindingDivisorEXT (GLuint vaobj, GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glTexturePageCommitmentEXT (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +GLAPI void APIENTRY glVertexArrayVertexAttribDivisorEXT (GLuint vaobj, GLuint index, GLuint divisor); +#endif +#endif /* GL_EXT_direct_state_access */ + +#ifndef GL_EXT_draw_buffers2 +#define GL_EXT_draw_buffers2 1 +typedef void (APIENTRYP PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaskIndexedEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +#endif +#endif /* GL_EXT_draw_buffers2 */ + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_EXT_draw_instanced */ + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +#endif +#endif /* GL_EXT_draw_range_elements */ + +#ifndef GL_EXT_external_buffer +#define GL_EXT_external_buffer 1 +typedef void *GLeglClientBufferEXT; +typedef void (APIENTRYP PFNGLBUFFERSTORAGEEXTERNALEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTERNALEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferStorageExternalEXT (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +GLAPI void APIENTRY glNamedBufferStorageExternalEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +#endif +#endif /* GL_EXT_external_buffer */ + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat coord); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *coord); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble coord); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_fog_coord */ + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit 1 +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlitFramebufferEXT (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* GL_EXT_framebuffer_blit */ + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample 1 +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_EXT_framebuffer_multisample */ + +#ifndef GL_EXT_framebuffer_multisample_blit_scaled +#define GL_EXT_framebuffer_multisample_blit_scaled 1 +#define GL_SCALED_RESOLVE_FASTEST_EXT 0x90BA +#define GL_SCALED_RESOLVE_NICEST_EXT 0x90BB +#endif /* GL_EXT_framebuffer_multisample_blit_scaled */ + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum target); +#endif +#endif /* GL_EXT_framebuffer_object */ + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB 1 +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA +#endif /* GL_EXT_framebuffer_sRGB */ + +#ifndef GL_EXT_geometry_shader4 +#define GL_EXT_geometry_shader4 1 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE +#define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 +#define GL_LINES_ADJACENCY_EXT 0x000A +#define GL_LINE_STRIP_ADJACENCY_EXT 0x000B +#define GL_TRIANGLES_ADJACENCY_EXT 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 +#define GL_PROGRAM_POINT_SIZE_EXT 0x8642 +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); +#endif +#endif /* GL_EXT_geometry_shader4 */ + +#ifndef GL_EXT_gpu_program_parameters +#define GL_EXT_gpu_program_parameters 1 +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramEnvParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif +#endif /* GL_EXT_gpu_program_parameters */ + +#ifndef GL_EXT_gpu_shader4 +#define GL_EXT_gpu_shader4 1 +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 +#define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 +#define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 +#define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 +#define GL_INT_SAMPLER_1D_EXT 0x8DC9 +#define GL_INT_SAMPLER_2D_EXT 0x8DCA +#define GL_INT_SAMPLER_3D_EXT 0x8DCB +#define GL_INT_SAMPLER_CUBE_EXT 0x8DCC +#define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD +#define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_EXT 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_EXT 0x8905 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_EXT 0x88FD +typedef void (APIENTRYP PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformuivEXT (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocationEXT (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1uiEXT (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2uiEXT (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glVertexAttribI1iEXT (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2iEXT (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3iEXT (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4iEXT (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1uiEXT (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2uiEXT (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3uiEXT (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4uiEXT (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bvEXT (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4svEXT (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubvEXT (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usvEXT (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribIPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribIivEXT (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuivEXT (GLuint index, GLenum pname, GLuint *params); +#endif +#endif /* GL_EXT_gpu_shader4 */ + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogramEXT (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmaxEXT (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogramEXT (GLenum target); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum target); +#endif +#endif /* GL_EXT_histogram */ + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif /* GL_EXT_index_array_formats */ + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum func, GLclampf ref); +#endif +#endif /* GL_EXT_index_func */ + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum face, GLenum mode); +#endif +#endif /* GL_EXT_index_material */ + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif /* GL_EXT_index_texture */ + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum mode); +GLAPI void APIENTRY glTextureLightEXT (GLenum pname); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum face, GLenum mode); +#endif +#endif /* GL_EXT_light_texture */ + +#ifndef GL_EXT_memory_object +#define GL_EXT_memory_object 1 +#define GL_TEXTURE_TILING_EXT 0x9580 +#define GL_DEDICATED_MEMORY_OBJECT_EXT 0x9581 +#define GL_PROTECTED_MEMORY_OBJECT_EXT 0x959B +#define GL_NUM_TILING_TYPES_EXT 0x9582 +#define GL_TILING_TYPES_EXT 0x9583 +#define GL_OPTIMAL_TILING_EXT 0x9584 +#define GL_LINEAR_TILING_EXT 0x9585 +#define GL_NUM_DEVICE_UUIDS_EXT 0x9596 +#define GL_DEVICE_UUID_EXT 0x9597 +#define GL_DRIVER_UUID_EXT 0x9598 +#define GL_UUID_SIZE_EXT 16 +typedef void (APIENTRYP PFNGLGETUNSIGNEDBYTEVEXTPROC) (GLenum pname, GLubyte *data); +typedef void (APIENTRYP PFNGLGETUNSIGNEDBYTEI_VEXTPROC) (GLenum target, GLuint index, GLubyte *data); +typedef void (APIENTRYP PFNGLDELETEMEMORYOBJECTSEXTPROC) (GLsizei n, const GLuint *memoryObjects); +typedef GLboolean (APIENTRYP PFNGLISMEMORYOBJECTEXTPROC) (GLuint memoryObject); +typedef void (APIENTRYP PFNGLCREATEMEMORYOBJECTSEXTPROC) (GLsizei n, GLuint *memoryObjects); +typedef void (APIENTRYP PFNGLMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM2DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM3DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLBUFFERSTORAGEMEMEXTPROC) (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM2DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM2DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM3DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM3DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEMEMEXTPROC) (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM1DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUnsignedBytevEXT (GLenum pname, GLubyte *data); +GLAPI void APIENTRY glGetUnsignedBytei_vEXT (GLenum target, GLuint index, GLubyte *data); +GLAPI void APIENTRY glDeleteMemoryObjectsEXT (GLsizei n, const GLuint *memoryObjects); +GLAPI GLboolean APIENTRY glIsMemoryObjectEXT (GLuint memoryObject); +GLAPI void APIENTRY glCreateMemoryObjectsEXT (GLsizei n, GLuint *memoryObjects); +GLAPI void APIENTRY glMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, GLint *params); +GLAPI void APIENTRY glTexStorageMem2DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem2DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem3DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem3DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glBufferStorageMemEXT (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem2DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem2DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem3DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem3DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glNamedBufferStorageMemEXT (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem1DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem1DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +#endif +#endif /* GL_EXT_memory_object */ + +#ifndef GL_EXT_memory_object_fd +#define GL_EXT_memory_object_fd 1 +#define GL_HANDLE_TYPE_OPAQUE_FD_EXT 0x9586 +typedef void (APIENTRYP PFNGLIMPORTMEMORYFDEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportMemoryFdEXT (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); +#endif +#endif /* GL_EXT_memory_object_fd */ + +#ifndef GL_EXT_memory_object_win32 +#define GL_EXT_memory_object_win32 1 +#define GL_HANDLE_TYPE_OPAQUE_WIN32_EXT 0x9587 +#define GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT 0x9588 +#define GL_DEVICE_LUID_EXT 0x9599 +#define GL_DEVICE_NODE_MASK_EXT 0x959A +#define GL_LUID_SIZE_EXT 8 +#define GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT 0x9589 +#define GL_HANDLE_TYPE_D3D12_RESOURCE_EXT 0x958A +#define GL_HANDLE_TYPE_D3D11_IMAGE_EXT 0x958B +#define GL_HANDLE_TYPE_D3D11_IMAGE_KMT_EXT 0x958C +typedef void (APIENTRYP PFNGLIMPORTMEMORYWIN32HANDLEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, void *handle); +typedef void (APIENTRYP PFNGLIMPORTMEMORYWIN32NAMEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, const void *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportMemoryWin32HandleEXT (GLuint memory, GLuint64 size, GLenum handleType, void *handle); +GLAPI void APIENTRY glImportMemoryWin32NameEXT (GLuint memory, GLuint64 size, GLenum handleType, const void *name); +#endif +#endif /* GL_EXT_memory_object_win32 */ + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif /* GL_EXT_misc_attribute */ + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#endif +#endif /* GL_EXT_multi_draw_arrays */ + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternEXT (GLenum pattern); +#endif +#endif /* GL_EXT_multisample */ + +#ifndef GL_EXT_multiview_tessellation_geometry_shader +#define GL_EXT_multiview_tessellation_geometry_shader 1 +#endif /* GL_EXT_multiview_tessellation_geometry_shader */ + +#ifndef GL_EXT_multiview_texture_multisample +#define GL_EXT_multiview_texture_multisample 1 +#endif /* GL_EXT_multiview_texture_multisample */ + +#ifndef GL_EXT_multiview_timer_query +#define GL_EXT_multiview_timer_query 1 +#endif /* GL_EXT_multiview_timer_query */ + +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 +#endif /* GL_EXT_packed_depth_stencil */ + +#ifndef GL_EXT_packed_float +#define GL_EXT_packed_float 1 +#define GL_R11F_G11F_B10F_EXT 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B +#define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C +#endif /* GL_EXT_packed_float */ + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif /* GL_EXT_packed_pixels */ + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, void *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glGetColorTableEXT (GLenum target, GLenum format, GLenum type, void *data); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_paletted_texture */ + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif /* GL_EXT_pixel_buffer_object */ + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTransformParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTransformParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_pixel_transform */ + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif /* GL_EXT_pixel_transform_color_table */ + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_EXT_point_parameters */ + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat factor, GLfloat bias); +#endif +#endif /* GL_EXT_polygon_offset */ + +#ifndef GL_EXT_polygon_offset_clamp +#define GL_EXT_polygon_offset_clamp 1 +#define GL_POLYGON_OFFSET_CLAMP_EXT 0x8E1B +typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPEXTPROC) (GLfloat factor, GLfloat units, GLfloat clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetClampEXT (GLfloat factor, GLfloat units, GLfloat clamp); +#endif +#endif /* GL_EXT_polygon_offset_clamp */ + +#ifndef GL_EXT_post_depth_coverage +#define GL_EXT_post_depth_coverage 1 +#endif /* GL_EXT_post_depth_coverage */ + +#ifndef GL_EXT_provoking_vertex +#define GL_EXT_provoking_vertex 1 +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E +#define GL_PROVOKING_VERTEX_EXT 0x8E4F +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertexEXT (GLenum mode); +#endif +#endif /* GL_EXT_provoking_vertex */ + +#ifndef GL_EXT_raster_multisample +#define GL_EXT_raster_multisample 1 +#define GL_RASTER_MULTISAMPLE_EXT 0x9327 +#define GL_RASTER_SAMPLES_EXT 0x9328 +#define GL_MAX_RASTER_SAMPLES_EXT 0x9329 +#define GL_RASTER_FIXED_SAMPLE_LOCATIONS_EXT 0x932A +#define GL_MULTISAMPLE_RASTERIZATION_ALLOWED_EXT 0x932B +#define GL_EFFECTIVE_RASTER_SAMPLES_EXT 0x932C +typedef void (APIENTRYP PFNGLRASTERSAMPLESEXTPROC) (GLuint samples, GLboolean fixedsamplelocations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRasterSamplesEXT (GLuint samples, GLboolean fixedsamplelocations); +#endif +#endif /* GL_EXT_raster_multisample */ + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif /* GL_EXT_rescale_normal */ + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_secondary_color */ + +#ifndef GL_EXT_semaphore +#define GL_EXT_semaphore 1 +#define GL_LAYOUT_GENERAL_EXT 0x958D +#define GL_LAYOUT_COLOR_ATTACHMENT_EXT 0x958E +#define GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT 0x958F +#define GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT 0x9590 +#define GL_LAYOUT_SHADER_READ_ONLY_EXT 0x9591 +#define GL_LAYOUT_TRANSFER_SRC_EXT 0x9592 +#define GL_LAYOUT_TRANSFER_DST_EXT 0x9593 +#define GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT 0x9530 +#define GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT 0x9531 +typedef void (APIENTRYP PFNGLGENSEMAPHORESEXTPROC) (GLsizei n, GLuint *semaphores); +typedef void (APIENTRYP PFNGLDELETESEMAPHORESEXTPROC) (GLsizei n, const GLuint *semaphores); +typedef GLboolean (APIENTRYP PFNGLISSEMAPHOREEXTPROC) (GLuint semaphore); +typedef void (APIENTRYP PFNGLSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, const GLuint64 *params); +typedef void (APIENTRYP PFNGLGETSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, GLuint64 *params); +typedef void (APIENTRYP PFNGLWAITSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); +typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenSemaphoresEXT (GLsizei n, GLuint *semaphores); +GLAPI void APIENTRY glDeleteSemaphoresEXT (GLsizei n, const GLuint *semaphores); +GLAPI GLboolean APIENTRY glIsSemaphoreEXT (GLuint semaphore); +GLAPI void APIENTRY glSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, const GLuint64 *params); +GLAPI void APIENTRY glGetSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, GLuint64 *params); +GLAPI void APIENTRY glWaitSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); +GLAPI void APIENTRY glSignalSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); +#endif +#endif /* GL_EXT_semaphore */ + +#ifndef GL_EXT_semaphore_fd +#define GL_EXT_semaphore_fd 1 +typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREFDEXTPROC) (GLuint semaphore, GLenum handleType, GLint fd); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportSemaphoreFdEXT (GLuint semaphore, GLenum handleType, GLint fd); +#endif +#endif /* GL_EXT_semaphore_fd */ + +#ifndef GL_EXT_semaphore_win32 +#define GL_EXT_semaphore_win32 1 +#define GL_HANDLE_TYPE_D3D12_FENCE_EXT 0x9594 +#define GL_D3D12_FENCE_VALUE_EXT 0x9595 +typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREWIN32HANDLEEXTPROC) (GLuint semaphore, GLenum handleType, void *handle); +typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREWIN32NAMEEXTPROC) (GLuint semaphore, GLenum handleType, const void *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportSemaphoreWin32HandleEXT (GLuint semaphore, GLenum handleType, void *handle); +GLAPI void APIENTRY glImportSemaphoreWin32NameEXT (GLuint semaphore, GLenum handleType, const void *name); +#endif +#endif /* GL_EXT_semaphore_win32 */ + +#ifndef GL_EXT_separate_shader_objects +#define GL_EXT_separate_shader_objects 1 +#define GL_ACTIVE_PROGRAM_EXT 0x8B8D +typedef void (APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC) (GLenum type, GLuint program); +typedef void (APIENTRYP PFNGLACTIVEPROGRAMEXTPROC) (GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC) (GLenum type, const GLchar *string); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseShaderProgramEXT (GLenum type, GLuint program); +GLAPI void APIENTRY glActiveProgramEXT (GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramEXT (GLenum type, const GLchar *string); +#endif +#endif /* GL_EXT_separate_shader_objects */ + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif /* GL_EXT_separate_specular_color */ + +#ifndef GL_EXT_shader_framebuffer_fetch +#define GL_EXT_shader_framebuffer_fetch 1 +#define GL_FRAGMENT_SHADER_DISCARDS_SAMPLES_EXT 0x8A52 +#endif /* GL_EXT_shader_framebuffer_fetch */ + +#ifndef GL_EXT_shader_framebuffer_fetch_non_coherent +#define GL_EXT_shader_framebuffer_fetch_non_coherent 1 +typedef void (APIENTRYP PFNGLFRAMEBUFFERFETCHBARRIEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferFetchBarrierEXT (void); +#endif +#endif /* GL_EXT_shader_framebuffer_fetch_non_coherent */ + +#ifndef GL_EXT_shader_image_load_formatted +#define GL_EXT_shader_image_load_formatted 1 +#endif /* GL_EXT_shader_image_load_formatted */ + +#ifndef GL_EXT_shader_image_load_store +#define GL_EXT_shader_image_load_store 1 +#define GL_MAX_IMAGE_UNITS_EXT 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS_EXT 0x8F39 +#define GL_IMAGE_BINDING_NAME_EXT 0x8F3A +#define GL_IMAGE_BINDING_LEVEL_EXT 0x8F3B +#define GL_IMAGE_BINDING_LAYERED_EXT 0x8F3C +#define GL_IMAGE_BINDING_LAYER_EXT 0x8F3D +#define GL_IMAGE_BINDING_ACCESS_EXT 0x8F3E +#define GL_IMAGE_1D_EXT 0x904C +#define GL_IMAGE_2D_EXT 0x904D +#define GL_IMAGE_3D_EXT 0x904E +#define GL_IMAGE_2D_RECT_EXT 0x904F +#define GL_IMAGE_CUBE_EXT 0x9050 +#define GL_IMAGE_BUFFER_EXT 0x9051 +#define GL_IMAGE_1D_ARRAY_EXT 0x9052 +#define GL_IMAGE_2D_ARRAY_EXT 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE_EXT 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9056 +#define GL_INT_IMAGE_1D_EXT 0x9057 +#define GL_INT_IMAGE_2D_EXT 0x9058 +#define GL_INT_IMAGE_3D_EXT 0x9059 +#define GL_INT_IMAGE_2D_RECT_EXT 0x905A +#define GL_INT_IMAGE_CUBE_EXT 0x905B +#define GL_INT_IMAGE_BUFFER_EXT 0x905C +#define GL_INT_IMAGE_1D_ARRAY_EXT 0x905D +#define GL_INT_IMAGE_2D_ARRAY_EXT 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE_EXT 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D_EXT 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D_EXT 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D_EXT 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT_EXT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE_EXT 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY_EXT 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_EXT 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x906C +#define GL_MAX_IMAGE_SAMPLES_EXT 0x906D +#define GL_IMAGE_BINDING_FORMAT_EXT 0x906E +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT_EXT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT_EXT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT_EXT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT_EXT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT_EXT 0x00000020 +#define GL_COMMAND_BARRIER_BIT_EXT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT_EXT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT_EXT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT_EXT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT_EXT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT_EXT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT_EXT 0x00001000 +#define GL_ALL_BARRIER_BITS_EXT 0xFFFFFFFF +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREEXTPROC) (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +typedef void (APIENTRYP PFNGLMEMORYBARRIEREXTPROC) (GLbitfield barriers); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindImageTextureEXT (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers); +#endif +#endif /* GL_EXT_shader_image_load_store */ + +#ifndef GL_EXT_shader_integer_mix +#define GL_EXT_shader_integer_mix 1 +#endif /* GL_EXT_shader_integer_mix */ + +#ifndef GL_EXT_shader_samples_identical +#define GL_EXT_shader_samples_identical 1 +#endif /* GL_EXT_shader_samples_identical */ + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif /* GL_EXT_shadow_funcs */ + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif /* GL_EXT_shared_texture_palette */ + +#ifndef GL_EXT_sparse_texture2 +#define GL_EXT_sparse_texture2 1 +#endif /* GL_EXT_sparse_texture2 */ + +#ifndef GL_EXT_stencil_clear_tag +#define GL_EXT_stencil_clear_tag 1 +#define GL_STENCIL_TAG_BITS_EXT 0x88F2 +#define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 +typedef void (APIENTRYP PFNGLSTENCILCLEARTAGEXTPROC) (GLsizei stencilTagBits, GLuint stencilClearTag); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilClearTagEXT (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif +#endif /* GL_EXT_stencil_clear_tag */ + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum face); +#endif +#endif /* GL_EXT_stencil_two_side */ + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif /* GL_EXT_stencil_wrap */ + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_EXT_subtexture */ + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif /* GL_EXT_texture */ + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_EXT_texture3D */ + +#ifndef GL_EXT_texture_array +#define GL_EXT_texture_array 1 +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferTextureLayerEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#endif +#endif /* GL_EXT_texture_array */ + +#ifndef GL_EXT_texture_buffer_object +#define GL_EXT_texture_buffer_object 1 +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E +typedef void (APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); +#endif +#endif /* GL_EXT_texture_buffer_object */ + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc 1 +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 +#endif /* GL_EXT_texture_compression_latc */ + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif /* GL_EXT_texture_compression_rgtc */ + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif /* GL_EXT_texture_compression_s3tc */ + +#ifndef GL_EXT_texture_cube_map +#define GL_EXT_texture_cube_map 1 +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif /* GL_EXT_texture_cube_map */ + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif /* GL_EXT_texture_env_add */ + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif /* GL_EXT_texture_env_combine */ + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif /* GL_EXT_texture_env_dot3 */ + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif /* GL_EXT_texture_filter_anisotropic */ + +#ifndef GL_EXT_texture_filter_minmax +#define GL_EXT_texture_filter_minmax 1 +#define GL_TEXTURE_REDUCTION_MODE_EXT 0x9366 +#define GL_WEIGHTED_AVERAGE_EXT 0x9367 +#endif /* GL_EXT_texture_filter_minmax */ + +#ifndef GL_EXT_texture_integer +#define GL_EXT_texture_integer 1 +#define GL_RGBA32UI_EXT 0x8D70 +#define GL_RGB32UI_EXT 0x8D71 +#define GL_ALPHA32UI_EXT 0x8D72 +#define GL_INTENSITY32UI_EXT 0x8D73 +#define GL_LUMINANCE32UI_EXT 0x8D74 +#define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 +#define GL_RGBA16UI_EXT 0x8D76 +#define GL_RGB16UI_EXT 0x8D77 +#define GL_ALPHA16UI_EXT 0x8D78 +#define GL_INTENSITY16UI_EXT 0x8D79 +#define GL_LUMINANCE16UI_EXT 0x8D7A +#define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B +#define GL_RGBA8UI_EXT 0x8D7C +#define GL_RGB8UI_EXT 0x8D7D +#define GL_ALPHA8UI_EXT 0x8D7E +#define GL_INTENSITY8UI_EXT 0x8D7F +#define GL_LUMINANCE8UI_EXT 0x8D80 +#define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 +#define GL_RGBA32I_EXT 0x8D82 +#define GL_RGB32I_EXT 0x8D83 +#define GL_ALPHA32I_EXT 0x8D84 +#define GL_INTENSITY32I_EXT 0x8D85 +#define GL_LUMINANCE32I_EXT 0x8D86 +#define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 +#define GL_RGBA16I_EXT 0x8D88 +#define GL_RGB16I_EXT 0x8D89 +#define GL_ALPHA16I_EXT 0x8D8A +#define GL_INTENSITY16I_EXT 0x8D8B +#define GL_LUMINANCE16I_EXT 0x8D8C +#define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D +#define GL_RGBA8I_EXT 0x8D8E +#define GL_RGB8I_EXT 0x8D8F +#define GL_ALPHA8I_EXT 0x8D90 +#define GL_INTENSITY8I_EXT 0x8D91 +#define GL_LUMINANCE8I_EXT 0x8D92 +#define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 +#define GL_RED_INTEGER_EXT 0x8D94 +#define GL_GREEN_INTEGER_EXT 0x8D95 +#define GL_BLUE_INTEGER_EXT 0x8D96 +#define GL_ALPHA_INTEGER_EXT 0x8D97 +#define GL_RGB_INTEGER_EXT 0x8D98 +#define GL_RGBA_INTEGER_EXT 0x8D99 +#define GL_BGR_INTEGER_EXT 0x8D9A +#define GL_BGRA_INTEGER_EXT 0x8D9B +#define GL_LUMINANCE_INTEGER_EXT 0x8D9C +#define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D +#define GL_RGBA_INTEGER_MODE_EXT 0x8D9E +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (APIENTRYP PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearColorIiEXT (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void APIENTRY glClearColorIuiEXT (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif +#endif /* GL_EXT_texture_integer */ + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif /* GL_EXT_texture_lod_bias */ + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif /* GL_EXT_texture_mirror_clamp */ + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void APIENTRY glBindTextureEXT (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei n, GLuint *textures); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint texture); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif +#endif /* GL_EXT_texture_object */ + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum mode); +#endif +#endif /* GL_EXT_texture_perturb_normal */ + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB 1 +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif /* GL_EXT_texture_sRGB */ + +#ifndef GL_EXT_texture_sRGB_R8 +#define GL_EXT_texture_sRGB_R8 1 +#define GL_SR8_EXT 0x8FBD +#endif /* GL_EXT_texture_sRGB_R8 */ + +#ifndef GL_EXT_texture_sRGB_RG8 +#define GL_EXT_texture_sRGB_RG8 1 +#define GL_SRG8_EXT 0x8FBE +#endif /* GL_EXT_texture_sRGB_RG8 */ + +#ifndef GL_EXT_texture_sRGB_decode +#define GL_EXT_texture_sRGB_decode 1 +#define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 +#define GL_DECODE_EXT 0x8A49 +#define GL_SKIP_DECODE_EXT 0x8A4A +#endif /* GL_EXT_texture_sRGB_decode */ + +#ifndef GL_EXT_texture_shadow_lod +#define GL_EXT_texture_shadow_lod 1 +#endif /* GL_EXT_texture_shadow_lod */ + +#ifndef GL_EXT_texture_shared_exponent +#define GL_EXT_texture_shared_exponent 1 +#define GL_RGB9_E5_EXT 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E +#define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F +#endif /* GL_EXT_texture_shared_exponent */ + +#ifndef GL_EXT_texture_snorm +#define GL_EXT_texture_snorm 1 +#define GL_ALPHA_SNORM 0x9010 +#define GL_LUMINANCE_SNORM 0x9011 +#define GL_LUMINANCE_ALPHA_SNORM 0x9012 +#define GL_INTENSITY_SNORM 0x9013 +#define GL_ALPHA8_SNORM 0x9014 +#define GL_LUMINANCE8_SNORM 0x9015 +#define GL_LUMINANCE8_ALPHA8_SNORM 0x9016 +#define GL_INTENSITY8_SNORM 0x9017 +#define GL_ALPHA16_SNORM 0x9018 +#define GL_LUMINANCE16_SNORM 0x9019 +#define GL_LUMINANCE16_ALPHA16_SNORM 0x901A +#define GL_INTENSITY16_SNORM 0x901B +#define GL_RED_SNORM 0x8F90 +#define GL_RG_SNORM 0x8F91 +#define GL_RGB_SNORM 0x8F92 +#define GL_RGBA_SNORM 0x8F93 +#endif /* GL_EXT_texture_snorm */ + +#ifndef GL_EXT_texture_storage +#define GL_EXT_texture_storage 1 +#define GL_TEXTURE_IMMUTABLE_FORMAT_EXT 0x912F +#define GL_RGBA32F_EXT 0x8814 +#define GL_RGB32F_EXT 0x8815 +#define GL_ALPHA32F_EXT 0x8816 +#define GL_LUMINANCE32F_EXT 0x8818 +#define GL_LUMINANCE_ALPHA32F_EXT 0x8819 +#define GL_RGBA16F_EXT 0x881A +#define GL_RGB16F_EXT 0x881B +#define GL_ALPHA16F_EXT 0x881C +#define GL_LUMINANCE16F_EXT 0x881E +#define GL_LUMINANCE_ALPHA16F_EXT 0x881F +#define GL_BGRA8_EXT 0x93A1 +#define GL_R8_EXT 0x8229 +#define GL_RG8_EXT 0x822B +#define GL_R32F_EXT 0x822E +#define GL_RG32F_EXT 0x8230 +#define GL_R16F_EXT 0x822D +#define GL_RG16F_EXT 0x822F +typedef void (APIENTRYP PFNGLTEXSTORAGE1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexStorage1DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTexStorage2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexStorage3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GL_EXT_texture_storage */ + +#ifndef GL_EXT_texture_swizzle +#define GL_EXT_texture_swizzle 1 +#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 +#define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 +#define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 +#define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 +#endif /* GL_EXT_texture_swizzle */ + +#ifndef GL_EXT_timer_query +#define GL_EXT_timer_query 1 +#define GL_TIME_ELAPSED_EXT 0x88BF +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64 *params); +#endif +#endif /* GL_EXT_timer_query */ + +#ifndef GL_EXT_transform_feedback +#define GL_EXT_transform_feedback 1 +#define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F +#define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C +#define GL_SEPARATE_ATTRIBS_EXT 0x8C8D +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 +#define GL_RASTERIZER_DISCARD_EXT 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackEXT (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackEXT (void); +GLAPI void APIENTRY glBindBufferRangeEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseEXT (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsEXT (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVaryingEXT (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif +#endif /* GL_EXT_transform_feedback */ + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, void **params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint i); +GLAPI void APIENTRY glColorPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glDrawArraysEXT (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei stride, GLsizei count, const GLboolean *pointer); +GLAPI void APIENTRY glGetPointervEXT (GLenum pname, void **params); +GLAPI void APIENTRY glIndexPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glNormalPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glVertexPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +#endif +#endif /* GL_EXT_vertex_array */ + +#ifndef GL_EXT_vertex_array_bgra +#define GL_EXT_vertex_array_bgra 1 +#endif /* GL_EXT_vertex_array_bgra */ + +#ifndef GL_EXT_vertex_attrib_64bit +#define GL_EXT_vertex_attrib_64bit 1 +#define GL_DOUBLE_VEC2_EXT 0x8FFC +#define GL_DOUBLE_VEC3_EXT 0x8FFD +#define GL_DOUBLE_VEC4_EXT 0x8FFE +#define GL_DOUBLE_MAT2_EXT 0x8F46 +#define GL_DOUBLE_MAT3_EXT 0x8F47 +#define GL_DOUBLE_MAT4_EXT 0x8F48 +#define GL_DOUBLE_MAT2x3_EXT 0x8F49 +#define GL_DOUBLE_MAT2x4_EXT 0x8F4A +#define GL_DOUBLE_MAT3x2_EXT 0x8F4B +#define GL_DOUBLE_MAT3x4_EXT 0x8F4C +#define GL_DOUBLE_MAT4x2_EXT 0x8F4D +#define GL_DOUBLE_MAT4x3_EXT 0x8F4E +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DEXTPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DEXTPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVEXTPROC) (GLuint index, GLenum pname, GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1dEXT (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2dEXT (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribLdvEXT (GLuint index, GLenum pname, GLdouble *params); +#endif +#endif /* GL_EXT_vertex_attrib_64bit */ + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const void *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const void *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const void *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, void **data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint id); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint range); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint id); +GLAPI void APIENTRY glShaderOp1EXT (GLenum op, GLuint res, GLuint arg1); +GLAPI void APIENTRY glShaderOp2EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +GLAPI void APIENTRY glShaderOp3EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +GLAPI void APIENTRY glSwizzleEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glWriteMaskEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glInsertComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI void APIENTRY glExtractComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +GLAPI void APIENTRY glSetInvariantEXT (GLuint id, GLenum type, const void *addr); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint id, GLenum type, const void *addr); +GLAPI void APIENTRY glVariantbvEXT (GLuint id, const GLbyte *addr); +GLAPI void APIENTRY glVariantsvEXT (GLuint id, const GLshort *addr); +GLAPI void APIENTRY glVariantivEXT (GLuint id, const GLint *addr); +GLAPI void APIENTRY glVariantfvEXT (GLuint id, const GLfloat *addr); +GLAPI void APIENTRY glVariantdvEXT (GLuint id, const GLdouble *addr); +GLAPI void APIENTRY glVariantubvEXT (GLuint id, const GLubyte *addr); +GLAPI void APIENTRY glVariantusvEXT (GLuint id, const GLushort *addr); +GLAPI void APIENTRY glVariantuivEXT (GLuint id, const GLuint *addr); +GLAPI void APIENTRY glVariantPointerEXT (GLuint id, GLenum type, GLuint stride, const void *addr); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint id); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint id); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum light, GLenum value); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum face, GLenum value); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum unit, GLenum coord, GLenum value); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum unit, GLenum value); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum value); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint id, GLenum cap); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint id, GLenum value, void **data); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +#endif +#endif /* GL_EXT_vertex_shader */ + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#define GL_MODELVIEW0_STACK_DEPTH_EXT 0x0BA3 +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT 0x0BA6 +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT 0x1700 +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat weight); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *weight); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_vertex_weighting */ + +#ifndef GL_EXT_win32_keyed_mutex +#define GL_EXT_win32_keyed_mutex 1 +typedef GLboolean (APIENTRYP PFNGLACQUIREKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key, GLuint timeout); +typedef GLboolean (APIENTRYP PFNGLRELEASEKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAcquireKeyedMutexWin32EXT (GLuint memory, GLuint64 key, GLuint timeout); +GLAPI GLboolean APIENTRY glReleaseKeyedMutexWin32EXT (GLuint memory, GLuint64 key); +#endif +#endif /* GL_EXT_win32_keyed_mutex */ + +#ifndef GL_EXT_window_rectangles +#define GL_EXT_window_rectangles 1 +#define GL_INCLUSIVE_EXT 0x8F10 +#define GL_EXCLUSIVE_EXT 0x8F11 +#define GL_WINDOW_RECTANGLE_EXT 0x8F12 +#define GL_WINDOW_RECTANGLE_MODE_EXT 0x8F13 +#define GL_MAX_WINDOW_RECTANGLES_EXT 0x8F14 +#define GL_NUM_WINDOW_RECTANGLES_EXT 0x8F15 +typedef void (APIENTRYP PFNGLWINDOWRECTANGLESEXTPROC) (GLenum mode, GLsizei count, const GLint *box); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowRectanglesEXT (GLenum mode, GLsizei count, const GLint *box); +#endif +#endif /* GL_EXT_window_rectangles */ + +#ifndef GL_EXT_x11_sync_object +#define GL_EXT_x11_sync_object 1 +#define GL_SYNC_X11_FENCE_EXT 0x90E1 +typedef GLsync (APIENTRYP PFNGLIMPORTSYNCEXTPROC) (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glImportSyncEXT (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#endif +#endif /* GL_EXT_x11_sync_object */ + +#ifndef GL_GREMEDY_frame_terminator +#define GL_GREMEDY_frame_terminator 1 +typedef void (APIENTRYP PFNGLFRAMETERMINATORGREMEDYPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameTerminatorGREMEDY (void); +#endif +#endif /* GL_GREMEDY_frame_terminator */ + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const void *string); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei len, const void *string); +#endif +#endif /* GL_GREMEDY_string_marker */ + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif /* GL_HP_convolution_border_modes */ + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_HP_image_transform */ + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif /* GL_HP_occlusion_test */ + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif /* GL_HP_texture_lighting */ + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#define GL_CULL_VERTEX_IBM 103050 +#endif /* GL_IBM_cull_vertex */ + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); +#endif +#endif /* GL_IBM_multimode_draw_arrays */ + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif /* GL_IBM_rasterpos_clip */ + +#ifndef GL_IBM_static_data +#define GL_IBM_static_data 1 +#define GL_ALL_STATIC_DATA_IBM 103060 +#define GL_STATIC_VERTEX_ARRAY_IBM 103061 +typedef void (APIENTRYP PFNGLFLUSHSTATICDATAIBMPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushStaticDataIBM (GLenum target); +#endif +#endif /* GL_IBM_static_data */ + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_IBM_texture_mirrored_repeat 1 +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif /* GL_IBM_texture_mirrored_repeat */ + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint stride, const GLboolean **pointer, GLint ptrstride); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glVertexPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +#endif +#endif /* GL_IBM_vertex_array_lists */ + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif +#endif /* GL_INGR_blend_func_separate */ + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif /* GL_INGR_color_clamp */ + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#define GL_INTERLACE_READ_INGR 0x8568 +#endif /* GL_INGR_interlace_read */ + +#ifndef GL_INTEL_blackhole_render +#define GL_INTEL_blackhole_render 1 +#define GL_BLACKHOLE_RENDER_INTEL 0x83FC +#endif /* GL_INTEL_blackhole_render */ + +#ifndef GL_INTEL_conservative_rasterization +#define GL_INTEL_conservative_rasterization 1 +#define GL_CONSERVATIVE_RASTERIZATION_INTEL 0x83FE +#endif /* GL_INTEL_conservative_rasterization */ + +#ifndef GL_INTEL_fragment_shader_ordering +#define GL_INTEL_fragment_shader_ordering 1 +#endif /* GL_INTEL_fragment_shader_ordering */ + +#ifndef GL_INTEL_framebuffer_CMAA +#define GL_INTEL_framebuffer_CMAA 1 +typedef void (APIENTRYP PFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyFramebufferAttachmentCMAAINTEL (void); +#endif +#endif /* GL_INTEL_framebuffer_CMAA */ + +#ifndef GL_INTEL_map_texture +#define GL_INTEL_map_texture 1 +#define GL_TEXTURE_MEMORY_LAYOUT_INTEL 0x83FF +#define GL_LAYOUT_DEFAULT_INTEL 0 +#define GL_LAYOUT_LINEAR_INTEL 1 +#define GL_LAYOUT_LINEAR_CPU_CACHED_INTEL 2 +typedef void (APIENTRYP PFNGLSYNCTEXTUREINTELPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLUNMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level); +typedef void *(APIENTRYP PFNGLMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSyncTextureINTEL (GLuint texture); +GLAPI void APIENTRY glUnmapTexture2DINTEL (GLuint texture, GLint level); +GLAPI void *APIENTRY glMapTexture2DINTEL (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); +#endif +#endif /* GL_INTEL_map_texture */ + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint size, GLenum type, const void **pointer); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum type, const void **pointer); +GLAPI void APIENTRY glColorPointervINTEL (GLint size, GLenum type, const void **pointer); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint size, GLenum type, const void **pointer); +#endif +#endif /* GL_INTEL_parallel_arrays */ + +#ifndef GL_INTEL_performance_query +#define GL_INTEL_performance_query 1 +#define GL_PERFQUERY_SINGLE_CONTEXT_INTEL 0x00000000 +#define GL_PERFQUERY_GLOBAL_CONTEXT_INTEL 0x00000001 +#define GL_PERFQUERY_WAIT_INTEL 0x83FB +#define GL_PERFQUERY_FLUSH_INTEL 0x83FA +#define GL_PERFQUERY_DONOT_FLUSH_INTEL 0x83F9 +#define GL_PERFQUERY_COUNTER_EVENT_INTEL 0x94F0 +#define GL_PERFQUERY_COUNTER_DURATION_NORM_INTEL 0x94F1 +#define GL_PERFQUERY_COUNTER_DURATION_RAW_INTEL 0x94F2 +#define GL_PERFQUERY_COUNTER_THROUGHPUT_INTEL 0x94F3 +#define GL_PERFQUERY_COUNTER_RAW_INTEL 0x94F4 +#define GL_PERFQUERY_COUNTER_TIMESTAMP_INTEL 0x94F5 +#define GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL 0x94F8 +#define GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL 0x94F9 +#define GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL 0x94FA +#define GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL 0x94FB +#define GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL 0x94FC +#define GL_PERFQUERY_QUERY_NAME_LENGTH_MAX_INTEL 0x94FD +#define GL_PERFQUERY_COUNTER_NAME_LENGTH_MAX_INTEL 0x94FE +#define GL_PERFQUERY_COUNTER_DESC_LENGTH_MAX_INTEL 0x94FF +#define GL_PERFQUERY_GPA_EXTENDED_COUNTERS_INTEL 0x9500 +typedef void (APIENTRYP PFNGLBEGINPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLCREATEPERFQUERYINTELPROC) (GLuint queryId, GLuint *queryHandle); +typedef void (APIENTRYP PFNGLDELETEPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLENDPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLGETFIRSTPERFQUERYIDINTELPROC) (GLuint *queryId); +typedef void (APIENTRYP PFNGLGETNEXTPERFQUERYIDINTELPROC) (GLuint queryId, GLuint *nextQueryId); +typedef void (APIENTRYP PFNGLGETPERFCOUNTERINFOINTELPROC) (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +typedef void (APIENTRYP PFNGLGETPERFQUERYDATAINTELPROC) (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); +typedef void (APIENTRYP PFNGLGETPERFQUERYIDBYNAMEINTELPROC) (GLchar *queryName, GLuint *queryId); +typedef void (APIENTRYP PFNGLGETPERFQUERYINFOINTELPROC) (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginPerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glCreatePerfQueryINTEL (GLuint queryId, GLuint *queryHandle); +GLAPI void APIENTRY glDeletePerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glEndPerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glGetFirstPerfQueryIdINTEL (GLuint *queryId); +GLAPI void APIENTRY glGetNextPerfQueryIdINTEL (GLuint queryId, GLuint *nextQueryId); +GLAPI void APIENTRY glGetPerfCounterInfoINTEL (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +GLAPI void APIENTRY glGetPerfQueryDataINTEL (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); +GLAPI void APIENTRY glGetPerfQueryIdByNameINTEL (GLchar *queryName, GLuint *queryId); +GLAPI void APIENTRY glGetPerfQueryInfoINTEL (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#endif +#endif /* GL_INTEL_performance_query */ + +#ifndef GL_MESAX_texture_stack +#define GL_MESAX_texture_stack 1 +#define GL_TEXTURE_1D_STACK_MESAX 0x8759 +#define GL_TEXTURE_2D_STACK_MESAX 0x875A +#define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B +#define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C +#define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D +#define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E +#endif /* GL_MESAX_texture_stack */ + +#ifndef GL_MESA_framebuffer_flip_x +#define GL_MESA_framebuffer_flip_x 1 +#define GL_FRAMEBUFFER_FLIP_X_MESA 0x8BBC +#endif /* GL_MESA_framebuffer_flip_x */ + +#ifndef GL_MESA_framebuffer_flip_y +#define GL_MESA_framebuffer_flip_y 1 +#define GL_FRAMEBUFFER_FLIP_Y_MESA 0x8BBB +typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIMESAPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVMESAPROC) (GLenum target, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferParameteriMESA (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glGetFramebufferParameterivMESA (GLenum target, GLenum pname, GLint *params); +#endif +#endif /* GL_MESA_framebuffer_flip_y */ + +#ifndef GL_MESA_framebuffer_swap_xy +#define GL_MESA_framebuffer_swap_xy 1 +#define GL_FRAMEBUFFER_SWAP_XY_MESA 0x8BBD +#endif /* GL_MESA_framebuffer_swap_xy */ + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#define GL_PACK_INVERT_MESA 0x8758 +#endif /* GL_MESA_pack_invert */ + +#ifndef GL_MESA_program_binary_formats +#define GL_MESA_program_binary_formats 1 +#define GL_PROGRAM_BINARY_FORMAT_MESA 0x875F +#endif /* GL_MESA_program_binary_formats */ + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif +#endif /* GL_MESA_resize_buffers */ + +#ifndef GL_MESA_shader_integer_functions +#define GL_MESA_shader_integer_functions 1 +#endif /* GL_MESA_shader_integer_functions */ + +#ifndef GL_MESA_tile_raster_order +#define GL_MESA_tile_raster_order 1 +#define GL_TILE_RASTER_ORDER_FIXED_MESA 0x8BB8 +#define GL_TILE_RASTER_ORDER_INCREASING_X_MESA 0x8BB9 +#define GL_TILE_RASTER_ORDER_INCREASING_Y_MESA 0x8BBA +#endif /* GL_MESA_tile_raster_order */ + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iMESA (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iMESA (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos4iMESA (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *v); +#endif +#endif /* GL_MESA_window_pos */ + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif /* GL_MESA_ycbcr_texture */ + +#ifndef GL_NVX_blend_equation_advanced_multi_draw_buffers +#define GL_NVX_blend_equation_advanced_multi_draw_buffers 1 +#endif /* GL_NVX_blend_equation_advanced_multi_draw_buffers */ + +#ifndef GL_NVX_conditional_render +#define GL_NVX_conditional_render 1 +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVXPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNVX (GLuint id); +GLAPI void APIENTRY glEndConditionalRenderNVX (void); +#endif +#endif /* GL_NVX_conditional_render */ + +#ifndef GL_NVX_gpu_memory_info +#define GL_NVX_gpu_memory_info 1 +#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047 +#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048 +#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 +#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A +#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B +#endif /* GL_NVX_gpu_memory_info */ + +#ifndef GL_NVX_gpu_multicast2 +#define GL_NVX_gpu_multicast2 1 +#define GL_UPLOAD_GPU_MASK_NVX 0x954A +typedef void (APIENTRYP PFNGLUPLOADGPUMASKNVXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTPOSITIONWSCALENVXPROC) (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); +typedef void (APIENTRYP PFNGLMULTICASTSCISSORARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLint *v); +typedef GLuint (APIENTRYP PFNGLASYNCCOPYBUFFERSUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +typedef GLuint (APIENTRYP PFNGLASYNCCOPYIMAGESUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUploadGpuMaskNVX (GLbitfield mask); +GLAPI void APIENTRY glMulticastViewportArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glMulticastViewportPositionWScaleNVX (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); +GLAPI void APIENTRY glMulticastScissorArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLint *v); +GLAPI GLuint APIENTRY glAsyncCopyBufferSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +GLAPI GLuint APIENTRY glAsyncCopyImageSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +#endif +#endif /* GL_NVX_gpu_multicast2 */ + +#ifndef GL_NVX_linked_gpu_multicast +#define GL_NVX_linked_gpu_multicast 1 +#define GL_LGPU_SEPARATE_STORAGE_BIT_NVX 0x0800 +#define GL_MAX_LGPU_GPUS_NVX 0x92BA +typedef void (APIENTRYP PFNGLLGPUNAMEDBUFFERSUBDATANVXPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLLGPUCOPYIMAGESUBDATANVXPROC) (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLLGPUINTERLOCKNVXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLGPUNamedBufferSubDataNVX (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glLGPUCopyImageSubDataNVX (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glLGPUInterlockNVX (void); +#endif +#endif /* GL_NVX_linked_gpu_multicast */ + +#ifndef GL_NVX_progress_fence +#define GL_NVX_progress_fence 1 +typedef GLuint (APIENTRYP PFNGLCREATEPROGRESSFENCENVXPROC) (void); +typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREUI64NVXPROC) (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +typedef void (APIENTRYP PFNGLWAITSEMAPHOREUI64NVXPROC) (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +typedef void (APIENTRYP PFNGLCLIENTWAITSEMAPHOREUI64NVXPROC) (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glCreateProgressFenceNVX (void); +GLAPI void APIENTRY glSignalSemaphoreui64NVX (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +GLAPI void APIENTRY glWaitSemaphoreui64NVX (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +GLAPI void APIENTRY glClientWaitSemaphoreui64NVX (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +#endif +#endif /* GL_NVX_progress_fence */ + +#ifndef GL_NV_alpha_to_coverage_dither_control +#define GL_NV_alpha_to_coverage_dither_control 1 +#define GL_ALPHA_TO_COVERAGE_DITHER_DEFAULT_NV 0x934D +#define GL_ALPHA_TO_COVERAGE_DITHER_ENABLE_NV 0x934E +#define GL_ALPHA_TO_COVERAGE_DITHER_DISABLE_NV 0x934F +#define GL_ALPHA_TO_COVERAGE_DITHER_MODE_NV 0x92BF +typedef void (APIENTRYP PFNGLALPHATOCOVERAGEDITHERCONTROLNVPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAlphaToCoverageDitherControlNV (GLenum mode); +#endif +#endif /* GL_NV_alpha_to_coverage_dither_control */ + +#ifndef GL_NV_bindless_multi_draw_indirect +#define GL_NV_bindless_multi_draw_indirect 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +#endif +#endif /* GL_NV_bindless_multi_draw_indirect */ + +#ifndef GL_NV_bindless_multi_draw_indirect_count +#define GL_NV_bindless_multi_draw_indirect_count 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSCOUNTNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSCOUNTNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessCountNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessCountNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +#endif +#endif /* GL_NV_bindless_multi_draw_indirect_count */ + +#ifndef GL_NV_bindless_texture +#define GL_NV_bindless_texture 1 +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLENVPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLENVPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleNV (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleNV (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentNV (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentNV (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleNV (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentNV (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentNV (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64NV (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vNV (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64NV (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentNV (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentNV (GLuint64 handle); +#endif +#endif /* GL_NV_bindless_texture */ + +#ifndef GL_NV_blend_equation_advanced +#define GL_NV_blend_equation_advanced 1 +#define GL_BLEND_OVERLAP_NV 0x9281 +#define GL_BLEND_PREMULTIPLIED_SRC_NV 0x9280 +#define GL_BLUE_NV 0x1905 +#define GL_COLORBURN_NV 0x929A +#define GL_COLORDODGE_NV 0x9299 +#define GL_CONJOINT_NV 0x9284 +#define GL_CONTRAST_NV 0x92A1 +#define GL_DARKEN_NV 0x9297 +#define GL_DIFFERENCE_NV 0x929E +#define GL_DISJOINT_NV 0x9283 +#define GL_DST_ATOP_NV 0x928F +#define GL_DST_IN_NV 0x928B +#define GL_DST_NV 0x9287 +#define GL_DST_OUT_NV 0x928D +#define GL_DST_OVER_NV 0x9289 +#define GL_EXCLUSION_NV 0x92A0 +#define GL_GREEN_NV 0x1904 +#define GL_HARDLIGHT_NV 0x929B +#define GL_HARDMIX_NV 0x92A9 +#define GL_HSL_COLOR_NV 0x92AF +#define GL_HSL_HUE_NV 0x92AD +#define GL_HSL_LUMINOSITY_NV 0x92B0 +#define GL_HSL_SATURATION_NV 0x92AE +#define GL_INVERT_OVG_NV 0x92B4 +#define GL_INVERT_RGB_NV 0x92A3 +#define GL_LIGHTEN_NV 0x9298 +#define GL_LINEARBURN_NV 0x92A5 +#define GL_LINEARDODGE_NV 0x92A4 +#define GL_LINEARLIGHT_NV 0x92A7 +#define GL_MINUS_CLAMPED_NV 0x92B3 +#define GL_MINUS_NV 0x929F +#define GL_MULTIPLY_NV 0x9294 +#define GL_OVERLAY_NV 0x9296 +#define GL_PINLIGHT_NV 0x92A8 +#define GL_PLUS_CLAMPED_ALPHA_NV 0x92B2 +#define GL_PLUS_CLAMPED_NV 0x92B1 +#define GL_PLUS_DARKER_NV 0x9292 +#define GL_PLUS_NV 0x9291 +#define GL_RED_NV 0x1903 +#define GL_SCREEN_NV 0x9295 +#define GL_SOFTLIGHT_NV 0x929C +#define GL_SRC_ATOP_NV 0x928E +#define GL_SRC_IN_NV 0x928A +#define GL_SRC_NV 0x9286 +#define GL_SRC_OUT_NV 0x928C +#define GL_SRC_OVER_NV 0x9288 +#define GL_UNCORRELATED_NV 0x9282 +#define GL_VIVIDLIGHT_NV 0x92A6 +#define GL_XOR_NV 0x1506 +typedef void (APIENTRYP PFNGLBLENDPARAMETERINVPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLBLENDBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendParameteriNV (GLenum pname, GLint value); +GLAPI void APIENTRY glBlendBarrierNV (void); +#endif +#endif /* GL_NV_blend_equation_advanced */ + +#ifndef GL_NV_blend_equation_advanced_coherent +#define GL_NV_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_NV 0x9285 +#endif /* GL_NV_blend_equation_advanced_coherent */ + +#ifndef GL_NV_blend_minmax_factor +#define GL_NV_blend_minmax_factor 1 +#endif /* GL_NV_blend_minmax_factor */ + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif /* GL_NV_blend_square */ + +#ifndef GL_NV_clip_space_w_scaling +#define GL_NV_clip_space_w_scaling 1 +#define GL_VIEWPORT_POSITION_W_SCALE_NV 0x937C +#define GL_VIEWPORT_POSITION_W_SCALE_X_COEFF_NV 0x937D +#define GL_VIEWPORT_POSITION_W_SCALE_Y_COEFF_NV 0x937E +typedef void (APIENTRYP PFNGLVIEWPORTPOSITIONWSCALENVPROC) (GLuint index, GLfloat xcoeff, GLfloat ycoeff); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glViewportPositionWScaleNV (GLuint index, GLfloat xcoeff, GLfloat ycoeff); +#endif +#endif /* GL_NV_clip_space_w_scaling */ + +#ifndef GL_NV_command_list +#define GL_NV_command_list 1 +#define GL_TERMINATE_SEQUENCE_COMMAND_NV 0x0000 +#define GL_NOP_COMMAND_NV 0x0001 +#define GL_DRAW_ELEMENTS_COMMAND_NV 0x0002 +#define GL_DRAW_ARRAYS_COMMAND_NV 0x0003 +#define GL_DRAW_ELEMENTS_STRIP_COMMAND_NV 0x0004 +#define GL_DRAW_ARRAYS_STRIP_COMMAND_NV 0x0005 +#define GL_DRAW_ELEMENTS_INSTANCED_COMMAND_NV 0x0006 +#define GL_DRAW_ARRAYS_INSTANCED_COMMAND_NV 0x0007 +#define GL_ELEMENT_ADDRESS_COMMAND_NV 0x0008 +#define GL_ATTRIBUTE_ADDRESS_COMMAND_NV 0x0009 +#define GL_UNIFORM_ADDRESS_COMMAND_NV 0x000A +#define GL_BLEND_COLOR_COMMAND_NV 0x000B +#define GL_STENCIL_REF_COMMAND_NV 0x000C +#define GL_LINE_WIDTH_COMMAND_NV 0x000D +#define GL_POLYGON_OFFSET_COMMAND_NV 0x000E +#define GL_ALPHA_REF_COMMAND_NV 0x000F +#define GL_VIEWPORT_COMMAND_NV 0x0010 +#define GL_SCISSOR_COMMAND_NV 0x0011 +#define GL_FRONT_FACE_COMMAND_NV 0x0012 +typedef void (APIENTRYP PFNGLCREATESTATESNVPROC) (GLsizei n, GLuint *states); +typedef void (APIENTRYP PFNGLDELETESTATESNVPROC) (GLsizei n, const GLuint *states); +typedef GLboolean (APIENTRYP PFNGLISSTATENVPROC) (GLuint state); +typedef void (APIENTRYP PFNGLSTATECAPTURENVPROC) (GLuint state, GLenum mode); +typedef GLuint (APIENTRYP PFNGLGETCOMMANDHEADERNVPROC) (GLenum tokenID, GLuint size); +typedef GLushort (APIENTRYP PFNGLGETSTAGEINDEXNVPROC) (GLenum shadertype); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSNVPROC) (GLenum primitiveMode, GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, GLuint count); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSADDRESSNVPROC) (GLenum primitiveMode, const GLuint64 *indirects, const GLsizei *sizes, GLuint count); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSSTATESNVPROC) (GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSSTATESADDRESSNVPROC) (const GLuint64 *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +typedef void (APIENTRYP PFNGLCREATECOMMANDLISTSNVPROC) (GLsizei n, GLuint *lists); +typedef void (APIENTRYP PFNGLDELETECOMMANDLISTSNVPROC) (GLsizei n, const GLuint *lists); +typedef GLboolean (APIENTRYP PFNGLISCOMMANDLISTNVPROC) (GLuint list); +typedef void (APIENTRYP PFNGLLISTDRAWCOMMANDSSTATESCLIENTNVPROC) (GLuint list, GLuint segment, const void **indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +typedef void (APIENTRYP PFNGLCOMMANDLISTSEGMENTSNVPROC) (GLuint list, GLuint segments); +typedef void (APIENTRYP PFNGLCOMPILECOMMANDLISTNVPROC) (GLuint list); +typedef void (APIENTRYP PFNGLCALLCOMMANDLISTNVPROC) (GLuint list); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCreateStatesNV (GLsizei n, GLuint *states); +GLAPI void APIENTRY glDeleteStatesNV (GLsizei n, const GLuint *states); +GLAPI GLboolean APIENTRY glIsStateNV (GLuint state); +GLAPI void APIENTRY glStateCaptureNV (GLuint state, GLenum mode); +GLAPI GLuint APIENTRY glGetCommandHeaderNV (GLenum tokenID, GLuint size); +GLAPI GLushort APIENTRY glGetStageIndexNV (GLenum shadertype); +GLAPI void APIENTRY glDrawCommandsNV (GLenum primitiveMode, GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, GLuint count); +GLAPI void APIENTRY glDrawCommandsAddressNV (GLenum primitiveMode, const GLuint64 *indirects, const GLsizei *sizes, GLuint count); +GLAPI void APIENTRY glDrawCommandsStatesNV (GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +GLAPI void APIENTRY glDrawCommandsStatesAddressNV (const GLuint64 *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +GLAPI void APIENTRY glCreateCommandListsNV (GLsizei n, GLuint *lists); +GLAPI void APIENTRY glDeleteCommandListsNV (GLsizei n, const GLuint *lists); +GLAPI GLboolean APIENTRY glIsCommandListNV (GLuint list); +GLAPI void APIENTRY glListDrawCommandsStatesClientNV (GLuint list, GLuint segment, const void **indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +GLAPI void APIENTRY glCommandListSegmentsNV (GLuint list, GLuint segments); +GLAPI void APIENTRY glCompileCommandListNV (GLuint list); +GLAPI void APIENTRY glCallCommandListNV (GLuint list); +#endif +#endif /* GL_NV_command_list */ + +#ifndef GL_NV_compute_program5 +#define GL_NV_compute_program5 1 +#define GL_COMPUTE_PROGRAM_NV 0x90FB +#define GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV 0x90FC +#endif /* GL_NV_compute_program5 */ + +#ifndef GL_NV_compute_shader_derivatives +#define GL_NV_compute_shader_derivatives 1 +#endif /* GL_NV_compute_shader_derivatives */ + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRenderNV (void); +#endif +#endif /* GL_NV_conditional_render */ + +#ifndef GL_NV_conservative_raster +#define GL_NV_conservative_raster 1 +#define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346 +#define GL_SUBPIXEL_PRECISION_BIAS_X_BITS_NV 0x9347 +#define GL_SUBPIXEL_PRECISION_BIAS_Y_BITS_NV 0x9348 +#define GL_MAX_SUBPIXEL_PRECISION_BIAS_BITS_NV 0x9349 +typedef void (APIENTRYP PFNGLSUBPIXELPRECISIONBIASNVPROC) (GLuint xbits, GLuint ybits); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSubpixelPrecisionBiasNV (GLuint xbits, GLuint ybits); +#endif +#endif /* GL_NV_conservative_raster */ + +#ifndef GL_NV_conservative_raster_dilate +#define GL_NV_conservative_raster_dilate 1 +#define GL_CONSERVATIVE_RASTER_DILATE_NV 0x9379 +#define GL_CONSERVATIVE_RASTER_DILATE_RANGE_NV 0x937A +#define GL_CONSERVATIVE_RASTER_DILATE_GRANULARITY_NV 0x937B +typedef void (APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERFNVPROC) (GLenum pname, GLfloat value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConservativeRasterParameterfNV (GLenum pname, GLfloat value); +#endif +#endif /* GL_NV_conservative_raster_dilate */ + +#ifndef GL_NV_conservative_raster_pre_snap +#define GL_NV_conservative_raster_pre_snap 1 +#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_NV 0x9550 +#endif /* GL_NV_conservative_raster_pre_snap */ + +#ifndef GL_NV_conservative_raster_pre_snap_triangles +#define GL_NV_conservative_raster_pre_snap_triangles 1 +#define GL_CONSERVATIVE_RASTER_MODE_NV 0x954D +#define GL_CONSERVATIVE_RASTER_MODE_POST_SNAP_NV 0x954E +#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_TRIANGLES_NV 0x954F +typedef void (APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERINVPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConservativeRasterParameteriNV (GLenum pname, GLint param); +#endif +#endif /* GL_NV_conservative_raster_pre_snap_triangles */ + +#ifndef GL_NV_conservative_raster_underestimation +#define GL_NV_conservative_raster_underestimation 1 +#endif /* GL_NV_conservative_raster_underestimation */ + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif /* GL_NV_copy_depth_to_color */ + +#ifndef GL_NV_copy_image +#define GL_NV_copy_image 1 +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATANVPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyImageSubDataNV (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GL_NV_copy_image */ + +#ifndef GL_NV_deep_texture3D +#define GL_NV_deep_texture3D 1 +#define GL_MAX_DEEP_3D_TEXTURE_WIDTH_HEIGHT_NV 0x90D0 +#define GL_MAX_DEEP_3D_TEXTURE_DEPTH_NV 0x90D1 +#endif /* GL_NV_deep_texture3D */ + +#ifndef GL_NV_depth_buffer_float +#define GL_NV_depth_buffer_float 1 +#define GL_DEPTH_COMPONENT32F_NV 0x8DAB +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD +#define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF +typedef void (APIENTRYP PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); +typedef void (APIENTRYP PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangedNV (GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glClearDepthdNV (GLdouble depth); +GLAPI void APIENTRY glDepthBoundsdNV (GLdouble zmin, GLdouble zmax); +#endif +#endif /* GL_NV_depth_buffer_float */ + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#define GL_DEPTH_CLAMP_NV 0x864F +#endif /* GL_NV_depth_clamp */ + +#ifndef GL_NV_draw_texture +#define GL_NV_draw_texture 1 +typedef void (APIENTRYP PFNGLDRAWTEXTURENVPROC) (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawTextureNV (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +#endif +#endif /* GL_NV_draw_texture */ + +#ifndef GL_NV_draw_vulkan_image +#define GL_NV_draw_vulkan_image 1 +typedef void (APIENTRY *GLVULKANPROCNV)(void); +typedef void (APIENTRYP PFNGLDRAWVKIMAGENVPROC) (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +typedef GLVULKANPROCNV (APIENTRYP PFNGLGETVKPROCADDRNVPROC) (const GLchar *name); +typedef void (APIENTRYP PFNGLWAITVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); +typedef void (APIENTRYP PFNGLSIGNALVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); +typedef void (APIENTRYP PFNGLSIGNALVKFENCENVPROC) (GLuint64 vkFence); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawVkImageNV (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +GLAPI GLVULKANPROCNV APIENTRY glGetVkProcAddrNV (const GLchar *name); +GLAPI void APIENTRY glWaitVkSemaphoreNV (GLuint64 vkSemaphore); +GLAPI void APIENTRY glSignalVkSemaphoreNV (GLuint64 vkSemaphore); +GLAPI void APIENTRY glSignalVkFenceNV (GLuint64 vkFence); +#endif +#endif /* GL_NV_draw_vulkan_image */ + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); +GLAPI void APIENTRY glMapParameterivNV (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMapParameterfvNV (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum target, GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glEvalMapsNV (GLenum target, GLenum mode); +#endif +#endif /* GL_NV_evaluators */ + +#ifndef GL_NV_explicit_multisample +#define GL_NV_explicit_multisample 1 +#define GL_SAMPLE_POSITION_NV 0x8E50 +#define GL_SAMPLE_MASK_NV 0x8E51 +#define GL_SAMPLE_MASK_VALUE_NV 0x8E52 +#define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 +#define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 +#define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 +#define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 +#define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 +#define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 +#define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMultisamplefvNV (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaskIndexedNV (GLuint index, GLbitfield mask); +GLAPI void APIENTRY glTexRenderbufferNV (GLenum target, GLuint renderbuffer); +#endif +#endif /* GL_NV_explicit_multisample */ + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint fence); +GLAPI void APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); +GLAPI void APIENTRY glFinishFenceNV (GLuint fence); +GLAPI void APIENTRY glSetFenceNV (GLuint fence, GLenum condition); +#endif +#endif /* GL_NV_fence */ + +#ifndef GL_NV_fill_rectangle +#define GL_NV_fill_rectangle 1 +#define GL_FILL_RECTANGLE_NV 0x933C +#endif /* GL_NV_fill_rectangle */ + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif /* GL_NV_float_buffer */ + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +#endif /* GL_NV_fog_distance */ + +#ifndef GL_NV_fragment_coverage_to_color +#define GL_NV_fragment_coverage_to_color 1 +#define GL_FRAGMENT_COVERAGE_TO_COLOR_NV 0x92DD +#define GL_FRAGMENT_COVERAGE_COLOR_NV 0x92DE +typedef void (APIENTRYP PFNGLFRAGMENTCOVERAGECOLORNVPROC) (GLuint color); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentCoverageColorNV (GLuint color); +#endif +#endif /* GL_NV_fragment_coverage_to_color */ + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif +#endif /* GL_NV_fragment_program */ + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif /* GL_NV_fragment_program2 */ + +#ifndef GL_NV_fragment_program4 +#define GL_NV_fragment_program4 1 +#endif /* GL_NV_fragment_program4 */ + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif /* GL_NV_fragment_program_option */ + +#ifndef GL_NV_fragment_shader_barycentric +#define GL_NV_fragment_shader_barycentric 1 +#endif /* GL_NV_fragment_shader_barycentric */ + +#ifndef GL_NV_fragment_shader_interlock +#define GL_NV_fragment_shader_interlock 1 +#endif /* GL_NV_fragment_shader_interlock */ + +#ifndef GL_NV_framebuffer_mixed_samples +#define GL_NV_framebuffer_mixed_samples 1 +#define GL_COVERAGE_MODULATION_TABLE_NV 0x9331 +#define GL_COLOR_SAMPLES_NV 0x8E20 +#define GL_DEPTH_SAMPLES_NV 0x932D +#define GL_STENCIL_SAMPLES_NV 0x932E +#define GL_MIXED_DEPTH_SAMPLES_SUPPORTED_NV 0x932F +#define GL_MIXED_STENCIL_SAMPLES_SUPPORTED_NV 0x9330 +#define GL_COVERAGE_MODULATION_NV 0x9332 +#define GL_COVERAGE_MODULATION_TABLE_SIZE_NV 0x9333 +typedef void (APIENTRYP PFNGLCOVERAGEMODULATIONTABLENVPROC) (GLsizei n, const GLfloat *v); +typedef void (APIENTRYP PFNGLGETCOVERAGEMODULATIONTABLENVPROC) (GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLCOVERAGEMODULATIONNVPROC) (GLenum components); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCoverageModulationTableNV (GLsizei n, const GLfloat *v); +GLAPI void APIENTRY glGetCoverageModulationTableNV (GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glCoverageModulationNV (GLenum components); +#endif +#endif /* GL_NV_framebuffer_mixed_samples */ + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_NV_framebuffer_multisample_coverage 1 +#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB +#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 +#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 +#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_NV_framebuffer_multisample_coverage */ + +#ifndef GL_NV_geometry_program4 +#define GL_NV_geometry_program4 1 +#define GL_GEOMETRY_PROGRAM_NV 0x8C26 +#define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 +#define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 +typedef void (APIENTRYP PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramVertexLimitNV (GLenum target, GLint limit); +GLAPI void APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif +#endif /* GL_NV_geometry_program4 */ + +#ifndef GL_NV_geometry_shader4 +#define GL_NV_geometry_shader4 1 +#endif /* GL_NV_geometry_shader4 */ + +#ifndef GL_NV_geometry_shader_passthrough +#define GL_NV_geometry_shader_passthrough 1 +#endif /* GL_NV_geometry_shader_passthrough */ + +#ifndef GL_NV_gpu_multicast +#define GL_NV_gpu_multicast 1 +#define GL_PER_GPU_STORAGE_BIT_NV 0x0800 +#define GL_MULTICAST_GPUS_NV 0x92BA +#define GL_RENDER_GPU_MASK_NV 0x9558 +#define GL_PER_GPU_STORAGE_NV 0x9548 +#define GL_MULTICAST_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9549 +typedef void (APIENTRYP PFNGLRENDERGPUMASKNVPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMULTICASTBUFFERSUBDATANVPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLMULTICASTCOPYBUFFERSUBDATANVPROC) (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLMULTICASTCOPYIMAGESUBDATANVPROC) (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +typedef void (APIENTRYP PFNGLMULTICASTBLITFRAMEBUFFERNVPROC) (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLMULTICASTFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTICASTBARRIERNVPROC) (void); +typedef void (APIENTRYP PFNGLMULTICASTWAITSYNCNVPROC) (GLuint signalGpu, GLbitfield waitGpuMask); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderGpuMaskNV (GLbitfield mask); +GLAPI void APIENTRY glMulticastBufferSubDataNV (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glMulticastCopyBufferSubDataNV (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glMulticastCopyImageSubDataNV (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +GLAPI void APIENTRY glMulticastBlitFramebufferNV (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glMulticastFramebufferSampleLocationsfvNV (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glMulticastBarrierNV (void); +GLAPI void APIENTRY glMulticastWaitSyncNV (GLuint signalGpu, GLbitfield waitGpuMask); +GLAPI void APIENTRY glMulticastGetQueryObjectivNV (GLuint gpu, GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glMulticastGetQueryObjectuivNV (GLuint gpu, GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMulticastGetQueryObjecti64vNV (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glMulticastGetQueryObjectui64vNV (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); +#endif +#endif /* GL_NV_gpu_multicast */ + +#ifndef GL_NV_gpu_program4 +#define GL_NV_gpu_program4 1 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 +#define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 +#define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 +#define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 +#define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 +#define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 +#define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramLocalParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramLocalParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramLocalParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramLocalParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramLocalParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramLocalParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramEnvParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramEnvParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramEnvParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramEnvParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIuivNV (GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIuivNV (GLenum target, GLuint index, GLuint *params); +#endif +#endif /* GL_NV_gpu_program4 */ + +#ifndef GL_NV_gpu_program5 +#define GL_NV_gpu_program5 1 +#define GL_MAX_GEOMETRY_PROGRAM_INVOCATIONS_NV 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5C +#define GL_FRAGMENT_PROGRAM_INTERPOLATION_OFFSET_BITS_NV 0x8E5D +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5F +#define GL_MAX_PROGRAM_SUBROUTINE_PARAMETERS_NV 0x8F44 +#define GL_MAX_PROGRAM_SUBROUTINE_NUM_NV 0x8F45 +typedef void (APIENTRYP PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC) (GLenum target, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC) (GLenum target, GLuint index, GLuint *param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramSubroutineParametersuivNV (GLenum target, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramSubroutineParameteruivNV (GLenum target, GLuint index, GLuint *param); +#endif +#endif /* GL_NV_gpu_program5 */ + +#ifndef GL_NV_gpu_program5_mem_extended +#define GL_NV_gpu_program5_mem_extended 1 +#endif /* GL_NV_gpu_program5_mem_extended */ + +#ifndef GL_NV_gpu_shader5 +#define GL_NV_gpu_shader5 1 +#endif /* GL_NV_gpu_shader5 */ + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +typedef unsigned short GLhalfNV; +#define GL_HALF_FLOAT_NV 0x140B +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor4hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV s); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum target, GLhalfNV s); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum target, GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV fog); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *fog); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV weight); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *weight); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint index, GLhalfNV x); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint index, GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +#endif +#endif /* GL_NV_half_float */ + +#ifndef GL_NV_internalformat_sample_query +#define GL_NV_internalformat_sample_query 1 +#define GL_MULTISAMPLES_NV 0x9371 +#define GL_SUPERSAMPLE_SCALE_X_NV 0x9372 +#define GL_SUPERSAMPLE_SCALE_Y_NV 0x9373 +#define GL_CONFORMANT_NV 0x9374 +typedef void (APIENTRYP PFNGLGETINTERNALFORMATSAMPLEIVNVPROC) (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetInternalformatSampleivNV (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); +#endif +#endif /* GL_NV_internalformat_sample_query */ + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif /* GL_NV_light_max_exponent */ + +#ifndef GL_NV_memory_attachment +#define GL_NV_memory_attachment 1 +#define GL_ATTACHED_MEMORY_OBJECT_NV 0x95A4 +#define GL_ATTACHED_MEMORY_OFFSET_NV 0x95A5 +#define GL_MEMORY_ATTACHABLE_ALIGNMENT_NV 0x95A6 +#define GL_MEMORY_ATTACHABLE_SIZE_NV 0x95A7 +#define GL_MEMORY_ATTACHABLE_NV 0x95A8 +#define GL_DETACHED_MEMORY_INCARNATION_NV 0x95A9 +#define GL_DETACHED_TEXTURES_NV 0x95AA +#define GL_DETACHED_BUFFERS_NV 0x95AB +#define GL_MAX_DETACHED_TEXTURES_NV 0x95AC +#define GL_MAX_DETACHED_BUFFERS_NV 0x95AD +typedef void (APIENTRYP PFNGLGETMEMORYOBJECTDETACHEDRESOURCESUIVNVPROC) (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); +typedef void (APIENTRYP PFNGLRESETMEMORYOBJECTPARAMETERNVPROC) (GLuint memory, GLenum pname); +typedef void (APIENTRYP PFNGLTEXATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLBUFFERATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTUREATTACHMEMORYNVPROC) (GLuint texture, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLNAMEDBUFFERATTACHMEMORYNVPROC) (GLuint buffer, GLuint memory, GLuint64 offset); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMemoryObjectDetachedResourcesuivNV (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); +GLAPI void APIENTRY glResetMemoryObjectParameterNV (GLuint memory, GLenum pname); +GLAPI void APIENTRY glTexAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glBufferAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureAttachMemoryNV (GLuint texture, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glNamedBufferAttachMemoryNV (GLuint buffer, GLuint memory, GLuint64 offset); +#endif +#endif /* GL_NV_memory_attachment */ + +#ifndef GL_NV_memory_object_sparse +#define GL_NV_memory_object_sparse 1 +typedef void (APIENTRYP PFNGLBUFFERPAGECOMMITMENTMEMNVPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTMEMNVPROC) (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTMEMNVPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTMEMNVPROC) (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferPageCommitmentMemNV (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +GLAPI void APIENTRY glTexPageCommitmentMemNV (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +GLAPI void APIENTRY glNamedBufferPageCommitmentMemNV (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +GLAPI void APIENTRY glTexturePageCommitmentMemNV (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +#endif +#endif /* GL_NV_memory_object_sparse */ + +#ifndef GL_NV_mesh_shader +#define GL_NV_mesh_shader 1 +#define GL_MESH_SHADER_NV 0x9559 +#define GL_TASK_SHADER_NV 0x955A +#define GL_MAX_MESH_UNIFORM_BLOCKS_NV 0x8E60 +#define GL_MAX_MESH_TEXTURE_IMAGE_UNITS_NV 0x8E61 +#define GL_MAX_MESH_IMAGE_UNIFORMS_NV 0x8E62 +#define GL_MAX_MESH_UNIFORM_COMPONENTS_NV 0x8E63 +#define GL_MAX_MESH_ATOMIC_COUNTER_BUFFERS_NV 0x8E64 +#define GL_MAX_MESH_ATOMIC_COUNTERS_NV 0x8E65 +#define GL_MAX_MESH_SHADER_STORAGE_BLOCKS_NV 0x8E66 +#define GL_MAX_COMBINED_MESH_UNIFORM_COMPONENTS_NV 0x8E67 +#define GL_MAX_TASK_UNIFORM_BLOCKS_NV 0x8E68 +#define GL_MAX_TASK_TEXTURE_IMAGE_UNITS_NV 0x8E69 +#define GL_MAX_TASK_IMAGE_UNIFORMS_NV 0x8E6A +#define GL_MAX_TASK_UNIFORM_COMPONENTS_NV 0x8E6B +#define GL_MAX_TASK_ATOMIC_COUNTER_BUFFERS_NV 0x8E6C +#define GL_MAX_TASK_ATOMIC_COUNTERS_NV 0x8E6D +#define GL_MAX_TASK_SHADER_STORAGE_BLOCKS_NV 0x8E6E +#define GL_MAX_COMBINED_TASK_UNIFORM_COMPONENTS_NV 0x8E6F +#define GL_MAX_MESH_WORK_GROUP_INVOCATIONS_NV 0x95A2 +#define GL_MAX_TASK_WORK_GROUP_INVOCATIONS_NV 0x95A3 +#define GL_MAX_MESH_TOTAL_MEMORY_SIZE_NV 0x9536 +#define GL_MAX_TASK_TOTAL_MEMORY_SIZE_NV 0x9537 +#define GL_MAX_MESH_OUTPUT_VERTICES_NV 0x9538 +#define GL_MAX_MESH_OUTPUT_PRIMITIVES_NV 0x9539 +#define GL_MAX_TASK_OUTPUT_COUNT_NV 0x953A +#define GL_MAX_DRAW_MESH_TASKS_COUNT_NV 0x953D +#define GL_MAX_MESH_VIEWS_NV 0x9557 +#define GL_MESH_OUTPUT_PER_VERTEX_GRANULARITY_NV 0x92DF +#define GL_MESH_OUTPUT_PER_PRIMITIVE_GRANULARITY_NV 0x9543 +#define GL_MAX_MESH_WORK_GROUP_SIZE_NV 0x953B +#define GL_MAX_TASK_WORK_GROUP_SIZE_NV 0x953C +#define GL_MESH_WORK_GROUP_SIZE_NV 0x953E +#define GL_TASK_WORK_GROUP_SIZE_NV 0x953F +#define GL_MESH_VERTICES_OUT_NV 0x9579 +#define GL_MESH_PRIMITIVES_OUT_NV 0x957A +#define GL_MESH_OUTPUT_TYPE_NV 0x957B +#define GL_UNIFORM_BLOCK_REFERENCED_BY_MESH_SHADER_NV 0x959C +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TASK_SHADER_NV 0x959D +#define GL_REFERENCED_BY_MESH_SHADER_NV 0x95A0 +#define GL_REFERENCED_BY_TASK_SHADER_NV 0x95A1 +#define GL_MESH_SHADER_BIT_NV 0x00000040 +#define GL_TASK_SHADER_BIT_NV 0x00000080 +#define GL_MESH_SUBROUTINE_NV 0x957C +#define GL_TASK_SUBROUTINE_NV 0x957D +#define GL_MESH_SUBROUTINE_UNIFORM_NV 0x957E +#define GL_TASK_SUBROUTINE_UNIFORM_NV 0x957F +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_MESH_SHADER_NV 0x959E +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TASK_SHADER_NV 0x959F +typedef void (APIENTRYP PFNGLDRAWMESHTASKSNVPROC) (GLuint first, GLuint count); +typedef void (APIENTRYP PFNGLDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect); +typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTNVPROC) (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshTasksNV (GLuint first, GLuint count); +GLAPI void APIENTRY glDrawMeshTasksIndirectNV (GLintptr indirect); +GLAPI void APIENTRY glMultiDrawMeshTasksIndirectNV (GLintptr indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawMeshTasksIndirectCountNV (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_NV_mesh_shader */ + +#ifndef GL_NV_multisample_coverage +#define GL_NV_multisample_coverage 1 +#endif /* GL_NV_multisample_coverage */ + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif /* GL_NV_multisample_filter_hint */ + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params); +#endif +#endif /* GL_NV_occlusion_query */ + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif /* GL_NV_packed_depth_stencil */ + +#ifndef GL_NV_parameter_buffer_object +#define GL_NV_parameter_buffer_object 1 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 +#define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 +#define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 +#define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramBufferParametersfvNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramBufferParametersIivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramBufferParametersIuivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); +#endif +#endif /* GL_NV_parameter_buffer_object */ + +#ifndef GL_NV_parameter_buffer_object2 +#define GL_NV_parameter_buffer_object2 1 +#endif /* GL_NV_parameter_buffer_object2 */ + +#ifndef GL_NV_path_rendering +#define GL_NV_path_rendering 1 +#define GL_PATH_FORMAT_SVG_NV 0x9070 +#define GL_PATH_FORMAT_PS_NV 0x9071 +#define GL_STANDARD_FONT_NAME_NV 0x9072 +#define GL_SYSTEM_FONT_NAME_NV 0x9073 +#define GL_FILE_NAME_NV 0x9074 +#define GL_PATH_STROKE_WIDTH_NV 0x9075 +#define GL_PATH_END_CAPS_NV 0x9076 +#define GL_PATH_INITIAL_END_CAP_NV 0x9077 +#define GL_PATH_TERMINAL_END_CAP_NV 0x9078 +#define GL_PATH_JOIN_STYLE_NV 0x9079 +#define GL_PATH_MITER_LIMIT_NV 0x907A +#define GL_PATH_DASH_CAPS_NV 0x907B +#define GL_PATH_INITIAL_DASH_CAP_NV 0x907C +#define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D +#define GL_PATH_DASH_OFFSET_NV 0x907E +#define GL_PATH_CLIENT_LENGTH_NV 0x907F +#define GL_PATH_FILL_MODE_NV 0x9080 +#define GL_PATH_FILL_MASK_NV 0x9081 +#define GL_PATH_FILL_COVER_MODE_NV 0x9082 +#define GL_PATH_STROKE_COVER_MODE_NV 0x9083 +#define GL_PATH_STROKE_MASK_NV 0x9084 +#define GL_COUNT_UP_NV 0x9088 +#define GL_COUNT_DOWN_NV 0x9089 +#define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A +#define GL_CONVEX_HULL_NV 0x908B +#define GL_BOUNDING_BOX_NV 0x908D +#define GL_TRANSLATE_X_NV 0x908E +#define GL_TRANSLATE_Y_NV 0x908F +#define GL_TRANSLATE_2D_NV 0x9090 +#define GL_TRANSLATE_3D_NV 0x9091 +#define GL_AFFINE_2D_NV 0x9092 +#define GL_AFFINE_3D_NV 0x9094 +#define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 +#define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 +#define GL_UTF8_NV 0x909A +#define GL_UTF16_NV 0x909B +#define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C +#define GL_PATH_COMMAND_COUNT_NV 0x909D +#define GL_PATH_COORD_COUNT_NV 0x909E +#define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F +#define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 +#define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 +#define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 +#define GL_SQUARE_NV 0x90A3 +#define GL_ROUND_NV 0x90A4 +#define GL_TRIANGULAR_NV 0x90A5 +#define GL_BEVEL_NV 0x90A6 +#define GL_MITER_REVERT_NV 0x90A7 +#define GL_MITER_TRUNCATE_NV 0x90A8 +#define GL_SKIP_MISSING_GLYPH_NV 0x90A9 +#define GL_USE_MISSING_GLYPH_NV 0x90AA +#define GL_PATH_ERROR_POSITION_NV 0x90AB +#define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD +#define GL_ADJACENT_PAIRS_NV 0x90AE +#define GL_FIRST_TO_REST_NV 0x90AF +#define GL_PATH_GEN_MODE_NV 0x90B0 +#define GL_PATH_GEN_COEFF_NV 0x90B1 +#define GL_PATH_GEN_COMPONENTS_NV 0x90B3 +#define GL_PATH_STENCIL_FUNC_NV 0x90B7 +#define GL_PATH_STENCIL_REF_NV 0x90B8 +#define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 +#define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD +#define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE +#define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF +#define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 +#define GL_MOVE_TO_RESETS_NV 0x90B5 +#define GL_MOVE_TO_CONTINUES_NV 0x90B6 +#define GL_CLOSE_PATH_NV 0x00 +#define GL_MOVE_TO_NV 0x02 +#define GL_RELATIVE_MOVE_TO_NV 0x03 +#define GL_LINE_TO_NV 0x04 +#define GL_RELATIVE_LINE_TO_NV 0x05 +#define GL_HORIZONTAL_LINE_TO_NV 0x06 +#define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 +#define GL_VERTICAL_LINE_TO_NV 0x08 +#define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 +#define GL_QUADRATIC_CURVE_TO_NV 0x0A +#define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B +#define GL_CUBIC_CURVE_TO_NV 0x0C +#define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D +#define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E +#define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F +#define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 +#define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 +#define GL_SMALL_CCW_ARC_TO_NV 0x12 +#define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 +#define GL_SMALL_CW_ARC_TO_NV 0x14 +#define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 +#define GL_LARGE_CCW_ARC_TO_NV 0x16 +#define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 +#define GL_LARGE_CW_ARC_TO_NV 0x18 +#define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 +#define GL_RESTART_PATH_NV 0xF0 +#define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 +#define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 +#define GL_RECT_NV 0xF6 +#define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 +#define GL_CIRCULAR_CW_ARC_TO_NV 0xFA +#define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC +#define GL_ARC_TO_NV 0xFE +#define GL_RELATIVE_ARC_TO_NV 0xFF +#define GL_BOLD_BIT_NV 0x01 +#define GL_ITALIC_BIT_NV 0x02 +#define GL_GLYPH_WIDTH_BIT_NV 0x01 +#define GL_GLYPH_HEIGHT_BIT_NV 0x02 +#define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 +#define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 +#define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 +#define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 +#define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 +#define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 +#define GL_GLYPH_HAS_KERNING_BIT_NV 0x100 +#define GL_FONT_X_MIN_BOUNDS_BIT_NV 0x00010000 +#define GL_FONT_Y_MIN_BOUNDS_BIT_NV 0x00020000 +#define GL_FONT_X_MAX_BOUNDS_BIT_NV 0x00040000 +#define GL_FONT_Y_MAX_BOUNDS_BIT_NV 0x00080000 +#define GL_FONT_UNITS_PER_EM_BIT_NV 0x00100000 +#define GL_FONT_ASCENDER_BIT_NV 0x00200000 +#define GL_FONT_DESCENDER_BIT_NV 0x00400000 +#define GL_FONT_HEIGHT_BIT_NV 0x00800000 +#define GL_FONT_MAX_ADVANCE_WIDTH_BIT_NV 0x01000000 +#define GL_FONT_MAX_ADVANCE_HEIGHT_BIT_NV 0x02000000 +#define GL_FONT_UNDERLINE_POSITION_BIT_NV 0x04000000 +#define GL_FONT_UNDERLINE_THICKNESS_BIT_NV 0x08000000 +#define GL_FONT_HAS_KERNING_BIT_NV 0x10000000 +#define GL_ROUNDED_RECT_NV 0xE8 +#define GL_RELATIVE_ROUNDED_RECT_NV 0xE9 +#define GL_ROUNDED_RECT2_NV 0xEA +#define GL_RELATIVE_ROUNDED_RECT2_NV 0xEB +#define GL_ROUNDED_RECT4_NV 0xEC +#define GL_RELATIVE_ROUNDED_RECT4_NV 0xED +#define GL_ROUNDED_RECT8_NV 0xEE +#define GL_RELATIVE_ROUNDED_RECT8_NV 0xEF +#define GL_RELATIVE_RECT_NV 0xF7 +#define GL_FONT_GLYPHS_AVAILABLE_NV 0x9368 +#define GL_FONT_TARGET_UNAVAILABLE_NV 0x9369 +#define GL_FONT_UNAVAILABLE_NV 0x936A +#define GL_FONT_UNINTELLIGIBLE_NV 0x936B +#define GL_CONIC_CURVE_TO_NV 0x1A +#define GL_RELATIVE_CONIC_CURVE_TO_NV 0x1B +#define GL_FONT_NUM_GLYPH_INDICES_BIT_NV 0x20000000 +#define GL_STANDARD_FONT_FORMAT_NV 0x936C +#define GL_2_BYTES_NV 0x1407 +#define GL_3_BYTES_NV 0x1408 +#define GL_4_BYTES_NV 0x1409 +#define GL_EYE_LINEAR_NV 0x2400 +#define GL_OBJECT_LINEAR_NV 0x2401 +#define GL_CONSTANT_NV 0x8576 +#define GL_PATH_FOG_GEN_MODE_NV 0x90AC +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_PATH_GEN_COLOR_FORMAT_NV 0x90B2 +#define GL_PATH_PROJECTION_NV 0x1701 +#define GL_PATH_MODELVIEW_NV 0x1700 +#define GL_PATH_MODELVIEW_STACK_DEPTH_NV 0x0BA3 +#define GL_PATH_MODELVIEW_MATRIX_NV 0x0BA6 +#define GL_PATH_MAX_MODELVIEW_STACK_DEPTH_NV 0x0D36 +#define GL_PATH_TRANSPOSE_MODELVIEW_MATRIX_NV 0x84E3 +#define GL_PATH_PROJECTION_STACK_DEPTH_NV 0x0BA4 +#define GL_PATH_PROJECTION_MATRIX_NV 0x0BA7 +#define GL_PATH_MAX_PROJECTION_STACK_DEPTH_NV 0x0D38 +#define GL_PATH_TRANSPOSE_PROJECTION_MATRIX_NV 0x84E4 +#define GL_FRAGMENT_INPUT_NV 0x936D +typedef GLuint (APIENTRYP PFNGLGENPATHSNVPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEPATHSNVPROC) (GLuint path, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISPATHNVPROC) (GLuint path); +typedef void (APIENTRYP PFNGLPATHCOMMANDSNVPROC) (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHCOORDSNVPROC) (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC) (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOORDSNVPROC) (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSTRINGNVPROC) (GLuint path, GLenum format, GLsizei length, const void *pathString); +typedef void (APIENTRYP PFNGLPATHGLYPHSNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLPATHGLYPHRANGENVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLWEIGHTPATHSNVPROC) (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +typedef void (APIENTRYP PFNGLCOPYPATHNVPROC) (GLuint resultPath, GLuint srcPath); +typedef void (APIENTRYP PFNGLINTERPOLATEPATHSNVPROC) (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +typedef void (APIENTRYP PFNGLTRANSFORMPATHNVPROC) (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, const GLint *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERINVPROC) (GLuint path, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, const GLfloat *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFNVPROC) (GLuint path, GLenum pname, GLfloat value); +typedef void (APIENTRYP PFNGLPATHDASHARRAYNVPROC) (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +typedef void (APIENTRYP PFNGLPATHSTENCILFUNCNVPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC) (GLfloat factor, GLfloat units); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC) (GLenum func); +typedef void (APIENTRYP PFNGLCOVERFILLPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHCOMMANDSNVPROC) (GLuint path, GLubyte *commands); +typedef void (APIENTRYP PFNGLGETPATHCOORDSNVPROC) (GLuint path, GLfloat *coords); +typedef void (APIENTRYP PFNGLGETPATHDASHARRAYNVPROC) (GLuint path, GLfloat *dashArray); +typedef void (APIENTRYP PFNGLGETPATHMETRICSNVPROC) (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHMETRICRANGENVPROC) (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHSPACINGNVPROC) (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +typedef GLboolean (APIENTRYP PFNGLISPOINTINFILLPATHNVPROC) (GLuint path, GLuint mask, GLfloat x, GLfloat y); +typedef GLboolean (APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC) (GLuint path, GLfloat x, GLfloat y); +typedef GLfloat (APIENTRYP PFNGLGETPATHLENGTHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments); +typedef GLboolean (APIENTRYP PFNGLPOINTALONGPATHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +typedef void (APIENTRYP PFNGLMATRIXLOAD3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOAD3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULT3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULT3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask, GLenum coverMode); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef GLenum (APIENTRYP PFNGLPATHGLYPHINDEXRANGENVPROC) (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); +typedef GLenum (APIENTRYP PFNGLPATHGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef GLenum (APIENTRYP PFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC) (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEFVNVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); +typedef void (APIENTRYP PFNGLPATHCOLORGENNVPROC) (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHTEXGENNVPROC) (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHFOGGENNVPROC) (GLenum genMode); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENIVNVPROC) (GLenum color, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENFVNVPROC) (GLenum color, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENIVNVPROC) (GLenum texCoordSet, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENFVNVPROC) (GLenum texCoordSet, GLenum pname, GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenPathsNV (GLsizei range); +GLAPI void APIENTRY glDeletePathsNV (GLuint path, GLsizei range); +GLAPI GLboolean APIENTRY glIsPathNV (GLuint path); +GLAPI void APIENTRY glPathCommandsNV (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathCoordsNV (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathSubCommandsNV (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathSubCoordsNV (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathStringNV (GLuint path, GLenum format, GLsizei length, const void *pathString); +GLAPI void APIENTRY glPathGlyphsNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glPathGlyphRangeNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glWeightPathsNV (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +GLAPI void APIENTRY glCopyPathNV (GLuint resultPath, GLuint srcPath); +GLAPI void APIENTRY glInterpolatePathsNV (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +GLAPI void APIENTRY glTransformPathNV (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathParameterivNV (GLuint path, GLenum pname, const GLint *value); +GLAPI void APIENTRY glPathParameteriNV (GLuint path, GLenum pname, GLint value); +GLAPI void APIENTRY glPathParameterfvNV (GLuint path, GLenum pname, const GLfloat *value); +GLAPI void APIENTRY glPathParameterfNV (GLuint path, GLenum pname, GLfloat value); +GLAPI void APIENTRY glPathDashArrayNV (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +GLAPI void APIENTRY glPathStencilFuncNV (GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glPathStencilDepthOffsetNV (GLfloat factor, GLfloat units); +GLAPI void APIENTRY glStencilFillPathNV (GLuint path, GLenum fillMode, GLuint mask); +GLAPI void APIENTRY glStencilStrokePathNV (GLuint path, GLint reference, GLuint mask); +GLAPI void APIENTRY glStencilFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glStencilStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathCoverDepthFuncNV (GLenum func); +GLAPI void APIENTRY glCoverFillPathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverStrokePathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glGetPathParameterivNV (GLuint path, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathParameterfvNV (GLuint path, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathCommandsNV (GLuint path, GLubyte *commands); +GLAPI void APIENTRY glGetPathCoordsNV (GLuint path, GLfloat *coords); +GLAPI void APIENTRY glGetPathDashArrayNV (GLuint path, GLfloat *dashArray); +GLAPI void APIENTRY glGetPathMetricsNV (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathMetricRangeNV (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathSpacingNV (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +GLAPI GLboolean APIENTRY glIsPointInFillPathNV (GLuint path, GLuint mask, GLfloat x, GLfloat y); +GLAPI GLboolean APIENTRY glIsPointInStrokePathNV (GLuint path, GLfloat x, GLfloat y); +GLAPI GLfloat APIENTRY glGetPathLengthNV (GLuint path, GLsizei startSegment, GLsizei numSegments); +GLAPI GLboolean APIENTRY glPointAlongPathNV (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +GLAPI void APIENTRY glMatrixLoad3x2fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoad3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMult3x2fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMult3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glStencilThenCoverFillPathNV (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); +GLAPI void APIENTRY glStencilThenCoverStrokePathNV (GLuint path, GLint reference, GLuint mask, GLenum coverMode); +GLAPI void APIENTRY glStencilThenCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glStencilThenCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI GLenum APIENTRY glPathGlyphIndexRangeNV (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); +GLAPI GLenum APIENTRY glPathGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI GLenum APIENTRY glPathMemoryGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glProgramPathFragmentInputGenNV (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); +GLAPI void APIENTRY glGetProgramResourcefvNV (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); +GLAPI void APIENTRY glPathColorGenNV (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +GLAPI void APIENTRY glPathTexGenNV (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +GLAPI void APIENTRY glPathFogGenNV (GLenum genMode); +GLAPI void APIENTRY glGetPathColorGenivNV (GLenum color, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathColorGenfvNV (GLenum color, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathTexGenivNV (GLenum texCoordSet, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathTexGenfvNV (GLenum texCoordSet, GLenum pname, GLfloat *value); +#endif +#endif /* GL_NV_path_rendering */ + +#ifndef GL_NV_path_rendering_shared_edge +#define GL_NV_path_rendering_shared_edge 1 +#define GL_SHARED_EDGE_NV 0xC0 +#endif /* GL_NV_path_rendering_shared_edge */ + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, const void *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum target, GLsizei length, const void *pointer); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum target); +#endif +#endif /* GL_NV_pixel_data_range */ + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameterivNV (GLenum pname, const GLint *params); +#endif +#endif /* GL_NV_point_sprite */ + +#ifndef GL_NV_present_video +#define GL_NV_present_video 1 +#define GL_FRAME_NV 0x8E26 +#define GL_FIELDS_NV 0x8E27 +#define GL_CURRENT_TIME_NV 0x8E28 +#define GL_NUM_FILL_STREAMS_NV 0x8E29 +#define GL_PRESENT_TIME_NV 0x8E2A +#define GL_PRESENT_DURATION_NV 0x8E2B +typedef void (APIENTRYP PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +typedef void (APIENTRYP PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +typedef void (APIENTRYP PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPresentFrameKeyedNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +GLAPI void APIENTRY glPresentFrameDualFillNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +GLAPI void APIENTRY glGetVideoivNV (GLuint video_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideouivNV (GLuint video_slot, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetVideoi64vNV (GLuint video_slot, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVideoui64vNV (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif +#endif /* GL_NV_present_video */ + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint index); +#endif +#endif /* GL_NV_primitive_restart */ + +#ifndef GL_NV_primitive_shading_rate +#define GL_NV_primitive_shading_rate 1 +#define GL_SHADING_RATE_IMAGE_PER_PRIMITIVE_NV 0x95B1 +#define GL_SHADING_RATE_IMAGE_PALETTE_COUNT_NV 0x95B2 +#endif /* GL_NV_primitive_shading_rate */ + +#ifndef GL_NV_query_resource +#define GL_NV_query_resource 1 +#define GL_QUERY_RESOURCE_TYPE_VIDMEM_ALLOC_NV 0x9540 +#define GL_QUERY_RESOURCE_MEMTYPE_VIDMEM_NV 0x9542 +#define GL_QUERY_RESOURCE_SYS_RESERVED_NV 0x9544 +#define GL_QUERY_RESOURCE_TEXTURE_NV 0x9545 +#define GL_QUERY_RESOURCE_RENDERBUFFER_NV 0x9546 +#define GL_QUERY_RESOURCE_BUFFEROBJECT_NV 0x9547 +typedef GLint (APIENTRYP PFNGLQUERYRESOURCENVPROC) (GLenum queryType, GLint tagId, GLuint count, GLint *buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glQueryResourceNV (GLenum queryType, GLint tagId, GLuint count, GLint *buffer); +#endif +#endif /* GL_NV_query_resource */ + +#ifndef GL_NV_query_resource_tag +#define GL_NV_query_resource_tag 1 +typedef void (APIENTRYP PFNGLGENQUERYRESOURCETAGNVPROC) (GLsizei n, GLint *tagIds); +typedef void (APIENTRYP PFNGLDELETEQUERYRESOURCETAGNVPROC) (GLsizei n, const GLint *tagIds); +typedef void (APIENTRYP PFNGLQUERYRESOURCETAGNVPROC) (GLint tagId, const GLchar *tagString); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueryResourceTagNV (GLsizei n, GLint *tagIds); +GLAPI void APIENTRY glDeleteQueryResourceTagNV (GLsizei n, const GLint *tagIds); +GLAPI void APIENTRY glQueryResourceTagNV (GLint tagId, const GLchar *tagString); +#endif +#endif /* GL_NV_query_resource_tag */ + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum pname, GLfloat param); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum pname, const GLint *params); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glCombinerInputNV (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glCombinerOutputNV (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum stage, GLenum portion, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum variable, GLenum pname, GLint *params); +#endif +#endif /* GL_NV_register_combiners */ + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum stage, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum stage, GLenum pname, GLfloat *params); +#endif +#endif /* GL_NV_register_combiners2 */ + +#ifndef GL_NV_representative_fragment_test +#define GL_NV_representative_fragment_test 1 +#define GL_REPRESENTATIVE_FRAGMENT_TEST_NV 0x937F +#endif /* GL_NV_representative_fragment_test */ + +#ifndef GL_NV_robustness_video_memory_purge +#define GL_NV_robustness_video_memory_purge 1 +#define GL_PURGED_CONTEXT_RESET_NV 0x92BB +#endif /* GL_NV_robustness_video_memory_purge */ + +#ifndef GL_NV_sample_locations +#define GL_NV_sample_locations 1 +#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_NV 0x933D +#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_NV 0x933E +#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_NV 0x933F +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_NV 0x9340 +#define GL_SAMPLE_LOCATION_NV 0x8E50 +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9341 +#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_NV 0x9342 +#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_NV 0x9343 +typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLRESOLVEDEPTHVALUESNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferSampleLocationsfvNV (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvNV (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glResolveDepthValuesNV (void); +#endif +#endif /* GL_NV_sample_locations */ + +#ifndef GL_NV_sample_mask_override_coverage +#define GL_NV_sample_mask_override_coverage 1 +#endif /* GL_NV_sample_mask_override_coverage */ + +#ifndef GL_NV_scissor_exclusive +#define GL_NV_scissor_exclusive 1 +#define GL_SCISSOR_TEST_EXCLUSIVE_NV 0x9555 +#define GL_SCISSOR_BOX_EXCLUSIVE_NV 0x9556 +typedef void (APIENTRYP PFNGLSCISSOREXCLUSIVENVPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSOREXCLUSIVEARRAYVNVPROC) (GLuint first, GLsizei count, const GLint *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glScissorExclusiveNV (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorExclusiveArrayvNV (GLuint first, GLsizei count, const GLint *v); +#endif +#endif /* GL_NV_scissor_exclusive */ + +#ifndef GL_NV_shader_atomic_counters +#define GL_NV_shader_atomic_counters 1 +#endif /* GL_NV_shader_atomic_counters */ + +#ifndef GL_NV_shader_atomic_float +#define GL_NV_shader_atomic_float 1 +#endif /* GL_NV_shader_atomic_float */ + +#ifndef GL_NV_shader_atomic_float64 +#define GL_NV_shader_atomic_float64 1 +#endif /* GL_NV_shader_atomic_float64 */ + +#ifndef GL_NV_shader_atomic_fp16_vector +#define GL_NV_shader_atomic_fp16_vector 1 +#endif /* GL_NV_shader_atomic_fp16_vector */ + +#ifndef GL_NV_shader_atomic_int64 +#define GL_NV_shader_atomic_int64 1 +#endif /* GL_NV_shader_atomic_int64 */ + +#ifndef GL_NV_shader_buffer_load +#define GL_NV_shader_buffer_load 1 +#define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D +#define GL_GPU_ADDRESS_NV 0x8F34 +#define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 +typedef void (APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC) (GLenum target, GLenum access); +typedef void (APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC) (GLenum target); +typedef GLboolean (APIENTRYP PFNGLISBUFFERRESIDENTNVPROC) (GLenum target); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC) (GLuint buffer, GLenum access); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC) (GLuint buffer); +typedef GLboolean (APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC) (GLenum target, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC) (GLuint buffer, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETINTEGERUI64VNVPROC) (GLenum value, GLuint64EXT *result); +typedef void (APIENTRYP PFNGLUNIFORMUI64NVPROC) (GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLUNIFORMUI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC) (GLuint program, GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMakeBufferResidentNV (GLenum target, GLenum access); +GLAPI void APIENTRY glMakeBufferNonResidentNV (GLenum target); +GLAPI GLboolean APIENTRY glIsBufferResidentNV (GLenum target); +GLAPI void APIENTRY glMakeNamedBufferResidentNV (GLuint buffer, GLenum access); +GLAPI void APIENTRY glMakeNamedBufferNonResidentNV (GLuint buffer); +GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV (GLuint buffer); +GLAPI void APIENTRY glGetBufferParameterui64vNV (GLenum target, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetNamedBufferParameterui64vNV (GLuint buffer, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetIntegerui64vNV (GLenum value, GLuint64EXT *result); +GLAPI void APIENTRY glUniformui64NV (GLint location, GLuint64EXT value); +GLAPI void APIENTRY glUniformui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniformui64NV (GLuint program, GLint location, GLuint64EXT value); +GLAPI void APIENTRY glProgramUniformui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_NV_shader_buffer_load */ + +#ifndef GL_NV_shader_buffer_store +#define GL_NV_shader_buffer_store 1 +#define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 +#endif /* GL_NV_shader_buffer_store */ + +#ifndef GL_NV_shader_storage_buffer_object +#define GL_NV_shader_storage_buffer_object 1 +#endif /* GL_NV_shader_storage_buffer_object */ + +#ifndef GL_NV_shader_subgroup_partitioned +#define GL_NV_shader_subgroup_partitioned 1 +#define GL_SUBGROUP_FEATURE_PARTITIONED_BIT_NV 0x00000100 +#endif /* GL_NV_shader_subgroup_partitioned */ + +#ifndef GL_NV_shader_texture_footprint +#define GL_NV_shader_texture_footprint 1 +#endif /* GL_NV_shader_texture_footprint */ + +#ifndef GL_NV_shader_thread_group +#define GL_NV_shader_thread_group 1 +#define GL_WARP_SIZE_NV 0x9339 +#define GL_WARPS_PER_SM_NV 0x933A +#define GL_SM_COUNT_NV 0x933B +#endif /* GL_NV_shader_thread_group */ + +#ifndef GL_NV_shader_thread_shuffle +#define GL_NV_shader_thread_shuffle 1 +#endif /* GL_NV_shader_thread_shuffle */ + +#ifndef GL_NV_shading_rate_image +#define GL_NV_shading_rate_image 1 +#define GL_SHADING_RATE_IMAGE_NV 0x9563 +#define GL_SHADING_RATE_NO_INVOCATIONS_NV 0x9564 +#define GL_SHADING_RATE_1_INVOCATION_PER_PIXEL_NV 0x9565 +#define GL_SHADING_RATE_1_INVOCATION_PER_1X2_PIXELS_NV 0x9566 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X1_PIXELS_NV 0x9567 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X2_PIXELS_NV 0x9568 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X4_PIXELS_NV 0x9569 +#define GL_SHADING_RATE_1_INVOCATION_PER_4X2_PIXELS_NV 0x956A +#define GL_SHADING_RATE_1_INVOCATION_PER_4X4_PIXELS_NV 0x956B +#define GL_SHADING_RATE_2_INVOCATIONS_PER_PIXEL_NV 0x956C +#define GL_SHADING_RATE_4_INVOCATIONS_PER_PIXEL_NV 0x956D +#define GL_SHADING_RATE_8_INVOCATIONS_PER_PIXEL_NV 0x956E +#define GL_SHADING_RATE_16_INVOCATIONS_PER_PIXEL_NV 0x956F +#define GL_SHADING_RATE_IMAGE_BINDING_NV 0x955B +#define GL_SHADING_RATE_IMAGE_TEXEL_WIDTH_NV 0x955C +#define GL_SHADING_RATE_IMAGE_TEXEL_HEIGHT_NV 0x955D +#define GL_SHADING_RATE_IMAGE_PALETTE_SIZE_NV 0x955E +#define GL_MAX_COARSE_FRAGMENT_SAMPLES_NV 0x955F +#define GL_SHADING_RATE_SAMPLE_ORDER_DEFAULT_NV 0x95AE +#define GL_SHADING_RATE_SAMPLE_ORDER_PIXEL_MAJOR_NV 0x95AF +#define GL_SHADING_RATE_SAMPLE_ORDER_SAMPLE_MAJOR_NV 0x95B0 +typedef void (APIENTRYP PFNGLBINDSHADINGRATEIMAGENVPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLGETSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint entry, GLenum *rate); +typedef void (APIENTRYP PFNGLGETSHADINGRATESAMPLELOCATIONIVNVPROC) (GLenum rate, GLuint samples, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLSHADINGRATEIMAGEBARRIERNVPROC) (GLboolean synchronize); +typedef void (APIENTRYP PFNGLSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); +typedef void (APIENTRYP PFNGLSHADINGRATESAMPLEORDERNVPROC) (GLenum order); +typedef void (APIENTRYP PFNGLSHADINGRATESAMPLEORDERCUSTOMNVPROC) (GLenum rate, GLuint samples, const GLint *locations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindShadingRateImageNV (GLuint texture); +GLAPI void APIENTRY glGetShadingRateImagePaletteNV (GLuint viewport, GLuint entry, GLenum *rate); +GLAPI void APIENTRY glGetShadingRateSampleLocationivNV (GLenum rate, GLuint samples, GLuint index, GLint *location); +GLAPI void APIENTRY glShadingRateImageBarrierNV (GLboolean synchronize); +GLAPI void APIENTRY glShadingRateImagePaletteNV (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); +GLAPI void APIENTRY glShadingRateSampleOrderNV (GLenum order); +GLAPI void APIENTRY glShadingRateSampleOrderCustomNV (GLenum rate, GLuint samples, const GLint *locations); +#endif +#endif /* GL_NV_shading_rate_image */ + +#ifndef GL_NV_stereo_view_rendering +#define GL_NV_stereo_view_rendering 1 +#endif /* GL_NV_stereo_view_rendering */ + +#ifndef GL_NV_tessellation_program5 +#define GL_NV_tessellation_program5 1 +#define GL_MAX_PROGRAM_PATCH_ATTRIBS_NV 0x86D8 +#define GL_TESS_CONTROL_PROGRAM_NV 0x891E +#define GL_TESS_EVALUATION_PROGRAM_NV 0x891F +#define GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV 0x8C74 +#define GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV 0x8C75 +#endif /* GL_NV_tessellation_program5 */ + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif /* GL_NV_texgen_emboss */ + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif /* GL_NV_texgen_reflection */ + +#ifndef GL_NV_texture_barrier +#define GL_NV_texture_barrier 1 +typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureBarrierNV (void); +#endif +#endif /* GL_NV_texture_barrier */ + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif /* GL_NV_texture_compression_vtc */ + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif /* GL_NV_texture_env_combine4 */ + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif /* GL_NV_texture_expand_normal */ + +#ifndef GL_NV_texture_multisample +#define GL_NV_texture_multisample 1 +#define GL_TEXTURE_COVERAGE_SAMPLES_NV 0x9045 +#define GL_TEXTURE_COLOR_SAMPLES_NV 0x9046 +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage2DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTexImage3DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#endif +#endif /* GL_NV_texture_multisample */ + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif /* GL_NV_texture_rectangle */ + +#ifndef GL_NV_texture_rectangle_compressed +#define GL_NV_texture_rectangle_compressed 1 +#endif /* GL_NV_texture_rectangle_compressed */ + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_2D_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_2D_BIAS_NV 0x86E3 +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif /* GL_NV_texture_shader */ + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif /* GL_NV_texture_shader2 */ + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif /* GL_NV_texture_shader3 */ + +#ifndef GL_NV_timeline_semaphore +#define GL_NV_timeline_semaphore 1 +#define GL_TIMELINE_SEMAPHORE_VALUE_NV 0x9595 +#define GL_SEMAPHORE_TYPE_NV 0x95B3 +#define GL_SEMAPHORE_TYPE_BINARY_NV 0x95B4 +#define GL_SEMAPHORE_TYPE_TIMELINE_NV 0x95B5 +#define GL_MAX_TIMELINE_SEMAPHORE_VALUE_DIFFERENCE_NV 0x95B6 +typedef void (APIENTRYP PFNGLCREATESEMAPHORESNVPROC) (GLsizei n, GLuint *semaphores); +typedef void (APIENTRYP PFNGLSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCreateSemaphoresNV (GLsizei n, GLuint *semaphores); +GLAPI void APIENTRY glSemaphoreParameterivNV (GLuint semaphore, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetSemaphoreParameterivNV (GLuint semaphore, GLenum pname, GLint *params); +#endif +#endif /* GL_NV_timeline_semaphore */ + +#ifndef GL_NV_transform_feedback +#define GL_NV_transform_feedback 1 +#define GL_BACK_PRIMARY_COLOR_NV 0x8C77 +#define GL_BACK_SECONDARY_COLOR_NV 0x8C78 +#define GL_TEXTURE_COORD_NV 0x8C79 +#define GL_CLIP_DISTANCE_NV 0x8C7A +#define GL_VERTEX_ID_NV 0x8C7B +#define GL_PRIMITIVE_ID_NV 0x8C7C +#define GL_GENERIC_ATTRIB_NV 0x8C7D +#define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 +#define GL_ACTIVE_VARYINGS_NV 0x8C81 +#define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 +#define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 +#define GL_PRIMITIVES_GENERATED_NV 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 +#define GL_RASTERIZER_DISCARD_NV 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_NV 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C +#define GL_SEPARATE_ATTRIBS_NV 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F +#define GL_LAYER_NV 0x8DAA +#define GL_NEXT_BUFFER_NV -2 +#define GL_SKIP_COMPONENTS4_NV -3 +#define GL_SKIP_COMPONENTS3_NV -4 +#define GL_SKIP_COMPONENTS2_NV -5 +#define GL_SKIP_COMPONENTS1_NV -6 +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLenum bufferMode); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +typedef void (APIENTRYP PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackNV (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackNV (void); +GLAPI void APIENTRY glTransformFeedbackAttribsNV (GLsizei count, const GLint *attribs, GLenum bufferMode); +GLAPI void APIENTRY glBindBufferRangeNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseNV (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsNV (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +GLAPI void APIENTRY glActiveVaryingNV (GLuint program, const GLchar *name); +GLAPI GLint APIENTRY glGetVaryingLocationNV (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetActiveVaryingNV (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetTransformFeedbackVaryingNV (GLuint program, GLuint index, GLint *location); +GLAPI void APIENTRY glTransformFeedbackStreamAttribsNV (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif +#endif /* GL_NV_transform_feedback */ + +#ifndef GL_NV_transform_feedback2 +#define GL_NV_transform_feedback2 1 +#define GL_TRANSFORM_FEEDBACK_NV 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED_NV 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE_NV 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING_NV 0x8E25 +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSNVPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSNVPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKNVPROC) (GLenum mode, GLuint id); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedbackNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacksNV (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacksNV (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedbackNV (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedbackNV (void); +GLAPI void APIENTRY glResumeTransformFeedbackNV (void); +GLAPI void APIENTRY glDrawTransformFeedbackNV (GLenum mode, GLuint id); +#endif +#endif /* GL_NV_transform_feedback2 */ + +#ifndef GL_NV_uniform_buffer_unified_memory +#define GL_NV_uniform_buffer_unified_memory 1 +#define GL_UNIFORM_BUFFER_UNIFIED_NV 0x936E +#define GL_UNIFORM_BUFFER_ADDRESS_NV 0x936F +#define GL_UNIFORM_BUFFER_LENGTH_NV 0x9370 +#endif /* GL_NV_uniform_buffer_unified_memory */ + +#ifndef GL_NV_vdpau_interop +#define GL_NV_vdpau_interop 1 +typedef GLintptr GLvdpauSurfaceNV; +#define GL_SURFACE_STATE_NV 0x86EB +#define GL_SURFACE_REGISTERED_NV 0x86FD +#define GL_SURFACE_MAPPED_NV 0x8700 +#define GL_WRITE_DISCARD_NV 0x88BE +typedef void (APIENTRYP PFNGLVDPAUINITNVPROC) (const void *vdpDevice, const void *getProcAddress); +typedef void (APIENTRYP PFNGLVDPAUFININVPROC) (void); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLboolean (APIENTRYP PFNGLVDPAUISSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUUNREGISTERSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUGETSURFACEIVNVPROC) (GLvdpauSurfaceNV surface, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLVDPAUSURFACEACCESSNVPROC) (GLvdpauSurfaceNV surface, GLenum access); +typedef void (APIENTRYP PFNGLVDPAUMAPSURFACESNVPROC) (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +typedef void (APIENTRYP PFNGLVDPAUUNMAPSURFACESNVPROC) (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVDPAUInitNV (const void *vdpDevice, const void *getProcAddress); +GLAPI void APIENTRY glVDPAUFiniNV (void); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterOutputSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLboolean APIENTRY glVDPAUIsSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUUnregisterSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUGetSurfaceivNV (GLvdpauSurfaceNV surface, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +GLAPI void APIENTRY glVDPAUSurfaceAccessNV (GLvdpauSurfaceNV surface, GLenum access); +GLAPI void APIENTRY glVDPAUMapSurfacesNV (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +GLAPI void APIENTRY glVDPAUUnmapSurfacesNV (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif +#endif /* GL_NV_vdpau_interop */ + +#ifndef GL_NV_vdpau_interop2 +#define GL_NV_vdpau_interop2 1 +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACEWITHPICTURESTRUCTURENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames, GLboolean isFrameStructure); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceWithPictureStructureNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames, GLboolean isFrameStructure); +#endif +#endif /* GL_NV_vdpau_interop2 */ + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei length, const void *pointer); +#endif +#endif /* GL_NV_vertex_array_range */ + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif /* GL_NV_vertex_array_range2 */ + +#ifndef GL_NV_vertex_attrib_integer_64bit +#define GL_NV_vertex_attrib_integer_64bit 1 +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC) (GLuint index, GLint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC) (GLuint index, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1i64NV (GLuint index, GLint64EXT x); +GLAPI void APIENTRY glVertexAttribL2i64NV (GLuint index, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glVertexAttribL3i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glVertexAttribL4i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glVertexAttribL1i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL1ui64NV (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL2ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glVertexAttribL3ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glVertexAttribL4ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glVertexAttribL1ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLi64vNV (GLuint index, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVertexAttribLui64vNV (GLuint index, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glVertexAttribLFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif +#endif /* GL_NV_vertex_attrib_integer_64bit */ + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_NV_vertex_buffer_unified_memory 1 +#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E +#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F +#define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 +#define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 +#define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 +#define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 +#define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 +#define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 +#define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 +#define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 +#define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 +#define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 +#define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A +#define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B +#define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C +#define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D +#define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E +#define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F +#define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 +#define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 +#define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 +#define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 +#define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 +#define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 +#define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 +typedef void (APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC) (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +typedef void (APIENTRYP PFNGLVERTEXFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLNORMALFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLINDEXFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLTEXCOORDFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLEDGEFLAGFORMATNVPROC) (GLsizei stride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLFOGCOORDFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferAddressRangeNV (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +GLAPI void APIENTRY glVertexFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glNormalFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glIndexFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glTexCoordFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glEdgeFlagFormatNV (GLsizei stride); +GLAPI void APIENTRY glSecondaryColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glFogCoordFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormatNV (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +GLAPI void APIENTRY glVertexAttribIFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glGetIntegerui64i_vNV (GLenum value, GLuint index, GLuint64EXT *result); +#endif +#endif /* GL_NV_vertex_buffer_unified_memory */ + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei n, const GLuint *programs, GLboolean *residences); +GLAPI void APIENTRY glBindProgramNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glExecuteProgramNV (GLenum target, GLuint id, const GLfloat *params); +GLAPI void APIENTRY glGenProgramsNV (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum target, GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetProgramivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringNV (GLuint id, GLenum pname, GLubyte *program); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum target, GLuint address, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint id); +GLAPI void APIENTRY glLoadProgramNV (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum target, GLuint index, const GLdouble *v); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum target, GLuint index, const GLfloat *v); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glTrackMatrixNV (GLenum target, GLuint address, GLenum matrix, GLenum transform); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint index, GLsizei count, const GLubyte *v); +#endif +#endif /* GL_NV_vertex_program */ + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif /* GL_NV_vertex_program1_1 */ + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif /* GL_NV_vertex_program2 */ + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif /* GL_NV_vertex_program2_option */ + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif /* GL_NV_vertex_program3 */ + +#ifndef GL_NV_vertex_program4 +#define GL_NV_vertex_program4 1 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_NV 0x88FD +#endif /* GL_NV_vertex_program4 */ + +#ifndef GL_NV_video_capture +#define GL_NV_video_capture 1 +#define GL_VIDEO_BUFFER_NV 0x9020 +#define GL_VIDEO_BUFFER_BINDING_NV 0x9021 +#define GL_FIELD_UPPER_NV 0x9022 +#define GL_FIELD_LOWER_NV 0x9023 +#define GL_NUM_VIDEO_CAPTURE_STREAMS_NV 0x9024 +#define GL_NEXT_VIDEO_CAPTURE_BUFFER_STATUS_NV 0x9025 +#define GL_VIDEO_CAPTURE_TO_422_SUPPORTED_NV 0x9026 +#define GL_LAST_VIDEO_CAPTURE_STATUS_NV 0x9027 +#define GL_VIDEO_BUFFER_PITCH_NV 0x9028 +#define GL_VIDEO_COLOR_CONVERSION_MATRIX_NV 0x9029 +#define GL_VIDEO_COLOR_CONVERSION_MAX_NV 0x902A +#define GL_VIDEO_COLOR_CONVERSION_MIN_NV 0x902B +#define GL_VIDEO_COLOR_CONVERSION_OFFSET_NV 0x902C +#define GL_VIDEO_BUFFER_INTERNAL_FORMAT_NV 0x902D +#define GL_PARTIAL_SUCCESS_NV 0x902E +#define GL_SUCCESS_NV 0x902F +#define GL_FAILURE_NV 0x9030 +#define GL_YCBYCR8_422_NV 0x9031 +#define GL_YCBAYCR8A_4224_NV 0x9032 +#define GL_Z6Y10Z6CB10Z6Y10Z6CR10_422_NV 0x9033 +#define GL_Z6Y10Z6CB10Z6A10Z6Y10Z6CR10Z6A10_4224_NV 0x9034 +#define GL_Z4Y12Z4CB12Z4Y12Z4CR12_422_NV 0x9035 +#define GL_Z4Y12Z4CB12Z4A12Z4Y12Z4CR12Z4A12_4224_NV 0x9036 +#define GL_Z4Y12Z4CB12Z4CR12_444_NV 0x9037 +#define GL_VIDEO_CAPTURE_FRAME_WIDTH_NV 0x9038 +#define GL_VIDEO_CAPTURE_FRAME_HEIGHT_NV 0x9039 +#define GL_VIDEO_CAPTURE_FIELD_UPPER_HEIGHT_NV 0x903A +#define GL_VIDEO_CAPTURE_FIELD_LOWER_HEIGHT_NV 0x903B +#define GL_VIDEO_CAPTURE_SURFACE_ORIGIN_NV 0x903C +typedef void (APIENTRYP PFNGLBEGINVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENDVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTUREIVNVPROC) (GLuint video_capture_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +typedef GLenum (APIENTRYP PFNGLVIDEOCAPTURENVPROC) (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glBindVideoCaptureStreamBufferNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +GLAPI void APIENTRY glBindVideoCaptureStreamTextureNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +GLAPI void APIENTRY glEndVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glGetVideoCaptureivNV (GLuint video_capture_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVideoCaptureStreamdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +GLAPI GLenum APIENTRY glVideoCaptureNV (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +GLAPI void APIENTRY glVideoCaptureStreamParameterivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif +#endif /* GL_NV_video_capture */ + +#ifndef GL_NV_viewport_array2 +#define GL_NV_viewport_array2 1 +#endif /* GL_NV_viewport_array2 */ + +#ifndef GL_NV_viewport_swizzle +#define GL_NV_viewport_swizzle 1 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV 0x9350 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_X_NV 0x9351 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_Y_NV 0x9352 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Y_NV 0x9353 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_Z_NV 0x9354 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Z_NV 0x9355 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_W_NV 0x9356 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_W_NV 0x9357 +#define GL_VIEWPORT_SWIZZLE_X_NV 0x9358 +#define GL_VIEWPORT_SWIZZLE_Y_NV 0x9359 +#define GL_VIEWPORT_SWIZZLE_Z_NV 0x935A +#define GL_VIEWPORT_SWIZZLE_W_NV 0x935B +typedef void (APIENTRYP PFNGLVIEWPORTSWIZZLENVPROC) (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glViewportSwizzleNV (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); +#endif +#endif /* GL_NV_viewport_swizzle */ + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif /* GL_OML_interlace */ + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif /* GL_OML_resample */ + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif /* GL_OML_subsample */ + +#ifndef GL_OVR_multiview +#define GL_OVR_multiview 1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR 0x9630 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR 0x9632 +#define GL_MAX_VIEWS_OVR 0x9631 +#define GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR 0x9633 +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferTextureMultiviewOVR (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +#endif +#endif /* GL_OVR_multiview */ + +#ifndef GL_OVR_multiview2 +#define GL_OVR_multiview2 1 +#endif /* GL_OVR_multiview2 */ + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum target, GLint mode); +#endif +#endif /* GL_PGI_misc_hints */ + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif /* GL_PGI_vertex_hints */ + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif /* GL_REND_screen_coordinates */ + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#define GL_RGBA_DXT5_S3TC 0x83A4 +#define GL_RGBA4_DXT5_S3TC 0x83A5 +#endif /* GL_S3_s3tc */ + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum target, GLfloat *points); +#endif +#endif /* GL_SGIS_detail_texture */ + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *points); +#endif +#endif /* GL_SGIS_fog_function */ + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif /* GL_SGIS_generate_mipmap */ + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum pattern); +#endif +#endif /* GL_SGIS_multisample */ + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum pname, GLfloat *params); +#endif +#endif /* GL_SGIS_pixel_texture */ + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif /* GL_SGIS_point_line_texgen */ + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_SGIS_point_parameters */ + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum target, GLfloat *points); +#endif +#endif /* GL_SGIS_sharpen_texture */ + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_SGIS_texture4D */ + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif /* GL_SGIS_texture_border_clamp */ + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif +#endif /* GL_SGIS_texture_color_mask */ + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif /* GL_SGIS_texture_edge_clamp */ + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum target, GLenum filter, GLfloat *weights); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif +#endif /* GL_SGIS_texture_filter4 */ + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif /* GL_SGIS_texture_lod */ + +#ifndef GL_SGIS_texture_select +#define GL_SGIS_texture_select 1 +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif /* GL_SGIS_texture_select */ + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#define GL_ASYNC_MARKER_SGIX 0x8329 +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint marker); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *markerp); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *markerp); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei range); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint marker, GLsizei range); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint marker); +#endif +#endif /* GL_SGIX_async */ + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif /* GL_SGIX_async_histogram */ + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif /* GL_SGIX_async_pixel */ + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif /* GL_SGIX_blend_alpha_minmax */ + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif /* GL_SGIX_calligraphic_fragment */ + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif /* GL_SGIX_clipmap */ + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif /* GL_SGIX_convolution_accuracy */ + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif /* GL_SGIX_depth_pass_instrument */ + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif /* GL_SGIX_depth_texture */ + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif +#endif /* GL_SGIX_flush_raster */ + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif /* GL_SGIX_fog_offset */ + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum face, GLenum mode); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum light, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum light, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum light, GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum face, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum face, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum face, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum light, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum light, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum face, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum face, GLenum pname, GLint *params); +GLAPI void APIENTRY glLightEnviSGIX (GLenum pname, GLint param); +#endif +#endif /* GL_SGIX_fragment_lighting */ + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint factor); +#endif +#endif /* GL_SGIX_framezoom */ + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const void *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum pname, const void *params); +#endif +#endif /* GL_SGIX_igloo_interface */ + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei size, GLint *buffer); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *marker_p); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint marker); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint marker); +#endif +#endif /* GL_SGIX_instruments */ + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#define GL_INTERLACE_SGIX 0x8094 +#endif /* GL_SGIX_interlace */ + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif /* GL_SGIX_ir_instrument1 */ + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#define GL_LIST_PRIORITY_SGIX 0x8182 +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint list, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint list, GLenum pname, GLint *params); +GLAPI void APIENTRY glListParameterfSGIX (GLuint list, GLenum pname, GLfloat param); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint list, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glListParameteriSGIX (GLuint list, GLenum pname, GLint param); +GLAPI void APIENTRY glListParameterivSGIX (GLuint list, GLenum pname, const GLint *params); +#endif +#endif /* GL_SGIX_list_priority */ + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum mode); +#endif +#endif /* GL_SGIX_pixel_texture */ + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif /* GL_SGIX_pixel_tiles */ + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +GLAPI void APIENTRY glDeformSGIX (GLbitfield mask); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield mask); +#endif +#endif /* GL_SGIX_polynomial_ffd */ + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *equation); +#endif +#endif /* GL_SGIX_reference_plane */ + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#define GL_PACK_RESAMPLE_SGIX 0x842E +#define GL_UNPACK_RESAMPLE_SGIX 0x842F +#define GL_RESAMPLE_REPLICATE_SGIX 0x8433 +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x8434 +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif /* GL_SGIX_resample */ + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif /* GL_SGIX_scalebias_hint */ + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif /* GL_SGIX_shadow */ + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif /* GL_SGIX_shadow_ambient */ + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum pname, const GLint *params); +#endif +#endif /* GL_SGIX_sprite */ + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif /* GL_SGIX_subsample */ + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif +#endif /* GL_SGIX_tag_sample_buffer */ + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif /* GL_SGIX_texture_add_env */ + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif /* GL_SGIX_texture_coordinate_clamp */ + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif /* GL_SGIX_texture_lod_bias */ + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif /* GL_SGIX_texture_multi_buffer */ + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif /* GL_SGIX_texture_scale_bias */ + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif /* GL_SGIX_vertex_preclip */ + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif /* GL_SGIX_ycrcb */ + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif /* GL_SGIX_ycrcb_subsample */ + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif /* GL_SGIX_ycrcba */ + +#ifndef GL_SGI_color_matrix +#define GL_SGI_color_matrix 1 +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif /* GL_SGI_color_matrix */ + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTableSGI (GLenum target, GLenum format, GLenum type, void *table); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum target, GLenum pname, GLint *params); +#endif +#endif /* GL_SGI_color_table */ + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif /* GL_SGI_texture_color_table */ + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif +#endif /* GL_SUNX_constant_data */ + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif /* GL_SUN_convolution_border_modes */ + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort factor); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint factor); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat factor); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble factor); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort factor); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint factor); +#endif +#endif /* GL_SUN_global_alpha */ + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif +#endif /* GL_SUN_mesh_array */ + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif /* GL_SUN_slice_accum */ + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint code); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort code); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte code); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *code); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *code); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *code); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum type, GLsizei stride, const void **pointer); +#endif +#endif /* GL_SUN_triangle_list */ + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *rc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *rc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif +#endif /* GL_SUN_vertex */ + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif /* GL_WIN_phong_shading */ + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif /* GL_WIN_specular_fog */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles.h new file mode 100644 index 00000000..adf6ef78 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * This is a simple file to encapsulate the OpenGL ES 1.X API headers. + */ + +#include "SDL_config.h" + +#ifdef __IPHONEOS__ +#include +#include +#else +#include +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2.h new file mode 100644 index 00000000..55141971 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2.h @@ -0,0 +1,51 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * This is a simple file to encapsulate the OpenGL ES 2.0 API headers. + */ + +#include "SDL_config.h" + +#if !defined(_MSC_VER) && !defined(SDL_USE_BUILTIN_OPENGL_DEFINITIONS) + +#ifdef __IPHONEOS__ +#include +#include +#else +#include +#include +#include +#endif + +#else /* _MSC_VER */ + +/* OpenGL ES2 headers for Visual Studio */ +#include "SDL_opengles2_khrplatform.h" +#include "SDL_opengles2_gl2platform.h" +#include "SDL_opengles2_gl2.h" +#include "SDL_opengles2_gl2ext.h" + +#endif /* _MSC_VER */ + +#ifndef APIENTRY +#define APIENTRY GL_APIENTRY +#endif diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2.h new file mode 100644 index 00000000..d13622aa --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2.h @@ -0,0 +1,656 @@ +#ifndef __gles2_gl2_h_ +#define __gles2_gl2_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ + +/*#include */ + +#ifndef GL_APIENTRYP +#define GL_APIENTRYP GL_APIENTRY* +#endif + +#ifndef GL_GLES_PROTOTYPES +#define GL_GLES_PROTOTYPES 1 +#endif + +/* Generated on date 20220530 */ + +/* Generated C header for: + * API: gles2 + * Profile: common + * Versions considered: 2\.[0-9] + * Versions emitted: .* + * Default extensions included: None + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef GL_ES_VERSION_2_0 +#define GL_ES_VERSION_2_0 1 +/*#include */ +typedef khronos_int8_t GLbyte; +typedef khronos_float_t GLclampf; +typedef khronos_int32_t GLfixed; +typedef khronos_int16_t GLshort; +typedef khronos_uint16_t GLushort; +typedef void GLvoid; +typedef struct __GLsync *GLsync; +typedef khronos_int64_t GLint64; +typedef khronos_uint64_t GLuint64; +typedef unsigned int GLenum; +typedef unsigned int GLuint; +typedef char GLchar; +typedef khronos_float_t GLfloat; +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +typedef unsigned int GLbitfield; +typedef int GLint; +typedef unsigned char GLboolean; +typedef int GLsizei; +typedef khronos_uint8_t GLubyte; +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_FUNC_ADD 0x8006 +#define GL_BLEND_EQUATION 0x8009 +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_STREAM_DRAW 0x88E0 +#define GL_STATIC_DRAW 0x88E4 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_CULL_FACE 0x0B44 +#define GL_BLEND 0x0BE2 +#define GL_DITHER 0x0BD0 +#define GL_STENCIL_TEST 0x0B90 +#define GL_DEPTH_TEST 0x0B71 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_LINE_WIDTH 0x0B21 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_VIEWPORT 0x0BA2 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_FIXED 0x140C +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#define GL_SHADER_TYPE 0x8B4F +#define GL_DELETE_STATUS 0x8B80 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_INVERT 0x150A +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE 0x1702 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_REPEAT 0x2901 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_COMPILE_STATUS 0x8B81 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_SHADER_COMPILER 0x8DFA +#define GL_SHADER_BINARY_FORMATS 0x8DF8 +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGB565 0x8D62 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_NONE 0 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9 +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +typedef void (GL_APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (GL_APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (GL_APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (GL_APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (GL_APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (GL_APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (GL_APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); +typedef void (GL_APIENTRYP PFNGLBLENDCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (GL_APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (GL_APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (GL_APIENTRYP PFNGLBLENDFUNCPROC) (GLenum sfactor, GLenum dfactor); +typedef void (GL_APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (GL_APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (GL_APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef GLenum (GL_APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (GL_APIENTRYP PFNGLCLEARPROC) (GLbitfield mask); +typedef void (GL_APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (GL_APIENTRYP PFNGLCLEARDEPTHFPROC) (GLfloat d); +typedef void (GL_APIENTRYP PFNGLCLEARSTENCILPROC) (GLint s); +typedef void (GL_APIENTRYP PFNGLCOLORMASKPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +typedef void (GL_APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef void (GL_APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (GL_APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (GL_APIENTRYP PFNGLCOPYTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (GL_APIENTRYP PFNGLCOPYTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef GLuint (GL_APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (GL_APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (GL_APIENTRYP PFNGLCULLFACEPROC) (GLenum mode); +typedef void (GL_APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (GL_APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (GL_APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (GL_APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (GL_APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (GL_APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); +typedef void (GL_APIENTRYP PFNGLDEPTHFUNCPROC) (GLenum func); +typedef void (GL_APIENTRYP PFNGLDEPTHMASKPROC) (GLboolean flag); +typedef void (GL_APIENTRYP PFNGLDEPTHRANGEFPROC) (GLfloat n, GLfloat f); +typedef void (GL_APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (GL_APIENTRYP PFNGLDISABLEPROC) (GLenum cap); +typedef void (GL_APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (GL_APIENTRYP PFNGLDRAWARRAYSPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (GL_APIENTRYP PFNGLENABLEPROC) (GLenum cap); +typedef void (GL_APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (GL_APIENTRYP PFNGLFINISHPROC) (void); +typedef void (GL_APIENTRYP PFNGLFLUSHPROC) (void); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GL_APIENTRYP PFNGLFRONTFACEPROC) (GLenum mode); +typedef void (GL_APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef void (GL_APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (GL_APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef void (GL_APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (GL_APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); +typedef void (GL_APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (GL_APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (GL_APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +typedef GLint (GL_APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (GL_APIENTRYP PFNGLGETBOOLEANVPROC) (GLenum pname, GLboolean *data); +typedef void (GL_APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLenum (GL_APIENTRYP PFNGLGETERRORPROC) (void); +typedef void (GL_APIENTRYP PFNGLGETFLOATVPROC) (GLenum pname, GLfloat *data); +typedef void (GL_APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data); +typedef void (GL_APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (GL_APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (GL_APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +typedef void (GL_APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef const GLubyte *(GL_APIENTRYP PFNGLGETSTRINGPROC) (GLenum name); +typedef void (GL_APIENTRYP PFNGLGETTEXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (GL_APIENTRYP PFNGLGETTEXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (GL_APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef GLint (GL_APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (GL_APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (GL_APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); +typedef void (GL_APIENTRYP PFNGLHINTPROC) (GLenum target, GLenum mode); +typedef GLboolean (GL_APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef GLboolean (GL_APIENTRYP PFNGLISENABLEDPROC) (GLenum cap); +typedef GLboolean (GL_APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef GLboolean (GL_APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (GL_APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef GLboolean (GL_APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef GLboolean (GL_APIENTRYP PFNGLISTEXTUREPROC) (GLuint texture); +typedef void (GL_APIENTRYP PFNGLLINEWIDTHPROC) (GLfloat width); +typedef void (GL_APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (GL_APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param); +typedef void (GL_APIENTRYP PFNGLPOLYGONOFFSETPROC) (GLfloat factor, GLfloat units); +typedef void (GL_APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +typedef void (GL_APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLfloat value, GLboolean invert); +typedef void (GL_APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +typedef void (GL_APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (GL_APIENTRYP PFNGLSTENCILFUNCPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (GL_APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (GL_APIENTRYP PFNGLSTENCILMASKPROC) (GLuint mask); +typedef void (GL_APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (GL_APIENTRYP PFNGLSTENCILOPPROC) (GLenum fail, GLenum zfail, GLenum zpass); +typedef void (GL_APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (GL_APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (GL_APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (GL_APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (GL_APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (GL_APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (GL_APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (GL_APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (GL_APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (GL_APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (GL_APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (GL_APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (GL_APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (GL_APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +#if GL_GLES_PROTOTYPES +GL_APICALL void GL_APIENTRY glActiveTexture (GLenum texture); +GL_APICALL void GL_APIENTRY glAttachShader (GLuint program, GLuint shader); +GL_APICALL void GL_APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); +GL_APICALL void GL_APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GL_APICALL void GL_APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); +GL_APICALL void GL_APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); +GL_APICALL void GL_APIENTRY glBindTexture (GLenum target, GLuint texture); +GL_APICALL void GL_APIENTRY glBlendColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GL_APICALL void GL_APIENTRY glBlendEquation (GLenum mode); +GL_APICALL void GL_APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GL_APICALL void GL_APIENTRY glBlendFunc (GLenum sfactor, GLenum dfactor); +GL_APICALL void GL_APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GL_APICALL void GL_APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GL_APICALL void GL_APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +GL_APICALL GLenum GL_APIENTRY glCheckFramebufferStatus (GLenum target); +GL_APICALL void GL_APIENTRY glClear (GLbitfield mask); +GL_APICALL void GL_APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GL_APICALL void GL_APIENTRY glClearDepthf (GLfloat d); +GL_APICALL void GL_APIENTRY glClearStencil (GLint s); +GL_APICALL void GL_APIENTRY glColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +GL_APICALL void GL_APIENTRY glCompileShader (GLuint shader); +GL_APICALL void GL_APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GL_APICALL void GL_APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GL_APICALL void GL_APIENTRY glCopyTexImage2D (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GL_APICALL void GL_APIENTRY glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GL_APICALL GLuint GL_APIENTRY glCreateProgram (void); +GL_APICALL GLuint GL_APIENTRY glCreateShader (GLenum type); +GL_APICALL void GL_APIENTRY glCullFace (GLenum mode); +GL_APICALL void GL_APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GL_APICALL void GL_APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); +GL_APICALL void GL_APIENTRY glDeleteProgram (GLuint program); +GL_APICALL void GL_APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); +GL_APICALL void GL_APIENTRY glDeleteShader (GLuint shader); +GL_APICALL void GL_APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); +GL_APICALL void GL_APIENTRY glDepthFunc (GLenum func); +GL_APICALL void GL_APIENTRY glDepthMask (GLboolean flag); +GL_APICALL void GL_APIENTRY glDepthRangef (GLfloat n, GLfloat f); +GL_APICALL void GL_APIENTRY glDetachShader (GLuint program, GLuint shader); +GL_APICALL void GL_APIENTRY glDisable (GLenum cap); +GL_APICALL void GL_APIENTRY glDisableVertexAttribArray (GLuint index); +GL_APICALL void GL_APIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count); +GL_APICALL void GL_APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); +GL_APICALL void GL_APIENTRY glEnable (GLenum cap); +GL_APICALL void GL_APIENTRY glEnableVertexAttribArray (GLuint index); +GL_APICALL void GL_APIENTRY glFinish (void); +GL_APICALL void GL_APIENTRY glFlush (void); +GL_APICALL void GL_APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GL_APICALL void GL_APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GL_APICALL void GL_APIENTRY glFrontFace (GLenum mode); +GL_APICALL void GL_APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GL_APICALL void GL_APIENTRY glGenerateMipmap (GLenum target); +GL_APICALL void GL_APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); +GL_APICALL void GL_APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); +GL_APICALL void GL_APIENTRY glGenTextures (GLsizei n, GLuint *textures); +GL_APICALL void GL_APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GL_APICALL void GL_APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GL_APICALL void GL_APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +GL_APICALL GLint GL_APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GL_APICALL void GL_APIENTRY glGetBooleanv (GLenum pname, GLboolean *data); +GL_APICALL void GL_APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); +GL_APICALL GLenum GL_APIENTRY glGetError (void); +GL_APICALL void GL_APIENTRY glGetFloatv (GLenum pname, GLfloat *data); +GL_APICALL void GL_APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetIntegerv (GLenum pname, GLint *data); +GL_APICALL void GL_APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GL_APICALL void GL_APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GL_APICALL void GL_APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GL_APICALL void GL_APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GL_APICALL const GLubyte *GL_APIENTRY glGetString (GLenum name); +GL_APICALL void GL_APIENTRY glGetTexParameterfv (GLenum target, GLenum pname, GLfloat *params); +GL_APICALL void GL_APIENTRY glGetTexParameteriv (GLenum target, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); +GL_APICALL void GL_APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); +GL_APICALL GLint GL_APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GL_APICALL void GL_APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); +GL_APICALL void GL_APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); +GL_APICALL void GL_APIENTRY glHint (GLenum target, GLenum mode); +GL_APICALL GLboolean GL_APIENTRY glIsBuffer (GLuint buffer); +GL_APICALL GLboolean GL_APIENTRY glIsEnabled (GLenum cap); +GL_APICALL GLboolean GL_APIENTRY glIsFramebuffer (GLuint framebuffer); +GL_APICALL GLboolean GL_APIENTRY glIsProgram (GLuint program); +GL_APICALL GLboolean GL_APIENTRY glIsRenderbuffer (GLuint renderbuffer); +GL_APICALL GLboolean GL_APIENTRY glIsShader (GLuint shader); +GL_APICALL GLboolean GL_APIENTRY glIsTexture (GLuint texture); +GL_APICALL void GL_APIENTRY glLineWidth (GLfloat width); +GL_APICALL void GL_APIENTRY glLinkProgram (GLuint program); +GL_APICALL void GL_APIENTRY glPixelStorei (GLenum pname, GLint param); +GL_APICALL void GL_APIENTRY glPolygonOffset (GLfloat factor, GLfloat units); +GL_APICALL void GL_APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GL_APICALL void GL_APIENTRY glReleaseShaderCompiler (void); +GL_APICALL void GL_APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glSampleCoverage (GLfloat value, GLboolean invert); +GL_APICALL void GL_APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +GL_APICALL void GL_APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GL_APICALL void GL_APIENTRY glStencilFunc (GLenum func, GLint ref, GLuint mask); +GL_APICALL void GL_APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); +GL_APICALL void GL_APIENTRY glStencilMask (GLuint mask); +GL_APICALL void GL_APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); +GL_APICALL void GL_APIENTRY glStencilOp (GLenum fail, GLenum zfail, GLenum zpass); +GL_APICALL void GL_APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GL_APICALL void GL_APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GL_APICALL void GL_APIENTRY glTexParameterf (GLenum target, GLenum pname, GLfloat param); +GL_APICALL void GL_APIENTRY glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GL_APICALL void GL_APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param); +GL_APICALL void GL_APIENTRY glTexParameteriv (GLenum target, GLenum pname, const GLint *params); +GL_APICALL void GL_APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GL_APICALL void GL_APIENTRY glUniform1f (GLint location, GLfloat v0); +GL_APICALL void GL_APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniform1i (GLint location, GLint v0); +GL_APICALL void GL_APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); +GL_APICALL void GL_APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); +GL_APICALL void GL_APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GL_APICALL void GL_APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); +GL_APICALL void GL_APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GL_APICALL void GL_APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GL_APICALL void GL_APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUseProgram (GLuint program); +GL_APICALL void GL_APIENTRY glValidateProgram (GLuint program); +GL_APICALL void GL_APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); +GL_APICALL void GL_APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); +GL_APICALL void GL_APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); +GL_APICALL void GL_APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); +GL_APICALL void GL_APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GL_APICALL void GL_APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); +GL_APICALL void GL_APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GL_APICALL void GL_APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); +GL_APICALL void GL_APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +GL_APICALL void GL_APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_ES_VERSION_2_0 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2ext.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2ext.h new file mode 100644 index 00000000..9448ce09 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2ext.h @@ -0,0 +1,4033 @@ +#ifndef __gles2_gl2ext_h_ +#define __gles2_gl2ext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ + +#ifndef GL_APIENTRYP +#define GL_APIENTRYP GL_APIENTRY* +#endif + +/* Generated on date 20220530 */ + +/* Generated C header for: + * API: gles2 + * Profile: common + * Versions considered: 2\.[0-9] + * Versions emitted: _nomatch_^ + * Default extensions included: gles2 + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef GL_KHR_blend_equation_advanced +#define GL_KHR_blend_equation_advanced 1 +#define GL_MULTIPLY_KHR 0x9294 +#define GL_SCREEN_KHR 0x9295 +#define GL_OVERLAY_KHR 0x9296 +#define GL_DARKEN_KHR 0x9297 +#define GL_LIGHTEN_KHR 0x9298 +#define GL_COLORDODGE_KHR 0x9299 +#define GL_COLORBURN_KHR 0x929A +#define GL_HARDLIGHT_KHR 0x929B +#define GL_SOFTLIGHT_KHR 0x929C +#define GL_DIFFERENCE_KHR 0x929E +#define GL_EXCLUSION_KHR 0x92A0 +#define GL_HSL_HUE_KHR 0x92AD +#define GL_HSL_SATURATION_KHR 0x92AE +#define GL_HSL_COLOR_KHR 0x92AF +#define GL_HSL_LUMINOSITY_KHR 0x92B0 +typedef void (GL_APIENTRYP PFNGLBLENDBARRIERKHRPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBlendBarrierKHR (void); +#endif +#endif /* GL_KHR_blend_equation_advanced */ + +#ifndef GL_KHR_blend_equation_advanced_coherent +#define GL_KHR_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285 +#endif /* GL_KHR_blend_equation_advanced_coherent */ + +#ifndef GL_KHR_context_flush_control +#define GL_KHR_context_flush_control 1 +#define GL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x82FB +#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x82FC +#endif /* GL_KHR_context_flush_control */ + +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +typedef void (GL_APIENTRY *GLDEBUGPROCKHR)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_SAMPLER 0x82E6 +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_KHR 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_KHR 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_KHR 0x8245 +#define GL_DEBUG_SOURCE_API_KHR 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_KHR 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_KHR 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_KHR 0x824A +#define GL_DEBUG_SOURCE_OTHER_KHR 0x824B +#define GL_DEBUG_TYPE_ERROR_KHR 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_KHR 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_KHR 0x8250 +#define GL_DEBUG_TYPE_OTHER_KHR 0x8251 +#define GL_DEBUG_TYPE_MARKER_KHR 0x8268 +#define GL_DEBUG_TYPE_PUSH_GROUP_KHR 0x8269 +#define GL_DEBUG_TYPE_POP_GROUP_KHR 0x826A +#define GL_DEBUG_SEVERITY_NOTIFICATION_KHR 0x826B +#define GL_MAX_DEBUG_GROUP_STACK_DEPTH_KHR 0x826C +#define GL_DEBUG_GROUP_STACK_DEPTH_KHR 0x826D +#define GL_BUFFER_KHR 0x82E0 +#define GL_SHADER_KHR 0x82E1 +#define GL_PROGRAM_KHR 0x82E2 +#define GL_VERTEX_ARRAY_KHR 0x8074 +#define GL_QUERY_KHR 0x82E3 +#define GL_PROGRAM_PIPELINE_KHR 0x82E4 +#define GL_SAMPLER_KHR 0x82E6 +#define GL_MAX_LABEL_LENGTH_KHR 0x82E8 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_KHR 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_KHR 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_KHR 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_KHR 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_KHR 0x9147 +#define GL_DEBUG_SEVERITY_LOW_KHR 0x9148 +#define GL_DEBUG_OUTPUT_KHR 0x92E0 +#define GL_CONTEXT_FLAG_DEBUG_BIT_KHR 0x00000002 +#define GL_STACK_OVERFLOW_KHR 0x0503 +#define GL_STACK_UNDERFLOW_KHR 0x0504 +typedef void (GL_APIENTRYP PFNGLDEBUGMESSAGECONTROLKHRPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (GL_APIENTRYP PFNGLDEBUGMESSAGEINSERTKHRPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (GL_APIENTRYP PFNGLDEBUGMESSAGECALLBACKKHRPROC) (GLDEBUGPROCKHR callback, const void *userParam); +typedef GLuint (GL_APIENTRYP PFNGLGETDEBUGMESSAGELOGKHRPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +typedef void (GL_APIENTRYP PFNGLPUSHDEBUGGROUPKHRPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message); +typedef void (GL_APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC) (void); +typedef void (GL_APIENTRYP PFNGLOBJECTLABELKHRPROC) (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +typedef void (GL_APIENTRYP PFNGLGETOBJECTLABELKHRPROC) (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +typedef void (GL_APIENTRYP PFNGLOBJECTPTRLABELKHRPROC) (const void *ptr, GLsizei length, const GLchar *label); +typedef void (GL_APIENTRYP PFNGLGETOBJECTPTRLABELKHRPROC) (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +typedef void (GL_APIENTRYP PFNGLGETPOINTERVKHRPROC) (GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDebugMessageControlKHR (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GL_APICALL void GL_APIENTRY glDebugMessageInsertKHR (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GL_APICALL void GL_APIENTRY glDebugMessageCallbackKHR (GLDEBUGPROCKHR callback, const void *userParam); +GL_APICALL GLuint GL_APIENTRY glGetDebugMessageLogKHR (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +GL_APICALL void GL_APIENTRY glPushDebugGroupKHR (GLenum source, GLuint id, GLsizei length, const GLchar *message); +GL_APICALL void GL_APIENTRY glPopDebugGroupKHR (void); +GL_APICALL void GL_APIENTRY glObjectLabelKHR (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +GL_APICALL void GL_APIENTRY glGetObjectLabelKHR (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +GL_APICALL void GL_APIENTRY glObjectPtrLabelKHR (const void *ptr, GLsizei length, const GLchar *label); +GL_APICALL void GL_APIENTRY glGetObjectPtrLabelKHR (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +GL_APICALL void GL_APIENTRY glGetPointervKHR (GLenum pname, void **params); +#endif +#endif /* GL_KHR_debug */ + +#ifndef GL_KHR_no_error +#define GL_KHR_no_error 1 +#define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 +#endif /* GL_KHR_no_error */ + +#ifndef GL_KHR_parallel_shader_compile +#define GL_KHR_parallel_shader_compile 1 +#define GL_MAX_SHADER_COMPILER_THREADS_KHR 0x91B0 +#define GL_COMPLETION_STATUS_KHR 0x91B1 +typedef void (GL_APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSKHRPROC) (GLuint count); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glMaxShaderCompilerThreadsKHR (GLuint count); +#endif +#endif /* GL_KHR_parallel_shader_compile */ + +#ifndef GL_KHR_robust_buffer_access_behavior +#define GL_KHR_robust_buffer_access_behavior 1 +#endif /* GL_KHR_robust_buffer_access_behavior */ + +#ifndef GL_KHR_robustness +#define GL_KHR_robustness 1 +#define GL_CONTEXT_ROBUST_ACCESS_KHR 0x90F3 +#define GL_LOSE_CONTEXT_ON_RESET_KHR 0x8252 +#define GL_GUILTY_CONTEXT_RESET_KHR 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_KHR 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_KHR 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY_KHR 0x8256 +#define GL_NO_RESET_NOTIFICATION_KHR 0x8261 +#define GL_CONTEXT_LOST_KHR 0x0507 +typedef GLenum (GL_APIENTRYP PFNGLGETGRAPHICSRESETSTATUSKHRPROC) (void); +typedef void (GL_APIENTRYP PFNGLREADNPIXELSKHRPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (GL_APIENTRYP PFNGLGETNUNIFORMFVKHRPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (GL_APIENTRYP PFNGLGETNUNIFORMIVKHRPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETNUNIFORMUIVKHRPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLenum GL_APIENTRY glGetGraphicsResetStatusKHR (void); +GL_APICALL void GL_APIENTRY glReadnPixelsKHR (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GL_APICALL void GL_APIENTRY glGetnUniformfvKHR (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GL_APICALL void GL_APIENTRY glGetnUniformivKHR (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GL_APICALL void GL_APIENTRY glGetnUniformuivKHR (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +#endif +#endif /* GL_KHR_robustness */ + +#ifndef GL_KHR_shader_subgroup +#define GL_KHR_shader_subgroup 1 +#define GL_SUBGROUP_SIZE_KHR 0x9532 +#define GL_SUBGROUP_SUPPORTED_STAGES_KHR 0x9533 +#define GL_SUBGROUP_SUPPORTED_FEATURES_KHR 0x9534 +#define GL_SUBGROUP_QUAD_ALL_STAGES_KHR 0x9535 +#define GL_SUBGROUP_FEATURE_BASIC_BIT_KHR 0x00000001 +#define GL_SUBGROUP_FEATURE_VOTE_BIT_KHR 0x00000002 +#define GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR 0x00000004 +#define GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR 0x00000008 +#define GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR 0x00000010 +#define GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR 0x00000020 +#define GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR 0x00000040 +#define GL_SUBGROUP_FEATURE_QUAD_BIT_KHR 0x00000080 +#endif /* GL_KHR_shader_subgroup */ + +#ifndef GL_KHR_texture_compression_astc_hdr +#define GL_KHR_texture_compression_astc_hdr 1 +#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 +#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 +#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 +#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 +#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 +#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 +#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 +#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 +#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 +#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 +#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA +#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB +#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC +#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD +#endif /* GL_KHR_texture_compression_astc_hdr */ + +#ifndef GL_KHR_texture_compression_astc_ldr +#define GL_KHR_texture_compression_astc_ldr 1 +#endif /* GL_KHR_texture_compression_astc_ldr */ + +#ifndef GL_KHR_texture_compression_astc_sliced_3d +#define GL_KHR_texture_compression_astc_sliced_3d 1 +#endif /* GL_KHR_texture_compression_astc_sliced_3d */ + +#ifndef GL_OES_EGL_image +#define GL_OES_EGL_image 1 +typedef void *GLeglImageOES; +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image); +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) (GLenum target, GLeglImageOES image); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image); +GL_APICALL void GL_APIENTRY glEGLImageTargetRenderbufferStorageOES (GLenum target, GLeglImageOES image); +#endif +#endif /* GL_OES_EGL_image */ + +#ifndef GL_OES_EGL_image_external +#define GL_OES_EGL_image_external 1 +#define GL_TEXTURE_EXTERNAL_OES 0x8D65 +#define GL_TEXTURE_BINDING_EXTERNAL_OES 0x8D67 +#define GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES 0x8D68 +#define GL_SAMPLER_EXTERNAL_OES 0x8D66 +#endif /* GL_OES_EGL_image_external */ + +#ifndef GL_OES_EGL_image_external_essl3 +#define GL_OES_EGL_image_external_essl3 1 +#endif /* GL_OES_EGL_image_external_essl3 */ + +#ifndef GL_OES_compressed_ETC1_RGB8_sub_texture +#define GL_OES_compressed_ETC1_RGB8_sub_texture 1 +#endif /* GL_OES_compressed_ETC1_RGB8_sub_texture */ + +#ifndef GL_OES_compressed_ETC1_RGB8_texture +#define GL_OES_compressed_ETC1_RGB8_texture 1 +#define GL_ETC1_RGB8_OES 0x8D64 +#endif /* GL_OES_compressed_ETC1_RGB8_texture */ + +#ifndef GL_OES_compressed_paletted_texture +#define GL_OES_compressed_paletted_texture 1 +#define GL_PALETTE4_RGB8_OES 0x8B90 +#define GL_PALETTE4_RGBA8_OES 0x8B91 +#define GL_PALETTE4_R5_G6_B5_OES 0x8B92 +#define GL_PALETTE4_RGBA4_OES 0x8B93 +#define GL_PALETTE4_RGB5_A1_OES 0x8B94 +#define GL_PALETTE8_RGB8_OES 0x8B95 +#define GL_PALETTE8_RGBA8_OES 0x8B96 +#define GL_PALETTE8_R5_G6_B5_OES 0x8B97 +#define GL_PALETTE8_RGBA4_OES 0x8B98 +#define GL_PALETTE8_RGB5_A1_OES 0x8B99 +#endif /* GL_OES_compressed_paletted_texture */ + +#ifndef GL_OES_copy_image +#define GL_OES_copy_image 1 +typedef void (GL_APIENTRYP PFNGLCOPYIMAGESUBDATAOESPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCopyImageSubDataOES (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +#endif +#endif /* GL_OES_copy_image */ + +#ifndef GL_OES_depth24 +#define GL_OES_depth24 1 +#define GL_DEPTH_COMPONENT24_OES 0x81A6 +#endif /* GL_OES_depth24 */ + +#ifndef GL_OES_depth32 +#define GL_OES_depth32 1 +#define GL_DEPTH_COMPONENT32_OES 0x81A7 +#endif /* GL_OES_depth32 */ + +#ifndef GL_OES_depth_texture +#define GL_OES_depth_texture 1 +#endif /* GL_OES_depth_texture */ + +#ifndef GL_OES_draw_buffers_indexed +#define GL_OES_draw_buffers_indexed 1 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +typedef void (GL_APIENTRYP PFNGLENABLEIOESPROC) (GLenum target, GLuint index); +typedef void (GL_APIENTRYP PFNGLDISABLEIOESPROC) (GLenum target, GLuint index); +typedef void (GL_APIENTRYP PFNGLBLENDEQUATIONIOESPROC) (GLuint buf, GLenum mode); +typedef void (GL_APIENTRYP PFNGLBLENDEQUATIONSEPARATEIOESPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (GL_APIENTRYP PFNGLBLENDFUNCIOESPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (GL_APIENTRYP PFNGLBLENDFUNCSEPARATEIOESPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (GL_APIENTRYP PFNGLCOLORMASKIOESPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef GLboolean (GL_APIENTRYP PFNGLISENABLEDIOESPROC) (GLenum target, GLuint index); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glEnableiOES (GLenum target, GLuint index); +GL_APICALL void GL_APIENTRY glDisableiOES (GLenum target, GLuint index); +GL_APICALL void GL_APIENTRY glBlendEquationiOES (GLuint buf, GLenum mode); +GL_APICALL void GL_APIENTRY glBlendEquationSeparateiOES (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GL_APICALL void GL_APIENTRY glBlendFunciOES (GLuint buf, GLenum src, GLenum dst); +GL_APICALL void GL_APIENTRY glBlendFuncSeparateiOES (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GL_APICALL void GL_APIENTRY glColorMaskiOES (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GL_APICALL GLboolean GL_APIENTRY glIsEnablediOES (GLenum target, GLuint index); +#endif +#endif /* GL_OES_draw_buffers_indexed */ + +#ifndef GL_OES_draw_elements_base_vertex +#define GL_OES_draw_elements_base_vertex 1 +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXOESPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (GL_APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXOESPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXOESPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +typedef void (GL_APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawElementsBaseVertexOES (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GL_APICALL void GL_APIENTRY glDrawRangeElementsBaseVertexOES (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedBaseVertexOES (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +GL_APICALL void GL_APIENTRY glMultiDrawElementsBaseVertexEXT (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +#endif +#endif /* GL_OES_draw_elements_base_vertex */ + +#ifndef GL_OES_element_index_uint +#define GL_OES_element_index_uint 1 +#endif /* GL_OES_element_index_uint */ + +#ifndef GL_OES_fbo_render_mipmap +#define GL_OES_fbo_render_mipmap 1 +#endif /* GL_OES_fbo_render_mipmap */ + +#ifndef GL_OES_fragment_precision_high +#define GL_OES_fragment_precision_high 1 +#endif /* GL_OES_fragment_precision_high */ + +#ifndef GL_OES_geometry_point_size +#define GL_OES_geometry_point_size 1 +#endif /* GL_OES_geometry_point_size */ + +#ifndef GL_OES_geometry_shader +#define GL_OES_geometry_shader 1 +#define GL_GEOMETRY_SHADER_OES 0x8DD9 +#define GL_GEOMETRY_SHADER_BIT_OES 0x00000004 +#define GL_GEOMETRY_LINKED_VERTICES_OUT_OES 0x8916 +#define GL_GEOMETRY_LINKED_INPUT_TYPE_OES 0x8917 +#define GL_GEOMETRY_LINKED_OUTPUT_TYPE_OES 0x8918 +#define GL_GEOMETRY_SHADER_INVOCATIONS_OES 0x887F +#define GL_LAYER_PROVOKING_VERTEX_OES 0x825E +#define GL_LINES_ADJACENCY_OES 0x000A +#define GL_LINE_STRIP_ADJACENCY_OES 0x000B +#define GL_TRIANGLES_ADJACENCY_OES 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_OES 0x000D +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_OES 0x8DDF +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS_OES 0x8A2C +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS_OES 0x8A32 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS_OES 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS_OES 0x9124 +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_OES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_OES 0x8DE1 +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS_OES 0x8E5A +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_OES 0x8C29 +#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS_OES 0x92CF +#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS_OES 0x92D5 +#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS_OES 0x90CD +#define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_OES 0x90D7 +#define GL_FIRST_VERTEX_CONVENTION_OES 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_OES 0x8E4E +#define GL_UNDEFINED_VERTEX_OES 0x8260 +#define GL_PRIMITIVES_GENERATED_OES 0x8C87 +#define GL_FRAMEBUFFER_DEFAULT_LAYERS_OES 0x9312 +#define GL_MAX_FRAMEBUFFER_LAYERS_OES 0x9317 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_OES 0x8DA8 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_OES 0x8DA7 +#define GL_REFERENCED_BY_GEOMETRY_SHADER_OES 0x9309 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREOESPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferTextureOES (GLenum target, GLenum attachment, GLuint texture, GLint level); +#endif +#endif /* GL_OES_geometry_shader */ + +#ifndef GL_OES_get_program_binary +#define GL_OES_get_program_binary 1 +#define GL_PROGRAM_BINARY_LENGTH_OES 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS_OES 0x87FE +#define GL_PROGRAM_BINARY_FORMATS_OES 0x87FF +typedef void (GL_APIENTRYP PFNGLGETPROGRAMBINARYOESPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +typedef void (GL_APIENTRYP PFNGLPROGRAMBINARYOESPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLint length); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetProgramBinaryOES (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +GL_APICALL void GL_APIENTRY glProgramBinaryOES (GLuint program, GLenum binaryFormat, const void *binary, GLint length); +#endif +#endif /* GL_OES_get_program_binary */ + +#ifndef GL_OES_gpu_shader5 +#define GL_OES_gpu_shader5 1 +#endif /* GL_OES_gpu_shader5 */ + +#ifndef GL_OES_mapbuffer +#define GL_OES_mapbuffer 1 +#define GL_WRITE_ONLY_OES 0x88B9 +#define GL_BUFFER_ACCESS_OES 0x88BB +#define GL_BUFFER_MAPPED_OES 0x88BC +#define GL_BUFFER_MAP_POINTER_OES 0x88BD +typedef void *(GL_APIENTRYP PFNGLMAPBUFFEROESPROC) (GLenum target, GLenum access); +typedef GLboolean (GL_APIENTRYP PFNGLUNMAPBUFFEROESPROC) (GLenum target); +typedef void (GL_APIENTRYP PFNGLGETBUFFERPOINTERVOESPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void *GL_APIENTRY glMapBufferOES (GLenum target, GLenum access); +GL_APICALL GLboolean GL_APIENTRY glUnmapBufferOES (GLenum target); +GL_APICALL void GL_APIENTRY glGetBufferPointervOES (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_OES_mapbuffer */ + +#ifndef GL_OES_packed_depth_stencil +#define GL_OES_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_OES 0x84F9 +#define GL_UNSIGNED_INT_24_8_OES 0x84FA +#define GL_DEPTH24_STENCIL8_OES 0x88F0 +#endif /* GL_OES_packed_depth_stencil */ + +#ifndef GL_OES_primitive_bounding_box +#define GL_OES_primitive_bounding_box 1 +#define GL_PRIMITIVE_BOUNDING_BOX_OES 0x92BE +typedef void (GL_APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXOESPROC) (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glPrimitiveBoundingBoxOES (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#endif +#endif /* GL_OES_primitive_bounding_box */ + +#ifndef GL_OES_required_internalformat +#define GL_OES_required_internalformat 1 +#define GL_ALPHA8_OES 0x803C +#define GL_DEPTH_COMPONENT16_OES 0x81A5 +#define GL_LUMINANCE4_ALPHA4_OES 0x8043 +#define GL_LUMINANCE8_ALPHA8_OES 0x8045 +#define GL_LUMINANCE8_OES 0x8040 +#define GL_RGBA4_OES 0x8056 +#define GL_RGB5_A1_OES 0x8057 +#define GL_RGB565_OES 0x8D62 +#define GL_RGB8_OES 0x8051 +#define GL_RGBA8_OES 0x8058 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB10_A2_EXT 0x8059 +#endif /* GL_OES_required_internalformat */ + +#ifndef GL_OES_rgb8_rgba8 +#define GL_OES_rgb8_rgba8 1 +#endif /* GL_OES_rgb8_rgba8 */ + +#ifndef GL_OES_sample_shading +#define GL_OES_sample_shading 1 +#define GL_SAMPLE_SHADING_OES 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE_OES 0x8C37 +typedef void (GL_APIENTRYP PFNGLMINSAMPLESHADINGOESPROC) (GLfloat value); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glMinSampleShadingOES (GLfloat value); +#endif +#endif /* GL_OES_sample_shading */ + +#ifndef GL_OES_sample_variables +#define GL_OES_sample_variables 1 +#endif /* GL_OES_sample_variables */ + +#ifndef GL_OES_shader_image_atomic +#define GL_OES_shader_image_atomic 1 +#endif /* GL_OES_shader_image_atomic */ + +#ifndef GL_OES_shader_io_blocks +#define GL_OES_shader_io_blocks 1 +#endif /* GL_OES_shader_io_blocks */ + +#ifndef GL_OES_shader_multisample_interpolation +#define GL_OES_shader_multisample_interpolation 1 +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_OES 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_OES 0x8E5C +#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS_OES 0x8E5D +#endif /* GL_OES_shader_multisample_interpolation */ + +#ifndef GL_OES_standard_derivatives +#define GL_OES_standard_derivatives 1 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES 0x8B8B +#endif /* GL_OES_standard_derivatives */ + +#ifndef GL_OES_stencil1 +#define GL_OES_stencil1 1 +#define GL_STENCIL_INDEX1_OES 0x8D46 +#endif /* GL_OES_stencil1 */ + +#ifndef GL_OES_stencil4 +#define GL_OES_stencil4 1 +#define GL_STENCIL_INDEX4_OES 0x8D47 +#endif /* GL_OES_stencil4 */ + +#ifndef GL_OES_surfaceless_context +#define GL_OES_surfaceless_context 1 +#define GL_FRAMEBUFFER_UNDEFINED_OES 0x8219 +#endif /* GL_OES_surfaceless_context */ + +#ifndef GL_OES_tessellation_point_size +#define GL_OES_tessellation_point_size 1 +#endif /* GL_OES_tessellation_point_size */ + +#ifndef GL_OES_tessellation_shader +#define GL_OES_tessellation_shader 1 +#define GL_PATCHES_OES 0x000E +#define GL_PATCH_VERTICES_OES 0x8E72 +#define GL_TESS_CONTROL_OUTPUT_VERTICES_OES 0x8E75 +#define GL_TESS_GEN_MODE_OES 0x8E76 +#define GL_TESS_GEN_SPACING_OES 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER_OES 0x8E78 +#define GL_TESS_GEN_POINT_MODE_OES 0x8E79 +#define GL_ISOLINES_OES 0x8E7A +#define GL_QUADS_OES 0x0007 +#define GL_FRACTIONAL_ODD_OES 0x8E7B +#define GL_FRACTIONAL_EVEN_OES 0x8E7C +#define GL_MAX_PATCH_VERTICES_OES 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL_OES 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS_OES 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS_OES 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS_OES 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS_OES 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS_OES 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS_OES 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS_OES 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS_OES 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS_OES 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS_OES 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS_OES 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS_OES 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS_OES 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS_OES 0x8E1F +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS_OES 0x92CD +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS_OES 0x92CE +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS_OES 0x92D3 +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS_OES 0x92D4 +#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS_OES 0x90CB +#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS_OES 0x90CC +#define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS_OES 0x90D8 +#define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS_OES 0x90D9 +#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED_OES 0x8221 +#define GL_IS_PER_PATCH_OES 0x92E7 +#define GL_REFERENCED_BY_TESS_CONTROL_SHADER_OES 0x9307 +#define GL_REFERENCED_BY_TESS_EVALUATION_SHADER_OES 0x9308 +#define GL_TESS_CONTROL_SHADER_OES 0x8E88 +#define GL_TESS_EVALUATION_SHADER_OES 0x8E87 +#define GL_TESS_CONTROL_SHADER_BIT_OES 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT_OES 0x00000010 +typedef void (GL_APIENTRYP PFNGLPATCHPARAMETERIOESPROC) (GLenum pname, GLint value); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glPatchParameteriOES (GLenum pname, GLint value); +#endif +#endif /* GL_OES_tessellation_shader */ + +#ifndef GL_OES_texture_3D +#define GL_OES_texture_3D 1 +#define GL_TEXTURE_WRAP_R_OES 0x8072 +#define GL_TEXTURE_3D_OES 0x806F +#define GL_TEXTURE_BINDING_3D_OES 0x806A +#define GL_MAX_3D_TEXTURE_SIZE_OES 0x8073 +#define GL_SAMPLER_3D_OES 0x8B5F +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_OES 0x8CD4 +typedef void (GL_APIENTRYP PFNGLTEXIMAGE3DOESPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (GL_APIENTRYP PFNGLTEXSUBIMAGE3DOESPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (GL_APIENTRYP PFNGLCOPYTEXSUBIMAGE3DOESPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DOESPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (GL_APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DOESPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DOESPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexImage3DOES (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GL_APICALL void GL_APIENTRY glTexSubImage3DOES (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GL_APICALL void GL_APIENTRY glCopyTexSubImage3DOES (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glCompressedTexImage3DOES (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GL_APICALL void GL_APIENTRY glCompressedTexSubImage3DOES (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GL_APICALL void GL_APIENTRY glFramebufferTexture3DOES (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +#endif +#endif /* GL_OES_texture_3D */ + +#ifndef GL_OES_texture_border_clamp +#define GL_OES_texture_border_clamp 1 +#define GL_TEXTURE_BORDER_COLOR_OES 0x1004 +#define GL_CLAMP_TO_BORDER_OES 0x812D +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERIIVOESPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERIUIVOESPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (GL_APIENTRYP PFNGLGETTEXPARAMETERIIVOESPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETTEXPARAMETERIUIVOESPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (GL_APIENTRYP PFNGLSAMPLERPARAMETERIIVOESPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (GL_APIENTRYP PFNGLSAMPLERPARAMETERIUIVOESPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (GL_APIENTRYP PFNGLGETSAMPLERPARAMETERIIVOESPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVOESPROC) (GLuint sampler, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexParameterIivOES (GLenum target, GLenum pname, const GLint *params); +GL_APICALL void GL_APIENTRY glTexParameterIuivOES (GLenum target, GLenum pname, const GLuint *params); +GL_APICALL void GL_APIENTRY glGetTexParameterIivOES (GLenum target, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetTexParameterIuivOES (GLenum target, GLenum pname, GLuint *params); +GL_APICALL void GL_APIENTRY glSamplerParameterIivOES (GLuint sampler, GLenum pname, const GLint *param); +GL_APICALL void GL_APIENTRY glSamplerParameterIuivOES (GLuint sampler, GLenum pname, const GLuint *param); +GL_APICALL void GL_APIENTRY glGetSamplerParameterIivOES (GLuint sampler, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetSamplerParameterIuivOES (GLuint sampler, GLenum pname, GLuint *params); +#endif +#endif /* GL_OES_texture_border_clamp */ + +#ifndef GL_OES_texture_buffer +#define GL_OES_texture_buffer 1 +#define GL_TEXTURE_BUFFER_OES 0x8C2A +#define GL_TEXTURE_BUFFER_BINDING_OES 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_OES 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_OES 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_OES 0x8C2D +#define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT_OES 0x919F +#define GL_SAMPLER_BUFFER_OES 0x8DC2 +#define GL_INT_SAMPLER_BUFFER_OES 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_OES 0x8DD8 +#define GL_IMAGE_BUFFER_OES 0x9051 +#define GL_INT_IMAGE_BUFFER_OES 0x905C +#define GL_UNSIGNED_INT_IMAGE_BUFFER_OES 0x9067 +#define GL_TEXTURE_BUFFER_OFFSET_OES 0x919D +#define GL_TEXTURE_BUFFER_SIZE_OES 0x919E +typedef void (GL_APIENTRYP PFNGLTEXBUFFEROESPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (GL_APIENTRYP PFNGLTEXBUFFERRANGEOESPROC) (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexBufferOES (GLenum target, GLenum internalformat, GLuint buffer); +GL_APICALL void GL_APIENTRY glTexBufferRangeOES (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +#endif +#endif /* GL_OES_texture_buffer */ + +#ifndef GL_OES_texture_compression_astc +#define GL_OES_texture_compression_astc 1 +#define GL_COMPRESSED_RGBA_ASTC_3x3x3_OES 0x93C0 +#define GL_COMPRESSED_RGBA_ASTC_4x3x3_OES 0x93C1 +#define GL_COMPRESSED_RGBA_ASTC_4x4x3_OES 0x93C2 +#define GL_COMPRESSED_RGBA_ASTC_4x4x4_OES 0x93C3 +#define GL_COMPRESSED_RGBA_ASTC_5x4x4_OES 0x93C4 +#define GL_COMPRESSED_RGBA_ASTC_5x5x4_OES 0x93C5 +#define GL_COMPRESSED_RGBA_ASTC_5x5x5_OES 0x93C6 +#define GL_COMPRESSED_RGBA_ASTC_6x5x5_OES 0x93C7 +#define GL_COMPRESSED_RGBA_ASTC_6x6x5_OES 0x93C8 +#define GL_COMPRESSED_RGBA_ASTC_6x6x6_OES 0x93C9 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES 0x93E0 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES 0x93E1 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES 0x93E2 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES 0x93E3 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES 0x93E4 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES 0x93E5 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES 0x93E6 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES 0x93E7 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES 0x93E8 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES 0x93E9 +#endif /* GL_OES_texture_compression_astc */ + +#ifndef GL_OES_texture_cube_map_array +#define GL_OES_texture_cube_map_array 1 +#define GL_TEXTURE_CUBE_MAP_ARRAY_OES 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_OES 0x900A +#define GL_SAMPLER_CUBE_MAP_ARRAY_OES 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_OES 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_OES 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_OES 0x900F +#define GL_IMAGE_CUBE_MAP_ARRAY_OES 0x9054 +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_OES 0x905F +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_OES 0x906A +#endif /* GL_OES_texture_cube_map_array */ + +#ifndef GL_OES_texture_float +#define GL_OES_texture_float 1 +#endif /* GL_OES_texture_float */ + +#ifndef GL_OES_texture_float_linear +#define GL_OES_texture_float_linear 1 +#endif /* GL_OES_texture_float_linear */ + +#ifndef GL_OES_texture_half_float +#define GL_OES_texture_half_float 1 +#define GL_HALF_FLOAT_OES 0x8D61 +#endif /* GL_OES_texture_half_float */ + +#ifndef GL_OES_texture_half_float_linear +#define GL_OES_texture_half_float_linear 1 +#endif /* GL_OES_texture_half_float_linear */ + +#ifndef GL_OES_texture_npot +#define GL_OES_texture_npot 1 +#endif /* GL_OES_texture_npot */ + +#ifndef GL_OES_texture_stencil8 +#define GL_OES_texture_stencil8 1 +#define GL_STENCIL_INDEX_OES 0x1901 +#define GL_STENCIL_INDEX8_OES 0x8D48 +#endif /* GL_OES_texture_stencil8 */ + +#ifndef GL_OES_texture_storage_multisample_2d_array +#define GL_OES_texture_storage_multisample_2d_array 1 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY_OES 0x9102 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY_OES 0x9105 +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY_OES 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY_OES 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY_OES 0x910D +typedef void (GL_APIENTRYP PFNGLTEXSTORAGE3DMULTISAMPLEOESPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexStorage3DMultisampleOES (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +#endif +#endif /* GL_OES_texture_storage_multisample_2d_array */ + +#ifndef GL_OES_texture_view +#define GL_OES_texture_view 1 +#define GL_TEXTURE_VIEW_MIN_LEVEL_OES 0x82DB +#define GL_TEXTURE_VIEW_NUM_LEVELS_OES 0x82DC +#define GL_TEXTURE_VIEW_MIN_LAYER_OES 0x82DD +#define GL_TEXTURE_VIEW_NUM_LAYERS_OES 0x82DE +#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF +typedef void (GL_APIENTRYP PFNGLTEXTUREVIEWOESPROC) (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTextureViewOES (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +#endif +#endif /* GL_OES_texture_view */ + +#ifndef GL_OES_vertex_array_object +#define GL_OES_vertex_array_object 1 +#define GL_VERTEX_ARRAY_BINDING_OES 0x85B5 +typedef void (GL_APIENTRYP PFNGLBINDVERTEXARRAYOESPROC) (GLuint array); +typedef void (GL_APIENTRYP PFNGLDELETEVERTEXARRAYSOESPROC) (GLsizei n, const GLuint *arrays); +typedef void (GL_APIENTRYP PFNGLGENVERTEXARRAYSOESPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (GL_APIENTRYP PFNGLISVERTEXARRAYOESPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBindVertexArrayOES (GLuint array); +GL_APICALL void GL_APIENTRY glDeleteVertexArraysOES (GLsizei n, const GLuint *arrays); +GL_APICALL void GL_APIENTRY glGenVertexArraysOES (GLsizei n, GLuint *arrays); +GL_APICALL GLboolean GL_APIENTRY glIsVertexArrayOES (GLuint array); +#endif +#endif /* GL_OES_vertex_array_object */ + +#ifndef GL_OES_vertex_half_float +#define GL_OES_vertex_half_float 1 +#endif /* GL_OES_vertex_half_float */ + +#ifndef GL_OES_vertex_type_10_10_10_2 +#define GL_OES_vertex_type_10_10_10_2 1 +#define GL_UNSIGNED_INT_10_10_10_2_OES 0x8DF6 +#define GL_INT_10_10_10_2_OES 0x8DF7 +#endif /* GL_OES_vertex_type_10_10_10_2 */ + +#ifndef GL_OES_viewport_array +#define GL_OES_viewport_array 1 +#define GL_MAX_VIEWPORTS_OES 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS_OES 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE_OES 0x825D +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX_OES 0x825F +typedef void (GL_APIENTRYP PFNGLVIEWPORTARRAYVOESPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLVIEWPORTINDEXEDFOESPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (GL_APIENTRYP PFNGLVIEWPORTINDEXEDFVOESPROC) (GLuint index, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLSCISSORARRAYVOESPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (GL_APIENTRYP PFNGLSCISSORINDEXEDOESPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLSCISSORINDEXEDVOESPROC) (GLuint index, const GLint *v); +typedef void (GL_APIENTRYP PFNGLDEPTHRANGEARRAYFVOESPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLDEPTHRANGEINDEXEDFOESPROC) (GLuint index, GLfloat n, GLfloat f); +typedef void (GL_APIENTRYP PFNGLGETFLOATI_VOESPROC) (GLenum target, GLuint index, GLfloat *data); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glViewportArrayvOES (GLuint first, GLsizei count, const GLfloat *v); +GL_APICALL void GL_APIENTRY glViewportIndexedfOES (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GL_APICALL void GL_APIENTRY glViewportIndexedfvOES (GLuint index, const GLfloat *v); +GL_APICALL void GL_APIENTRY glScissorArrayvOES (GLuint first, GLsizei count, const GLint *v); +GL_APICALL void GL_APIENTRY glScissorIndexedOES (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glScissorIndexedvOES (GLuint index, const GLint *v); +GL_APICALL void GL_APIENTRY glDepthRangeArrayfvOES (GLuint first, GLsizei count, const GLfloat *v); +GL_APICALL void GL_APIENTRY glDepthRangeIndexedfOES (GLuint index, GLfloat n, GLfloat f); +GL_APICALL void GL_APIENTRY glGetFloati_vOES (GLenum target, GLuint index, GLfloat *data); +#endif +#endif /* GL_OES_viewport_array */ + +#ifndef GL_AMD_compressed_3DC_texture +#define GL_AMD_compressed_3DC_texture 1 +#define GL_3DC_X_AMD 0x87F9 +#define GL_3DC_XY_AMD 0x87FA +#endif /* GL_AMD_compressed_3DC_texture */ + +#ifndef GL_AMD_compressed_ATC_texture +#define GL_AMD_compressed_ATC_texture 1 +#define GL_ATC_RGB_AMD 0x8C92 +#define GL_ATC_RGBA_EXPLICIT_ALPHA_AMD 0x8C93 +#define GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD 0x87EE +#endif /* GL_AMD_compressed_ATC_texture */ + +#ifndef GL_AMD_framebuffer_multisample_advanced +#define GL_AMD_framebuffer_multisample_advanced 1 +#define GL_RENDERBUFFER_STORAGE_SAMPLES_AMD 0x91B2 +#define GL_MAX_COLOR_FRAMEBUFFER_SAMPLES_AMD 0x91B3 +#define GL_MAX_COLOR_FRAMEBUFFER_STORAGE_SAMPLES_AMD 0x91B4 +#define GL_MAX_DEPTH_STENCIL_FRAMEBUFFER_SAMPLES_AMD 0x91B5 +#define GL_NUM_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B6 +#define GL_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B7 +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleAdvancedAMD (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glNamedRenderbufferStorageMultisampleAdvancedAMD (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_AMD_framebuffer_multisample_advanced */ + +#ifndef GL_AMD_performance_monitor +#define GL_AMD_performance_monitor 1 +#define GL_COUNTER_TYPE_AMD 0x8BC0 +#define GL_COUNTER_RANGE_AMD 0x8BC1 +#define GL_UNSIGNED_INT64_AMD 0x8BC2 +#define GL_PERCENTAGE_AMD 0x8BC3 +#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 +#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 +#define GL_PERFMON_RESULT_AMD 0x8BC6 +typedef void (GL_APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +typedef void (GL_APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, void *data); +typedef void (GL_APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (GL_APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (GL_APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +typedef void (GL_APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); +typedef void (GL_APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); +typedef void (GL_APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +GL_APICALL void GL_APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +GL_APICALL void GL_APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +GL_APICALL void GL_APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +GL_APICALL void GL_APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, void *data); +GL_APICALL void GL_APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); +GL_APICALL void GL_APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); +GL_APICALL void GL_APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +GL_APICALL void GL_APIENTRY glBeginPerfMonitorAMD (GLuint monitor); +GL_APICALL void GL_APIENTRY glEndPerfMonitorAMD (GLuint monitor); +GL_APICALL void GL_APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif +#endif /* GL_AMD_performance_monitor */ + +#ifndef GL_AMD_program_binary_Z400 +#define GL_AMD_program_binary_Z400 1 +#define GL_Z400_BINARY_AMD 0x8740 +#endif /* GL_AMD_program_binary_Z400 */ + +#ifndef GL_ANDROID_extension_pack_es31a +#define GL_ANDROID_extension_pack_es31a 1 +#endif /* GL_ANDROID_extension_pack_es31a */ + +#ifndef GL_ANGLE_depth_texture +#define GL_ANGLE_depth_texture 1 +#endif /* GL_ANGLE_depth_texture */ + +#ifndef GL_ANGLE_framebuffer_blit +#define GL_ANGLE_framebuffer_blit 1 +#define GL_READ_FRAMEBUFFER_ANGLE 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_ANGLE 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_ANGLE 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_ANGLE 0x8CAA +typedef void (GL_APIENTRYP PFNGLBLITFRAMEBUFFERANGLEPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBlitFramebufferANGLE (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* GL_ANGLE_framebuffer_blit */ + +#ifndef GL_ANGLE_framebuffer_multisample +#define GL_ANGLE_framebuffer_multisample 1 +#define GL_RENDERBUFFER_SAMPLES_ANGLE 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_ANGLE 0x8D56 +#define GL_MAX_SAMPLES_ANGLE 0x8D57 +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEANGLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleANGLE (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_ANGLE_framebuffer_multisample */ + +#ifndef GL_ANGLE_instanced_arrays +#define GL_ANGLE_instanced_arrays 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE 0x88FE +typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDANGLEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDANGLEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISORANGLEPROC) (GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawArraysInstancedANGLE (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedANGLE (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +GL_APICALL void GL_APIENTRY glVertexAttribDivisorANGLE (GLuint index, GLuint divisor); +#endif +#endif /* GL_ANGLE_instanced_arrays */ + +#ifndef GL_ANGLE_pack_reverse_row_order +#define GL_ANGLE_pack_reverse_row_order 1 +#define GL_PACK_REVERSE_ROW_ORDER_ANGLE 0x93A4 +#endif /* GL_ANGLE_pack_reverse_row_order */ + +#ifndef GL_ANGLE_program_binary +#define GL_ANGLE_program_binary 1 +#define GL_PROGRAM_BINARY_ANGLE 0x93A6 +#endif /* GL_ANGLE_program_binary */ + +#ifndef GL_ANGLE_texture_compression_dxt3 +#define GL_ANGLE_texture_compression_dxt3 1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE 0x83F2 +#endif /* GL_ANGLE_texture_compression_dxt3 */ + +#ifndef GL_ANGLE_texture_compression_dxt5 +#define GL_ANGLE_texture_compression_dxt5 1 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE 0x83F3 +#endif /* GL_ANGLE_texture_compression_dxt5 */ + +#ifndef GL_ANGLE_texture_usage +#define GL_ANGLE_texture_usage 1 +#define GL_TEXTURE_USAGE_ANGLE 0x93A2 +#define GL_FRAMEBUFFER_ATTACHMENT_ANGLE 0x93A3 +#endif /* GL_ANGLE_texture_usage */ + +#ifndef GL_ANGLE_translated_shader_source +#define GL_ANGLE_translated_shader_source 1 +#define GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE 0x93A0 +typedef void (GL_APIENTRYP PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetTranslatedShaderSourceANGLE (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +#endif +#endif /* GL_ANGLE_translated_shader_source */ + +#ifndef GL_APPLE_clip_distance +#define GL_APPLE_clip_distance 1 +#define GL_MAX_CLIP_DISTANCES_APPLE 0x0D32 +#define GL_CLIP_DISTANCE0_APPLE 0x3000 +#define GL_CLIP_DISTANCE1_APPLE 0x3001 +#define GL_CLIP_DISTANCE2_APPLE 0x3002 +#define GL_CLIP_DISTANCE3_APPLE 0x3003 +#define GL_CLIP_DISTANCE4_APPLE 0x3004 +#define GL_CLIP_DISTANCE5_APPLE 0x3005 +#define GL_CLIP_DISTANCE6_APPLE 0x3006 +#define GL_CLIP_DISTANCE7_APPLE 0x3007 +#endif /* GL_APPLE_clip_distance */ + +#ifndef GL_APPLE_color_buffer_packed_float +#define GL_APPLE_color_buffer_packed_float 1 +#endif /* GL_APPLE_color_buffer_packed_float */ + +#ifndef GL_APPLE_copy_texture_levels +#define GL_APPLE_copy_texture_levels 1 +typedef void (GL_APIENTRYP PFNGLCOPYTEXTURELEVELSAPPLEPROC) (GLuint destinationTexture, GLuint sourceTexture, GLint sourceBaseLevel, GLsizei sourceLevelCount); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCopyTextureLevelsAPPLE (GLuint destinationTexture, GLuint sourceTexture, GLint sourceBaseLevel, GLsizei sourceLevelCount); +#endif +#endif /* GL_APPLE_copy_texture_levels */ + +#ifndef GL_APPLE_framebuffer_multisample +#define GL_APPLE_framebuffer_multisample 1 +#define GL_RENDERBUFFER_SAMPLES_APPLE 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_APPLE 0x8D56 +#define GL_MAX_SAMPLES_APPLE 0x8D57 +#define GL_READ_FRAMEBUFFER_APPLE 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_APPLE 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_APPLE 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_APPLE 0x8CAA +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEAPPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLRESOLVEMULTISAMPLEFRAMEBUFFERAPPLEPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleAPPLE (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glResolveMultisampleFramebufferAPPLE (void); +#endif +#endif /* GL_APPLE_framebuffer_multisample */ + +#ifndef GL_APPLE_rgb_422 +#define GL_APPLE_rgb_422 1 +#define GL_RGB_422_APPLE 0x8A1F +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#define GL_RGB_RAW_422_APPLE 0x8A51 +#endif /* GL_APPLE_rgb_422 */ + +#ifndef GL_APPLE_sync +#define GL_APPLE_sync 1 +#define GL_SYNC_OBJECT_APPLE 0x8A53 +#define GL_MAX_SERVER_WAIT_TIMEOUT_APPLE 0x9111 +#define GL_OBJECT_TYPE_APPLE 0x9112 +#define GL_SYNC_CONDITION_APPLE 0x9113 +#define GL_SYNC_STATUS_APPLE 0x9114 +#define GL_SYNC_FLAGS_APPLE 0x9115 +#define GL_SYNC_FENCE_APPLE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE_APPLE 0x9117 +#define GL_UNSIGNALED_APPLE 0x9118 +#define GL_SIGNALED_APPLE 0x9119 +#define GL_ALREADY_SIGNALED_APPLE 0x911A +#define GL_TIMEOUT_EXPIRED_APPLE 0x911B +#define GL_CONDITION_SATISFIED_APPLE 0x911C +#define GL_WAIT_FAILED_APPLE 0x911D +#define GL_SYNC_FLUSH_COMMANDS_BIT_APPLE 0x00000001 +#define GL_TIMEOUT_IGNORED_APPLE 0xFFFFFFFFFFFFFFFFull +typedef GLsync (GL_APIENTRYP PFNGLFENCESYNCAPPLEPROC) (GLenum condition, GLbitfield flags); +typedef GLboolean (GL_APIENTRYP PFNGLISSYNCAPPLEPROC) (GLsync sync); +typedef void (GL_APIENTRYP PFNGLDELETESYNCAPPLEPROC) (GLsync sync); +typedef GLenum (GL_APIENTRYP PFNGLCLIENTWAITSYNCAPPLEPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (GL_APIENTRYP PFNGLWAITSYNCAPPLEPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (GL_APIENTRYP PFNGLGETINTEGER64VAPPLEPROC) (GLenum pname, GLint64 *params); +typedef void (GL_APIENTRYP PFNGLGETSYNCIVAPPLEPROC) (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLsync GL_APIENTRY glFenceSyncAPPLE (GLenum condition, GLbitfield flags); +GL_APICALL GLboolean GL_APIENTRY glIsSyncAPPLE (GLsync sync); +GL_APICALL void GL_APIENTRY glDeleteSyncAPPLE (GLsync sync); +GL_APICALL GLenum GL_APIENTRY glClientWaitSyncAPPLE (GLsync sync, GLbitfield flags, GLuint64 timeout); +GL_APICALL void GL_APIENTRY glWaitSyncAPPLE (GLsync sync, GLbitfield flags, GLuint64 timeout); +GL_APICALL void GL_APIENTRY glGetInteger64vAPPLE (GLenum pname, GLint64 *params); +GL_APICALL void GL_APIENTRY glGetSyncivAPPLE (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +#endif +#endif /* GL_APPLE_sync */ + +#ifndef GL_APPLE_texture_format_BGRA8888 +#define GL_APPLE_texture_format_BGRA8888 1 +#define GL_BGRA_EXT 0x80E1 +#define GL_BGRA8_EXT 0x93A1 +#endif /* GL_APPLE_texture_format_BGRA8888 */ + +#ifndef GL_APPLE_texture_max_level +#define GL_APPLE_texture_max_level 1 +#define GL_TEXTURE_MAX_LEVEL_APPLE 0x813D +#endif /* GL_APPLE_texture_max_level */ + +#ifndef GL_APPLE_texture_packed_float +#define GL_APPLE_texture_packed_float 1 +#define GL_UNSIGNED_INT_10F_11F_11F_REV_APPLE 0x8C3B +#define GL_UNSIGNED_INT_5_9_9_9_REV_APPLE 0x8C3E +#define GL_R11F_G11F_B10F_APPLE 0x8C3A +#define GL_RGB9_E5_APPLE 0x8C3D +#endif /* GL_APPLE_texture_packed_float */ + +#ifndef GL_ARM_mali_program_binary +#define GL_ARM_mali_program_binary 1 +#define GL_MALI_PROGRAM_BINARY_ARM 0x8F61 +#endif /* GL_ARM_mali_program_binary */ + +#ifndef GL_ARM_mali_shader_binary +#define GL_ARM_mali_shader_binary 1 +#define GL_MALI_SHADER_BINARY_ARM 0x8F60 +#endif /* GL_ARM_mali_shader_binary */ + +#ifndef GL_ARM_rgba8 +#define GL_ARM_rgba8 1 +#endif /* GL_ARM_rgba8 */ + +#ifndef GL_ARM_shader_framebuffer_fetch +#define GL_ARM_shader_framebuffer_fetch 1 +#define GL_FETCH_PER_SAMPLE_ARM 0x8F65 +#define GL_FRAGMENT_SHADER_FRAMEBUFFER_FETCH_MRT_ARM 0x8F66 +#endif /* GL_ARM_shader_framebuffer_fetch */ + +#ifndef GL_ARM_shader_framebuffer_fetch_depth_stencil +#define GL_ARM_shader_framebuffer_fetch_depth_stencil 1 +#endif /* GL_ARM_shader_framebuffer_fetch_depth_stencil */ + +#ifndef GL_ARM_texture_unnormalized_coordinates +#define GL_ARM_texture_unnormalized_coordinates 1 +#define GL_TEXTURE_UNNORMALIZED_COORDINATES_ARM 0x8F6A +#endif /* GL_ARM_texture_unnormalized_coordinates */ + +#ifndef GL_DMP_program_binary +#define GL_DMP_program_binary 1 +#define GL_SMAPHS30_PROGRAM_BINARY_DMP 0x9251 +#define GL_SMAPHS_PROGRAM_BINARY_DMP 0x9252 +#define GL_DMP_PROGRAM_BINARY_DMP 0x9253 +#endif /* GL_DMP_program_binary */ + +#ifndef GL_DMP_shader_binary +#define GL_DMP_shader_binary 1 +#define GL_SHADER_BINARY_DMP 0x9250 +#endif /* GL_DMP_shader_binary */ + +#ifndef GL_EXT_EGL_image_array +#define GL_EXT_EGL_image_array 1 +#endif /* GL_EXT_EGL_image_array */ + +#ifndef GL_EXT_EGL_image_storage +#define GL_EXT_EGL_image_storage 1 +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC) (GLenum target, GLeglImageOES image, const GLint* attrib_list); +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURESTORAGEEXTPROC) (GLuint texture, GLeglImageOES image, const GLint* attrib_list); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glEGLImageTargetTexStorageEXT (GLenum target, GLeglImageOES image, const GLint* attrib_list); +GL_APICALL void GL_APIENTRY glEGLImageTargetTextureStorageEXT (GLuint texture, GLeglImageOES image, const GLint* attrib_list); +#endif +#endif /* GL_EXT_EGL_image_storage */ + +#ifndef GL_EXT_EGL_image_storage_compression +#define GL_EXT_EGL_image_storage_compression 1 +#define GL_SURFACE_COMPRESSION_EXT 0x96C0 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_NONE_EXT 0x96C1 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_DEFAULT_EXT 0x96C2 +#endif /* GL_EXT_EGL_image_storage_compression */ + +#ifndef GL_EXT_YUV_target +#define GL_EXT_YUV_target 1 +#define GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT 0x8BE7 +#endif /* GL_EXT_YUV_target */ + +#ifndef GL_EXT_base_instance +#define GL_EXT_base_instance 1 +typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEEXTPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawArraysInstancedBaseInstanceEXT (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedBaseInstanceEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedBaseVertexBaseInstanceEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +#endif +#endif /* GL_EXT_base_instance */ + +#ifndef GL_EXT_blend_func_extended +#define GL_EXT_blend_func_extended 1 +#define GL_SRC1_COLOR_EXT 0x88F9 +#define GL_SRC1_ALPHA_EXT 0x8589 +#define GL_ONE_MINUS_SRC1_COLOR_EXT 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA_EXT 0x88FB +#define GL_SRC_ALPHA_SATURATE_EXT 0x0308 +#define GL_LOCATION_INDEX_EXT 0x930F +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT 0x88FC +typedef void (GL_APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDEXTPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +typedef void (GL_APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (GL_APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXEXTPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef GLint (GL_APIENTRYP PFNGLGETFRAGDATAINDEXEXTPROC) (GLuint program, const GLchar *name); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBindFragDataLocationIndexedEXT (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GL_APICALL void GL_APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); +GL_APICALL GLint GL_APIENTRY glGetProgramResourceLocationIndexEXT (GLuint program, GLenum programInterface, const GLchar *name); +GL_APICALL GLint GL_APIENTRY glGetFragDataIndexEXT (GLuint program, const GLchar *name); +#endif +#endif /* GL_EXT_blend_func_extended */ + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#endif /* GL_EXT_blend_minmax */ + +#ifndef GL_EXT_buffer_storage +#define GL_EXT_buffer_storage 1 +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_PERSISTENT_BIT_EXT 0x0040 +#define GL_MAP_COHERENT_BIT_EXT 0x0080 +#define GL_DYNAMIC_STORAGE_BIT_EXT 0x0100 +#define GL_CLIENT_STORAGE_BIT_EXT 0x0200 +#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT_EXT 0x00004000 +#define GL_BUFFER_IMMUTABLE_STORAGE_EXT 0x821F +#define GL_BUFFER_STORAGE_FLAGS_EXT 0x8220 +typedef void (GL_APIENTRYP PFNGLBUFFERSTORAGEEXTPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBufferStorageEXT (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +#endif +#endif /* GL_EXT_buffer_storage */ + +#ifndef GL_EXT_clear_texture +#define GL_EXT_clear_texture 1 +typedef void (GL_APIENTRYP PFNGLCLEARTEXIMAGEEXTPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +typedef void (GL_APIENTRYP PFNGLCLEARTEXSUBIMAGEEXTPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glClearTexImageEXT (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +GL_APICALL void GL_APIENTRY glClearTexSubImageEXT (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +#endif +#endif /* GL_EXT_clear_texture */ + +#ifndef GL_EXT_clip_control +#define GL_EXT_clip_control 1 +#define GL_LOWER_LEFT_EXT 0x8CA1 +#define GL_UPPER_LEFT_EXT 0x8CA2 +#define GL_NEGATIVE_ONE_TO_ONE_EXT 0x935E +#define GL_ZERO_TO_ONE_EXT 0x935F +#define GL_CLIP_ORIGIN_EXT 0x935C +#define GL_CLIP_DEPTH_MODE_EXT 0x935D +typedef void (GL_APIENTRYP PFNGLCLIPCONTROLEXTPROC) (GLenum origin, GLenum depth); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glClipControlEXT (GLenum origin, GLenum depth); +#endif +#endif /* GL_EXT_clip_control */ + +#ifndef GL_EXT_clip_cull_distance +#define GL_EXT_clip_cull_distance 1 +#define GL_MAX_CLIP_DISTANCES_EXT 0x0D32 +#define GL_MAX_CULL_DISTANCES_EXT 0x82F9 +#define GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT 0x82FA +#define GL_CLIP_DISTANCE0_EXT 0x3000 +#define GL_CLIP_DISTANCE1_EXT 0x3001 +#define GL_CLIP_DISTANCE2_EXT 0x3002 +#define GL_CLIP_DISTANCE3_EXT 0x3003 +#define GL_CLIP_DISTANCE4_EXT 0x3004 +#define GL_CLIP_DISTANCE5_EXT 0x3005 +#define GL_CLIP_DISTANCE6_EXT 0x3006 +#define GL_CLIP_DISTANCE7_EXT 0x3007 +#endif /* GL_EXT_clip_cull_distance */ + +#ifndef GL_EXT_color_buffer_float +#define GL_EXT_color_buffer_float 1 +#endif /* GL_EXT_color_buffer_float */ + +#ifndef GL_EXT_color_buffer_half_float +#define GL_EXT_color_buffer_half_float 1 +#define GL_RGBA16F_EXT 0x881A +#define GL_RGB16F_EXT 0x881B +#define GL_RG16F_EXT 0x822F +#define GL_R16F_EXT 0x822D +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT 0x8211 +#define GL_UNSIGNED_NORMALIZED_EXT 0x8C17 +#endif /* GL_EXT_color_buffer_half_float */ + +#ifndef GL_EXT_conservative_depth +#define GL_EXT_conservative_depth 1 +#endif /* GL_EXT_conservative_depth */ + +#ifndef GL_EXT_copy_image +#define GL_EXT_copy_image 1 +typedef void (GL_APIENTRYP PFNGLCOPYIMAGESUBDATAEXTPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCopyImageSubDataEXT (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +#endif +#endif /* GL_EXT_copy_image */ + +#ifndef GL_EXT_debug_label +#define GL_EXT_debug_label 1 +#define GL_PROGRAM_PIPELINE_OBJECT_EXT 0x8A4F +#define GL_PROGRAM_OBJECT_EXT 0x8B40 +#define GL_SHADER_OBJECT_EXT 0x8B48 +#define GL_BUFFER_OBJECT_EXT 0x9151 +#define GL_QUERY_OBJECT_EXT 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_EXT 0x9154 +#define GL_TRANSFORM_FEEDBACK 0x8E22 +typedef void (GL_APIENTRYP PFNGLLABELOBJECTEXTPROC) (GLenum type, GLuint object, GLsizei length, const GLchar *label); +typedef void (GL_APIENTRYP PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glLabelObjectEXT (GLenum type, GLuint object, GLsizei length, const GLchar *label); +GL_APICALL void GL_APIENTRY glGetObjectLabelEXT (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_EXT_debug_label */ + +#ifndef GL_EXT_debug_marker +#define GL_EXT_debug_marker 1 +typedef void (GL_APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (GL_APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (GL_APIENTRYP PFNGLPOPGROUPMARKEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glInsertEventMarkerEXT (GLsizei length, const GLchar *marker); +GL_APICALL void GL_APIENTRY glPushGroupMarkerEXT (GLsizei length, const GLchar *marker); +GL_APICALL void GL_APIENTRY glPopGroupMarkerEXT (void); +#endif +#endif /* GL_EXT_debug_marker */ + +#ifndef GL_EXT_depth_clamp +#define GL_EXT_depth_clamp 1 +#define GL_DEPTH_CLAMP_EXT 0x864F +#endif /* GL_EXT_depth_clamp */ + +#ifndef GL_EXT_discard_framebuffer +#define GL_EXT_discard_framebuffer 1 +#define GL_COLOR_EXT 0x1800 +#define GL_DEPTH_EXT 0x1801 +#define GL_STENCIL_EXT 0x1802 +typedef void (GL_APIENTRYP PFNGLDISCARDFRAMEBUFFEREXTPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDiscardFramebufferEXT (GLenum target, GLsizei numAttachments, const GLenum *attachments); +#endif +#endif /* GL_EXT_discard_framebuffer */ + +#ifndef GL_EXT_disjoint_timer_query +#define GL_EXT_disjoint_timer_query 1 +#define GL_QUERY_COUNTER_BITS_EXT 0x8864 +#define GL_CURRENT_QUERY_EXT 0x8865 +#define GL_QUERY_RESULT_EXT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_EXT 0x8867 +#define GL_TIME_ELAPSED_EXT 0x88BF +#define GL_TIMESTAMP_EXT 0x8E28 +#define GL_GPU_DISJOINT_EXT 0x8FBB +typedef void (GL_APIENTRYP PFNGLGENQUERIESEXTPROC) (GLsizei n, GLuint *ids); +typedef void (GL_APIENTRYP PFNGLDELETEQUERIESEXTPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (GL_APIENTRYP PFNGLISQUERYEXTPROC) (GLuint id); +typedef void (GL_APIENTRYP PFNGLBEGINQUERYEXTPROC) (GLenum target, GLuint id); +typedef void (GL_APIENTRYP PFNGLENDQUERYEXTPROC) (GLenum target); +typedef void (GL_APIENTRYP PFNGLQUERYCOUNTEREXTPROC) (GLuint id, GLenum target); +typedef void (GL_APIENTRYP PFNGLGETQUERYIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTIVEXTPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTUIVEXTPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params); +typedef void (GL_APIENTRYP PFNGLGETINTEGER64VEXTPROC) (GLenum pname, GLint64 *data); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGenQueriesEXT (GLsizei n, GLuint *ids); +GL_APICALL void GL_APIENTRY glDeleteQueriesEXT (GLsizei n, const GLuint *ids); +GL_APICALL GLboolean GL_APIENTRY glIsQueryEXT (GLuint id); +GL_APICALL void GL_APIENTRY glBeginQueryEXT (GLenum target, GLuint id); +GL_APICALL void GL_APIENTRY glEndQueryEXT (GLenum target); +GL_APICALL void GL_APIENTRY glQueryCounterEXT (GLuint id, GLenum target); +GL_APICALL void GL_APIENTRY glGetQueryivEXT (GLenum target, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetQueryObjectivEXT (GLuint id, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetQueryObjectuivEXT (GLuint id, GLenum pname, GLuint *params); +GL_APICALL void GL_APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64 *params); +GL_APICALL void GL_APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64 *params); +GL_APICALL void GL_APIENTRY glGetInteger64vEXT (GLenum pname, GLint64 *data); +#endif +#endif /* GL_EXT_disjoint_timer_query */ + +#ifndef GL_EXT_draw_buffers +#define GL_EXT_draw_buffers 1 +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_MAX_DRAW_BUFFERS_EXT 0x8824 +#define GL_DRAW_BUFFER0_EXT 0x8825 +#define GL_DRAW_BUFFER1_EXT 0x8826 +#define GL_DRAW_BUFFER2_EXT 0x8827 +#define GL_DRAW_BUFFER3_EXT 0x8828 +#define GL_DRAW_BUFFER4_EXT 0x8829 +#define GL_DRAW_BUFFER5_EXT 0x882A +#define GL_DRAW_BUFFER6_EXT 0x882B +#define GL_DRAW_BUFFER7_EXT 0x882C +#define GL_DRAW_BUFFER8_EXT 0x882D +#define GL_DRAW_BUFFER9_EXT 0x882E +#define GL_DRAW_BUFFER10_EXT 0x882F +#define GL_DRAW_BUFFER11_EXT 0x8830 +#define GL_DRAW_BUFFER12_EXT 0x8831 +#define GL_DRAW_BUFFER13_EXT 0x8832 +#define GL_DRAW_BUFFER14_EXT 0x8833 +#define GL_DRAW_BUFFER15_EXT 0x8834 +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +typedef void (GL_APIENTRYP PFNGLDRAWBUFFERSEXTPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawBuffersEXT (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_EXT_draw_buffers */ + +#ifndef GL_EXT_draw_buffers_indexed +#define GL_EXT_draw_buffers_indexed 1 +typedef void (GL_APIENTRYP PFNGLENABLEIEXTPROC) (GLenum target, GLuint index); +typedef void (GL_APIENTRYP PFNGLDISABLEIEXTPROC) (GLenum target, GLuint index); +typedef void (GL_APIENTRYP PFNGLBLENDEQUATIONIEXTPROC) (GLuint buf, GLenum mode); +typedef void (GL_APIENTRYP PFNGLBLENDEQUATIONSEPARATEIEXTPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (GL_APIENTRYP PFNGLBLENDFUNCIEXTPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (GL_APIENTRYP PFNGLBLENDFUNCSEPARATEIEXTPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (GL_APIENTRYP PFNGLCOLORMASKIEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef GLboolean (GL_APIENTRYP PFNGLISENABLEDIEXTPROC) (GLenum target, GLuint index); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glEnableiEXT (GLenum target, GLuint index); +GL_APICALL void GL_APIENTRY glDisableiEXT (GLenum target, GLuint index); +GL_APICALL void GL_APIENTRY glBlendEquationiEXT (GLuint buf, GLenum mode); +GL_APICALL void GL_APIENTRY glBlendEquationSeparateiEXT (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GL_APICALL void GL_APIENTRY glBlendFunciEXT (GLuint buf, GLenum src, GLenum dst); +GL_APICALL void GL_APIENTRY glBlendFuncSeparateiEXT (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GL_APICALL void GL_APIENTRY glColorMaskiEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GL_APICALL GLboolean GL_APIENTRY glIsEnablediEXT (GLenum target, GLuint index); +#endif +#endif /* GL_EXT_draw_buffers_indexed */ + +#ifndef GL_EXT_draw_elements_base_vertex +#define GL_EXT_draw_elements_base_vertex 1 +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (GL_APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawElementsBaseVertexEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GL_APICALL void GL_APIENTRY glDrawRangeElementsBaseVertexEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedBaseVertexEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +#endif +#endif /* GL_EXT_draw_elements_base_vertex */ + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 +typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_EXT_draw_instanced */ + +#ifndef GL_EXT_draw_transform_feedback +#define GL_EXT_draw_transform_feedback 1 +typedef void (GL_APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKEXTPROC) (GLenum mode, GLuint id); +typedef void (GL_APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDEXTPROC) (GLenum mode, GLuint id, GLsizei instancecount); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawTransformFeedbackEXT (GLenum mode, GLuint id); +GL_APICALL void GL_APIENTRY glDrawTransformFeedbackInstancedEXT (GLenum mode, GLuint id, GLsizei instancecount); +#endif +#endif /* GL_EXT_draw_transform_feedback */ + +#ifndef GL_EXT_external_buffer +#define GL_EXT_external_buffer 1 +typedef void *GLeglClientBufferEXT; +typedef void (GL_APIENTRYP PFNGLBUFFERSTORAGEEXTERNALEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +typedef void (GL_APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTERNALEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBufferStorageExternalEXT (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +GL_APICALL void GL_APIENTRY glNamedBufferStorageExternalEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +#endif +#endif /* GL_EXT_external_buffer */ + +#ifndef GL_EXT_float_blend +#define GL_EXT_float_blend 1 +#endif /* GL_EXT_float_blend */ + +#ifndef GL_EXT_fragment_shading_rate +#define GL_EXT_fragment_shading_rate 1 +#define GL_SHADING_RATE_1X1_PIXELS_EXT 0x96A6 +#define GL_SHADING_RATE_1X2_PIXELS_EXT 0x96A7 +#define GL_SHADING_RATE_2X1_PIXELS_EXT 0x96A8 +#define GL_SHADING_RATE_2X2_PIXELS_EXT 0x96A9 +#define GL_SHADING_RATE_1X4_PIXELS_EXT 0x96AA +#define GL_SHADING_RATE_4X1_PIXELS_EXT 0x96AB +#define GL_SHADING_RATE_4X2_PIXELS_EXT 0x96AC +#define GL_SHADING_RATE_2X4_PIXELS_EXT 0x96AD +#define GL_SHADING_RATE_4X4_PIXELS_EXT 0x96AE +#define GL_SHADING_RATE_EXT 0x96D0 +#define GL_SHADING_RATE_ATTACHMENT_EXT 0x96D1 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT 0x96D2 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT 0x96D3 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_EXT 0x96D4 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_EXT 0x96D5 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_EXT 0x96D6 +#define GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT 0x96D7 +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT 0x96D8 +#define GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT 0x96D9 +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT 0x96DA +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_ASPECT_RATIO_EXT 0x96DB +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_LAYERS_EXT 0x96DC +#define GL_FRAGMENT_SHADING_RATE_WITH_SHADER_DEPTH_STENCIL_WRITES_SUPPORTED_EXT 0x96DD +#define GL_FRAGMENT_SHADING_RATE_WITH_SAMPLE_MASK_SUPPORTED_EXT 0x96DE +#define GL_FRAGMENT_SHADING_RATE_ATTACHMENT_WITH_DEFAULT_FRAMEBUFFER_SUPPORTED_EXT 0x96DF +#define GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT 0x8F6F +typedef void (GL_APIENTRYP PFNGLGETFRAGMENTSHADINGRATESEXTPROC) (GLsizei samples, GLsizei maxCount, GLsizei *count, GLenum *shadingRates); +typedef void (GL_APIENTRYP PFNGLSHADINGRATEEXTPROC) (GLenum rate); +typedef void (GL_APIENTRYP PFNGLSHADINGRATECOMBINEROPSEXTPROC) (GLenum combinerOp0, GLenum combinerOp1); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERSHADINGRATEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetFragmentShadingRatesEXT (GLsizei samples, GLsizei maxCount, GLsizei *count, GLenum *shadingRates); +GL_APICALL void GL_APIENTRY glShadingRateEXT (GLenum rate); +GL_APICALL void GL_APIENTRY glShadingRateCombinerOpsEXT (GLenum combinerOp0, GLenum combinerOp1); +GL_APICALL void GL_APIENTRY glFramebufferShadingRateEXT (GLenum target, GLenum attachment, GLuint texture, GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight); +#endif +#endif /* GL_EXT_fragment_shading_rate */ + +#ifndef GL_EXT_geometry_point_size +#define GL_EXT_geometry_point_size 1 +#endif /* GL_EXT_geometry_point_size */ + +#ifndef GL_EXT_geometry_shader +#define GL_EXT_geometry_shader 1 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +#define GL_GEOMETRY_SHADER_BIT_EXT 0x00000004 +#define GL_GEOMETRY_LINKED_VERTICES_OUT_EXT 0x8916 +#define GL_GEOMETRY_LINKED_INPUT_TYPE_EXT 0x8917 +#define GL_GEOMETRY_LINKED_OUTPUT_TYPE_EXT 0x8918 +#define GL_GEOMETRY_SHADER_INVOCATIONS_EXT 0x887F +#define GL_LAYER_PROVOKING_VERTEX_EXT 0x825E +#define GL_LINES_ADJACENCY_EXT 0x000A +#define GL_LINE_STRIP_ADJACENCY_EXT 0x000B +#define GL_TRIANGLES_ADJACENCY_EXT 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS_EXT 0x8A2C +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8A32 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS_EXT 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS_EXT 0x9124 +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS_EXT 0x8E5A +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS_EXT 0x92CF +#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS_EXT 0x92D5 +#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS_EXT 0x90CD +#define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT 0x90D7 +#define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E +#define GL_UNDEFINED_VERTEX_EXT 0x8260 +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_FRAMEBUFFER_DEFAULT_LAYERS_EXT 0x9312 +#define GL_MAX_FRAMEBUFFER_LAYERS_EXT 0x9317 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_REFERENCED_BY_GEOMETRY_SHADER_EXT 0x9309 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); +#endif +#endif /* GL_EXT_geometry_shader */ + +#ifndef GL_EXT_gpu_shader5 +#define GL_EXT_gpu_shader5 1 +#endif /* GL_EXT_gpu_shader5 */ + +#ifndef GL_EXT_instanced_arrays +#define GL_EXT_instanced_arrays 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_EXT 0x88FE +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glVertexAttribDivisorEXT (GLuint index, GLuint divisor); +#endif +#endif /* GL_EXT_instanced_arrays */ + +#ifndef GL_EXT_map_buffer_range +#define GL_EXT_map_buffer_range 1 +#define GL_MAP_READ_BIT_EXT 0x0001 +#define GL_MAP_WRITE_BIT_EXT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT_EXT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT_EXT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT_EXT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT_EXT 0x0020 +typedef void *(GL_APIENTRYP PFNGLMAPBUFFERRANGEEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (GL_APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void *GL_APIENTRY glMapBufferRangeEXT (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GL_APICALL void GL_APIENTRY glFlushMappedBufferRangeEXT (GLenum target, GLintptr offset, GLsizeiptr length); +#endif +#endif /* GL_EXT_map_buffer_range */ + +#ifndef GL_EXT_memory_object +#define GL_EXT_memory_object 1 +#define GL_TEXTURE_TILING_EXT 0x9580 +#define GL_DEDICATED_MEMORY_OBJECT_EXT 0x9581 +#define GL_PROTECTED_MEMORY_OBJECT_EXT 0x959B +#define GL_NUM_TILING_TYPES_EXT 0x9582 +#define GL_TILING_TYPES_EXT 0x9583 +#define GL_OPTIMAL_TILING_EXT 0x9584 +#define GL_LINEAR_TILING_EXT 0x9585 +#define GL_NUM_DEVICE_UUIDS_EXT 0x9596 +#define GL_DEVICE_UUID_EXT 0x9597 +#define GL_DRIVER_UUID_EXT 0x9598 +#define GL_UUID_SIZE_EXT 16 +typedef void (GL_APIENTRYP PFNGLGETUNSIGNEDBYTEVEXTPROC) (GLenum pname, GLubyte *data); +typedef void (GL_APIENTRYP PFNGLGETUNSIGNEDBYTEI_VEXTPROC) (GLenum target, GLuint index, GLubyte *data); +typedef void (GL_APIENTRYP PFNGLDELETEMEMORYOBJECTSEXTPROC) (GLsizei n, const GLuint *memoryObjects); +typedef GLboolean (GL_APIENTRYP PFNGLISMEMORYOBJECTEXTPROC) (GLuint memoryObject); +typedef void (GL_APIENTRYP PFNGLCREATEMEMORYOBJECTSEXTPROC) (GLsizei n, GLuint *memoryObjects); +typedef void (GL_APIENTRYP PFNGLMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, const GLint *params); +typedef void (GL_APIENTRYP PFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGEMEM2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGEMEM2DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGEMEM3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGEMEM3DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLBUFFERSTORAGEMEMEXTPROC) (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGEMEM2DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGEMEM2DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGEMEM3DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGEMEM3DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLNAMEDBUFFERSTORAGEMEMEXTPROC) (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetUnsignedBytevEXT (GLenum pname, GLubyte *data); +GL_APICALL void GL_APIENTRY glGetUnsignedBytei_vEXT (GLenum target, GLuint index, GLubyte *data); +GL_APICALL void GL_APIENTRY glDeleteMemoryObjectsEXT (GLsizei n, const GLuint *memoryObjects); +GL_APICALL GLboolean GL_APIENTRY glIsMemoryObjectEXT (GLuint memoryObject); +GL_APICALL void GL_APIENTRY glCreateMemoryObjectsEXT (GLsizei n, GLuint *memoryObjects); +GL_APICALL void GL_APIENTRY glMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, const GLint *params); +GL_APICALL void GL_APIENTRY glGetMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glTexStorageMem2DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTexStorageMem2DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTexStorageMem3DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTexStorageMem3DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glBufferStorageMemEXT (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTextureStorageMem2DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTextureStorageMem2DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTextureStorageMem3DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTextureStorageMem3DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glNamedBufferStorageMemEXT (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); +#endif +#endif /* GL_EXT_memory_object */ + +#ifndef GL_EXT_memory_object_fd +#define GL_EXT_memory_object_fd 1 +#define GL_HANDLE_TYPE_OPAQUE_FD_EXT 0x9586 +typedef void (GL_APIENTRYP PFNGLIMPORTMEMORYFDEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glImportMemoryFdEXT (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); +#endif +#endif /* GL_EXT_memory_object_fd */ + +#ifndef GL_EXT_memory_object_win32 +#define GL_EXT_memory_object_win32 1 +#define GL_HANDLE_TYPE_OPAQUE_WIN32_EXT 0x9587 +#define GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT 0x9588 +#define GL_DEVICE_LUID_EXT 0x9599 +#define GL_DEVICE_NODE_MASK_EXT 0x959A +#define GL_LUID_SIZE_EXT 8 +#define GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT 0x9589 +#define GL_HANDLE_TYPE_D3D12_RESOURCE_EXT 0x958A +#define GL_HANDLE_TYPE_D3D11_IMAGE_EXT 0x958B +#define GL_HANDLE_TYPE_D3D11_IMAGE_KMT_EXT 0x958C +typedef void (GL_APIENTRYP PFNGLIMPORTMEMORYWIN32HANDLEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, void *handle); +typedef void (GL_APIENTRYP PFNGLIMPORTMEMORYWIN32NAMEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, const void *name); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glImportMemoryWin32HandleEXT (GLuint memory, GLuint64 size, GLenum handleType, void *handle); +GL_APICALL void GL_APIENTRY glImportMemoryWin32NameEXT (GLuint memory, GLuint64 size, GLenum handleType, const void *name); +#endif +#endif /* GL_EXT_memory_object_win32 */ + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +typedef void (GL_APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (GL_APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GL_APICALL void GL_APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#endif +#endif /* GL_EXT_multi_draw_arrays */ + +#ifndef GL_EXT_multi_draw_indirect +#define GL_EXT_multi_draw_indirect 1 +typedef void (GL_APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTEXTPROC) (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (GL_APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTEXTPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glMultiDrawArraysIndirectEXT (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +GL_APICALL void GL_APIENTRY glMultiDrawElementsIndirectEXT (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +#endif +#endif /* GL_EXT_multi_draw_indirect */ + +#ifndef GL_EXT_multisampled_compatibility +#define GL_EXT_multisampled_compatibility 1 +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#endif /* GL_EXT_multisampled_compatibility */ + +#ifndef GL_EXT_multisampled_render_to_texture +#define GL_EXT_multisampled_render_to_texture 1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT 0x8D6C +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glFramebufferTexture2DMultisampleEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples); +#endif +#endif /* GL_EXT_multisampled_render_to_texture */ + +#ifndef GL_EXT_multisampled_render_to_texture2 +#define GL_EXT_multisampled_render_to_texture2 1 +#endif /* GL_EXT_multisampled_render_to_texture2 */ + +#ifndef GL_EXT_multiview_draw_buffers +#define GL_EXT_multiview_draw_buffers 1 +#define GL_COLOR_ATTACHMENT_EXT 0x90F0 +#define GL_MULTIVIEW_EXT 0x90F1 +#define GL_DRAW_BUFFER_EXT 0x0C01 +#define GL_READ_BUFFER_EXT 0x0C02 +#define GL_MAX_MULTIVIEW_BUFFERS_EXT 0x90F2 +typedef void (GL_APIENTRYP PFNGLREADBUFFERINDEXEDEXTPROC) (GLenum src, GLint index); +typedef void (GL_APIENTRYP PFNGLDRAWBUFFERSINDEXEDEXTPROC) (GLint n, const GLenum *location, const GLint *indices); +typedef void (GL_APIENTRYP PFNGLGETINTEGERI_VEXTPROC) (GLenum target, GLuint index, GLint *data); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glReadBufferIndexedEXT (GLenum src, GLint index); +GL_APICALL void GL_APIENTRY glDrawBuffersIndexedEXT (GLint n, const GLenum *location, const GLint *indices); +GL_APICALL void GL_APIENTRY glGetIntegeri_vEXT (GLenum target, GLuint index, GLint *data); +#endif +#endif /* GL_EXT_multiview_draw_buffers */ + +#ifndef GL_EXT_multiview_tessellation_geometry_shader +#define GL_EXT_multiview_tessellation_geometry_shader 1 +#endif /* GL_EXT_multiview_tessellation_geometry_shader */ + +#ifndef GL_EXT_multiview_texture_multisample +#define GL_EXT_multiview_texture_multisample 1 +#endif /* GL_EXT_multiview_texture_multisample */ + +#ifndef GL_EXT_multiview_timer_query +#define GL_EXT_multiview_timer_query 1 +#endif /* GL_EXT_multiview_timer_query */ + +#ifndef GL_EXT_occlusion_query_boolean +#define GL_EXT_occlusion_query_boolean 1 +#define GL_ANY_SAMPLES_PASSED_EXT 0x8C2F +#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT 0x8D6A +#endif /* GL_EXT_occlusion_query_boolean */ + +#ifndef GL_EXT_polygon_offset_clamp +#define GL_EXT_polygon_offset_clamp 1 +#define GL_POLYGON_OFFSET_CLAMP_EXT 0x8E1B +typedef void (GL_APIENTRYP PFNGLPOLYGONOFFSETCLAMPEXTPROC) (GLfloat factor, GLfloat units, GLfloat clamp); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glPolygonOffsetClampEXT (GLfloat factor, GLfloat units, GLfloat clamp); +#endif +#endif /* GL_EXT_polygon_offset_clamp */ + +#ifndef GL_EXT_post_depth_coverage +#define GL_EXT_post_depth_coverage 1 +#endif /* GL_EXT_post_depth_coverage */ + +#ifndef GL_EXT_primitive_bounding_box +#define GL_EXT_primitive_bounding_box 1 +#define GL_PRIMITIVE_BOUNDING_BOX_EXT 0x92BE +typedef void (GL_APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXEXTPROC) (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glPrimitiveBoundingBoxEXT (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#endif +#endif /* GL_EXT_primitive_bounding_box */ + +#ifndef GL_EXT_protected_textures +#define GL_EXT_protected_textures 1 +#define GL_CONTEXT_FLAG_PROTECTED_CONTENT_BIT_EXT 0x00000010 +#define GL_TEXTURE_PROTECTED_EXT 0x8BFA +#endif /* GL_EXT_protected_textures */ + +#ifndef GL_EXT_pvrtc_sRGB +#define GL_EXT_pvrtc_sRGB 1 +#define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT 0x8A54 +#define GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT 0x8A55 +#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT 0x8A56 +#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT 0x8A57 +#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG 0x93F0 +#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG 0x93F1 +#endif /* GL_EXT_pvrtc_sRGB */ + +#ifndef GL_EXT_raster_multisample +#define GL_EXT_raster_multisample 1 +#define GL_RASTER_MULTISAMPLE_EXT 0x9327 +#define GL_RASTER_SAMPLES_EXT 0x9328 +#define GL_MAX_RASTER_SAMPLES_EXT 0x9329 +#define GL_RASTER_FIXED_SAMPLE_LOCATIONS_EXT 0x932A +#define GL_MULTISAMPLE_RASTERIZATION_ALLOWED_EXT 0x932B +#define GL_EFFECTIVE_RASTER_SAMPLES_EXT 0x932C +typedef void (GL_APIENTRYP PFNGLRASTERSAMPLESEXTPROC) (GLuint samples, GLboolean fixedsamplelocations); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRasterSamplesEXT (GLuint samples, GLboolean fixedsamplelocations); +#endif +#endif /* GL_EXT_raster_multisample */ + +#ifndef GL_EXT_read_format_bgra +#define GL_EXT_read_format_bgra 1 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT 0x8366 +#endif /* GL_EXT_read_format_bgra */ + +#ifndef GL_EXT_render_snorm +#define GL_EXT_render_snorm 1 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM_EXT 0x8F98 +#define GL_RG16_SNORM_EXT 0x8F99 +#define GL_RGBA16_SNORM_EXT 0x8F9B +#endif /* GL_EXT_render_snorm */ + +#ifndef GL_EXT_robustness +#define GL_EXT_robustness 1 +#define GL_GUILTY_CONTEXT_RESET_EXT 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_EXT 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_EXT 0x8255 +#define GL_CONTEXT_ROBUST_ACCESS_EXT 0x90F3 +#define GL_RESET_NOTIFICATION_STRATEGY_EXT 0x8256 +#define GL_LOSE_CONTEXT_ON_RESET_EXT 0x8252 +#define GL_NO_RESET_NOTIFICATION_EXT 0x8261 +typedef GLenum (GL_APIENTRYP PFNGLGETGRAPHICSRESETSTATUSEXTPROC) (void); +typedef void (GL_APIENTRYP PFNGLREADNPIXELSEXTPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (GL_APIENTRYP PFNGLGETNUNIFORMFVEXTPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (GL_APIENTRYP PFNGLGETNUNIFORMIVEXTPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLenum GL_APIENTRY glGetGraphicsResetStatusEXT (void); +GL_APICALL void GL_APIENTRY glReadnPixelsEXT (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GL_APICALL void GL_APIENTRY glGetnUniformfvEXT (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GL_APICALL void GL_APIENTRY glGetnUniformivEXT (GLuint program, GLint location, GLsizei bufSize, GLint *params); +#endif +#endif /* GL_EXT_robustness */ + +#ifndef GL_EXT_sRGB +#define GL_EXT_sRGB 1 +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT 0x8210 +#endif /* GL_EXT_sRGB */ + +#ifndef GL_EXT_sRGB_write_control +#define GL_EXT_sRGB_write_control 1 +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#endif /* GL_EXT_sRGB_write_control */ + +#ifndef GL_EXT_semaphore +#define GL_EXT_semaphore 1 +#define GL_LAYOUT_GENERAL_EXT 0x958D +#define GL_LAYOUT_COLOR_ATTACHMENT_EXT 0x958E +#define GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT 0x958F +#define GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT 0x9590 +#define GL_LAYOUT_SHADER_READ_ONLY_EXT 0x9591 +#define GL_LAYOUT_TRANSFER_SRC_EXT 0x9592 +#define GL_LAYOUT_TRANSFER_DST_EXT 0x9593 +#define GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT 0x9530 +#define GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT 0x9531 +typedef void (GL_APIENTRYP PFNGLGENSEMAPHORESEXTPROC) (GLsizei n, GLuint *semaphores); +typedef void (GL_APIENTRYP PFNGLDELETESEMAPHORESEXTPROC) (GLsizei n, const GLuint *semaphores); +typedef GLboolean (GL_APIENTRYP PFNGLISSEMAPHOREEXTPROC) (GLuint semaphore); +typedef void (GL_APIENTRYP PFNGLSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, const GLuint64 *params); +typedef void (GL_APIENTRYP PFNGLGETSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, GLuint64 *params); +typedef void (GL_APIENTRYP PFNGLWAITSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); +typedef void (GL_APIENTRYP PFNGLSIGNALSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGenSemaphoresEXT (GLsizei n, GLuint *semaphores); +GL_APICALL void GL_APIENTRY glDeleteSemaphoresEXT (GLsizei n, const GLuint *semaphores); +GL_APICALL GLboolean GL_APIENTRY glIsSemaphoreEXT (GLuint semaphore); +GL_APICALL void GL_APIENTRY glSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, const GLuint64 *params); +GL_APICALL void GL_APIENTRY glGetSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, GLuint64 *params); +GL_APICALL void GL_APIENTRY glWaitSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); +GL_APICALL void GL_APIENTRY glSignalSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); +#endif +#endif /* GL_EXT_semaphore */ + +#ifndef GL_EXT_semaphore_fd +#define GL_EXT_semaphore_fd 1 +typedef void (GL_APIENTRYP PFNGLIMPORTSEMAPHOREFDEXTPROC) (GLuint semaphore, GLenum handleType, GLint fd); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glImportSemaphoreFdEXT (GLuint semaphore, GLenum handleType, GLint fd); +#endif +#endif /* GL_EXT_semaphore_fd */ + +#ifndef GL_EXT_semaphore_win32 +#define GL_EXT_semaphore_win32 1 +#define GL_HANDLE_TYPE_D3D12_FENCE_EXT 0x9594 +#define GL_D3D12_FENCE_VALUE_EXT 0x9595 +typedef void (GL_APIENTRYP PFNGLIMPORTSEMAPHOREWIN32HANDLEEXTPROC) (GLuint semaphore, GLenum handleType, void *handle); +typedef void (GL_APIENTRYP PFNGLIMPORTSEMAPHOREWIN32NAMEEXTPROC) (GLuint semaphore, GLenum handleType, const void *name); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glImportSemaphoreWin32HandleEXT (GLuint semaphore, GLenum handleType, void *handle); +GL_APICALL void GL_APIENTRY glImportSemaphoreWin32NameEXT (GLuint semaphore, GLenum handleType, const void *name); +#endif +#endif /* GL_EXT_semaphore_win32 */ + +#ifndef GL_EXT_separate_depth_stencil +#define GL_EXT_separate_depth_stencil 1 +#endif /* GL_EXT_separate_depth_stencil */ + +#ifndef GL_EXT_separate_shader_objects +#define GL_EXT_separate_shader_objects 1 +#define GL_ACTIVE_PROGRAM_EXT 0x8259 +#define GL_VERTEX_SHADER_BIT_EXT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT_EXT 0x00000002 +#define GL_ALL_SHADER_BITS_EXT 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE_EXT 0x8258 +#define GL_PROGRAM_PIPELINE_BINDING_EXT 0x825A +typedef void (GL_APIENTRYP PFNGLACTIVESHADERPROGRAMEXTPROC) (GLuint pipeline, GLuint program); +typedef void (GL_APIENTRYP PFNGLBINDPROGRAMPIPELINEEXTPROC) (GLuint pipeline); +typedef GLuint (GL_APIENTRYP PFNGLCREATESHADERPROGRAMVEXTPROC) (GLenum type, GLsizei count, const GLchar **strings); +typedef void (GL_APIENTRYP PFNGLDELETEPROGRAMPIPELINESEXTPROC) (GLsizei n, const GLuint *pipelines); +typedef void (GL_APIENTRYP PFNGLGENPROGRAMPIPELINESEXTPROC) (GLsizei n, GLuint *pipelines); +typedef void (GL_APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGEXTPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (GL_APIENTRYP PFNGLGETPROGRAMPIPELINEIVEXTPROC) (GLuint pipeline, GLenum pname, GLint *params); +typedef GLboolean (GL_APIENTRYP PFNGLISPROGRAMPIPELINEEXTPROC) (GLuint pipeline); +typedef void (GL_APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUSEPROGRAMSTAGESEXTPROC) (GLuint pipeline, GLbitfield stages, GLuint program); +typedef void (GL_APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEEXTPROC) (GLuint pipeline); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glActiveShaderProgramEXT (GLuint pipeline, GLuint program); +GL_APICALL void GL_APIENTRY glBindProgramPipelineEXT (GLuint pipeline); +GL_APICALL GLuint GL_APIENTRY glCreateShaderProgramvEXT (GLenum type, GLsizei count, const GLchar **strings); +GL_APICALL void GL_APIENTRY glDeleteProgramPipelinesEXT (GLsizei n, const GLuint *pipelines); +GL_APICALL void GL_APIENTRY glGenProgramPipelinesEXT (GLsizei n, GLuint *pipelines); +GL_APICALL void GL_APIENTRY glGetProgramPipelineInfoLogEXT (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GL_APICALL void GL_APIENTRY glGetProgramPipelineivEXT (GLuint pipeline, GLenum pname, GLint *params); +GL_APICALL GLboolean GL_APIENTRY glIsProgramPipelineEXT (GLuint pipeline); +GL_APICALL void GL_APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); +GL_APICALL void GL_APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); +GL_APICALL void GL_APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); +GL_APICALL void GL_APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GL_APICALL void GL_APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); +GL_APICALL void GL_APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GL_APICALL void GL_APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GL_APICALL void GL_APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GL_APICALL void GL_APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GL_APICALL void GL_APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUseProgramStagesEXT (GLuint pipeline, GLbitfield stages, GLuint program); +GL_APICALL void GL_APIENTRY glValidateProgramPipelineEXT (GLuint pipeline); +GL_APICALL void GL_APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); +GL_APICALL void GL_APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); +GL_APICALL void GL_APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GL_APICALL void GL_APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GL_APICALL void GL_APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GL_APICALL void GL_APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GL_APICALL void GL_APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GL_APICALL void GL_APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif +#endif /* GL_EXT_separate_shader_objects */ + +#ifndef GL_EXT_shader_framebuffer_fetch +#define GL_EXT_shader_framebuffer_fetch 1 +#define GL_FRAGMENT_SHADER_DISCARDS_SAMPLES_EXT 0x8A52 +#endif /* GL_EXT_shader_framebuffer_fetch */ + +#ifndef GL_EXT_shader_framebuffer_fetch_non_coherent +#define GL_EXT_shader_framebuffer_fetch_non_coherent 1 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERFETCHBARRIEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferFetchBarrierEXT (void); +#endif +#endif /* GL_EXT_shader_framebuffer_fetch_non_coherent */ + +#ifndef GL_EXT_shader_group_vote +#define GL_EXT_shader_group_vote 1 +#endif /* GL_EXT_shader_group_vote */ + +#ifndef GL_EXT_shader_implicit_conversions +#define GL_EXT_shader_implicit_conversions 1 +#endif /* GL_EXT_shader_implicit_conversions */ + +#ifndef GL_EXT_shader_integer_mix +#define GL_EXT_shader_integer_mix 1 +#endif /* GL_EXT_shader_integer_mix */ + +#ifndef GL_EXT_shader_io_blocks +#define GL_EXT_shader_io_blocks 1 +#endif /* GL_EXT_shader_io_blocks */ + +#ifndef GL_EXT_shader_non_constant_global_initializers +#define GL_EXT_shader_non_constant_global_initializers 1 +#endif /* GL_EXT_shader_non_constant_global_initializers */ + +#ifndef GL_EXT_shader_pixel_local_storage +#define GL_EXT_shader_pixel_local_storage 1 +#define GL_MAX_SHADER_PIXEL_LOCAL_STORAGE_FAST_SIZE_EXT 0x8F63 +#define GL_MAX_SHADER_PIXEL_LOCAL_STORAGE_SIZE_EXT 0x8F67 +#define GL_SHADER_PIXEL_LOCAL_STORAGE_EXT 0x8F64 +#endif /* GL_EXT_shader_pixel_local_storage */ + +#ifndef GL_EXT_shader_pixel_local_storage2 +#define GL_EXT_shader_pixel_local_storage2 1 +#define GL_MAX_SHADER_COMBINED_LOCAL_STORAGE_FAST_SIZE_EXT 0x9650 +#define GL_MAX_SHADER_COMBINED_LOCAL_STORAGE_SIZE_EXT 0x9651 +#define GL_FRAMEBUFFER_INCOMPLETE_INSUFFICIENT_SHADER_COMBINED_LOCAL_STORAGE_EXT 0x9652 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERPIXELLOCALSTORAGESIZEEXTPROC) (GLuint target, GLsizei size); +typedef GLsizei (GL_APIENTRYP PFNGLGETFRAMEBUFFERPIXELLOCALSTORAGESIZEEXTPROC) (GLuint target); +typedef void (GL_APIENTRYP PFNGLCLEARPIXELLOCALSTORAGEUIEXTPROC) (GLsizei offset, GLsizei n, const GLuint *values); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferPixelLocalStorageSizeEXT (GLuint target, GLsizei size); +GL_APICALL GLsizei GL_APIENTRY glGetFramebufferPixelLocalStorageSizeEXT (GLuint target); +GL_APICALL void GL_APIENTRY glClearPixelLocalStorageuiEXT (GLsizei offset, GLsizei n, const GLuint *values); +#endif +#endif /* GL_EXT_shader_pixel_local_storage2 */ + +#ifndef GL_EXT_shader_samples_identical +#define GL_EXT_shader_samples_identical 1 +#endif /* GL_EXT_shader_samples_identical */ + +#ifndef GL_EXT_shader_texture_lod +#define GL_EXT_shader_texture_lod 1 +#endif /* GL_EXT_shader_texture_lod */ + +#ifndef GL_EXT_shadow_samplers +#define GL_EXT_shadow_samplers 1 +#define GL_TEXTURE_COMPARE_MODE_EXT 0x884C +#define GL_TEXTURE_COMPARE_FUNC_EXT 0x884D +#define GL_COMPARE_REF_TO_TEXTURE_EXT 0x884E +#define GL_SAMPLER_2D_SHADOW_EXT 0x8B62 +#endif /* GL_EXT_shadow_samplers */ + +#ifndef GL_EXT_sparse_texture +#define GL_EXT_sparse_texture 1 +#define GL_TEXTURE_SPARSE_EXT 0x91A6 +#define GL_VIRTUAL_PAGE_SIZE_INDEX_EXT 0x91A7 +#define GL_NUM_SPARSE_LEVELS_EXT 0x91AA +#define GL_NUM_VIRTUAL_PAGE_SIZES_EXT 0x91A8 +#define GL_VIRTUAL_PAGE_SIZE_X_EXT 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_EXT 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_EXT 0x9197 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TEXTURE_3D 0x806F +#define GL_MAX_SPARSE_TEXTURE_SIZE_EXT 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_EXT 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS_EXT 0x919A +#define GL_SPARSE_TEXTURE_FULL_ARRAY_CUBE_MIPMAPS_EXT 0x91A9 +typedef void (GL_APIENTRYP PFNGLTEXPAGECOMMITMENTEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexPageCommitmentEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +#endif +#endif /* GL_EXT_sparse_texture */ + +#ifndef GL_EXT_sparse_texture2 +#define GL_EXT_sparse_texture2 1 +#endif /* GL_EXT_sparse_texture2 */ + +#ifndef GL_EXT_tessellation_point_size +#define GL_EXT_tessellation_point_size 1 +#endif /* GL_EXT_tessellation_point_size */ + +#ifndef GL_EXT_tessellation_shader +#define GL_EXT_tessellation_shader 1 +#define GL_PATCHES_EXT 0x000E +#define GL_PATCH_VERTICES_EXT 0x8E72 +#define GL_TESS_CONTROL_OUTPUT_VERTICES_EXT 0x8E75 +#define GL_TESS_GEN_MODE_EXT 0x8E76 +#define GL_TESS_GEN_SPACING_EXT 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER_EXT 0x8E78 +#define GL_TESS_GEN_POINT_MODE_EXT 0x8E79 +#define GL_ISOLINES_EXT 0x8E7A +#define GL_QUADS_EXT 0x0007 +#define GL_FRACTIONAL_ODD_EXT 0x8E7B +#define GL_FRACTIONAL_EVEN_EXT 0x8E7C +#define GL_MAX_PATCH_VERTICES_EXT 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL_EXT 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS_EXT 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS_EXT 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS_EXT 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS_EXT 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS_EXT 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS_EXT 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS_EXT 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS_EXT 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS_EXT 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS_EXT 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS_EXT 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS_EXT 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS_EXT 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS_EXT 0x8E1F +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS_EXT 0x92CD +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS_EXT 0x92CE +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS_EXT 0x92D3 +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS_EXT 0x92D4 +#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS_EXT 0x90CB +#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS_EXT 0x90CC +#define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS_EXT 0x90D8 +#define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS_EXT 0x90D9 +#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221 +#define GL_IS_PER_PATCH_EXT 0x92E7 +#define GL_REFERENCED_BY_TESS_CONTROL_SHADER_EXT 0x9307 +#define GL_REFERENCED_BY_TESS_EVALUATION_SHADER_EXT 0x9308 +#define GL_TESS_CONTROL_SHADER_EXT 0x8E88 +#define GL_TESS_EVALUATION_SHADER_EXT 0x8E87 +#define GL_TESS_CONTROL_SHADER_BIT_EXT 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT_EXT 0x00000010 +typedef void (GL_APIENTRYP PFNGLPATCHPARAMETERIEXTPROC) (GLenum pname, GLint value); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glPatchParameteriEXT (GLenum pname, GLint value); +#endif +#endif /* GL_EXT_tessellation_shader */ + +#ifndef GL_EXT_texture_border_clamp +#define GL_EXT_texture_border_clamp 1 +#define GL_TEXTURE_BORDER_COLOR_EXT 0x1004 +#define GL_CLAMP_TO_BORDER_EXT 0x812D +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (GL_APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (GL_APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (GL_APIENTRYP PFNGLSAMPLERPARAMETERIIVEXTPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (GL_APIENTRYP PFNGLSAMPLERPARAMETERIUIVEXTPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (GL_APIENTRYP PFNGLGETSAMPLERPARAMETERIIVEXTPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVEXTPROC) (GLuint sampler, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); +GL_APICALL void GL_APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); +GL_APICALL void GL_APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); +GL_APICALL void GL_APIENTRY glSamplerParameterIivEXT (GLuint sampler, GLenum pname, const GLint *param); +GL_APICALL void GL_APIENTRY glSamplerParameterIuivEXT (GLuint sampler, GLenum pname, const GLuint *param); +GL_APICALL void GL_APIENTRY glGetSamplerParameterIivEXT (GLuint sampler, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glGetSamplerParameterIuivEXT (GLuint sampler, GLenum pname, GLuint *params); +#endif +#endif /* GL_EXT_texture_border_clamp */ + +#ifndef GL_EXT_texture_buffer +#define GL_EXT_texture_buffer 1 +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_TEXTURE_BUFFER_BINDING_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT_EXT 0x919F +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 +#define GL_IMAGE_BUFFER_EXT 0x9051 +#define GL_INT_IMAGE_BUFFER_EXT 0x905C +#define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 +#define GL_TEXTURE_BUFFER_OFFSET_EXT 0x919D +#define GL_TEXTURE_BUFFER_SIZE_EXT 0x919E +typedef void (GL_APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (GL_APIENTRYP PFNGLTEXBUFFERRANGEEXTPROC) (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); +GL_APICALL void GL_APIENTRY glTexBufferRangeEXT (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +#endif +#endif /* GL_EXT_texture_buffer */ + +#ifndef GL_EXT_texture_compression_astc_decode_mode +#define GL_EXT_texture_compression_astc_decode_mode 1 +#define GL_TEXTURE_ASTC_DECODE_PRECISION_EXT 0x8F69 +#endif /* GL_EXT_texture_compression_astc_decode_mode */ + +#ifndef GL_EXT_texture_compression_bptc +#define GL_EXT_texture_compression_bptc 1 +#define GL_COMPRESSED_RGBA_BPTC_UNORM_EXT 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT 0x8E8F +#endif /* GL_EXT_texture_compression_bptc */ + +#ifndef GL_EXT_texture_compression_dxt1 +#define GL_EXT_texture_compression_dxt1 1 +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif /* GL_EXT_texture_compression_dxt1 */ + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif /* GL_EXT_texture_compression_rgtc */ + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif /* GL_EXT_texture_compression_s3tc */ + +#ifndef GL_EXT_texture_compression_s3tc_srgb +#define GL_EXT_texture_compression_s3tc_srgb 1 +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif /* GL_EXT_texture_compression_s3tc_srgb */ + +#ifndef GL_EXT_texture_cube_map_array +#define GL_EXT_texture_cube_map_array 1 +#define GL_TEXTURE_CUBE_MAP_ARRAY_EXT 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_EXT 0x900A +#define GL_SAMPLER_CUBE_MAP_ARRAY_EXT 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_EXT 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_EXT 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_EXT 0x900F +#define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A +#endif /* GL_EXT_texture_cube_map_array */ + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif /* GL_EXT_texture_filter_anisotropic */ + +#ifndef GL_EXT_texture_filter_minmax +#define GL_EXT_texture_filter_minmax 1 +#define GL_TEXTURE_REDUCTION_MODE_EXT 0x9366 +#define GL_WEIGHTED_AVERAGE_EXT 0x9367 +#endif /* GL_EXT_texture_filter_minmax */ + +#ifndef GL_EXT_texture_format_BGRA8888 +#define GL_EXT_texture_format_BGRA8888 1 +#endif /* GL_EXT_texture_format_BGRA8888 */ + +#ifndef GL_EXT_texture_format_sRGB_override +#define GL_EXT_texture_format_sRGB_override 1 +#define GL_TEXTURE_FORMAT_SRGB_OVERRIDE_EXT 0x8FBF +#endif /* GL_EXT_texture_format_sRGB_override */ + +#ifndef GL_EXT_texture_mirror_clamp_to_edge +#define GL_EXT_texture_mirror_clamp_to_edge 1 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#endif /* GL_EXT_texture_mirror_clamp_to_edge */ + +#ifndef GL_EXT_texture_norm16 +#define GL_EXT_texture_norm16 1 +#define GL_R16_EXT 0x822A +#define GL_RG16_EXT 0x822C +#define GL_RGBA16_EXT 0x805B +#define GL_RGB16_EXT 0x8054 +#define GL_RGB16_SNORM_EXT 0x8F9A +#endif /* GL_EXT_texture_norm16 */ + +#ifndef GL_EXT_texture_query_lod +#define GL_EXT_texture_query_lod 1 +#endif /* GL_EXT_texture_query_lod */ + +#ifndef GL_EXT_texture_rg +#define GL_EXT_texture_rg 1 +#define GL_RED_EXT 0x1903 +#define GL_RG_EXT 0x8227 +#define GL_R8_EXT 0x8229 +#define GL_RG8_EXT 0x822B +#endif /* GL_EXT_texture_rg */ + +#ifndef GL_EXT_texture_sRGB_R8 +#define GL_EXT_texture_sRGB_R8 1 +#define GL_SR8_EXT 0x8FBD +#endif /* GL_EXT_texture_sRGB_R8 */ + +#ifndef GL_EXT_texture_sRGB_RG8 +#define GL_EXT_texture_sRGB_RG8 1 +#define GL_SRG8_EXT 0x8FBE +#endif /* GL_EXT_texture_sRGB_RG8 */ + +#ifndef GL_EXT_texture_sRGB_decode +#define GL_EXT_texture_sRGB_decode 1 +#define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 +#define GL_DECODE_EXT 0x8A49 +#define GL_SKIP_DECODE_EXT 0x8A4A +#endif /* GL_EXT_texture_sRGB_decode */ + +#ifndef GL_EXT_texture_shadow_lod +#define GL_EXT_texture_shadow_lod 1 +#endif /* GL_EXT_texture_shadow_lod */ + +#ifndef GL_EXT_texture_storage +#define GL_EXT_texture_storage 1 +#define GL_TEXTURE_IMMUTABLE_FORMAT_EXT 0x912F +#define GL_ALPHA8_EXT 0x803C +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_RGBA32F_EXT 0x8814 +#define GL_RGB32F_EXT 0x8815 +#define GL_ALPHA32F_EXT 0x8816 +#define GL_LUMINANCE32F_EXT 0x8818 +#define GL_LUMINANCE_ALPHA32F_EXT 0x8819 +#define GL_ALPHA16F_EXT 0x881C +#define GL_LUMINANCE16F_EXT 0x881E +#define GL_LUMINANCE_ALPHA16F_EXT 0x881F +#define GL_R32F_EXT 0x822E +#define GL_RG32F_EXT 0x8230 +typedef void (GL_APIENTRYP PFNGLTEXSTORAGE1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGE2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGE3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexStorage1DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GL_APICALL void GL_APIENTRY glTexStorage2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glTexStorage3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GL_APICALL void GL_APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GL_APICALL void GL_APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GL_EXT_texture_storage */ + +#ifndef GL_EXT_texture_storage_compression +#define GL_EXT_texture_storage_compression 1 +#define GL_NUM_SURFACE_COMPRESSION_FIXED_RATES_EXT 0x8F6E +#define GL_SURFACE_COMPRESSION_FIXED_RATE_1BPC_EXT 0x96C4 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_2BPC_EXT 0x96C5 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_3BPC_EXT 0x96C6 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_4BPC_EXT 0x96C7 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_5BPC_EXT 0x96C8 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_6BPC_EXT 0x96C9 +#define GL_SURFACE_COMPRESSION_FIXED_RATE_7BPC_EXT 0x96CA +#define GL_SURFACE_COMPRESSION_FIXED_RATE_8BPC_EXT 0x96CB +#define GL_SURFACE_COMPRESSION_FIXED_RATE_9BPC_EXT 0x96CC +#define GL_SURFACE_COMPRESSION_FIXED_RATE_10BPC_EXT 0x96CD +#define GL_SURFACE_COMPRESSION_FIXED_RATE_11BPC_EXT 0x96CE +#define GL_SURFACE_COMPRESSION_FIXED_RATE_12BPC_EXT 0x96CF +typedef void (GL_APIENTRYP PFNGLTEXSTORAGEATTRIBS2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, const GLint* attrib_list); +typedef void (GL_APIENTRYP PFNGLTEXSTORAGEATTRIBS3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, const GLint* attrib_list); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexStorageAttribs2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, const GLint* attrib_list); +GL_APICALL void GL_APIENTRY glTexStorageAttribs3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, const GLint* attrib_list); +#endif +#endif /* GL_EXT_texture_storage_compression */ + +#ifndef GL_EXT_texture_type_2_10_10_10_REV +#define GL_EXT_texture_type_2_10_10_10_REV 1 +#define GL_UNSIGNED_INT_2_10_10_10_REV_EXT 0x8368 +#endif /* GL_EXT_texture_type_2_10_10_10_REV */ + +#ifndef GL_EXT_texture_view +#define GL_EXT_texture_view 1 +#define GL_TEXTURE_VIEW_MIN_LEVEL_EXT 0x82DB +#define GL_TEXTURE_VIEW_NUM_LEVELS_EXT 0x82DC +#define GL_TEXTURE_VIEW_MIN_LAYER_EXT 0x82DD +#define GL_TEXTURE_VIEW_NUM_LAYERS_EXT 0x82DE +typedef void (GL_APIENTRYP PFNGLTEXTUREVIEWEXTPROC) (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTextureViewEXT (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +#endif +#endif /* GL_EXT_texture_view */ + +#ifndef GL_EXT_unpack_subimage +#define GL_EXT_unpack_subimage 1 +#define GL_UNPACK_ROW_LENGTH_EXT 0x0CF2 +#define GL_UNPACK_SKIP_ROWS_EXT 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS_EXT 0x0CF4 +#endif /* GL_EXT_unpack_subimage */ + +#ifndef GL_EXT_win32_keyed_mutex +#define GL_EXT_win32_keyed_mutex 1 +typedef GLboolean (GL_APIENTRYP PFNGLACQUIREKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key, GLuint timeout); +typedef GLboolean (GL_APIENTRYP PFNGLRELEASEKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLboolean GL_APIENTRY glAcquireKeyedMutexWin32EXT (GLuint memory, GLuint64 key, GLuint timeout); +GL_APICALL GLboolean GL_APIENTRY glReleaseKeyedMutexWin32EXT (GLuint memory, GLuint64 key); +#endif +#endif /* GL_EXT_win32_keyed_mutex */ + +#ifndef GL_EXT_window_rectangles +#define GL_EXT_window_rectangles 1 +#define GL_INCLUSIVE_EXT 0x8F10 +#define GL_EXCLUSIVE_EXT 0x8F11 +#define GL_WINDOW_RECTANGLE_EXT 0x8F12 +#define GL_WINDOW_RECTANGLE_MODE_EXT 0x8F13 +#define GL_MAX_WINDOW_RECTANGLES_EXT 0x8F14 +#define GL_NUM_WINDOW_RECTANGLES_EXT 0x8F15 +typedef void (GL_APIENTRYP PFNGLWINDOWRECTANGLESEXTPROC) (GLenum mode, GLsizei count, const GLint *box); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glWindowRectanglesEXT (GLenum mode, GLsizei count, const GLint *box); +#endif +#endif /* GL_EXT_window_rectangles */ + +#ifndef GL_FJ_shader_binary_GCCSO +#define GL_FJ_shader_binary_GCCSO 1 +#define GL_GCCSO_SHADER_BINARY_FJ 0x9260 +#endif /* GL_FJ_shader_binary_GCCSO */ + +#ifndef GL_IMG_bindless_texture +#define GL_IMG_bindless_texture 1 +typedef GLuint64 (GL_APIENTRYP PFNGLGETTEXTUREHANDLEIMGPROC) (GLuint texture); +typedef GLuint64 (GL_APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEIMGPROC) (GLuint texture, GLuint sampler); +typedef void (GL_APIENTRYP PFNGLUNIFORMHANDLEUI64IMGPROC) (GLint location, GLuint64 value); +typedef void (GL_APIENTRYP PFNGLUNIFORMHANDLEUI64VIMGPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64IMGPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VIMGPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLuint64 GL_APIENTRY glGetTextureHandleIMG (GLuint texture); +GL_APICALL GLuint64 GL_APIENTRY glGetTextureSamplerHandleIMG (GLuint texture, GLuint sampler); +GL_APICALL void GL_APIENTRY glUniformHandleui64IMG (GLint location, GLuint64 value); +GL_APICALL void GL_APIENTRY glUniformHandleui64vIMG (GLint location, GLsizei count, const GLuint64 *value); +GL_APICALL void GL_APIENTRY glProgramUniformHandleui64IMG (GLuint program, GLint location, GLuint64 value); +GL_APICALL void GL_APIENTRY glProgramUniformHandleui64vIMG (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +#endif +#endif /* GL_IMG_bindless_texture */ + +#ifndef GL_IMG_framebuffer_downsample +#define GL_IMG_framebuffer_downsample 1 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_AND_DOWNSAMPLE_IMG 0x913C +#define GL_NUM_DOWNSAMPLE_SCALES_IMG 0x913D +#define GL_DOWNSAMPLE_SCALES_IMG 0x913E +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SCALE_IMG 0x913F +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DDOWNSAMPLEIMGPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint xscale, GLint yscale); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERDOWNSAMPLEIMGPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer, GLint xscale, GLint yscale); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferTexture2DDownsampleIMG (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint xscale, GLint yscale); +GL_APICALL void GL_APIENTRY glFramebufferTextureLayerDownsampleIMG (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer, GLint xscale, GLint yscale); +#endif +#endif /* GL_IMG_framebuffer_downsample */ + +#ifndef GL_IMG_multisampled_render_to_texture +#define GL_IMG_multisampled_render_to_texture 1 +#define GL_RENDERBUFFER_SAMPLES_IMG 0x9133 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_IMG 0x9134 +#define GL_MAX_SAMPLES_IMG 0x9135 +#define GL_TEXTURE_SAMPLES_IMG 0x9136 +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEIMGPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEIMGPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleIMG (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glFramebufferTexture2DMultisampleIMG (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples); +#endif +#endif /* GL_IMG_multisampled_render_to_texture */ + +#ifndef GL_IMG_program_binary +#define GL_IMG_program_binary 1 +#define GL_SGX_PROGRAM_BINARY_IMG 0x9130 +#endif /* GL_IMG_program_binary */ + +#ifndef GL_IMG_read_format +#define GL_IMG_read_format 1 +#define GL_BGRA_IMG 0x80E1 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV_IMG 0x8365 +#endif /* GL_IMG_read_format */ + +#ifndef GL_IMG_shader_binary +#define GL_IMG_shader_binary 1 +#define GL_SGX_BINARY_IMG 0x8C0A +#endif /* GL_IMG_shader_binary */ + +#ifndef GL_IMG_texture_compression_pvrtc +#define GL_IMG_texture_compression_pvrtc 1 +#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#endif /* GL_IMG_texture_compression_pvrtc */ + +#ifndef GL_IMG_texture_compression_pvrtc2 +#define GL_IMG_texture_compression_pvrtc2 1 +#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG 0x9138 +#endif /* GL_IMG_texture_compression_pvrtc2 */ + +#ifndef GL_IMG_texture_filter_cubic +#define GL_IMG_texture_filter_cubic 1 +#define GL_CUBIC_IMG 0x9139 +#define GL_CUBIC_MIPMAP_NEAREST_IMG 0x913A +#define GL_CUBIC_MIPMAP_LINEAR_IMG 0x913B +#endif /* GL_IMG_texture_filter_cubic */ + +#ifndef GL_INTEL_blackhole_render +#define GL_INTEL_blackhole_render 1 +#define GL_BLACKHOLE_RENDER_INTEL 0x83FC +#endif /* GL_INTEL_blackhole_render */ + +#ifndef GL_INTEL_conservative_rasterization +#define GL_INTEL_conservative_rasterization 1 +#define GL_CONSERVATIVE_RASTERIZATION_INTEL 0x83FE +#endif /* GL_INTEL_conservative_rasterization */ + +#ifndef GL_INTEL_framebuffer_CMAA +#define GL_INTEL_framebuffer_CMAA 1 +typedef void (GL_APIENTRYP PFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glApplyFramebufferAttachmentCMAAINTEL (void); +#endif +#endif /* GL_INTEL_framebuffer_CMAA */ + +#ifndef GL_INTEL_performance_query +#define GL_INTEL_performance_query 1 +#define GL_PERFQUERY_SINGLE_CONTEXT_INTEL 0x00000000 +#define GL_PERFQUERY_GLOBAL_CONTEXT_INTEL 0x00000001 +#define GL_PERFQUERY_WAIT_INTEL 0x83FB +#define GL_PERFQUERY_FLUSH_INTEL 0x83FA +#define GL_PERFQUERY_DONOT_FLUSH_INTEL 0x83F9 +#define GL_PERFQUERY_COUNTER_EVENT_INTEL 0x94F0 +#define GL_PERFQUERY_COUNTER_DURATION_NORM_INTEL 0x94F1 +#define GL_PERFQUERY_COUNTER_DURATION_RAW_INTEL 0x94F2 +#define GL_PERFQUERY_COUNTER_THROUGHPUT_INTEL 0x94F3 +#define GL_PERFQUERY_COUNTER_RAW_INTEL 0x94F4 +#define GL_PERFQUERY_COUNTER_TIMESTAMP_INTEL 0x94F5 +#define GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL 0x94F8 +#define GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL 0x94F9 +#define GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL 0x94FA +#define GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL 0x94FB +#define GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL 0x94FC +#define GL_PERFQUERY_QUERY_NAME_LENGTH_MAX_INTEL 0x94FD +#define GL_PERFQUERY_COUNTER_NAME_LENGTH_MAX_INTEL 0x94FE +#define GL_PERFQUERY_COUNTER_DESC_LENGTH_MAX_INTEL 0x94FF +#define GL_PERFQUERY_GPA_EXTENDED_COUNTERS_INTEL 0x9500 +typedef void (GL_APIENTRYP PFNGLBEGINPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (GL_APIENTRYP PFNGLCREATEPERFQUERYINTELPROC) (GLuint queryId, GLuint *queryHandle); +typedef void (GL_APIENTRYP PFNGLDELETEPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (GL_APIENTRYP PFNGLENDPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (GL_APIENTRYP PFNGLGETFIRSTPERFQUERYIDINTELPROC) (GLuint *queryId); +typedef void (GL_APIENTRYP PFNGLGETNEXTPERFQUERYIDINTELPROC) (GLuint queryId, GLuint *nextQueryId); +typedef void (GL_APIENTRYP PFNGLGETPERFCOUNTERINFOINTELPROC) (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +typedef void (GL_APIENTRYP PFNGLGETPERFQUERYDATAINTELPROC) (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); +typedef void (GL_APIENTRYP PFNGLGETPERFQUERYIDBYNAMEINTELPROC) (GLchar *queryName, GLuint *queryId); +typedef void (GL_APIENTRYP PFNGLGETPERFQUERYINFOINTELPROC) (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBeginPerfQueryINTEL (GLuint queryHandle); +GL_APICALL void GL_APIENTRY glCreatePerfQueryINTEL (GLuint queryId, GLuint *queryHandle); +GL_APICALL void GL_APIENTRY glDeletePerfQueryINTEL (GLuint queryHandle); +GL_APICALL void GL_APIENTRY glEndPerfQueryINTEL (GLuint queryHandle); +GL_APICALL void GL_APIENTRY glGetFirstPerfQueryIdINTEL (GLuint *queryId); +GL_APICALL void GL_APIENTRY glGetNextPerfQueryIdINTEL (GLuint queryId, GLuint *nextQueryId); +GL_APICALL void GL_APIENTRY glGetPerfCounterInfoINTEL (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +GL_APICALL void GL_APIENTRY glGetPerfQueryDataINTEL (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); +GL_APICALL void GL_APIENTRY glGetPerfQueryIdByNameINTEL (GLchar *queryName, GLuint *queryId); +GL_APICALL void GL_APIENTRY glGetPerfQueryInfoINTEL (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#endif +#endif /* GL_INTEL_performance_query */ + +#ifndef GL_MESA_bgra +#define GL_MESA_bgra 1 +#define GL_BGR_EXT 0x80E0 +#endif /* GL_MESA_bgra */ + +#ifndef GL_MESA_framebuffer_flip_x +#define GL_MESA_framebuffer_flip_x 1 +#define GL_FRAMEBUFFER_FLIP_X_MESA 0x8BBC +#endif /* GL_MESA_framebuffer_flip_x */ + +#ifndef GL_MESA_framebuffer_flip_y +#define GL_MESA_framebuffer_flip_y 1 +#define GL_FRAMEBUFFER_FLIP_Y_MESA 0x8BBB +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERPARAMETERIMESAPROC) (GLenum target, GLenum pname, GLint param); +typedef void (GL_APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVMESAPROC) (GLenum target, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferParameteriMESA (GLenum target, GLenum pname, GLint param); +GL_APICALL void GL_APIENTRY glGetFramebufferParameterivMESA (GLenum target, GLenum pname, GLint *params); +#endif +#endif /* GL_MESA_framebuffer_flip_y */ + +#ifndef GL_MESA_framebuffer_swap_xy +#define GL_MESA_framebuffer_swap_xy 1 +#define GL_FRAMEBUFFER_SWAP_XY_MESA 0x8BBD +#endif /* GL_MESA_framebuffer_swap_xy */ + +#ifndef GL_MESA_program_binary_formats +#define GL_MESA_program_binary_formats 1 +#define GL_PROGRAM_BINARY_FORMAT_MESA 0x875F +#endif /* GL_MESA_program_binary_formats */ + +#ifndef GL_MESA_shader_integer_functions +#define GL_MESA_shader_integer_functions 1 +#endif /* GL_MESA_shader_integer_functions */ + +#ifndef GL_NVX_blend_equation_advanced_multi_draw_buffers +#define GL_NVX_blend_equation_advanced_multi_draw_buffers 1 +#endif /* GL_NVX_blend_equation_advanced_multi_draw_buffers */ + +#ifndef GL_NV_bindless_texture +#define GL_NV_bindless_texture 1 +typedef GLuint64 (GL_APIENTRYP PFNGLGETTEXTUREHANDLENVPROC) (GLuint texture); +typedef GLuint64 (GL_APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC) (GLuint texture, GLuint sampler); +typedef void (GL_APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef void (GL_APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef GLuint64 (GL_APIENTRYP PFNGLGETIMAGEHANDLENVPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (GL_APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle, GLenum access); +typedef void (GL_APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef void (GL_APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC) (GLint location, GLuint64 value); +typedef void (GL_APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (GL_APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef GLboolean (GL_APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLuint64 GL_APIENTRY glGetTextureHandleNV (GLuint texture); +GL_APICALL GLuint64 GL_APIENTRY glGetTextureSamplerHandleNV (GLuint texture, GLuint sampler); +GL_APICALL void GL_APIENTRY glMakeTextureHandleResidentNV (GLuint64 handle); +GL_APICALL void GL_APIENTRY glMakeTextureHandleNonResidentNV (GLuint64 handle); +GL_APICALL GLuint64 GL_APIENTRY glGetImageHandleNV (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GL_APICALL void GL_APIENTRY glMakeImageHandleResidentNV (GLuint64 handle, GLenum access); +GL_APICALL void GL_APIENTRY glMakeImageHandleNonResidentNV (GLuint64 handle); +GL_APICALL void GL_APIENTRY glUniformHandleui64NV (GLint location, GLuint64 value); +GL_APICALL void GL_APIENTRY glUniformHandleui64vNV (GLint location, GLsizei count, const GLuint64 *value); +GL_APICALL void GL_APIENTRY glProgramUniformHandleui64NV (GLuint program, GLint location, GLuint64 value); +GL_APICALL void GL_APIENTRY glProgramUniformHandleui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GL_APICALL GLboolean GL_APIENTRY glIsTextureHandleResidentNV (GLuint64 handle); +GL_APICALL GLboolean GL_APIENTRY glIsImageHandleResidentNV (GLuint64 handle); +#endif +#endif /* GL_NV_bindless_texture */ + +#ifndef GL_NV_blend_equation_advanced +#define GL_NV_blend_equation_advanced 1 +#define GL_BLEND_OVERLAP_NV 0x9281 +#define GL_BLEND_PREMULTIPLIED_SRC_NV 0x9280 +#define GL_BLUE_NV 0x1905 +#define GL_COLORBURN_NV 0x929A +#define GL_COLORDODGE_NV 0x9299 +#define GL_CONJOINT_NV 0x9284 +#define GL_CONTRAST_NV 0x92A1 +#define GL_DARKEN_NV 0x9297 +#define GL_DIFFERENCE_NV 0x929E +#define GL_DISJOINT_NV 0x9283 +#define GL_DST_ATOP_NV 0x928F +#define GL_DST_IN_NV 0x928B +#define GL_DST_NV 0x9287 +#define GL_DST_OUT_NV 0x928D +#define GL_DST_OVER_NV 0x9289 +#define GL_EXCLUSION_NV 0x92A0 +#define GL_GREEN_NV 0x1904 +#define GL_HARDLIGHT_NV 0x929B +#define GL_HARDMIX_NV 0x92A9 +#define GL_HSL_COLOR_NV 0x92AF +#define GL_HSL_HUE_NV 0x92AD +#define GL_HSL_LUMINOSITY_NV 0x92B0 +#define GL_HSL_SATURATION_NV 0x92AE +#define GL_INVERT_OVG_NV 0x92B4 +#define GL_INVERT_RGB_NV 0x92A3 +#define GL_LIGHTEN_NV 0x9298 +#define GL_LINEARBURN_NV 0x92A5 +#define GL_LINEARDODGE_NV 0x92A4 +#define GL_LINEARLIGHT_NV 0x92A7 +#define GL_MINUS_CLAMPED_NV 0x92B3 +#define GL_MINUS_NV 0x929F +#define GL_MULTIPLY_NV 0x9294 +#define GL_OVERLAY_NV 0x9296 +#define GL_PINLIGHT_NV 0x92A8 +#define GL_PLUS_CLAMPED_ALPHA_NV 0x92B2 +#define GL_PLUS_CLAMPED_NV 0x92B1 +#define GL_PLUS_DARKER_NV 0x9292 +#define GL_PLUS_NV 0x9291 +#define GL_RED_NV 0x1903 +#define GL_SCREEN_NV 0x9295 +#define GL_SOFTLIGHT_NV 0x929C +#define GL_SRC_ATOP_NV 0x928E +#define GL_SRC_IN_NV 0x928A +#define GL_SRC_NV 0x9286 +#define GL_SRC_OUT_NV 0x928C +#define GL_SRC_OVER_NV 0x9288 +#define GL_UNCORRELATED_NV 0x9282 +#define GL_VIVIDLIGHT_NV 0x92A6 +#define GL_XOR_NV 0x1506 +typedef void (GL_APIENTRYP PFNGLBLENDPARAMETERINVPROC) (GLenum pname, GLint value); +typedef void (GL_APIENTRYP PFNGLBLENDBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBlendParameteriNV (GLenum pname, GLint value); +GL_APICALL void GL_APIENTRY glBlendBarrierNV (void); +#endif +#endif /* GL_NV_blend_equation_advanced */ + +#ifndef GL_NV_blend_equation_advanced_coherent +#define GL_NV_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_NV 0x9285 +#endif /* GL_NV_blend_equation_advanced_coherent */ + +#ifndef GL_NV_blend_minmax_factor +#define GL_NV_blend_minmax_factor 1 +#define GL_FACTOR_MIN_AMD 0x901C +#define GL_FACTOR_MAX_AMD 0x901D +#endif /* GL_NV_blend_minmax_factor */ + +#ifndef GL_NV_clip_space_w_scaling +#define GL_NV_clip_space_w_scaling 1 +#define GL_VIEWPORT_POSITION_W_SCALE_NV 0x937C +#define GL_VIEWPORT_POSITION_W_SCALE_X_COEFF_NV 0x937D +#define GL_VIEWPORT_POSITION_W_SCALE_Y_COEFF_NV 0x937E +typedef void (GL_APIENTRYP PFNGLVIEWPORTPOSITIONWSCALENVPROC) (GLuint index, GLfloat xcoeff, GLfloat ycoeff); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glViewportPositionWScaleNV (GLuint index, GLfloat xcoeff, GLfloat ycoeff); +#endif +#endif /* GL_NV_clip_space_w_scaling */ + +#ifndef GL_NV_compute_shader_derivatives +#define GL_NV_compute_shader_derivatives 1 +#endif /* GL_NV_compute_shader_derivatives */ + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 +typedef void (GL_APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (GL_APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); +GL_APICALL void GL_APIENTRY glEndConditionalRenderNV (void); +#endif +#endif /* GL_NV_conditional_render */ + +#ifndef GL_NV_conservative_raster +#define GL_NV_conservative_raster 1 +#define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346 +#define GL_SUBPIXEL_PRECISION_BIAS_X_BITS_NV 0x9347 +#define GL_SUBPIXEL_PRECISION_BIAS_Y_BITS_NV 0x9348 +#define GL_MAX_SUBPIXEL_PRECISION_BIAS_BITS_NV 0x9349 +typedef void (GL_APIENTRYP PFNGLSUBPIXELPRECISIONBIASNVPROC) (GLuint xbits, GLuint ybits); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glSubpixelPrecisionBiasNV (GLuint xbits, GLuint ybits); +#endif +#endif /* GL_NV_conservative_raster */ + +#ifndef GL_NV_conservative_raster_pre_snap +#define GL_NV_conservative_raster_pre_snap 1 +#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_NV 0x9550 +#endif /* GL_NV_conservative_raster_pre_snap */ + +#ifndef GL_NV_conservative_raster_pre_snap_triangles +#define GL_NV_conservative_raster_pre_snap_triangles 1 +#define GL_CONSERVATIVE_RASTER_MODE_NV 0x954D +#define GL_CONSERVATIVE_RASTER_MODE_POST_SNAP_NV 0x954E +#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_TRIANGLES_NV 0x954F +typedef void (GL_APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERINVPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glConservativeRasterParameteriNV (GLenum pname, GLint param); +#endif +#endif /* GL_NV_conservative_raster_pre_snap_triangles */ + +#ifndef GL_NV_copy_buffer +#define GL_NV_copy_buffer 1 +#define GL_COPY_READ_BUFFER_NV 0x8F36 +#define GL_COPY_WRITE_BUFFER_NV 0x8F37 +typedef void (GL_APIENTRYP PFNGLCOPYBUFFERSUBDATANVPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCopyBufferSubDataNV (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#endif +#endif /* GL_NV_copy_buffer */ + +#ifndef GL_NV_coverage_sample +#define GL_NV_coverage_sample 1 +#define GL_COVERAGE_COMPONENT_NV 0x8ED0 +#define GL_COVERAGE_COMPONENT4_NV 0x8ED1 +#define GL_COVERAGE_ATTACHMENT_NV 0x8ED2 +#define GL_COVERAGE_BUFFERS_NV 0x8ED3 +#define GL_COVERAGE_SAMPLES_NV 0x8ED4 +#define GL_COVERAGE_ALL_FRAGMENTS_NV 0x8ED5 +#define GL_COVERAGE_EDGE_FRAGMENTS_NV 0x8ED6 +#define GL_COVERAGE_AUTOMATIC_NV 0x8ED7 +#define GL_COVERAGE_BUFFER_BIT_NV 0x00008000 +typedef void (GL_APIENTRYP PFNGLCOVERAGEMASKNVPROC) (GLboolean mask); +typedef void (GL_APIENTRYP PFNGLCOVERAGEOPERATIONNVPROC) (GLenum operation); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCoverageMaskNV (GLboolean mask); +GL_APICALL void GL_APIENTRY glCoverageOperationNV (GLenum operation); +#endif +#endif /* GL_NV_coverage_sample */ + +#ifndef GL_NV_depth_nonlinear +#define GL_NV_depth_nonlinear 1 +#define GL_DEPTH_COMPONENT16_NONLINEAR_NV 0x8E2C +#endif /* GL_NV_depth_nonlinear */ + +#ifndef GL_NV_draw_buffers +#define GL_NV_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_NV 0x8824 +#define GL_DRAW_BUFFER0_NV 0x8825 +#define GL_DRAW_BUFFER1_NV 0x8826 +#define GL_DRAW_BUFFER2_NV 0x8827 +#define GL_DRAW_BUFFER3_NV 0x8828 +#define GL_DRAW_BUFFER4_NV 0x8829 +#define GL_DRAW_BUFFER5_NV 0x882A +#define GL_DRAW_BUFFER6_NV 0x882B +#define GL_DRAW_BUFFER7_NV 0x882C +#define GL_DRAW_BUFFER8_NV 0x882D +#define GL_DRAW_BUFFER9_NV 0x882E +#define GL_DRAW_BUFFER10_NV 0x882F +#define GL_DRAW_BUFFER11_NV 0x8830 +#define GL_DRAW_BUFFER12_NV 0x8831 +#define GL_DRAW_BUFFER13_NV 0x8832 +#define GL_DRAW_BUFFER14_NV 0x8833 +#define GL_DRAW_BUFFER15_NV 0x8834 +#define GL_COLOR_ATTACHMENT0_NV 0x8CE0 +#define GL_COLOR_ATTACHMENT1_NV 0x8CE1 +#define GL_COLOR_ATTACHMENT2_NV 0x8CE2 +#define GL_COLOR_ATTACHMENT3_NV 0x8CE3 +#define GL_COLOR_ATTACHMENT4_NV 0x8CE4 +#define GL_COLOR_ATTACHMENT5_NV 0x8CE5 +#define GL_COLOR_ATTACHMENT6_NV 0x8CE6 +#define GL_COLOR_ATTACHMENT7_NV 0x8CE7 +#define GL_COLOR_ATTACHMENT8_NV 0x8CE8 +#define GL_COLOR_ATTACHMENT9_NV 0x8CE9 +#define GL_COLOR_ATTACHMENT10_NV 0x8CEA +#define GL_COLOR_ATTACHMENT11_NV 0x8CEB +#define GL_COLOR_ATTACHMENT12_NV 0x8CEC +#define GL_COLOR_ATTACHMENT13_NV 0x8CED +#define GL_COLOR_ATTACHMENT14_NV 0x8CEE +#define GL_COLOR_ATTACHMENT15_NV 0x8CEF +typedef void (GL_APIENTRYP PFNGLDRAWBUFFERSNVPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawBuffersNV (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_NV_draw_buffers */ + +#ifndef GL_NV_draw_instanced +#define GL_NV_draw_instanced 1 +typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDNVPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDNVPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawArraysInstancedNV (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GL_APICALL void GL_APIENTRY glDrawElementsInstancedNV (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_NV_draw_instanced */ + +#ifndef GL_NV_draw_vulkan_image +#define GL_NV_draw_vulkan_image 1 +typedef void (GL_APIENTRY *GLVULKANPROCNV)(void); +typedef void (GL_APIENTRYP PFNGLDRAWVKIMAGENVPROC) (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +typedef GLVULKANPROCNV (GL_APIENTRYP PFNGLGETVKPROCADDRNVPROC) (const GLchar *name); +typedef void (GL_APIENTRYP PFNGLWAITVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); +typedef void (GL_APIENTRYP PFNGLSIGNALVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); +typedef void (GL_APIENTRYP PFNGLSIGNALVKFENCENVPROC) (GLuint64 vkFence); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawVkImageNV (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +GL_APICALL GLVULKANPROCNV GL_APIENTRY glGetVkProcAddrNV (const GLchar *name); +GL_APICALL void GL_APIENTRY glWaitVkSemaphoreNV (GLuint64 vkSemaphore); +GL_APICALL void GL_APIENTRY glSignalVkSemaphoreNV (GLuint64 vkSemaphore); +GL_APICALL void GL_APIENTRY glSignalVkFenceNV (GLuint64 vkFence); +#endif +#endif /* GL_NV_draw_vulkan_image */ + +#ifndef GL_NV_explicit_attrib_location +#define GL_NV_explicit_attrib_location 1 +#endif /* GL_NV_explicit_attrib_location */ + +#ifndef GL_NV_fbo_color_attachments +#define GL_NV_fbo_color_attachments 1 +#define GL_MAX_COLOR_ATTACHMENTS_NV 0x8CDF +#endif /* GL_NV_fbo_color_attachments */ + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +typedef void (GL_APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (GL_APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (GL_APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (GL_APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (GL_APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (GL_APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); +GL_APICALL void GL_APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); +GL_APICALL GLboolean GL_APIENTRY glIsFenceNV (GLuint fence); +GL_APICALL GLboolean GL_APIENTRY glTestFenceNV (GLuint fence); +GL_APICALL void GL_APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glFinishFenceNV (GLuint fence); +GL_APICALL void GL_APIENTRY glSetFenceNV (GLuint fence, GLenum condition); +#endif +#endif /* GL_NV_fence */ + +#ifndef GL_NV_fill_rectangle +#define GL_NV_fill_rectangle 1 +#define GL_FILL_RECTANGLE_NV 0x933C +#endif /* GL_NV_fill_rectangle */ + +#ifndef GL_NV_fragment_coverage_to_color +#define GL_NV_fragment_coverage_to_color 1 +#define GL_FRAGMENT_COVERAGE_TO_COLOR_NV 0x92DD +#define GL_FRAGMENT_COVERAGE_COLOR_NV 0x92DE +typedef void (GL_APIENTRYP PFNGLFRAGMENTCOVERAGECOLORNVPROC) (GLuint color); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFragmentCoverageColorNV (GLuint color); +#endif +#endif /* GL_NV_fragment_coverage_to_color */ + +#ifndef GL_NV_fragment_shader_barycentric +#define GL_NV_fragment_shader_barycentric 1 +#endif /* GL_NV_fragment_shader_barycentric */ + +#ifndef GL_NV_fragment_shader_interlock +#define GL_NV_fragment_shader_interlock 1 +#endif /* GL_NV_fragment_shader_interlock */ + +#ifndef GL_NV_framebuffer_blit +#define GL_NV_framebuffer_blit 1 +#define GL_READ_FRAMEBUFFER_NV 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_NV 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_NV 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_NV 0x8CAA +typedef void (GL_APIENTRYP PFNGLBLITFRAMEBUFFERNVPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBlitFramebufferNV (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* GL_NV_framebuffer_blit */ + +#ifndef GL_NV_framebuffer_mixed_samples +#define GL_NV_framebuffer_mixed_samples 1 +#define GL_COVERAGE_MODULATION_TABLE_NV 0x9331 +#define GL_COLOR_SAMPLES_NV 0x8E20 +#define GL_DEPTH_SAMPLES_NV 0x932D +#define GL_STENCIL_SAMPLES_NV 0x932E +#define GL_MIXED_DEPTH_SAMPLES_SUPPORTED_NV 0x932F +#define GL_MIXED_STENCIL_SAMPLES_SUPPORTED_NV 0x9330 +#define GL_COVERAGE_MODULATION_NV 0x9332 +#define GL_COVERAGE_MODULATION_TABLE_SIZE_NV 0x9333 +typedef void (GL_APIENTRYP PFNGLCOVERAGEMODULATIONTABLENVPROC) (GLsizei n, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLGETCOVERAGEMODULATIONTABLENVPROC) (GLsizei bufSize, GLfloat *v); +typedef void (GL_APIENTRYP PFNGLCOVERAGEMODULATIONNVPROC) (GLenum components); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCoverageModulationTableNV (GLsizei n, const GLfloat *v); +GL_APICALL void GL_APIENTRY glGetCoverageModulationTableNV (GLsizei bufSize, GLfloat *v); +GL_APICALL void GL_APIENTRY glCoverageModulationNV (GLenum components); +#endif +#endif /* GL_NV_framebuffer_mixed_samples */ + +#ifndef GL_NV_framebuffer_multisample +#define GL_NV_framebuffer_multisample 1 +#define GL_RENDERBUFFER_SAMPLES_NV 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_NV 0x8D56 +#define GL_MAX_SAMPLES_NV 0x8D57 +typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLENVPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glRenderbufferStorageMultisampleNV (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_NV_framebuffer_multisample */ + +#ifndef GL_NV_generate_mipmap_sRGB +#define GL_NV_generate_mipmap_sRGB 1 +#endif /* GL_NV_generate_mipmap_sRGB */ + +#ifndef GL_NV_geometry_shader_passthrough +#define GL_NV_geometry_shader_passthrough 1 +#endif /* GL_NV_geometry_shader_passthrough */ + +#ifndef GL_NV_gpu_shader5 +#define GL_NV_gpu_shader5 1 +typedef khronos_int64_t GLint64EXT; +typedef khronos_uint64_t GLuint64EXT; +#define GL_INT64_NV 0x140E +#define GL_UNSIGNED_INT64_NV 0x140F +#define GL_INT8_NV 0x8FE0 +#define GL_INT8_VEC2_NV 0x8FE1 +#define GL_INT8_VEC3_NV 0x8FE2 +#define GL_INT8_VEC4_NV 0x8FE3 +#define GL_INT16_NV 0x8FE4 +#define GL_INT16_VEC2_NV 0x8FE5 +#define GL_INT16_VEC3_NV 0x8FE6 +#define GL_INT16_VEC4_NV 0x8FE7 +#define GL_INT64_VEC2_NV 0x8FE9 +#define GL_INT64_VEC3_NV 0x8FEA +#define GL_INT64_VEC4_NV 0x8FEB +#define GL_UNSIGNED_INT8_NV 0x8FEC +#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED +#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE +#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF +#define GL_UNSIGNED_INT16_NV 0x8FF0 +#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 +#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 +#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 +#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 +#define GL_FLOAT16_NV 0x8FF8 +#define GL_FLOAT16_VEC2_NV 0x8FF9 +#define GL_FLOAT16_VEC3_NV 0x8FFA +#define GL_FLOAT16_VEC4_NV 0x8FFB +#define GL_PATCHES 0x000E +typedef void (GL_APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); +typedef void (GL_APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); +typedef void (GL_APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (GL_APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (GL_APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); +typedef void (GL_APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (GL_APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (GL_APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (GL_APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (GL_APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); +GL_APICALL void GL_APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); +GL_APICALL void GL_APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GL_APICALL void GL_APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GL_APICALL void GL_APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); +GL_APICALL void GL_APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); +GL_APICALL void GL_APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GL_APICALL void GL_APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GL_APICALL void GL_APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); +GL_APICALL void GL_APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); +GL_APICALL void GL_APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +GL_APICALL void GL_APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GL_APICALL void GL_APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GL_APICALL void GL_APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); +GL_APICALL void GL_APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +GL_APICALL void GL_APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GL_APICALL void GL_APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GL_APICALL void GL_APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GL_APICALL void GL_APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_NV_gpu_shader5 */ + +#ifndef GL_NV_image_formats +#define GL_NV_image_formats 1 +#endif /* GL_NV_image_formats */ + +#ifndef GL_NV_instanced_arrays +#define GL_NV_instanced_arrays 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_NV 0x88FE +typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISORNVPROC) (GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glVertexAttribDivisorNV (GLuint index, GLuint divisor); +#endif +#endif /* GL_NV_instanced_arrays */ + +#ifndef GL_NV_internalformat_sample_query +#define GL_NV_internalformat_sample_query 1 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_MULTISAMPLES_NV 0x9371 +#define GL_SUPERSAMPLE_SCALE_X_NV 0x9372 +#define GL_SUPERSAMPLE_SCALE_Y_NV 0x9373 +#define GL_CONFORMANT_NV 0x9374 +typedef void (GL_APIENTRYP PFNGLGETINTERNALFORMATSAMPLEIVNVPROC) (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetInternalformatSampleivNV (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); +#endif +#endif /* GL_NV_internalformat_sample_query */ + +#ifndef GL_NV_memory_attachment +#define GL_NV_memory_attachment 1 +#define GL_ATTACHED_MEMORY_OBJECT_NV 0x95A4 +#define GL_ATTACHED_MEMORY_OFFSET_NV 0x95A5 +#define GL_MEMORY_ATTACHABLE_ALIGNMENT_NV 0x95A6 +#define GL_MEMORY_ATTACHABLE_SIZE_NV 0x95A7 +#define GL_MEMORY_ATTACHABLE_NV 0x95A8 +#define GL_DETACHED_MEMORY_INCARNATION_NV 0x95A9 +#define GL_DETACHED_TEXTURES_NV 0x95AA +#define GL_DETACHED_BUFFERS_NV 0x95AB +#define GL_MAX_DETACHED_TEXTURES_NV 0x95AC +#define GL_MAX_DETACHED_BUFFERS_NV 0x95AD +typedef void (GL_APIENTRYP PFNGLGETMEMORYOBJECTDETACHEDRESOURCESUIVNVPROC) (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); +typedef void (GL_APIENTRYP PFNGLRESETMEMORYOBJECTPARAMETERNVPROC) (GLuint memory, GLenum pname); +typedef void (GL_APIENTRYP PFNGLTEXATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLBUFFERATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLTEXTUREATTACHMEMORYNVPROC) (GLuint texture, GLuint memory, GLuint64 offset); +typedef void (GL_APIENTRYP PFNGLNAMEDBUFFERATTACHMEMORYNVPROC) (GLuint buffer, GLuint memory, GLuint64 offset); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetMemoryObjectDetachedResourcesuivNV (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); +GL_APICALL void GL_APIENTRY glResetMemoryObjectParameterNV (GLuint memory, GLenum pname); +GL_APICALL void GL_APIENTRY glTexAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glBufferAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glTextureAttachMemoryNV (GLuint texture, GLuint memory, GLuint64 offset); +GL_APICALL void GL_APIENTRY glNamedBufferAttachMemoryNV (GLuint buffer, GLuint memory, GLuint64 offset); +#endif +#endif /* GL_NV_memory_attachment */ + +#ifndef GL_NV_memory_object_sparse +#define GL_NV_memory_object_sparse 1 +typedef void (GL_APIENTRYP PFNGLBUFFERPAGECOMMITMENTMEMNVPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +typedef void (GL_APIENTRYP PFNGLTEXPAGECOMMITMENTMEMNVPROC) (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +typedef void (GL_APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTMEMNVPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +typedef void (GL_APIENTRYP PFNGLTEXTUREPAGECOMMITMENTMEMNVPROC) (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBufferPageCommitmentMemNV (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +GL_APICALL void GL_APIENTRY glTexPageCommitmentMemNV (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +GL_APICALL void GL_APIENTRY glNamedBufferPageCommitmentMemNV (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +GL_APICALL void GL_APIENTRY glTexturePageCommitmentMemNV (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +#endif +#endif /* GL_NV_memory_object_sparse */ + +#ifndef GL_NV_mesh_shader +#define GL_NV_mesh_shader 1 +#define GL_MESH_SHADER_NV 0x9559 +#define GL_TASK_SHADER_NV 0x955A +#define GL_MAX_MESH_UNIFORM_BLOCKS_NV 0x8E60 +#define GL_MAX_MESH_TEXTURE_IMAGE_UNITS_NV 0x8E61 +#define GL_MAX_MESH_IMAGE_UNIFORMS_NV 0x8E62 +#define GL_MAX_MESH_UNIFORM_COMPONENTS_NV 0x8E63 +#define GL_MAX_MESH_ATOMIC_COUNTER_BUFFERS_NV 0x8E64 +#define GL_MAX_MESH_ATOMIC_COUNTERS_NV 0x8E65 +#define GL_MAX_MESH_SHADER_STORAGE_BLOCKS_NV 0x8E66 +#define GL_MAX_COMBINED_MESH_UNIFORM_COMPONENTS_NV 0x8E67 +#define GL_MAX_TASK_UNIFORM_BLOCKS_NV 0x8E68 +#define GL_MAX_TASK_TEXTURE_IMAGE_UNITS_NV 0x8E69 +#define GL_MAX_TASK_IMAGE_UNIFORMS_NV 0x8E6A +#define GL_MAX_TASK_UNIFORM_COMPONENTS_NV 0x8E6B +#define GL_MAX_TASK_ATOMIC_COUNTER_BUFFERS_NV 0x8E6C +#define GL_MAX_TASK_ATOMIC_COUNTERS_NV 0x8E6D +#define GL_MAX_TASK_SHADER_STORAGE_BLOCKS_NV 0x8E6E +#define GL_MAX_COMBINED_TASK_UNIFORM_COMPONENTS_NV 0x8E6F +#define GL_MAX_MESH_WORK_GROUP_INVOCATIONS_NV 0x95A2 +#define GL_MAX_TASK_WORK_GROUP_INVOCATIONS_NV 0x95A3 +#define GL_MAX_MESH_TOTAL_MEMORY_SIZE_NV 0x9536 +#define GL_MAX_TASK_TOTAL_MEMORY_SIZE_NV 0x9537 +#define GL_MAX_MESH_OUTPUT_VERTICES_NV 0x9538 +#define GL_MAX_MESH_OUTPUT_PRIMITIVES_NV 0x9539 +#define GL_MAX_TASK_OUTPUT_COUNT_NV 0x953A +#define GL_MAX_DRAW_MESH_TASKS_COUNT_NV 0x953D +#define GL_MAX_MESH_VIEWS_NV 0x9557 +#define GL_MESH_OUTPUT_PER_VERTEX_GRANULARITY_NV 0x92DF +#define GL_MESH_OUTPUT_PER_PRIMITIVE_GRANULARITY_NV 0x9543 +#define GL_MAX_MESH_WORK_GROUP_SIZE_NV 0x953B +#define GL_MAX_TASK_WORK_GROUP_SIZE_NV 0x953C +#define GL_MESH_WORK_GROUP_SIZE_NV 0x953E +#define GL_TASK_WORK_GROUP_SIZE_NV 0x953F +#define GL_MESH_VERTICES_OUT_NV 0x9579 +#define GL_MESH_PRIMITIVES_OUT_NV 0x957A +#define GL_MESH_OUTPUT_TYPE_NV 0x957B +#define GL_UNIFORM_BLOCK_REFERENCED_BY_MESH_SHADER_NV 0x959C +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TASK_SHADER_NV 0x959D +#define GL_REFERENCED_BY_MESH_SHADER_NV 0x95A0 +#define GL_REFERENCED_BY_TASK_SHADER_NV 0x95A1 +#define GL_MESH_SHADER_BIT_NV 0x00000040 +#define GL_TASK_SHADER_BIT_NV 0x00000080 +#define GL_MESH_SUBROUTINE_NV 0x957C +#define GL_TASK_SUBROUTINE_NV 0x957D +#define GL_MESH_SUBROUTINE_UNIFORM_NV 0x957E +#define GL_TASK_SUBROUTINE_UNIFORM_NV 0x957F +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_MESH_SHADER_NV 0x959E +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TASK_SHADER_NV 0x959F +typedef void (GL_APIENTRYP PFNGLDRAWMESHTASKSNVPROC) (GLuint first, GLuint count); +typedef void (GL_APIENTRYP PFNGLDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect); +typedef void (GL_APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect, GLsizei drawcount, GLsizei stride); +typedef void (GL_APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTNVPROC) (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glDrawMeshTasksNV (GLuint first, GLuint count); +GL_APICALL void GL_APIENTRY glDrawMeshTasksIndirectNV (GLintptr indirect); +GL_APICALL void GL_APIENTRY glMultiDrawMeshTasksIndirectNV (GLintptr indirect, GLsizei drawcount, GLsizei stride); +GL_APICALL void GL_APIENTRY glMultiDrawMeshTasksIndirectCountNV (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_NV_mesh_shader */ + +#ifndef GL_NV_non_square_matrices +#define GL_NV_non_square_matrices 1 +#define GL_FLOAT_MAT2x3_NV 0x8B65 +#define GL_FLOAT_MAT2x4_NV 0x8B66 +#define GL_FLOAT_MAT3x2_NV 0x8B67 +#define GL_FLOAT_MAT3x4_NV 0x8B68 +#define GL_FLOAT_MAT4x2_NV 0x8B69 +#define GL_FLOAT_MAT4x3_NV 0x8B6A +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX2X3FVNVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX3X2FVNVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX2X4FVNVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX4X2FVNVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX3X4FVNVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLUNIFORMMATRIX4X3FVNVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glUniformMatrix2x3fvNV (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix3x2fvNV (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix2x4fvNV (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix4x2fvNV (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix3x4fvNV (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GL_APICALL void GL_APIENTRY glUniformMatrix4x3fvNV (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif +#endif /* GL_NV_non_square_matrices */ + +#ifndef GL_NV_path_rendering +#define GL_NV_path_rendering 1 +typedef double GLdouble; +#define GL_PATH_FORMAT_SVG_NV 0x9070 +#define GL_PATH_FORMAT_PS_NV 0x9071 +#define GL_STANDARD_FONT_NAME_NV 0x9072 +#define GL_SYSTEM_FONT_NAME_NV 0x9073 +#define GL_FILE_NAME_NV 0x9074 +#define GL_PATH_STROKE_WIDTH_NV 0x9075 +#define GL_PATH_END_CAPS_NV 0x9076 +#define GL_PATH_INITIAL_END_CAP_NV 0x9077 +#define GL_PATH_TERMINAL_END_CAP_NV 0x9078 +#define GL_PATH_JOIN_STYLE_NV 0x9079 +#define GL_PATH_MITER_LIMIT_NV 0x907A +#define GL_PATH_DASH_CAPS_NV 0x907B +#define GL_PATH_INITIAL_DASH_CAP_NV 0x907C +#define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D +#define GL_PATH_DASH_OFFSET_NV 0x907E +#define GL_PATH_CLIENT_LENGTH_NV 0x907F +#define GL_PATH_FILL_MODE_NV 0x9080 +#define GL_PATH_FILL_MASK_NV 0x9081 +#define GL_PATH_FILL_COVER_MODE_NV 0x9082 +#define GL_PATH_STROKE_COVER_MODE_NV 0x9083 +#define GL_PATH_STROKE_MASK_NV 0x9084 +#define GL_COUNT_UP_NV 0x9088 +#define GL_COUNT_DOWN_NV 0x9089 +#define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A +#define GL_CONVEX_HULL_NV 0x908B +#define GL_BOUNDING_BOX_NV 0x908D +#define GL_TRANSLATE_X_NV 0x908E +#define GL_TRANSLATE_Y_NV 0x908F +#define GL_TRANSLATE_2D_NV 0x9090 +#define GL_TRANSLATE_3D_NV 0x9091 +#define GL_AFFINE_2D_NV 0x9092 +#define GL_AFFINE_3D_NV 0x9094 +#define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 +#define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 +#define GL_UTF8_NV 0x909A +#define GL_UTF16_NV 0x909B +#define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C +#define GL_PATH_COMMAND_COUNT_NV 0x909D +#define GL_PATH_COORD_COUNT_NV 0x909E +#define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F +#define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 +#define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 +#define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 +#define GL_SQUARE_NV 0x90A3 +#define GL_ROUND_NV 0x90A4 +#define GL_TRIANGULAR_NV 0x90A5 +#define GL_BEVEL_NV 0x90A6 +#define GL_MITER_REVERT_NV 0x90A7 +#define GL_MITER_TRUNCATE_NV 0x90A8 +#define GL_SKIP_MISSING_GLYPH_NV 0x90A9 +#define GL_USE_MISSING_GLYPH_NV 0x90AA +#define GL_PATH_ERROR_POSITION_NV 0x90AB +#define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD +#define GL_ADJACENT_PAIRS_NV 0x90AE +#define GL_FIRST_TO_REST_NV 0x90AF +#define GL_PATH_GEN_MODE_NV 0x90B0 +#define GL_PATH_GEN_COEFF_NV 0x90B1 +#define GL_PATH_GEN_COMPONENTS_NV 0x90B3 +#define GL_PATH_STENCIL_FUNC_NV 0x90B7 +#define GL_PATH_STENCIL_REF_NV 0x90B8 +#define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 +#define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD +#define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE +#define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF +#define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 +#define GL_MOVE_TO_RESETS_NV 0x90B5 +#define GL_MOVE_TO_CONTINUES_NV 0x90B6 +#define GL_CLOSE_PATH_NV 0x00 +#define GL_MOVE_TO_NV 0x02 +#define GL_RELATIVE_MOVE_TO_NV 0x03 +#define GL_LINE_TO_NV 0x04 +#define GL_RELATIVE_LINE_TO_NV 0x05 +#define GL_HORIZONTAL_LINE_TO_NV 0x06 +#define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 +#define GL_VERTICAL_LINE_TO_NV 0x08 +#define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 +#define GL_QUADRATIC_CURVE_TO_NV 0x0A +#define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B +#define GL_CUBIC_CURVE_TO_NV 0x0C +#define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D +#define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E +#define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F +#define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 +#define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 +#define GL_SMALL_CCW_ARC_TO_NV 0x12 +#define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 +#define GL_SMALL_CW_ARC_TO_NV 0x14 +#define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 +#define GL_LARGE_CCW_ARC_TO_NV 0x16 +#define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 +#define GL_LARGE_CW_ARC_TO_NV 0x18 +#define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 +#define GL_RESTART_PATH_NV 0xF0 +#define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 +#define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 +#define GL_RECT_NV 0xF6 +#define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 +#define GL_CIRCULAR_CW_ARC_TO_NV 0xFA +#define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC +#define GL_ARC_TO_NV 0xFE +#define GL_RELATIVE_ARC_TO_NV 0xFF +#define GL_BOLD_BIT_NV 0x01 +#define GL_ITALIC_BIT_NV 0x02 +#define GL_GLYPH_WIDTH_BIT_NV 0x01 +#define GL_GLYPH_HEIGHT_BIT_NV 0x02 +#define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 +#define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 +#define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 +#define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 +#define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 +#define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 +#define GL_GLYPH_HAS_KERNING_BIT_NV 0x100 +#define GL_FONT_X_MIN_BOUNDS_BIT_NV 0x00010000 +#define GL_FONT_Y_MIN_BOUNDS_BIT_NV 0x00020000 +#define GL_FONT_X_MAX_BOUNDS_BIT_NV 0x00040000 +#define GL_FONT_Y_MAX_BOUNDS_BIT_NV 0x00080000 +#define GL_FONT_UNITS_PER_EM_BIT_NV 0x00100000 +#define GL_FONT_ASCENDER_BIT_NV 0x00200000 +#define GL_FONT_DESCENDER_BIT_NV 0x00400000 +#define GL_FONT_HEIGHT_BIT_NV 0x00800000 +#define GL_FONT_MAX_ADVANCE_WIDTH_BIT_NV 0x01000000 +#define GL_FONT_MAX_ADVANCE_HEIGHT_BIT_NV 0x02000000 +#define GL_FONT_UNDERLINE_POSITION_BIT_NV 0x04000000 +#define GL_FONT_UNDERLINE_THICKNESS_BIT_NV 0x08000000 +#define GL_FONT_HAS_KERNING_BIT_NV 0x10000000 +#define GL_ROUNDED_RECT_NV 0xE8 +#define GL_RELATIVE_ROUNDED_RECT_NV 0xE9 +#define GL_ROUNDED_RECT2_NV 0xEA +#define GL_RELATIVE_ROUNDED_RECT2_NV 0xEB +#define GL_ROUNDED_RECT4_NV 0xEC +#define GL_RELATIVE_ROUNDED_RECT4_NV 0xED +#define GL_ROUNDED_RECT8_NV 0xEE +#define GL_RELATIVE_ROUNDED_RECT8_NV 0xEF +#define GL_RELATIVE_RECT_NV 0xF7 +#define GL_FONT_GLYPHS_AVAILABLE_NV 0x9368 +#define GL_FONT_TARGET_UNAVAILABLE_NV 0x9369 +#define GL_FONT_UNAVAILABLE_NV 0x936A +#define GL_FONT_UNINTELLIGIBLE_NV 0x936B +#define GL_CONIC_CURVE_TO_NV 0x1A +#define GL_RELATIVE_CONIC_CURVE_TO_NV 0x1B +#define GL_FONT_NUM_GLYPH_INDICES_BIT_NV 0x20000000 +#define GL_STANDARD_FONT_FORMAT_NV 0x936C +#define GL_PATH_PROJECTION_NV 0x1701 +#define GL_PATH_MODELVIEW_NV 0x1700 +#define GL_PATH_MODELVIEW_STACK_DEPTH_NV 0x0BA3 +#define GL_PATH_MODELVIEW_MATRIX_NV 0x0BA6 +#define GL_PATH_MAX_MODELVIEW_STACK_DEPTH_NV 0x0D36 +#define GL_PATH_TRANSPOSE_MODELVIEW_MATRIX_NV 0x84E3 +#define GL_PATH_PROJECTION_STACK_DEPTH_NV 0x0BA4 +#define GL_PATH_PROJECTION_MATRIX_NV 0x0BA7 +#define GL_PATH_MAX_PROJECTION_STACK_DEPTH_NV 0x0D38 +#define GL_PATH_TRANSPOSE_PROJECTION_MATRIX_NV 0x84E4 +#define GL_FRAGMENT_INPUT_NV 0x936D +typedef GLuint (GL_APIENTRYP PFNGLGENPATHSNVPROC) (GLsizei range); +typedef void (GL_APIENTRYP PFNGLDELETEPATHSNVPROC) (GLuint path, GLsizei range); +typedef GLboolean (GL_APIENTRYP PFNGLISPATHNVPROC) (GLuint path); +typedef void (GL_APIENTRYP PFNGLPATHCOMMANDSNVPROC) (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (GL_APIENTRYP PFNGLPATHCOORDSNVPROC) (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (GL_APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC) (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (GL_APIENTRYP PFNGLPATHSUBCOORDSNVPROC) (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (GL_APIENTRYP PFNGLPATHSTRINGNVPROC) (GLuint path, GLenum format, GLsizei length, const void *pathString); +typedef void (GL_APIENTRYP PFNGLPATHGLYPHSNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (GL_APIENTRYP PFNGLPATHGLYPHRANGENVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (GL_APIENTRYP PFNGLWEIGHTPATHSNVPROC) (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +typedef void (GL_APIENTRYP PFNGLCOPYPATHNVPROC) (GLuint resultPath, GLuint srcPath); +typedef void (GL_APIENTRYP PFNGLINTERPOLATEPATHSNVPROC) (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +typedef void (GL_APIENTRYP PFNGLTRANSFORMPATHNVPROC) (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +typedef void (GL_APIENTRYP PFNGLPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, const GLint *value); +typedef void (GL_APIENTRYP PFNGLPATHPARAMETERINVPROC) (GLuint path, GLenum pname, GLint value); +typedef void (GL_APIENTRYP PFNGLPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, const GLfloat *value); +typedef void (GL_APIENTRYP PFNGLPATHPARAMETERFNVPROC) (GLuint path, GLenum pname, GLfloat value); +typedef void (GL_APIENTRYP PFNGLPATHDASHARRAYNVPROC) (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +typedef void (GL_APIENTRYP PFNGLPATHSTENCILFUNCNVPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (GL_APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC) (GLfloat factor, GLfloat units); +typedef void (GL_APIENTRYP PFNGLSTENCILFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask); +typedef void (GL_APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask); +typedef void (GL_APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (GL_APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (GL_APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC) (GLenum func); +typedef void (GL_APIENTRYP PFNGLCOVERFILLPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (GL_APIENTRYP PFNGLCOVERSTROKEPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (GL_APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (GL_APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (GL_APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, GLint *value); +typedef void (GL_APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, GLfloat *value); +typedef void (GL_APIENTRYP PFNGLGETPATHCOMMANDSNVPROC) (GLuint path, GLubyte *commands); +typedef void (GL_APIENTRYP PFNGLGETPATHCOORDSNVPROC) (GLuint path, GLfloat *coords); +typedef void (GL_APIENTRYP PFNGLGETPATHDASHARRAYNVPROC) (GLuint path, GLfloat *dashArray); +typedef void (GL_APIENTRYP PFNGLGETPATHMETRICSNVPROC) (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +typedef void (GL_APIENTRYP PFNGLGETPATHMETRICRANGENVPROC) (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +typedef void (GL_APIENTRYP PFNGLGETPATHSPACINGNVPROC) (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +typedef GLboolean (GL_APIENTRYP PFNGLISPOINTINFILLPATHNVPROC) (GLuint path, GLuint mask, GLfloat x, GLfloat y); +typedef GLboolean (GL_APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC) (GLuint path, GLfloat x, GLfloat y); +typedef GLfloat (GL_APIENTRYP PFNGLGETPATHLENGTHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments); +typedef GLboolean (GL_APIENTRYP PFNGLPOINTALONGPATHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +typedef void (GL_APIENTRYP PFNGLMATRIXLOAD3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXLOAD3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXLOADTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULT3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULT3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULTTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); +typedef void (GL_APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask, GLenum coverMode); +typedef void (GL_APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (GL_APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef GLenum (GL_APIENTRYP PFNGLPATHGLYPHINDEXRANGENVPROC) (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); +typedef GLenum (GL_APIENTRYP PFNGLPATHGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef GLenum (GL_APIENTRYP PFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (GL_APIENTRYP PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC) (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (GL_APIENTRYP PFNGLGETPROGRAMRESOURCEFVNVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); +typedef void (GL_APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (GL_APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); +typedef void (GL_APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (GL_APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (GL_APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (GL_APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (GL_APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); +typedef void (GL_APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); +typedef void (GL_APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (GL_APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (GL_APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (GL_APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (GL_APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (GL_APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL GLuint GL_APIENTRY glGenPathsNV (GLsizei range); +GL_APICALL void GL_APIENTRY glDeletePathsNV (GLuint path, GLsizei range); +GL_APICALL GLboolean GL_APIENTRY glIsPathNV (GLuint path); +GL_APICALL void GL_APIENTRY glPathCommandsNV (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GL_APICALL void GL_APIENTRY glPathCoordsNV (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +GL_APICALL void GL_APIENTRY glPathSubCommandsNV (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GL_APICALL void GL_APIENTRY glPathSubCoordsNV (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +GL_APICALL void GL_APIENTRY glPathStringNV (GLuint path, GLenum format, GLsizei length, const void *pathString); +GL_APICALL void GL_APIENTRY glPathGlyphsNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GL_APICALL void GL_APIENTRY glPathGlyphRangeNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GL_APICALL void GL_APIENTRY glWeightPathsNV (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +GL_APICALL void GL_APIENTRY glCopyPathNV (GLuint resultPath, GLuint srcPath); +GL_APICALL void GL_APIENTRY glInterpolatePathsNV (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +GL_APICALL void GL_APIENTRY glTransformPathNV (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +GL_APICALL void GL_APIENTRY glPathParameterivNV (GLuint path, GLenum pname, const GLint *value); +GL_APICALL void GL_APIENTRY glPathParameteriNV (GLuint path, GLenum pname, GLint value); +GL_APICALL void GL_APIENTRY glPathParameterfvNV (GLuint path, GLenum pname, const GLfloat *value); +GL_APICALL void GL_APIENTRY glPathParameterfNV (GLuint path, GLenum pname, GLfloat value); +GL_APICALL void GL_APIENTRY glPathDashArrayNV (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +GL_APICALL void GL_APIENTRY glPathStencilFuncNV (GLenum func, GLint ref, GLuint mask); +GL_APICALL void GL_APIENTRY glPathStencilDepthOffsetNV (GLfloat factor, GLfloat units); +GL_APICALL void GL_APIENTRY glStencilFillPathNV (GLuint path, GLenum fillMode, GLuint mask); +GL_APICALL void GL_APIENTRY glStencilStrokePathNV (GLuint path, GLint reference, GLuint mask); +GL_APICALL void GL_APIENTRY glStencilFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GL_APICALL void GL_APIENTRY glStencilStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GL_APICALL void GL_APIENTRY glPathCoverDepthFuncNV (GLenum func); +GL_APICALL void GL_APIENTRY glCoverFillPathNV (GLuint path, GLenum coverMode); +GL_APICALL void GL_APIENTRY glCoverStrokePathNV (GLuint path, GLenum coverMode); +GL_APICALL void GL_APIENTRY glCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GL_APICALL void GL_APIENTRY glCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GL_APICALL void GL_APIENTRY glGetPathParameterivNV (GLuint path, GLenum pname, GLint *value); +GL_APICALL void GL_APIENTRY glGetPathParameterfvNV (GLuint path, GLenum pname, GLfloat *value); +GL_APICALL void GL_APIENTRY glGetPathCommandsNV (GLuint path, GLubyte *commands); +GL_APICALL void GL_APIENTRY glGetPathCoordsNV (GLuint path, GLfloat *coords); +GL_APICALL void GL_APIENTRY glGetPathDashArrayNV (GLuint path, GLfloat *dashArray); +GL_APICALL void GL_APIENTRY glGetPathMetricsNV (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +GL_APICALL void GL_APIENTRY glGetPathMetricRangeNV (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +GL_APICALL void GL_APIENTRY glGetPathSpacingNV (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +GL_APICALL GLboolean GL_APIENTRY glIsPointInFillPathNV (GLuint path, GLuint mask, GLfloat x, GLfloat y); +GL_APICALL GLboolean GL_APIENTRY glIsPointInStrokePathNV (GLuint path, GLfloat x, GLfloat y); +GL_APICALL GLfloat GL_APIENTRY glGetPathLengthNV (GLuint path, GLsizei startSegment, GLsizei numSegments); +GL_APICALL GLboolean GL_APIENTRY glPointAlongPathNV (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +GL_APICALL void GL_APIENTRY glMatrixLoad3x2fNV (GLenum matrixMode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixLoad3x3fNV (GLenum matrixMode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixLoadTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixMult3x2fNV (GLenum matrixMode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixMult3x3fNV (GLenum matrixMode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixMultTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glStencilThenCoverFillPathNV (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); +GL_APICALL void GL_APIENTRY glStencilThenCoverStrokePathNV (GLuint path, GLint reference, GLuint mask, GLenum coverMode); +GL_APICALL void GL_APIENTRY glStencilThenCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GL_APICALL void GL_APIENTRY glStencilThenCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GL_APICALL GLenum GL_APIENTRY glPathGlyphIndexRangeNV (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); +GL_APICALL GLenum GL_APIENTRY glPathGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GL_APICALL GLenum GL_APIENTRY glPathMemoryGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GL_APICALL void GL_APIENTRY glProgramPathFragmentInputGenNV (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); +GL_APICALL void GL_APIENTRY glGetProgramResourcefvNV (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); +GL_APICALL void GL_APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GL_APICALL void GL_APIENTRY glMatrixLoadIdentityEXT (GLenum mode); +GL_APICALL void GL_APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); +GL_APICALL void GL_APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); +GL_APICALL void GL_APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); +GL_APICALL void GL_APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); +GL_APICALL void GL_APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); +GL_APICALL void GL_APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GL_APICALL void GL_APIENTRY glMatrixPopEXT (GLenum mode); +GL_APICALL void GL_APIENTRY glMatrixPushEXT (GLenum mode); +GL_APICALL void GL_APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GL_APICALL void GL_APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GL_APICALL void GL_APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GL_APICALL void GL_APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GL_APICALL void GL_APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GL_APICALL void GL_APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +#endif +#endif /* GL_NV_path_rendering */ + +#ifndef GL_NV_path_rendering_shared_edge +#define GL_NV_path_rendering_shared_edge 1 +#define GL_SHARED_EDGE_NV 0xC0 +#endif /* GL_NV_path_rendering_shared_edge */ + +#ifndef GL_NV_pixel_buffer_object +#define GL_NV_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_NV 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_NV 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_NV 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_NV 0x88EF +#endif /* GL_NV_pixel_buffer_object */ + +#ifndef GL_NV_polygon_mode +#define GL_NV_polygon_mode 1 +#define GL_POLYGON_MODE_NV 0x0B40 +#define GL_POLYGON_OFFSET_POINT_NV 0x2A01 +#define GL_POLYGON_OFFSET_LINE_NV 0x2A02 +#define GL_POINT_NV 0x1B00 +#define GL_LINE_NV 0x1B01 +#define GL_FILL_NV 0x1B02 +typedef void (GL_APIENTRYP PFNGLPOLYGONMODENVPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glPolygonModeNV (GLenum face, GLenum mode); +#endif +#endif /* GL_NV_polygon_mode */ + +#ifndef GL_NV_primitive_shading_rate +#define GL_NV_primitive_shading_rate 1 +#define GL_SHADING_RATE_IMAGE_PER_PRIMITIVE_NV 0x95B1 +#define GL_SHADING_RATE_IMAGE_PALETTE_COUNT_NV 0x95B2 +#endif /* GL_NV_primitive_shading_rate */ + +#ifndef GL_NV_read_buffer +#define GL_NV_read_buffer 1 +#define GL_READ_BUFFER_NV 0x0C02 +typedef void (GL_APIENTRYP PFNGLREADBUFFERNVPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glReadBufferNV (GLenum mode); +#endif +#endif /* GL_NV_read_buffer */ + +#ifndef GL_NV_read_buffer_front +#define GL_NV_read_buffer_front 1 +#endif /* GL_NV_read_buffer_front */ + +#ifndef GL_NV_read_depth +#define GL_NV_read_depth 1 +#endif /* GL_NV_read_depth */ + +#ifndef GL_NV_read_depth_stencil +#define GL_NV_read_depth_stencil 1 +#endif /* GL_NV_read_depth_stencil */ + +#ifndef GL_NV_read_stencil +#define GL_NV_read_stencil 1 +#endif /* GL_NV_read_stencil */ + +#ifndef GL_NV_representative_fragment_test +#define GL_NV_representative_fragment_test 1 +#define GL_REPRESENTATIVE_FRAGMENT_TEST_NV 0x937F +#endif /* GL_NV_representative_fragment_test */ + +#ifndef GL_NV_sRGB_formats +#define GL_NV_sRGB_formats 1 +#define GL_SLUMINANCE_NV 0x8C46 +#define GL_SLUMINANCE_ALPHA_NV 0x8C44 +#define GL_SRGB8_NV 0x8C41 +#define GL_SLUMINANCE8_NV 0x8C47 +#define GL_SLUMINANCE8_ALPHA8_NV 0x8C45 +#define GL_COMPRESSED_SRGB_S3TC_DXT1_NV 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_NV 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_NV 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_NV 0x8C4F +#define GL_ETC1_SRGB8_NV 0x88EE +#endif /* GL_NV_sRGB_formats */ + +#ifndef GL_NV_sample_locations +#define GL_NV_sample_locations 1 +#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_NV 0x933D +#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_NV 0x933E +#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_NV 0x933F +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_NV 0x9340 +#define GL_SAMPLE_LOCATION_NV 0x8E50 +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9341 +#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_NV 0x9342 +#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_NV 0x9343 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLRESOLVEDEPTHVALUESNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferSampleLocationsfvNV (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +GL_APICALL void GL_APIENTRY glNamedFramebufferSampleLocationsfvNV (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GL_APICALL void GL_APIENTRY glResolveDepthValuesNV (void); +#endif +#endif /* GL_NV_sample_locations */ + +#ifndef GL_NV_sample_mask_override_coverage +#define GL_NV_sample_mask_override_coverage 1 +#endif /* GL_NV_sample_mask_override_coverage */ + +#ifndef GL_NV_scissor_exclusive +#define GL_NV_scissor_exclusive 1 +#define GL_SCISSOR_TEST_EXCLUSIVE_NV 0x9555 +#define GL_SCISSOR_BOX_EXCLUSIVE_NV 0x9556 +typedef void (GL_APIENTRYP PFNGLSCISSOREXCLUSIVENVPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLSCISSOREXCLUSIVEARRAYVNVPROC) (GLuint first, GLsizei count, const GLint *v); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glScissorExclusiveNV (GLint x, GLint y, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glScissorExclusiveArrayvNV (GLuint first, GLsizei count, const GLint *v); +#endif +#endif /* GL_NV_scissor_exclusive */ + +#ifndef GL_NV_shader_atomic_fp16_vector +#define GL_NV_shader_atomic_fp16_vector 1 +#endif /* GL_NV_shader_atomic_fp16_vector */ + +#ifndef GL_NV_shader_noperspective_interpolation +#define GL_NV_shader_noperspective_interpolation 1 +#endif /* GL_NV_shader_noperspective_interpolation */ + +#ifndef GL_NV_shader_subgroup_partitioned +#define GL_NV_shader_subgroup_partitioned 1 +#define GL_SUBGROUP_FEATURE_PARTITIONED_BIT_NV 0x00000100 +#endif /* GL_NV_shader_subgroup_partitioned */ + +#ifndef GL_NV_shader_texture_footprint +#define GL_NV_shader_texture_footprint 1 +#endif /* GL_NV_shader_texture_footprint */ + +#ifndef GL_NV_shading_rate_image +#define GL_NV_shading_rate_image 1 +#define GL_SHADING_RATE_IMAGE_NV 0x9563 +#define GL_SHADING_RATE_NO_INVOCATIONS_NV 0x9564 +#define GL_SHADING_RATE_1_INVOCATION_PER_PIXEL_NV 0x9565 +#define GL_SHADING_RATE_1_INVOCATION_PER_1X2_PIXELS_NV 0x9566 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X1_PIXELS_NV 0x9567 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X2_PIXELS_NV 0x9568 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X4_PIXELS_NV 0x9569 +#define GL_SHADING_RATE_1_INVOCATION_PER_4X2_PIXELS_NV 0x956A +#define GL_SHADING_RATE_1_INVOCATION_PER_4X4_PIXELS_NV 0x956B +#define GL_SHADING_RATE_2_INVOCATIONS_PER_PIXEL_NV 0x956C +#define GL_SHADING_RATE_4_INVOCATIONS_PER_PIXEL_NV 0x956D +#define GL_SHADING_RATE_8_INVOCATIONS_PER_PIXEL_NV 0x956E +#define GL_SHADING_RATE_16_INVOCATIONS_PER_PIXEL_NV 0x956F +#define GL_SHADING_RATE_IMAGE_BINDING_NV 0x955B +#define GL_SHADING_RATE_IMAGE_TEXEL_WIDTH_NV 0x955C +#define GL_SHADING_RATE_IMAGE_TEXEL_HEIGHT_NV 0x955D +#define GL_SHADING_RATE_IMAGE_PALETTE_SIZE_NV 0x955E +#define GL_MAX_COARSE_FRAGMENT_SAMPLES_NV 0x955F +#define GL_SHADING_RATE_SAMPLE_ORDER_DEFAULT_NV 0x95AE +#define GL_SHADING_RATE_SAMPLE_ORDER_PIXEL_MAJOR_NV 0x95AF +#define GL_SHADING_RATE_SAMPLE_ORDER_SAMPLE_MAJOR_NV 0x95B0 +typedef void (GL_APIENTRYP PFNGLBINDSHADINGRATEIMAGENVPROC) (GLuint texture); +typedef void (GL_APIENTRYP PFNGLGETSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint entry, GLenum *rate); +typedef void (GL_APIENTRYP PFNGLGETSHADINGRATESAMPLELOCATIONIVNVPROC) (GLenum rate, GLuint samples, GLuint index, GLint *location); +typedef void (GL_APIENTRYP PFNGLSHADINGRATEIMAGEBARRIERNVPROC) (GLboolean synchronize); +typedef void (GL_APIENTRYP PFNGLSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); +typedef void (GL_APIENTRYP PFNGLSHADINGRATESAMPLEORDERNVPROC) (GLenum order); +typedef void (GL_APIENTRYP PFNGLSHADINGRATESAMPLEORDERCUSTOMNVPROC) (GLenum rate, GLuint samples, const GLint *locations); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glBindShadingRateImageNV (GLuint texture); +GL_APICALL void GL_APIENTRY glGetShadingRateImagePaletteNV (GLuint viewport, GLuint entry, GLenum *rate); +GL_APICALL void GL_APIENTRY glGetShadingRateSampleLocationivNV (GLenum rate, GLuint samples, GLuint index, GLint *location); +GL_APICALL void GL_APIENTRY glShadingRateImageBarrierNV (GLboolean synchronize); +GL_APICALL void GL_APIENTRY glShadingRateImagePaletteNV (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); +GL_APICALL void GL_APIENTRY glShadingRateSampleOrderNV (GLenum order); +GL_APICALL void GL_APIENTRY glShadingRateSampleOrderCustomNV (GLenum rate, GLuint samples, const GLint *locations); +#endif +#endif /* GL_NV_shading_rate_image */ + +#ifndef GL_NV_shadow_samplers_array +#define GL_NV_shadow_samplers_array 1 +#define GL_SAMPLER_2D_ARRAY_SHADOW_NV 0x8DC4 +#endif /* GL_NV_shadow_samplers_array */ + +#ifndef GL_NV_shadow_samplers_cube +#define GL_NV_shadow_samplers_cube 1 +#define GL_SAMPLER_CUBE_SHADOW_NV 0x8DC5 +#endif /* GL_NV_shadow_samplers_cube */ + +#ifndef GL_NV_stereo_view_rendering +#define GL_NV_stereo_view_rendering 1 +#endif /* GL_NV_stereo_view_rendering */ + +#ifndef GL_NV_texture_border_clamp +#define GL_NV_texture_border_clamp 1 +#define GL_TEXTURE_BORDER_COLOR_NV 0x1004 +#define GL_CLAMP_TO_BORDER_NV 0x812D +#endif /* GL_NV_texture_border_clamp */ + +#ifndef GL_NV_texture_compression_s3tc_update +#define GL_NV_texture_compression_s3tc_update 1 +#endif /* GL_NV_texture_compression_s3tc_update */ + +#ifndef GL_NV_texture_npot_2D_mipmap +#define GL_NV_texture_npot_2D_mipmap 1 +#endif /* GL_NV_texture_npot_2D_mipmap */ + +#ifndef GL_NV_timeline_semaphore +#define GL_NV_timeline_semaphore 1 +#define GL_TIMELINE_SEMAPHORE_VALUE_NV 0x9595 +#define GL_SEMAPHORE_TYPE_NV 0x95B3 +#define GL_SEMAPHORE_TYPE_BINARY_NV 0x95B4 +#define GL_SEMAPHORE_TYPE_TIMELINE_NV 0x95B5 +#define GL_MAX_TIMELINE_SEMAPHORE_VALUE_DIFFERENCE_NV 0x95B6 +typedef void (GL_APIENTRYP PFNGLCREATESEMAPHORESNVPROC) (GLsizei n, GLuint *semaphores); +typedef void (GL_APIENTRYP PFNGLSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, const GLint *params); +typedef void (GL_APIENTRYP PFNGLGETSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glCreateSemaphoresNV (GLsizei n, GLuint *semaphores); +GL_APICALL void GL_APIENTRY glSemaphoreParameterivNV (GLuint semaphore, GLenum pname, const GLint *params); +GL_APICALL void GL_APIENTRY glGetSemaphoreParameterivNV (GLuint semaphore, GLenum pname, GLint *params); +#endif +#endif /* GL_NV_timeline_semaphore */ + +#ifndef GL_NV_viewport_array +#define GL_NV_viewport_array 1 +#define GL_MAX_VIEWPORTS_NV 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS_NV 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE_NV 0x825D +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX_NV 0x825F +typedef void (GL_APIENTRYP PFNGLVIEWPORTARRAYVNVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLVIEWPORTINDEXEDFNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (GL_APIENTRYP PFNGLVIEWPORTINDEXEDFVNVPROC) (GLuint index, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLSCISSORARRAYVNVPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (GL_APIENTRYP PFNGLSCISSORINDEXEDNVPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP PFNGLSCISSORINDEXEDVNVPROC) (GLuint index, const GLint *v); +typedef void (GL_APIENTRYP PFNGLDEPTHRANGEARRAYFVNVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (GL_APIENTRYP PFNGLDEPTHRANGEINDEXEDFNVPROC) (GLuint index, GLfloat n, GLfloat f); +typedef void (GL_APIENTRYP PFNGLGETFLOATI_VNVPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (GL_APIENTRYP PFNGLENABLEINVPROC) (GLenum target, GLuint index); +typedef void (GL_APIENTRYP PFNGLDISABLEINVPROC) (GLenum target, GLuint index); +typedef GLboolean (GL_APIENTRYP PFNGLISENABLEDINVPROC) (GLenum target, GLuint index); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glViewportArrayvNV (GLuint first, GLsizei count, const GLfloat *v); +GL_APICALL void GL_APIENTRY glViewportIndexedfNV (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GL_APICALL void GL_APIENTRY glViewportIndexedfvNV (GLuint index, const GLfloat *v); +GL_APICALL void GL_APIENTRY glScissorArrayvNV (GLuint first, GLsizei count, const GLint *v); +GL_APICALL void GL_APIENTRY glScissorIndexedNV (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GL_APICALL void GL_APIENTRY glScissorIndexedvNV (GLuint index, const GLint *v); +GL_APICALL void GL_APIENTRY glDepthRangeArrayfvNV (GLuint first, GLsizei count, const GLfloat *v); +GL_APICALL void GL_APIENTRY glDepthRangeIndexedfNV (GLuint index, GLfloat n, GLfloat f); +GL_APICALL void GL_APIENTRY glGetFloati_vNV (GLenum target, GLuint index, GLfloat *data); +GL_APICALL void GL_APIENTRY glEnableiNV (GLenum target, GLuint index); +GL_APICALL void GL_APIENTRY glDisableiNV (GLenum target, GLuint index); +GL_APICALL GLboolean GL_APIENTRY glIsEnablediNV (GLenum target, GLuint index); +#endif +#endif /* GL_NV_viewport_array */ + +#ifndef GL_NV_viewport_array2 +#define GL_NV_viewport_array2 1 +#endif /* GL_NV_viewport_array2 */ + +#ifndef GL_NV_viewport_swizzle +#define GL_NV_viewport_swizzle 1 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV 0x9350 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_X_NV 0x9351 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_Y_NV 0x9352 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Y_NV 0x9353 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_Z_NV 0x9354 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Z_NV 0x9355 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_W_NV 0x9356 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_W_NV 0x9357 +#define GL_VIEWPORT_SWIZZLE_X_NV 0x9358 +#define GL_VIEWPORT_SWIZZLE_Y_NV 0x9359 +#define GL_VIEWPORT_SWIZZLE_Z_NV 0x935A +#define GL_VIEWPORT_SWIZZLE_W_NV 0x935B +typedef void (GL_APIENTRYP PFNGLVIEWPORTSWIZZLENVPROC) (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glViewportSwizzleNV (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); +#endif +#endif /* GL_NV_viewport_swizzle */ + +#ifndef GL_OVR_multiview +#define GL_OVR_multiview 1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR 0x9630 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR 0x9632 +#define GL_MAX_VIEWS_OVR 0x9631 +#define GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR 0x9633 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferTextureMultiviewOVR (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +#endif +#endif /* GL_OVR_multiview */ + +#ifndef GL_OVR_multiview2 +#define GL_OVR_multiview2 1 +#endif /* GL_OVR_multiview2 */ + +#ifndef GL_OVR_multiview_multisampled_render_to_texture +#define GL_OVR_multiview_multisampled_render_to_texture 1 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLsizei samples, GLint baseViewIndex, GLsizei numViews); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferTextureMultisampleMultiviewOVR (GLenum target, GLenum attachment, GLuint texture, GLint level, GLsizei samples, GLint baseViewIndex, GLsizei numViews); +#endif +#endif /* GL_OVR_multiview_multisampled_render_to_texture */ + +#ifndef GL_QCOM_YUV_texture_gather +#define GL_QCOM_YUV_texture_gather 1 +#endif /* GL_QCOM_YUV_texture_gather */ + +#ifndef GL_QCOM_alpha_test +#define GL_QCOM_alpha_test 1 +#define GL_ALPHA_TEST_QCOM 0x0BC0 +#define GL_ALPHA_TEST_FUNC_QCOM 0x0BC1 +#define GL_ALPHA_TEST_REF_QCOM 0x0BC2 +typedef void (GL_APIENTRYP PFNGLALPHAFUNCQCOMPROC) (GLenum func, GLclampf ref); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glAlphaFuncQCOM (GLenum func, GLclampf ref); +#endif +#endif /* GL_QCOM_alpha_test */ + +#ifndef GL_QCOM_binning_control +#define GL_QCOM_binning_control 1 +#define GL_BINNING_CONTROL_HINT_QCOM 0x8FB0 +#define GL_CPU_OPTIMIZED_QCOM 0x8FB1 +#define GL_GPU_OPTIMIZED_QCOM 0x8FB2 +#define GL_RENDER_DIRECT_TO_FRAMEBUFFER_QCOM 0x8FB3 +#endif /* GL_QCOM_binning_control */ + +#ifndef GL_QCOM_driver_control +#define GL_QCOM_driver_control 1 +typedef void (GL_APIENTRYP PFNGLGETDRIVERCONTROLSQCOMPROC) (GLint *num, GLsizei size, GLuint *driverControls); +typedef void (GL_APIENTRYP PFNGLGETDRIVERCONTROLSTRINGQCOMPROC) (GLuint driverControl, GLsizei bufSize, GLsizei *length, GLchar *driverControlString); +typedef void (GL_APIENTRYP PFNGLENABLEDRIVERCONTROLQCOMPROC) (GLuint driverControl); +typedef void (GL_APIENTRYP PFNGLDISABLEDRIVERCONTROLQCOMPROC) (GLuint driverControl); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glGetDriverControlsQCOM (GLint *num, GLsizei size, GLuint *driverControls); +GL_APICALL void GL_APIENTRY glGetDriverControlStringQCOM (GLuint driverControl, GLsizei bufSize, GLsizei *length, GLchar *driverControlString); +GL_APICALL void GL_APIENTRY glEnableDriverControlQCOM (GLuint driverControl); +GL_APICALL void GL_APIENTRY glDisableDriverControlQCOM (GLuint driverControl); +#endif +#endif /* GL_QCOM_driver_control */ + +#ifndef GL_QCOM_extended_get +#define GL_QCOM_extended_get 1 +#define GL_TEXTURE_WIDTH_QCOM 0x8BD2 +#define GL_TEXTURE_HEIGHT_QCOM 0x8BD3 +#define GL_TEXTURE_DEPTH_QCOM 0x8BD4 +#define GL_TEXTURE_INTERNAL_FORMAT_QCOM 0x8BD5 +#define GL_TEXTURE_FORMAT_QCOM 0x8BD6 +#define GL_TEXTURE_TYPE_QCOM 0x8BD7 +#define GL_TEXTURE_IMAGE_VALID_QCOM 0x8BD8 +#define GL_TEXTURE_NUM_LEVELS_QCOM 0x8BD9 +#define GL_TEXTURE_TARGET_QCOM 0x8BDA +#define GL_TEXTURE_OBJECT_VALID_QCOM 0x8BDB +#define GL_STATE_RESTORE 0x8BDC +typedef void (GL_APIENTRYP PFNGLEXTGETTEXTURESQCOMPROC) (GLuint *textures, GLint maxTextures, GLint *numTextures); +typedef void (GL_APIENTRYP PFNGLEXTGETBUFFERSQCOMPROC) (GLuint *buffers, GLint maxBuffers, GLint *numBuffers); +typedef void (GL_APIENTRYP PFNGLEXTGETRENDERBUFFERSQCOMPROC) (GLuint *renderbuffers, GLint maxRenderbuffers, GLint *numRenderbuffers); +typedef void (GL_APIENTRYP PFNGLEXTGETFRAMEBUFFERSQCOMPROC) (GLuint *framebuffers, GLint maxFramebuffers, GLint *numFramebuffers); +typedef void (GL_APIENTRYP PFNGLEXTGETTEXLEVELPARAMETERIVQCOMPROC) (GLuint texture, GLenum face, GLint level, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP PFNGLEXTTEXOBJECTSTATEOVERRIDEIQCOMPROC) (GLenum target, GLenum pname, GLint param); +typedef void (GL_APIENTRYP PFNGLEXTGETTEXSUBIMAGEQCOMPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, void *texels); +typedef void (GL_APIENTRYP PFNGLEXTGETBUFFERPOINTERVQCOMPROC) (GLenum target, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glExtGetTexturesQCOM (GLuint *textures, GLint maxTextures, GLint *numTextures); +GL_APICALL void GL_APIENTRY glExtGetBuffersQCOM (GLuint *buffers, GLint maxBuffers, GLint *numBuffers); +GL_APICALL void GL_APIENTRY glExtGetRenderbuffersQCOM (GLuint *renderbuffers, GLint maxRenderbuffers, GLint *numRenderbuffers); +GL_APICALL void GL_APIENTRY glExtGetFramebuffersQCOM (GLuint *framebuffers, GLint maxFramebuffers, GLint *numFramebuffers); +GL_APICALL void GL_APIENTRY glExtGetTexLevelParameterivQCOM (GLuint texture, GLenum face, GLint level, GLenum pname, GLint *params); +GL_APICALL void GL_APIENTRY glExtTexObjectStateOverrideiQCOM (GLenum target, GLenum pname, GLint param); +GL_APICALL void GL_APIENTRY glExtGetTexSubImageQCOM (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, void *texels); +GL_APICALL void GL_APIENTRY glExtGetBufferPointervQCOM (GLenum target, void **params); +#endif +#endif /* GL_QCOM_extended_get */ + +#ifndef GL_QCOM_extended_get2 +#define GL_QCOM_extended_get2 1 +typedef void (GL_APIENTRYP PFNGLEXTGETSHADERSQCOMPROC) (GLuint *shaders, GLint maxShaders, GLint *numShaders); +typedef void (GL_APIENTRYP PFNGLEXTGETPROGRAMSQCOMPROC) (GLuint *programs, GLint maxPrograms, GLint *numPrograms); +typedef GLboolean (GL_APIENTRYP PFNGLEXTISPROGRAMBINARYQCOMPROC) (GLuint program); +typedef void (GL_APIENTRYP PFNGLEXTGETPROGRAMBINARYSOURCEQCOMPROC) (GLuint program, GLenum shadertype, GLchar *source, GLint *length); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glExtGetShadersQCOM (GLuint *shaders, GLint maxShaders, GLint *numShaders); +GL_APICALL void GL_APIENTRY glExtGetProgramsQCOM (GLuint *programs, GLint maxPrograms, GLint *numPrograms); +GL_APICALL GLboolean GL_APIENTRY glExtIsProgramBinaryQCOM (GLuint program); +GL_APICALL void GL_APIENTRY glExtGetProgramBinarySourceQCOM (GLuint program, GLenum shadertype, GLchar *source, GLint *length); +#endif +#endif /* GL_QCOM_extended_get2 */ + +#ifndef GL_QCOM_frame_extrapolation +#define GL_QCOM_frame_extrapolation 1 +typedef void (GL_APIENTRYP PFNGLEXTRAPOLATETEX2DQCOMPROC) (GLuint src1, GLuint src2, GLuint output, GLfloat scaleFactor); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glExtrapolateTex2DQCOM (GLuint src1, GLuint src2, GLuint output, GLfloat scaleFactor); +#endif +#endif /* GL_QCOM_frame_extrapolation */ + +#ifndef GL_QCOM_framebuffer_foveated +#define GL_QCOM_framebuffer_foveated 1 +#define GL_FOVEATION_ENABLE_BIT_QCOM 0x00000001 +#define GL_FOVEATION_SCALED_BIN_METHOD_BIT_QCOM 0x00000002 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERFOVEATIONCONFIGQCOMPROC) (GLuint framebuffer, GLuint numLayers, GLuint focalPointsPerLayer, GLuint requestedFeatures, GLuint *providedFeatures); +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERFOVEATIONPARAMETERSQCOMPROC) (GLuint framebuffer, GLuint layer, GLuint focalPoint, GLfloat focalX, GLfloat focalY, GLfloat gainX, GLfloat gainY, GLfloat foveaArea); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferFoveationConfigQCOM (GLuint framebuffer, GLuint numLayers, GLuint focalPointsPerLayer, GLuint requestedFeatures, GLuint *providedFeatures); +GL_APICALL void GL_APIENTRY glFramebufferFoveationParametersQCOM (GLuint framebuffer, GLuint layer, GLuint focalPoint, GLfloat focalX, GLfloat focalY, GLfloat gainX, GLfloat gainY, GLfloat foveaArea); +#endif +#endif /* GL_QCOM_framebuffer_foveated */ + +#ifndef GL_QCOM_motion_estimation +#define GL_QCOM_motion_estimation 1 +#define GL_MOTION_ESTIMATION_SEARCH_BLOCK_X_QCOM 0x8C90 +#define GL_MOTION_ESTIMATION_SEARCH_BLOCK_Y_QCOM 0x8C91 +typedef void (GL_APIENTRYP PFNGLTEXESTIMATEMOTIONQCOMPROC) (GLuint ref, GLuint target, GLuint output); +typedef void (GL_APIENTRYP PFNGLTEXESTIMATEMOTIONREGIONSQCOMPROC) (GLuint ref, GLuint target, GLuint output, GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTexEstimateMotionQCOM (GLuint ref, GLuint target, GLuint output); +GL_APICALL void GL_APIENTRY glTexEstimateMotionRegionsQCOM (GLuint ref, GLuint target, GLuint output, GLuint mask); +#endif +#endif /* GL_QCOM_motion_estimation */ + +#ifndef GL_QCOM_perfmon_global_mode +#define GL_QCOM_perfmon_global_mode 1 +#define GL_PERFMON_GLOBAL_MODE_QCOM 0x8FA0 +#endif /* GL_QCOM_perfmon_global_mode */ + +#ifndef GL_QCOM_render_shared_exponent +#define GL_QCOM_render_shared_exponent 1 +#endif /* GL_QCOM_render_shared_exponent */ + +#ifndef GL_QCOM_shader_framebuffer_fetch_noncoherent +#define GL_QCOM_shader_framebuffer_fetch_noncoherent 1 +#define GL_FRAMEBUFFER_FETCH_NONCOHERENT_QCOM 0x96A2 +typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERFETCHBARRIERQCOMPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glFramebufferFetchBarrierQCOM (void); +#endif +#endif /* GL_QCOM_shader_framebuffer_fetch_noncoherent */ + +#ifndef GL_QCOM_shader_framebuffer_fetch_rate +#define GL_QCOM_shader_framebuffer_fetch_rate 1 +#endif /* GL_QCOM_shader_framebuffer_fetch_rate */ + +#ifndef GL_QCOM_shading_rate +#define GL_QCOM_shading_rate 1 +#define GL_SHADING_RATE_QCOM 0x96A4 +#define GL_SHADING_RATE_PRESERVE_ASPECT_RATIO_QCOM 0x96A5 +#define GL_SHADING_RATE_1X1_PIXELS_QCOM 0x96A6 +#define GL_SHADING_RATE_1X2_PIXELS_QCOM 0x96A7 +#define GL_SHADING_RATE_2X1_PIXELS_QCOM 0x96A8 +#define GL_SHADING_RATE_2X2_PIXELS_QCOM 0x96A9 +#define GL_SHADING_RATE_4X2_PIXELS_QCOM 0x96AC +#define GL_SHADING_RATE_4X4_PIXELS_QCOM 0x96AE +typedef void (GL_APIENTRYP PFNGLSHADINGRATEQCOMPROC) (GLenum rate); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glShadingRateQCOM (GLenum rate); +#endif +#endif /* GL_QCOM_shading_rate */ + +#ifndef GL_QCOM_texture_foveated +#define GL_QCOM_texture_foveated 1 +#define GL_TEXTURE_FOVEATED_FEATURE_BITS_QCOM 0x8BFB +#define GL_TEXTURE_FOVEATED_MIN_PIXEL_DENSITY_QCOM 0x8BFC +#define GL_TEXTURE_FOVEATED_FEATURE_QUERY_QCOM 0x8BFD +#define GL_TEXTURE_FOVEATED_NUM_FOCAL_POINTS_QUERY_QCOM 0x8BFE +#define GL_FRAMEBUFFER_INCOMPLETE_FOVEATION_QCOM 0x8BFF +typedef void (GL_APIENTRYP PFNGLTEXTUREFOVEATIONPARAMETERSQCOMPROC) (GLuint texture, GLuint layer, GLuint focalPoint, GLfloat focalX, GLfloat focalY, GLfloat gainX, GLfloat gainY, GLfloat foveaArea); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glTextureFoveationParametersQCOM (GLuint texture, GLuint layer, GLuint focalPoint, GLfloat focalX, GLfloat focalY, GLfloat gainX, GLfloat gainY, GLfloat foveaArea); +#endif +#endif /* GL_QCOM_texture_foveated */ + +#ifndef GL_QCOM_texture_foveated2 +#define GL_QCOM_texture_foveated2 1 +#define GL_TEXTURE_FOVEATED_CUTOFF_DENSITY_QCOM 0x96A0 +#endif /* GL_QCOM_texture_foveated2 */ + +#ifndef GL_QCOM_texture_foveated_subsampled_layout +#define GL_QCOM_texture_foveated_subsampled_layout 1 +#define GL_FOVEATION_SUBSAMPLED_LAYOUT_METHOD_BIT_QCOM 0x00000004 +#define GL_MAX_SHADER_SUBSAMPLED_IMAGE_UNITS_QCOM 0x8FA1 +#endif /* GL_QCOM_texture_foveated_subsampled_layout */ + +#ifndef GL_QCOM_tiled_rendering +#define GL_QCOM_tiled_rendering 1 +#define GL_COLOR_BUFFER_BIT0_QCOM 0x00000001 +#define GL_COLOR_BUFFER_BIT1_QCOM 0x00000002 +#define GL_COLOR_BUFFER_BIT2_QCOM 0x00000004 +#define GL_COLOR_BUFFER_BIT3_QCOM 0x00000008 +#define GL_COLOR_BUFFER_BIT4_QCOM 0x00000010 +#define GL_COLOR_BUFFER_BIT5_QCOM 0x00000020 +#define GL_COLOR_BUFFER_BIT6_QCOM 0x00000040 +#define GL_COLOR_BUFFER_BIT7_QCOM 0x00000080 +#define GL_DEPTH_BUFFER_BIT0_QCOM 0x00000100 +#define GL_DEPTH_BUFFER_BIT1_QCOM 0x00000200 +#define GL_DEPTH_BUFFER_BIT2_QCOM 0x00000400 +#define GL_DEPTH_BUFFER_BIT3_QCOM 0x00000800 +#define GL_DEPTH_BUFFER_BIT4_QCOM 0x00001000 +#define GL_DEPTH_BUFFER_BIT5_QCOM 0x00002000 +#define GL_DEPTH_BUFFER_BIT6_QCOM 0x00004000 +#define GL_DEPTH_BUFFER_BIT7_QCOM 0x00008000 +#define GL_STENCIL_BUFFER_BIT0_QCOM 0x00010000 +#define GL_STENCIL_BUFFER_BIT1_QCOM 0x00020000 +#define GL_STENCIL_BUFFER_BIT2_QCOM 0x00040000 +#define GL_STENCIL_BUFFER_BIT3_QCOM 0x00080000 +#define GL_STENCIL_BUFFER_BIT4_QCOM 0x00100000 +#define GL_STENCIL_BUFFER_BIT5_QCOM 0x00200000 +#define GL_STENCIL_BUFFER_BIT6_QCOM 0x00400000 +#define GL_STENCIL_BUFFER_BIT7_QCOM 0x00800000 +#define GL_MULTISAMPLE_BUFFER_BIT0_QCOM 0x01000000 +#define GL_MULTISAMPLE_BUFFER_BIT1_QCOM 0x02000000 +#define GL_MULTISAMPLE_BUFFER_BIT2_QCOM 0x04000000 +#define GL_MULTISAMPLE_BUFFER_BIT3_QCOM 0x08000000 +#define GL_MULTISAMPLE_BUFFER_BIT4_QCOM 0x10000000 +#define GL_MULTISAMPLE_BUFFER_BIT5_QCOM 0x20000000 +#define GL_MULTISAMPLE_BUFFER_BIT6_QCOM 0x40000000 +#define GL_MULTISAMPLE_BUFFER_BIT7_QCOM 0x80000000 +typedef void (GL_APIENTRYP PFNGLSTARTTILINGQCOMPROC) (GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask); +typedef void (GL_APIENTRYP PFNGLENDTILINGQCOMPROC) (GLbitfield preserveMask); +#ifdef GL_GLEXT_PROTOTYPES +GL_APICALL void GL_APIENTRY glStartTilingQCOM (GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask); +GL_APICALL void GL_APIENTRY glEndTilingQCOM (GLbitfield preserveMask); +#endif +#endif /* GL_QCOM_tiled_rendering */ + +#ifndef GL_QCOM_writeonly_rendering +#define GL_QCOM_writeonly_rendering 1 +#define GL_WRITEONLY_RENDERING_QCOM 0x8823 +#endif /* GL_QCOM_writeonly_rendering */ + +#ifndef GL_VIV_shader_binary +#define GL_VIV_shader_binary 1 +#define GL_SHADER_BINARY_VIV 0x8FC4 +#endif /* GL_VIV_shader_binary */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2platform.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2platform.h new file mode 100644 index 00000000..426796ef --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_gl2platform.h @@ -0,0 +1,27 @@ +#ifndef __gl2platform_h_ +#define __gl2platform_h_ + +/* +** Copyright 2017-2020 The Khronos Group Inc. +** SPDX-License-Identifier: Apache-2.0 +*/ + +/* Platform-specific types and definitions for OpenGL ES 2.X gl2.h + * + * Adopters may modify khrplatform.h and this file to suit their platform. + * Please contribute modifications back to Khronos as pull requests on the + * public github repository: + * https://github.com/KhronosGroup/OpenGL-Registry + */ + +/*#include */ + +#ifndef GL_APICALL +#define GL_APICALL KHRONOS_APICALL +#endif + +#ifndef GL_APIENTRY +#define GL_APIENTRY KHRONOS_APIENTRY +#endif + +#endif /* __gl2platform_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_khrplatform.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_khrplatform.h new file mode 100644 index 00000000..01646449 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_opengles2_khrplatform.h @@ -0,0 +1,311 @@ +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Khronos platform-specific types and definitions. + * + * The master copy of khrplatform.h is maintained in the Khronos EGL + * Registry repository at https://github.com/KhronosGroup/EGL-Registry + * The last semantic modification to khrplatform.h was at commit ID: + * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 + * + * Adopters may modify this file to suit their platform. Adopters are + * encouraged to submit platform specific modifications to the Khronos + * group so that they can be included in future versions of this file. + * Please submit changes by filing pull requests or issues on + * the EGL Registry repository linked above. + * + * + * See the Implementer's Guidelines for information about where this file + * should be located on your system and for more details of its use: + * http://www.khronos.org/registry/implementers_guide.pdf + * + * This file should be included as + * #include + * by Khronos client API header files that use its types and defines. + * + * The types in khrplatform.h should only be used to define API-specific types. + * + * Types defined in khrplatform.h: + * khronos_int8_t signed 8 bit + * khronos_uint8_t unsigned 8 bit + * khronos_int16_t signed 16 bit + * khronos_uint16_t unsigned 16 bit + * khronos_int32_t signed 32 bit + * khronos_uint32_t unsigned 32 bit + * khronos_int64_t signed 64 bit + * khronos_uint64_t unsigned 64 bit + * khronos_intptr_t signed same number of bits as a pointer + * khronos_uintptr_t unsigned same number of bits as a pointer + * khronos_ssize_t signed size + * khronos_usize_t unsigned size + * khronos_float_t signed 32 bit floating point + * khronos_time_ns_t unsigned 64 bit time in nanoseconds + * khronos_utime_nanoseconds_t unsigned time interval or absolute time in + * nanoseconds + * khronos_stime_nanoseconds_t signed time interval in nanoseconds + * khronos_boolean_enum_t enumerated boolean type. This should + * only be used as a base type when a client API's boolean type is + * an enum. Client APIs which use an integer or other type for + * booleans cannot use this as the base type for their boolean. + * + * Tokens defined in khrplatform.h: + * + * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. + * + * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. + * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. + * + * Calling convention macros defined in this file: + * KHRONOS_APICALL + * KHRONOS_APIENTRY + * KHRONOS_APIATTRIBUTES + * + * These may be used in function prototypes as: + * + * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( + * int arg1, + * int arg2) KHRONOS_APIATTRIBUTES; + */ + +#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) +# define KHRONOS_STATIC 1 +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APICALL + *------------------------------------------------------------------------- + * This precedes the return type of the function in the function prototype. + */ +#if defined(KHRONOS_STATIC) + /* If the preprocessor constant KHRONOS_STATIC is defined, make the + * header compatible with static linking. */ +# define KHRONOS_APICALL +#elif defined(_WIN32) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIENTRY + *------------------------------------------------------------------------- + * This follows the return type of the function and precedes the function + * name in the function prototype. + */ +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) + /* Win32 but not WinCE */ +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIATTRIBUTES + *------------------------------------------------------------------------- + * This follows the closing parenthesis of the function prototype arguments. + */ +#if defined (__ARMCC_2__) +#define KHRONOS_APIATTRIBUTES __softfp +#else +#define KHRONOS_APIATTRIBUTES +#endif + +/*------------------------------------------------------------------------- + * basic type definitions + *-----------------------------------------------------------------------*/ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) + + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 +/* + * To support platform where unsigned long cannot be used interchangeably with + * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. + * Ideally, we could just use (u)intptr_t everywhere, but this could result in + * ABI breakage if khronos_uintptr_t is changed from unsigned long to + * unsigned long long or similar (this results in different C++ name mangling). + * To avoid changes for existing platforms, we restrict usage of intptr_t to + * platforms where the size of a pointer is larger than the size of long. + */ +#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) +#if __SIZEOF_POINTER__ > __SIZEOF_LONG__ +#define KHRONOS_USE_INTPTR_T +#endif +#endif + +#elif defined(__VMS ) || defined(__sgi) + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) + +/* + * Win32 + */ +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) + +/* + * Sun or Digital + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif /* __arch64__ */ +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 + +/* + * Hypothetical platform with no float or int64 support + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#define KHRONOS_SUPPORT_INT64 0 +#define KHRONOS_SUPPORT_FLOAT 0 + +#else + +/* + * Generic fallback + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#endif + + +/* + * Types that are (so far) the same on all platforms + */ +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +/* + * Types that differ between LLP64 and LP64 architectures - in LLP64, + * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears + * to be the only LLP64 architecture in current use. + */ +#ifdef KHRONOS_USE_INTPTR_T +typedef intptr_t khronos_intptr_t; +typedef uintptr_t khronos_uintptr_t; +#elif defined(_WIN64) +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +#endif + +#if defined(_WIN64) +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + +#if KHRONOS_SUPPORT_FLOAT +/* + * Float type + */ +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +/* Time types + * + * These types can be used to represent a time interval in nanoseconds or + * an absolute Unadjusted System Time. Unadjusted System Time is the number + * of nanoseconds since some arbitrary system event (e.g. since the last + * time the system booted). The Unadjusted System Time is an unsigned + * 64 bit value that wraps back to 0 every 584 years. Time intervals + * may be either signed or unsigned. + */ +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +/* + * Dummy value used to pad enum types to 32 bits. + */ +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +/* + * Enumerated boolean type + * + * Values other than zero should be considered to be true. Therefore + * comparisons should not be made against KHRONOS_TRUE. + */ +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +#endif /* __khrplatform_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_pixels.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_pixels.h new file mode 100644 index 00000000..6f29811d --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_pixels.h @@ -0,0 +1,686 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryPixels + * + * Header for the enumerated pixel format definitions. + */ + +#ifndef SDL_pixels_h_ +#define SDL_pixels_h_ + +#include "SDL_stdinc.h" +#include "SDL_endian.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \name Transparency definitions + * + * These define alpha as the opacity of a surface. + */ +/* @{ */ +#define SDL_ALPHA_OPAQUE 255 +#define SDL_ALPHA_TRANSPARENT 0 +/* @} */ + +/** Pixel type. */ +typedef enum +{ + SDL_PIXELTYPE_UNKNOWN, + SDL_PIXELTYPE_INDEX1, + SDL_PIXELTYPE_INDEX4, + SDL_PIXELTYPE_INDEX8, + SDL_PIXELTYPE_PACKED8, + SDL_PIXELTYPE_PACKED16, + SDL_PIXELTYPE_PACKED32, + SDL_PIXELTYPE_ARRAYU8, + SDL_PIXELTYPE_ARRAYU16, + SDL_PIXELTYPE_ARRAYU32, + SDL_PIXELTYPE_ARRAYF16, + SDL_PIXELTYPE_ARRAYF32, + + /* This must be at the end of the list to avoid breaking the existing ABI */ + SDL_PIXELTYPE_INDEX2 +} SDL_PixelType; + +/** Bitmap pixel order, high bit -> low bit. */ +typedef enum +{ + SDL_BITMAPORDER_NONE, + SDL_BITMAPORDER_4321, + SDL_BITMAPORDER_1234 +} SDL_BitmapOrder; + +/** Packed component order, high bit -> low bit. */ +typedef enum +{ + SDL_PACKEDORDER_NONE, + SDL_PACKEDORDER_XRGB, + SDL_PACKEDORDER_RGBX, + SDL_PACKEDORDER_ARGB, + SDL_PACKEDORDER_RGBA, + SDL_PACKEDORDER_XBGR, + SDL_PACKEDORDER_BGRX, + SDL_PACKEDORDER_ABGR, + SDL_PACKEDORDER_BGRA +} SDL_PackedOrder; + +/** Array component order, low byte -> high byte. */ +/* !!! FIXME: in 2.1, make these not overlap differently with + !!! FIXME: SDL_PACKEDORDER_*, so we can simplify SDL_ISPIXELFORMAT_ALPHA */ +typedef enum +{ + SDL_ARRAYORDER_NONE, + SDL_ARRAYORDER_RGB, + SDL_ARRAYORDER_RGBA, + SDL_ARRAYORDER_ARGB, + SDL_ARRAYORDER_BGR, + SDL_ARRAYORDER_BGRA, + SDL_ARRAYORDER_ABGR +} SDL_ArrayOrder; + +/** Packed component layout. */ +typedef enum +{ + SDL_PACKEDLAYOUT_NONE, + SDL_PACKEDLAYOUT_332, + SDL_PACKEDLAYOUT_4444, + SDL_PACKEDLAYOUT_1555, + SDL_PACKEDLAYOUT_5551, + SDL_PACKEDLAYOUT_565, + SDL_PACKEDLAYOUT_8888, + SDL_PACKEDLAYOUT_2101010, + SDL_PACKEDLAYOUT_1010102 +} SDL_PackedLayout; + +#define SDL_DEFINE_PIXELFOURCC(A, B, C, D) SDL_FOURCC(A, B, C, D) + +#define SDL_DEFINE_PIXELFORMAT(type, order, layout, bits, bytes) \ + ((1 << 28) | ((type) << 24) | ((order) << 20) | ((layout) << 16) | \ + ((bits) << 8) | ((bytes) << 0)) + +#define SDL_PIXELFLAG(X) (((X) >> 28) & 0x0F) +#define SDL_PIXELTYPE(X) (((X) >> 24) & 0x0F) +#define SDL_PIXELORDER(X) (((X) >> 20) & 0x0F) +#define SDL_PIXELLAYOUT(X) (((X) >> 16) & 0x0F) +#define SDL_BITSPERPIXEL(X) (((X) >> 8) & 0xFF) +#define SDL_BYTESPERPIXEL(X) \ + (SDL_ISPIXELFORMAT_FOURCC(X) ? \ + ((((X) == SDL_PIXELFORMAT_YUY2) || \ + ((X) == SDL_PIXELFORMAT_UYVY) || \ + ((X) == SDL_PIXELFORMAT_YVYU)) ? 2 : 1) : (((X) >> 0) & 0xFF)) + +#define SDL_ISPIXELFORMAT_INDEXED(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX1) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX2) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX4) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX8))) + +#define SDL_ISPIXELFORMAT_PACKED(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED8) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED16) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED32))) + +#define SDL_ISPIXELFORMAT_ARRAY(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYU8) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYU16) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYU32) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYF16) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYF32))) + +#define SDL_ISPIXELFORMAT_ALPHA(format) \ + ((SDL_ISPIXELFORMAT_PACKED(format) && \ + ((SDL_PIXELORDER(format) == SDL_PACKEDORDER_ARGB) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_RGBA) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_ABGR) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_BGRA))) || \ + (SDL_ISPIXELFORMAT_ARRAY(format) && \ + ((SDL_PIXELORDER(format) == SDL_ARRAYORDER_ARGB) || \ + (SDL_PIXELORDER(format) == SDL_ARRAYORDER_RGBA) || \ + (SDL_PIXELORDER(format) == SDL_ARRAYORDER_ABGR) || \ + (SDL_PIXELORDER(format) == SDL_ARRAYORDER_BGRA)))) + +/* The flag is set to 1 because 0x1? is not in the printable ASCII range */ +#define SDL_ISPIXELFORMAT_FOURCC(format) \ + ((format) && (SDL_PIXELFLAG(format) != 1)) + +/* Note: If you modify this list, update SDL_GetPixelFormatName() */ +typedef enum +{ + SDL_PIXELFORMAT_UNKNOWN, + SDL_PIXELFORMAT_INDEX1LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_4321, 0, + 1, 0), + SDL_PIXELFORMAT_INDEX1MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_1234, 0, + 1, 0), + SDL_PIXELFORMAT_INDEX2LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX2, SDL_BITMAPORDER_4321, 0, + 2, 0), + SDL_PIXELFORMAT_INDEX2MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX2, SDL_BITMAPORDER_1234, 0, + 2, 0), + SDL_PIXELFORMAT_INDEX4LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_4321, 0, + 4, 0), + SDL_PIXELFORMAT_INDEX4MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_1234, 0, + 4, 0), + SDL_PIXELFORMAT_INDEX8 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1), + SDL_PIXELFORMAT_RGB332 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED8, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_332, 8, 1), + SDL_PIXELFORMAT_XRGB4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_4444, 12, 2), + SDL_PIXELFORMAT_RGB444 = SDL_PIXELFORMAT_XRGB4444, + SDL_PIXELFORMAT_XBGR4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_4444, 12, 2), + SDL_PIXELFORMAT_BGR444 = SDL_PIXELFORMAT_XBGR4444, + SDL_PIXELFORMAT_XRGB1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_1555, 15, 2), + SDL_PIXELFORMAT_RGB555 = SDL_PIXELFORMAT_XRGB1555, + SDL_PIXELFORMAT_XBGR1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_1555, 15, 2), + SDL_PIXELFORMAT_BGR555 = SDL_PIXELFORMAT_XBGR1555, + SDL_PIXELFORMAT_ARGB4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_RGBA4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_ABGR4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_BGRA4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_ARGB1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_1555, 16, 2), + SDL_PIXELFORMAT_RGBA5551 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_5551, 16, 2), + SDL_PIXELFORMAT_ABGR1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_1555, 16, 2), + SDL_PIXELFORMAT_BGRA5551 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_5551, 16, 2), + SDL_PIXELFORMAT_RGB565 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_565, 16, 2), + SDL_PIXELFORMAT_BGR565 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_565, 16, 2), + SDL_PIXELFORMAT_RGB24 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_RGB, 0, + 24, 3), + SDL_PIXELFORMAT_BGR24 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_BGR, 0, + 24, 3), + SDL_PIXELFORMAT_XRGB8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_RGB888 = SDL_PIXELFORMAT_XRGB8888, + SDL_PIXELFORMAT_RGBX8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBX, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_XBGR8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_BGR888 = SDL_PIXELFORMAT_XBGR8888, + SDL_PIXELFORMAT_BGRX8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRX, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_ARGB8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_RGBA8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_ABGR8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_BGRA8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_ARGB2101010 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_2101010, 32, 4), + + /* Aliases for RGBA byte arrays of color data, for the current platform */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + SDL_PIXELFORMAT_RGBA32 = SDL_PIXELFORMAT_RGBA8888, + SDL_PIXELFORMAT_ARGB32 = SDL_PIXELFORMAT_ARGB8888, + SDL_PIXELFORMAT_BGRA32 = SDL_PIXELFORMAT_BGRA8888, + SDL_PIXELFORMAT_ABGR32 = SDL_PIXELFORMAT_ABGR8888, + SDL_PIXELFORMAT_RGBX32 = SDL_PIXELFORMAT_RGBX8888, + SDL_PIXELFORMAT_XRGB32 = SDL_PIXELFORMAT_XRGB8888, + SDL_PIXELFORMAT_BGRX32 = SDL_PIXELFORMAT_BGRX8888, + SDL_PIXELFORMAT_XBGR32 = SDL_PIXELFORMAT_XBGR8888, +#else + SDL_PIXELFORMAT_RGBA32 = SDL_PIXELFORMAT_ABGR8888, + SDL_PIXELFORMAT_ARGB32 = SDL_PIXELFORMAT_BGRA8888, + SDL_PIXELFORMAT_BGRA32 = SDL_PIXELFORMAT_ARGB8888, + SDL_PIXELFORMAT_ABGR32 = SDL_PIXELFORMAT_RGBA8888, + SDL_PIXELFORMAT_RGBX32 = SDL_PIXELFORMAT_XBGR8888, + SDL_PIXELFORMAT_XRGB32 = SDL_PIXELFORMAT_BGRX8888, + SDL_PIXELFORMAT_BGRX32 = SDL_PIXELFORMAT_XRGB8888, + SDL_PIXELFORMAT_XBGR32 = SDL_PIXELFORMAT_RGBX8888, +#endif + + SDL_PIXELFORMAT_YV12 = /**< Planar mode: Y + V + U (3 planes) */ + SDL_DEFINE_PIXELFOURCC('Y', 'V', '1', '2'), + SDL_PIXELFORMAT_IYUV = /**< Planar mode: Y + U + V (3 planes) */ + SDL_DEFINE_PIXELFOURCC('I', 'Y', 'U', 'V'), + SDL_PIXELFORMAT_YUY2 = /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */ + SDL_DEFINE_PIXELFOURCC('Y', 'U', 'Y', '2'), + SDL_PIXELFORMAT_UYVY = /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */ + SDL_DEFINE_PIXELFOURCC('U', 'Y', 'V', 'Y'), + SDL_PIXELFORMAT_YVYU = /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */ + SDL_DEFINE_PIXELFOURCC('Y', 'V', 'Y', 'U'), + SDL_PIXELFORMAT_NV12 = /**< Planar mode: Y + U/V interleaved (2 planes) */ + SDL_DEFINE_PIXELFOURCC('N', 'V', '1', '2'), + SDL_PIXELFORMAT_NV21 = /**< Planar mode: Y + V/U interleaved (2 planes) */ + SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1'), + SDL_PIXELFORMAT_EXTERNAL_OES = /**< Android video texture format */ + SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ') +} SDL_PixelFormatEnum; + +/** + * The bits of this structure can be directly reinterpreted as an + * integer-packed color which uses the SDL_PIXELFORMAT_RGBA32 format + * (SDL_PIXELFORMAT_ABGR8888 on little-endian systems and + * SDL_PIXELFORMAT_RGBA8888 on big-endian systems). + */ +typedef struct SDL_Color +{ + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 a; +} SDL_Color; +#define SDL_Colour SDL_Color + +typedef struct SDL_Palette +{ + int ncolors; + SDL_Color *colors; + Uint32 version; + int refcount; +} SDL_Palette; + +/** + * A structure that contains pixel format information. + * + * Everything in the pixel format structure is read-only. + * + * A pixel format has either a palette or masks. If a palette is used `Rmask`, + * `Gmask`, `Bmask`, and `Amask` will be 0. + * + * An SDL_PixelFormat describes the format of the pixel data stored at the + * `pixels` field of an SDL_Surface. Every surface stores an SDL_PixelFormat + * in the `format` field. + * + * If you wish to do pixel level modifications on a surface, then + * understanding how SDL stores its color information is essential. + * + * For information on modern pixel color spaces, see the following Wikipedia + * article: http://en.wikipedia.org/wiki/RGBA_color_space + * + * \sa SDL_ConvertSurface + * \sa SDL_GetRGB + * \sa SDL_GetRGBA + * \sa SDL_MapRGB + * \sa SDL_MapRGBA + * \sa SDL_AllocFormat + * \sa SDL_FreeFormat + */ +typedef struct SDL_PixelFormat +{ + Uint32 format; + SDL_Palette *palette; + Uint8 BitsPerPixel; + Uint8 BytesPerPixel; + Uint8 padding[2]; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; + Uint8 Rloss; + Uint8 Gloss; + Uint8 Bloss; + Uint8 Aloss; + Uint8 Rshift; + Uint8 Gshift; + Uint8 Bshift; + Uint8 Ashift; + int refcount; + struct SDL_PixelFormat *next; +} SDL_PixelFormat; + +/** + * Get the human readable name of a pixel format. + * + * \param format the pixel format to query. + * \returns the human readable name of the specified pixel format or + * `SDL_PIXELFORMAT_UNKNOWN` if the format isn't recognized. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC const char* SDLCALL SDL_GetPixelFormatName(Uint32 format); + +/** + * Convert one of the enumerated pixel formats to a bpp value and RGBA masks. + * + * \param format one of the SDL_PixelFormatEnum values. + * \param bpp a bits per pixel value; usually 15, 16, or 32. + * \param Rmask a pointer filled in with the red mask for the format. + * \param Gmask a pointer filled in with the green mask for the format. + * \param Bmask a pointer filled in with the blue mask for the format. + * \param Amask a pointer filled in with the alpha mask for the format. + * \returns SDL_TRUE on success or SDL_FALSE if the conversion wasn't + * possible; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_MasksToPixelFormatEnum + */ +extern DECLSPEC SDL_bool SDLCALL SDL_PixelFormatEnumToMasks(Uint32 format, + int *bpp, + Uint32 * Rmask, + Uint32 * Gmask, + Uint32 * Bmask, + Uint32 * Amask); + +/** + * Convert a bpp value and RGBA masks to an enumerated pixel format. + * + * This will return `SDL_PIXELFORMAT_UNKNOWN` if the conversion wasn't + * possible. + * + * \param bpp a bits per pixel value; usually 15, 16, or 32. + * \param Rmask the red mask for the format. + * \param Gmask the green mask for the format. + * \param Bmask the blue mask for the format. + * \param Amask the alpha mask for the format. + * \returns one of the SDL_PixelFormatEnum values. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_PixelFormatEnumToMasks + */ +extern DECLSPEC Uint32 SDLCALL SDL_MasksToPixelFormatEnum(int bpp, + Uint32 Rmask, + Uint32 Gmask, + Uint32 Bmask, + Uint32 Amask); + +/** + * Create an SDL_PixelFormat structure corresponding to a pixel format. + * + * Returned structure may come from a shared global cache (i.e. not newly + * allocated), and hence should not be modified, especially the palette. Weird + * errors such as `Blit combination not supported` may occur. + * + * \param pixel_format one of the SDL_PixelFormatEnum values. + * \returns the new SDL_PixelFormat structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreeFormat + */ +extern DECLSPEC SDL_PixelFormat * SDLCALL SDL_AllocFormat(Uint32 pixel_format); + +/** + * Free an SDL_PixelFormat structure allocated by SDL_AllocFormat(). + * + * \param format the SDL_PixelFormat structure to free. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocFormat + */ +extern DECLSPEC void SDLCALL SDL_FreeFormat(SDL_PixelFormat *format); + +/** + * Create a palette structure with the specified number of color entries. + * + * The palette entries are initialized to white. + * + * \param ncolors represents the number of color entries in the color palette. + * \returns a new SDL_Palette structure on success or NULL on failure (e.g. if + * there wasn't enough memory); call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreePalette + */ +extern DECLSPEC SDL_Palette *SDLCALL SDL_AllocPalette(int ncolors); + +/** + * Set the palette for a pixel format structure. + * + * \param format the SDL_PixelFormat structure that will use the palette. + * \param palette the SDL_Palette structure that will be used. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocPalette + * \sa SDL_FreePalette + */ +extern DECLSPEC int SDLCALL SDL_SetPixelFormatPalette(SDL_PixelFormat * format, + SDL_Palette *palette); + +/** + * Set a range of colors in a palette. + * + * \param palette the SDL_Palette structure to modify. + * \param colors an array of SDL_Color structures to copy into the palette. + * \param firstcolor the index of the first palette entry to modify. + * \param ncolors the number of entries to modify. + * \returns 0 on success or a negative error code if not all of the colors + * could be set; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocPalette + * \sa SDL_CreateRGBSurface + */ +extern DECLSPEC int SDLCALL SDL_SetPaletteColors(SDL_Palette * palette, + const SDL_Color * colors, + int firstcolor, int ncolors); + +/** + * Free a palette created with SDL_AllocPalette(). + * + * \param palette the SDL_Palette structure to be freed. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocPalette + */ +extern DECLSPEC void SDLCALL SDL_FreePalette(SDL_Palette * palette); + +/** + * Map an RGB triple to an opaque pixel value for a given pixel format. + * + * This function maps the RGB color value to the specified pixel format and + * returns the pixel value best approximating the given RGB color value for + * the given pixel format. + * + * If the format has a palette (8-bit) the index of the closest matching color + * in the palette will be returned. + * + * If the specified pixel format has an alpha component it will be returned as + * all 1 bits (fully opaque). + * + * If the pixel format bpp (color depth) is less than 32-bpp then the unused + * upper bits of the return value can safely be ignored (e.g., with a 16-bpp + * format the return value can be assigned to a Uint16, and similarly a Uint8 + * for an 8-bpp format). + * + * \param format an SDL_PixelFormat structure describing the pixel format. + * \param r the red component of the pixel in the range 0-255. + * \param g the green component of the pixel in the range 0-255. + * \param b the blue component of the pixel in the range 0-255. + * \returns a pixel value. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRGB + * \sa SDL_GetRGBA + * \sa SDL_MapRGBA + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGB(const SDL_PixelFormat * format, + Uint8 r, Uint8 g, Uint8 b); + +/** + * Map an RGBA quadruple to a pixel value for a given pixel format. + * + * This function maps the RGBA color value to the specified pixel format and + * returns the pixel value best approximating the given RGBA color value for + * the given pixel format. + * + * If the specified pixel format has no alpha component the alpha value will + * be ignored (as it will be in formats with a palette). + * + * If the format has a palette (8-bit) the index of the closest matching color + * in the palette will be returned. + * + * If the pixel format bpp (color depth) is less than 32-bpp then the unused + * upper bits of the return value can safely be ignored (e.g., with a 16-bpp + * format the return value can be assigned to a Uint16, and similarly a Uint8 + * for an 8-bpp format). + * + * \param format an SDL_PixelFormat structure describing the format of the + * pixel. + * \param r the red component of the pixel in the range 0-255. + * \param g the green component of the pixel in the range 0-255. + * \param b the blue component of the pixel in the range 0-255. + * \param a the alpha component of the pixel in the range 0-255. + * \returns a pixel value. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRGB + * \sa SDL_GetRGBA + * \sa SDL_MapRGB + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormat * format, + Uint8 r, Uint8 g, Uint8 b, + Uint8 a); + +/** + * Get RGB values from a pixel in the specified format. + * + * This function uses the entire 8-bit [0..255] range when converting color + * components from pixel formats with less than 8-bits per RGB component + * (e.g., a completely white pixel in 16-bit RGB565 format would return [0xff, + * 0xff, 0xff] not [0xf8, 0xfc, 0xf8]). + * + * \param pixel a pixel value. + * \param format an SDL_PixelFormat structure describing the format of the + * pixel. + * \param r a pointer filled in with the red component. + * \param g a pointer filled in with the green component. + * \param b a pointer filled in with the blue component. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRGBA + * \sa SDL_MapRGB + * \sa SDL_MapRGBA + */ +extern DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, + const SDL_PixelFormat * format, + Uint8 * r, Uint8 * g, Uint8 * b); + +/** + * Get RGBA values from a pixel in the specified format. + * + * This function uses the entire 8-bit [0..255] range when converting color + * components from pixel formats with less than 8-bits per RGB component + * (e.g., a completely white pixel in 16-bit RGB565 format would return [0xff, + * 0xff, 0xff] not [0xf8, 0xfc, 0xf8]). + * + * If the surface has no alpha component, the alpha will be returned as 0xff + * (100% opaque). + * + * \param pixel a pixel value. + * \param format an SDL_PixelFormat structure describing the format of the + * pixel. + * \param r a pointer filled in with the red component. + * \param g a pointer filled in with the green component. + * \param b a pointer filled in with the blue component. + * \param a a pointer filled in with the alpha component. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRGB + * \sa SDL_MapRGB + * \sa SDL_MapRGBA + */ +extern DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, + const SDL_PixelFormat * format, + Uint8 * r, Uint8 * g, Uint8 * b, + Uint8 * a); + +/** + * Calculate a 256 entry gamma ramp for a gamma value. + * + * \param gamma a gamma value where 0.0 is black and 1.0 is identity. + * \param ramp an array of 256 values filled in with the gamma ramp. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowGammaRamp + */ +extern DECLSPEC void SDLCALL SDL_CalculateGammaRamp(float gamma, Uint16 * ramp); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_pixels_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_platform.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_platform.h new file mode 100644 index 00000000..64ece4fe --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_platform.h @@ -0,0 +1,275 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryPlatform + * + * Try to get a standard set of platform defines. + */ + +#ifndef SDL_platform_h_ +#define SDL_platform_h_ + +#if defined(_AIX) +#undef __AIX__ +#define __AIX__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(bsdi) || defined(__bsdi) || defined(__bsdi__) +#undef __BSDI__ +#define __BSDI__ 1 +#endif +#if defined(_arch_dreamcast) +#undef __DREAMCAST__ +#define __DREAMCAST__ 1 +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +#undef __FREEBSD__ +#define __FREEBSD__ 1 +#endif +#if defined(hpux) || defined(__hpux) || defined(__hpux__) +#undef __HPUX__ +#define __HPUX__ 1 +#endif +#if defined(sgi) || defined(__sgi) || defined(__sgi__) || defined(_SGI_SOURCE) +#undef __IRIX__ +#define __IRIX__ 1 +#endif +#if (defined(linux) || defined(__linux) || defined(__linux__)) +#undef __LINUX__ +#define __LINUX__ 1 +#endif +#if defined(ANDROID) || defined(__ANDROID__) +#undef __ANDROID__ +#undef __LINUX__ /* do we need to do this? */ +#define __ANDROID__ 1 +#endif +#if defined(__NGAGE__) +#undef __NGAGE__ +#define __NGAGE__ 1 +#endif + +#if defined(__APPLE__) +/* lets us know what version of Mac OS X we're compiling on */ +#include +#ifndef __has_extension /* Older compilers don't support this */ +#define __has_extension(x) 0 +#include +#undef __has_extension +#else +#include +#endif + +/* Fix building with older SDKs that don't define these + See this for more information: + https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets +*/ +#ifndef TARGET_OS_MACCATALYST +#define TARGET_OS_MACCATALYST 0 +#endif +#ifndef TARGET_OS_IOS +#define TARGET_OS_IOS 0 +#endif +#ifndef TARGET_OS_IPHONE +#define TARGET_OS_IPHONE 0 +#endif +#ifndef TARGET_OS_TV +#define TARGET_OS_TV 0 +#endif +#ifndef TARGET_OS_SIMULATOR +#define TARGET_OS_SIMULATOR 0 +#endif + +#if TARGET_OS_TV +#undef __TVOS__ +#define __TVOS__ 1 +#endif +#if TARGET_OS_IPHONE +/* if compiling for iOS */ +#undef __IPHONEOS__ +#define __IPHONEOS__ 1 +#undef __MACOSX__ +#else +/* if not compiling for iOS */ +#undef __MACOSX__ +#define __MACOSX__ 1 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 +# error SDL for Mac OS X only supports deploying on 10.7 and above. +#endif /* MAC_OS_X_VERSION_MIN_REQUIRED < 1070 */ +#endif /* TARGET_OS_IPHONE */ +#endif /* defined(__APPLE__) */ + +#if defined(__NetBSD__) +#undef __NETBSD__ +#define __NETBSD__ 1 +#endif +#if defined(__OpenBSD__) +#undef __OPENBSD__ +#define __OPENBSD__ 1 +#endif +#if defined(__OS2__) || defined(__EMX__) +#undef __OS2__ +#define __OS2__ 1 +#endif +#if defined(osf) || defined(__osf) || defined(__osf__) || defined(_OSF_SOURCE) +#undef __OSF__ +#define __OSF__ 1 +#endif +#if defined(__QNXNTO__) +#undef __QNXNTO__ +#define __QNXNTO__ 1 +#endif +#if defined(riscos) || defined(__riscos) || defined(__riscos__) +#undef __RISCOS__ +#define __RISCOS__ 1 +#endif +#if defined(__sun) && defined(__SVR4) +#undef __SOLARIS__ +#define __SOLARIS__ 1 +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +/* Try to find out if we're compiling for WinRT, GDK or non-WinRT/GDK */ +#if defined(_MSC_VER) && defined(__has_include) +#if __has_include() +#define HAVE_WINAPIFAMILY_H 1 +#else +#define HAVE_WINAPIFAMILY_H 0 +#endif + +/* If _USING_V110_SDK71_ is defined it means we are using the Windows XP toolset. */ +#elif defined(_MSC_VER) && (_MSC_VER >= 1700 && !_USING_V110_SDK71_) /* _MSC_VER == 1700 for Visual Studio 2012 */ +#define HAVE_WINAPIFAMILY_H 1 +#else +#define HAVE_WINAPIFAMILY_H 0 +#endif + +#if HAVE_WINAPIFAMILY_H +#include +#define WINAPI_FAMILY_WINRT (!WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)) +#else +#define WINAPI_FAMILY_WINRT 0 +#endif /* HAVE_WINAPIFAMILY_H */ + +#if (HAVE_WINAPIFAMILY_H) && defined(WINAPI_FAMILY_PHONE_APP) +#define SDL_WINAPI_FAMILY_PHONE (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) +#else +#define SDL_WINAPI_FAMILY_PHONE 0 +#endif + +#if WINAPI_FAMILY_WINRT +#undef __WINRT__ +#define __WINRT__ 1 +#elif defined(_GAMING_DESKTOP) /* GDK project configuration always defines _GAMING_XXX */ +#undef __WINGDK__ +#define __WINGDK__ 1 +#elif defined(_GAMING_XBOX_XBOXONE) +#undef __XBOXONE__ +#define __XBOXONE__ 1 +#elif defined(_GAMING_XBOX_SCARLETT) +#undef __XBOXSERIES__ +#define __XBOXSERIES__ 1 +#else +#undef __WINDOWS__ +#define __WINDOWS__ 1 +#endif +#endif /* defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) */ + +#if defined(__WINDOWS__) +#undef __WIN32__ +#define __WIN32__ 1 +#endif +/* This is to support generic "any GDK" separate from a platform-specific GDK */ +#if defined(__WINGDK__) || defined(__XBOXONE__) || defined(__XBOXSERIES__) +#undef __GDK__ +#define __GDK__ 1 +#endif +#if defined(__PSP__) || defined(__psp__) +#ifdef __PSP__ +#undef __PSP__ +#endif +#define __PSP__ 1 +#endif +#if defined(PS2) +#define __PS2__ 1 +#endif + +/* The NACL compiler defines __native_client__ and __pnacl__ + * Ref: http://www.chromium.org/nativeclient/pnacl/stability-of-the-pnacl-bitcode-abi + */ +#if defined(__native_client__) +#undef __LINUX__ +#undef __NACL__ +#define __NACL__ 1 +#endif +#if defined(__pnacl__) +#undef __LINUX__ +#undef __PNACL__ +#define __PNACL__ 1 +/* PNACL with newlib supports static linking only */ +#define __SDL_NOGETPROCADDR__ +#endif + +#if defined(__vita__) +#define __VITA__ 1 +#endif + +#if defined(__3DS__) +#undef __3DS__ +#define __3DS__ 1 +#endif + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get the name of the platform. + * + * Here are the names returned for some (but not all) supported platforms: + * + * - "Windows" + * - "Mac OS X" + * - "Linux" + * - "iOS" + * - "Android" + * + * \returns the name of the platform. If the correct platform name is not + * available, returns a string beginning with the text "Unknown". + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC const char * SDLCALL SDL_GetPlatform (void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_platform_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_power.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_power.h new file mode 100644 index 00000000..755c5d42 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_power.h @@ -0,0 +1,87 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_power_h_ +#define SDL_power_h_ + +/** + * # CategoryPower + * + * Header for the SDL power management routines. + */ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The basic state for the system's power supply. + */ +typedef enum SDL_PowerState +{ + SDL_POWERSTATE_UNKNOWN, /**< cannot determine power status */ + SDL_POWERSTATE_ON_BATTERY, /**< Not plugged in, running on the battery */ + SDL_POWERSTATE_NO_BATTERY, /**< Plugged in, no battery available */ + SDL_POWERSTATE_CHARGING, /**< Plugged in, charging battery */ + SDL_POWERSTATE_CHARGED /**< Plugged in, battery charged */ +} SDL_PowerState; + +/** + * Get the current power supply details. + * + * You should never take a battery status as absolute truth. Batteries + * (especially failing batteries) are delicate hardware, and the values + * reported here are best estimates based on what that hardware reports. It's + * not uncommon for older batteries to lose stored power much faster than it + * reports, or completely drain when reporting it has 20 percent left, etc. + * + * Battery status can change at any time; if you are concerned with power + * state, you should call this function frequently, and perhaps ignore changes + * until they seem to be stable for a few seconds. + * + * It's possible a platform can only report battery percentage or time left + * but not both. + * + * \param seconds seconds of battery life left, you can pass a NULL here if + * you don't care, will return -1 if we can't determine a + * value, or we're not running on a battery. + * \param percent percentage of battery life left, between 0 and 100, you can + * pass a NULL here if you don't care, will return -1 if we + * can't determine a value, or we're not running on a battery. + * \returns an SDL_PowerState enum representing the current battery state. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_PowerState SDLCALL SDL_GetPowerInfo(int *seconds, int *percent); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_power_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_quit.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_quit.h new file mode 100644 index 00000000..03630e23 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_quit.h @@ -0,0 +1,50 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryQuit + * + * An SDL_QUIT event is generated when the user tries to close the application + * window. If it is ignored or filtered out, the window will remain open. If + * it is not ignored or filtered, it is queued normally and the window is + * allowed to close. When the window is closed, screen updates will complete, + * but have no effect. + * + * SDL_Init() installs signal handlers for SIGINT (keyboard interrupt) and + * SIGTERM (system termination request), if handlers do not already exist, + * that generate SDL_QUIT events as well. There is no way to determine the + * cause of an SDL_QUIT event, but setting a signal handler in your + * application will override the default generation of quit events for that + * signal. + */ + +#ifndef SDL_quit_h_ +#define SDL_quit_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/* There are no functions directly affecting the quit event */ + +#define SDL_QuitRequested() \ + (SDL_PumpEvents(), (SDL_PeepEvents(NULL,0,SDL_PEEKEVENT,SDL_QUIT,SDL_QUIT) > 0)) + +#endif /* SDL_quit_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_rect.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_rect.h new file mode 100644 index 00000000..b7e609d9 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_rect.h @@ -0,0 +1,376 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryRect + * + * Header file for SDL_rect definition and management functions. + */ + +#ifndef SDL_rect_h_ +#define SDL_rect_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_pixels.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The structure that defines a point (integer) + * + * \sa SDL_EnclosePoints + * \sa SDL_PointInRect + */ +typedef struct SDL_Point +{ + int x; + int y; +} SDL_Point; + +/** + * The structure that defines a point (floating point) + * + * \sa SDL_EncloseFPoints + * \sa SDL_PointInFRect + */ +typedef struct SDL_FPoint +{ + float x; + float y; +} SDL_FPoint; + + +/** + * A rectangle, with the origin at the upper left (integer). + * + * \sa SDL_RectEmpty + * \sa SDL_RectEquals + * \sa SDL_HasIntersection + * \sa SDL_IntersectRect + * \sa SDL_IntersectRectAndLine + * \sa SDL_UnionRect + * \sa SDL_EnclosePoints + */ +typedef struct SDL_Rect +{ + int x, y; + int w, h; +} SDL_Rect; + + +/** + * A rectangle, with the origin at the upper left (floating point). + * + * \sa SDL_FRectEmpty + * \sa SDL_FRectEquals + * \sa SDL_FRectEqualsEpsilon + * \sa SDL_HasIntersectionF + * \sa SDL_IntersectFRect + * \sa SDL_IntersectFRectAndLine + * \sa SDL_UnionFRect + * \sa SDL_EncloseFPoints + * \sa SDL_PointInFRect + */ +typedef struct SDL_FRect +{ + float x; + float y; + float w; + float h; +} SDL_FRect; + + +/** + * Returns true if point resides inside a rectangle. + */ +SDL_FORCE_INLINE SDL_bool SDL_PointInRect(const SDL_Point *p, const SDL_Rect *r) +{ + return ( (p->x >= r->x) && (p->x < (r->x + r->w)) && + (p->y >= r->y) && (p->y < (r->y + r->h)) ) ? SDL_TRUE : SDL_FALSE; +} + +/** + * Returns true if the rectangle has no area. + */ +SDL_FORCE_INLINE SDL_bool SDL_RectEmpty(const SDL_Rect *r) +{ + return ((!r) || (r->w <= 0) || (r->h <= 0)) ? SDL_TRUE : SDL_FALSE; +} + +/** + * Returns true if the two rectangles are equal. + */ +SDL_FORCE_INLINE SDL_bool SDL_RectEquals(const SDL_Rect *a, const SDL_Rect *b) +{ + return (a && b && (a->x == b->x) && (a->y == b->y) && + (a->w == b->w) && (a->h == b->h)) ? SDL_TRUE : SDL_FALSE; +} + +/** + * Determine whether two rectangles intersect. + * + * If either pointer is NULL the function will return SDL_FALSE. + * + * \param A an SDL_Rect structure representing the first rectangle. + * \param B an SDL_Rect structure representing the second rectangle. + * \returns SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_IntersectRect + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasIntersection(const SDL_Rect * A, + const SDL_Rect * B); + +/** + * Calculate the intersection of two rectangles. + * + * If `result` is NULL then this function will return SDL_FALSE. + * + * \param A an SDL_Rect structure representing the first rectangle. + * \param B an SDL_Rect structure representing the second rectangle. + * \param result an SDL_Rect structure filled in with the intersection of + * rectangles `A` and `B`. + * \returns SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HasIntersection + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectRect(const SDL_Rect * A, + const SDL_Rect * B, + SDL_Rect * result); + +/** + * Calculate the union of two rectangles. + * + * \param A an SDL_Rect structure representing the first rectangle. + * \param B an SDL_Rect structure representing the second rectangle. + * \param result an SDL_Rect structure filled in with the union of rectangles + * `A` and `B`. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_UnionRect(const SDL_Rect * A, + const SDL_Rect * B, + SDL_Rect * result); + +/** + * Calculate a minimal rectangle enclosing a set of points. + * + * If `clip` is not NULL then only points inside of the clipping rectangle are + * considered. + * + * \param points an array of SDL_Point structures representing points to be + * enclosed. + * \param count the number of structures in the `points` array. + * \param clip an SDL_Rect used for clipping or NULL to enclose all points. + * \param result an SDL_Rect structure filled in with the minimal enclosing + * rectangle. + * \returns SDL_TRUE if any points were enclosed or SDL_FALSE if all the + * points were outside of the clipping rectangle. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_EnclosePoints(const SDL_Point * points, + int count, + const SDL_Rect * clip, + SDL_Rect * result); + +/** + * Calculate the intersection of a rectangle and line segment. + * + * This function is used to clip a line segment to a rectangle. A line segment + * contained entirely within the rectangle or that does not intersect will + * remain unchanged. A line segment that crosses the rectangle at either or + * both ends will be clipped to the boundary of the rectangle and the new + * coordinates saved in `X1`, `Y1`, `X2`, and/or `Y2` as necessary. + * + * \param rect an SDL_Rect structure representing the rectangle to intersect. + * \param X1 a pointer to the starting X-coordinate of the line. + * \param Y1 a pointer to the starting Y-coordinate of the line. + * \param X2 a pointer to the ending X-coordinate of the line. + * \param Y2 a pointer to the ending Y-coordinate of the line. + * \returns SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectRectAndLine(const SDL_Rect * + rect, int *X1, + int *Y1, int *X2, + int *Y2); + + +/* SDL_FRect versions... */ + +/** + * Returns true if point resides inside a rectangle. + */ +SDL_FORCE_INLINE SDL_bool SDL_PointInFRect(const SDL_FPoint *p, const SDL_FRect *r) +{ + return ( (p->x >= r->x) && (p->x < (r->x + r->w)) && + (p->y >= r->y) && (p->y < (r->y + r->h)) ) ? SDL_TRUE : SDL_FALSE; +} + +/** + * Returns true if the rectangle has no area. + */ +SDL_FORCE_INLINE SDL_bool SDL_FRectEmpty(const SDL_FRect *r) +{ + return ((!r) || (r->w <= 0.0f) || (r->h <= 0.0f)) ? SDL_TRUE : SDL_FALSE; +} + +/** + * Returns true if the two rectangles are equal, within some given epsilon. + * + * \since This function is available since SDL 2.0.22. + */ +SDL_FORCE_INLINE SDL_bool SDL_FRectEqualsEpsilon(const SDL_FRect *a, const SDL_FRect *b, const float epsilon) +{ + return (a && b && ((a == b) || + ((SDL_fabsf(a->x - b->x) <= epsilon) && + (SDL_fabsf(a->y - b->y) <= epsilon) && + (SDL_fabsf(a->w - b->w) <= epsilon) && + (SDL_fabsf(a->h - b->h) <= epsilon)))) + ? SDL_TRUE : SDL_FALSE; +} + +/** + * Returns true if the two rectangles are equal, using a default epsilon. + * + * \since This function is available since SDL 2.0.22. + */ +SDL_FORCE_INLINE SDL_bool SDL_FRectEquals(const SDL_FRect *a, const SDL_FRect *b) +{ + return SDL_FRectEqualsEpsilon(a, b, SDL_FLT_EPSILON); +} + +/** + * Determine whether two rectangles intersect with float precision. + * + * If either pointer is NULL the function will return SDL_FALSE. + * + * \param A an SDL_FRect structure representing the first rectangle. + * \param B an SDL_FRect structure representing the second rectangle. + * \returns SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.22. + * + * \sa SDL_IntersectRect + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasIntersectionF(const SDL_FRect * A, + const SDL_FRect * B); + +/** + * Calculate the intersection of two rectangles with float precision. + * + * If `result` is NULL then this function will return SDL_FALSE. + * + * \param A an SDL_FRect structure representing the first rectangle. + * \param B an SDL_FRect structure representing the second rectangle. + * \param result an SDL_FRect structure filled in with the intersection of + * rectangles `A` and `B`. + * \returns SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.22. + * + * \sa SDL_HasIntersectionF + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectFRect(const SDL_FRect * A, + const SDL_FRect * B, + SDL_FRect * result); + +/** + * Calculate the union of two rectangles with float precision. + * + * \param A an SDL_FRect structure representing the first rectangle. + * \param B an SDL_FRect structure representing the second rectangle. + * \param result an SDL_FRect structure filled in with the union of rectangles + * `A` and `B`. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC void SDLCALL SDL_UnionFRect(const SDL_FRect * A, + const SDL_FRect * B, + SDL_FRect * result); + +/** + * Calculate a minimal rectangle enclosing a set of points with float + * precision. + * + * If `clip` is not NULL then only points inside of the clipping rectangle are + * considered. + * + * \param points an array of SDL_FPoint structures representing points to be + * enclosed. + * \param count the number of structures in the `points` array. + * \param clip an SDL_FRect used for clipping or NULL to enclose all points. + * \param result an SDL_FRect structure filled in with the minimal enclosing + * rectangle. + * \returns SDL_TRUE if any points were enclosed or SDL_FALSE if all the + * points were outside of the clipping rectangle. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_EncloseFPoints(const SDL_FPoint * points, + int count, + const SDL_FRect * clip, + SDL_FRect * result); + +/** + * Calculate the intersection of a rectangle and line segment with float + * precision. + * + * This function is used to clip a line segment to a rectangle. A line segment + * contained entirely within the rectangle or that does not intersect will + * remain unchanged. A line segment that crosses the rectangle at either or + * both ends will be clipped to the boundary of the rectangle and the new + * coordinates saved in `X1`, `Y1`, `X2`, and/or `Y2` as necessary. + * + * \param rect an SDL_FRect structure representing the rectangle to intersect. + * \param X1 a pointer to the starting X-coordinate of the line. + * \param Y1 a pointer to the starting Y-coordinate of the line. + * \param X2 a pointer to the ending X-coordinate of the line. + * \param Y2 a pointer to the ending Y-coordinate of the line. + * \returns SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectFRectAndLine(const SDL_FRect * + rect, float *X1, + float *Y1, float *X2, + float *Y2); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_rect_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_render.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_render.h new file mode 100644 index 00000000..52741721 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_render.h @@ -0,0 +1,1932 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryRender + * + * Header file for SDL 2D rendering functions. + * + * This API supports the following features: + * + * - single pixel points + * - single pixel lines + * - filled rectangles + * - texture images + * + * The primitives may be drawn in opaque, blended, or additive modes. + * + * The texture images may be drawn in opaque, blended, or additive modes. They + * can have an additional color tint or alpha modulation applied to them, and + * may also be stretched with linear interpolation. + * + * This API is designed to accelerate simple 2D operations. You may want more + * functionality such as polygons and particle effects and in that case you + * should use SDL's OpenGL/Direct3D support or one of the many good 3D + * engines. + * + * These functions must be called from the main thread. See this bug for + * details: https://github.com/libsdl-org/SDL/issues/986 + */ + +#ifndef SDL_render_h_ +#define SDL_render_h_ + +#include "SDL_stdinc.h" +#include "SDL_rect.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Flags used when creating a rendering context + */ +typedef enum SDL_RendererFlags +{ + SDL_RENDERER_SOFTWARE = 0x00000001, /**< The renderer is a software fallback */ + SDL_RENDERER_ACCELERATED = 0x00000002, /**< The renderer uses hardware + acceleration */ + SDL_RENDERER_PRESENTVSYNC = 0x00000004, /**< Present is synchronized + with the refresh rate */ + SDL_RENDERER_TARGETTEXTURE = 0x00000008 /**< The renderer supports + rendering to texture */ +} SDL_RendererFlags; + +/** + * Information on the capabilities of a render driver or context. + */ +typedef struct SDL_RendererInfo +{ + const char *name; /**< The name of the renderer */ + Uint32 flags; /**< Supported SDL_RendererFlags */ + Uint32 num_texture_formats; /**< The number of available texture formats */ + Uint32 texture_formats[16]; /**< The available texture formats */ + int max_texture_width; /**< The maximum texture width */ + int max_texture_height; /**< The maximum texture height */ +} SDL_RendererInfo; + +/** + * Vertex structure + */ +typedef struct SDL_Vertex +{ + SDL_FPoint position; /**< Vertex position, in SDL_Renderer coordinates */ + SDL_Color color; /**< Vertex color */ + SDL_FPoint tex_coord; /**< Normalized texture coordinates, if needed */ +} SDL_Vertex; + +/** + * The scaling mode for a texture. + */ +typedef enum SDL_ScaleMode +{ + SDL_ScaleModeNearest, /**< nearest pixel sampling */ + SDL_ScaleModeLinear, /**< linear filtering */ + SDL_ScaleModeBest /**< anisotropic filtering */ +} SDL_ScaleMode; + +/** + * The access pattern allowed for a texture. + */ +typedef enum SDL_TextureAccess +{ + SDL_TEXTUREACCESS_STATIC, /**< Changes rarely, not lockable */ + SDL_TEXTUREACCESS_STREAMING, /**< Changes frequently, lockable */ + SDL_TEXTUREACCESS_TARGET /**< Texture can be used as a render target */ +} SDL_TextureAccess; + +/** + * The texture channel modulation used in SDL_RenderCopy(). + */ +typedef enum SDL_TextureModulate +{ + SDL_TEXTUREMODULATE_NONE = 0x00000000, /**< No modulation */ + SDL_TEXTUREMODULATE_COLOR = 0x00000001, /**< srcC = srcC * color */ + SDL_TEXTUREMODULATE_ALPHA = 0x00000002 /**< srcA = srcA * alpha */ +} SDL_TextureModulate; + +/** + * Flip constants for SDL_RenderCopyEx + */ +typedef enum SDL_RendererFlip +{ + SDL_FLIP_NONE = 0x00000000, /**< Do not flip */ + SDL_FLIP_HORIZONTAL = 0x00000001, /**< flip horizontally */ + SDL_FLIP_VERTICAL = 0x00000002 /**< flip vertically */ +} SDL_RendererFlip; + +/** + * A structure representing rendering state + */ +struct SDL_Renderer; +typedef struct SDL_Renderer SDL_Renderer; + +/** + * An efficient driver-specific representation of pixel data + */ +struct SDL_Texture; +typedef struct SDL_Texture SDL_Texture; + +/* Function prototypes */ + +/** + * Get the number of 2D rendering drivers available for the current display. + * + * A render driver is a set of code that handles rendering and texture + * management on a particular display. Normally there is only one, but some + * drivers may have several available with different capabilities. + * + * There may be none if SDL was compiled without render support. + * + * \returns a number >= 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + * \sa SDL_GetRenderDriverInfo + */ +extern DECLSPEC int SDLCALL SDL_GetNumRenderDrivers(void); + +/** + * Get info about a specific 2D rendering driver for the current display. + * + * \param index the index of the driver to query information about. + * \param info an SDL_RendererInfo structure to be filled with information on + * the rendering driver. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + * \sa SDL_GetNumRenderDrivers + */ +extern DECLSPEC int SDLCALL SDL_GetRenderDriverInfo(int index, + SDL_RendererInfo * info); + +/** + * Create a window and default renderer. + * + * \param width the width of the window. + * \param height the height of the window. + * \param window_flags the flags used to create the window (see + * SDL_CreateWindow()). + * \param window a pointer filled with the window, or NULL on error. + * \param renderer a pointer filled with the renderer, or NULL on error. + * \returns 0 on success, or -1 on error; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + * \sa SDL_CreateWindow + */ +extern DECLSPEC int SDLCALL SDL_CreateWindowAndRenderer( + int width, int height, Uint32 window_flags, + SDL_Window **window, SDL_Renderer **renderer); + + +/** + * Create a 2D rendering context for a window. + * + * \param window the window where rendering is displayed. + * \param index the index of the rendering driver to initialize, or -1 to + * initialize the first one supporting the requested flags. + * \param flags 0, or one or more SDL_RendererFlags OR'd together. + * \returns a valid rendering context or NULL if there was an error; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateSoftwareRenderer + * \sa SDL_DestroyRenderer + * \sa SDL_GetNumRenderDrivers + * \sa SDL_GetRendererInfo + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window, + int index, Uint32 flags); + +/** + * Create a 2D software rendering context for a surface. + * + * Two other API which can be used to create SDL_Renderer: + * SDL_CreateRenderer() and SDL_CreateWindowAndRenderer(). These can _also_ + * create a software renderer, but they are intended to be used with an + * SDL_Window as the final destination and not an SDL_Surface. + * + * \param surface the SDL_Surface structure representing the surface where + * rendering is done. + * \returns a valid rendering context or NULL if there was an error; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + * \sa SDL_CreateWindowAndRenderer + * \sa SDL_DestroyRenderer + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateSoftwareRenderer(SDL_Surface * surface); + +/** + * Get the renderer associated with a window. + * + * \param window the window to query. + * \returns the rendering context on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_GetRenderer(SDL_Window * window); + +/** + * Get the window associated with a renderer. + * + * \param renderer the renderer to query. + * \returns the window on success or NULL on failure; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_RenderGetWindow(SDL_Renderer *renderer); + +/** + * Get information about a rendering context. + * + * \param renderer the rendering context. + * \param info an SDL_RendererInfo structure filled with information about the + * current renderer. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + */ +extern DECLSPEC int SDLCALL SDL_GetRendererInfo(SDL_Renderer * renderer, + SDL_RendererInfo * info); + +/** + * Get the output size in pixels of a rendering context. + * + * Due to high-dpi displays, you might end up with a rendering context that + * has more pixels than the window that contains it, so use this instead of + * SDL_GetWindowSize() to decide how much drawing area you have. + * + * \param renderer the rendering context. + * \param w an int filled with the width. + * \param h an int filled with the height. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRenderer + */ +extern DECLSPEC int SDLCALL SDL_GetRendererOutputSize(SDL_Renderer * renderer, + int *w, int *h); + +/** + * Create a texture for a rendering context. + * + * You can set the texture scaling method by setting + * `SDL_HINT_RENDER_SCALE_QUALITY` before creating the texture. + * + * \param renderer the rendering context. + * \param format one of the enumerated values in SDL_PixelFormatEnum. + * \param access one of the enumerated values in SDL_TextureAccess. + * \param w the width of the texture in pixels. + * \param h the height of the texture in pixels. + * \returns a pointer to the created texture or NULL if no rendering context + * was active, the format was unsupported, or the width or height + * were out of range; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateTextureFromSurface + * \sa SDL_DestroyTexture + * \sa SDL_QueryTexture + * \sa SDL_UpdateTexture + */ +extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer, + Uint32 format, + int access, int w, + int h); + +/** + * Create a texture from an existing surface. + * + * The surface is not modified or freed by this function. + * + * The SDL_TextureAccess hint for the created texture is + * `SDL_TEXTUREACCESS_STATIC`. + * + * The pixel format of the created texture may be different from the pixel + * format of the surface. Use SDL_QueryTexture() to query the pixel format of + * the texture. + * + * \param renderer the rendering context. + * \param surface the SDL_Surface structure containing pixel data used to fill + * the texture. + * \returns the created texture or NULL on failure; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateTexture + * \sa SDL_DestroyTexture + * \sa SDL_QueryTexture + */ +extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface); + +/** + * Query the attributes of a texture. + * + * \param texture the texture to query. + * \param format a pointer filled in with the raw format of the texture; the + * actual format may differ, but pixel transfers will use this + * format (one of the SDL_PixelFormatEnum values). This argument + * can be NULL if you don't need this information. + * \param access a pointer filled in with the actual access to the texture + * (one of the SDL_TextureAccess values). This argument can be + * NULL if you don't need this information. + * \param w a pointer filled in with the width of the texture in pixels. This + * argument can be NULL if you don't need this information. + * \param h a pointer filled in with the height of the texture in pixels. This + * argument can be NULL if you don't need this information. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateTexture + */ +extern DECLSPEC int SDLCALL SDL_QueryTexture(SDL_Texture * texture, + Uint32 * format, int *access, + int *w, int *h); + +/** + * Set an additional color value multiplied into render copy operations. + * + * When this texture is rendered, during the copy operation each source color + * channel is modulated by the appropriate color value according to the + * following formula: + * + * `srcC = srcC * (color / 255)` + * + * Color modulation is not always supported by the renderer; it will return -1 + * if color modulation is not supported. + * + * \param texture the texture to update. + * \param r the red color value multiplied into copy operations. + * \param g the green color value multiplied into copy operations. + * \param b the blue color value multiplied into copy operations. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTextureColorMod + * \sa SDL_SetTextureAlphaMod + */ +extern DECLSPEC int SDLCALL SDL_SetTextureColorMod(SDL_Texture * texture, + Uint8 r, Uint8 g, Uint8 b); + + +/** + * Get the additional color value multiplied into render copy operations. + * + * \param texture the texture to query. + * \param r a pointer filled in with the current red color value. + * \param g a pointer filled in with the current green color value. + * \param b a pointer filled in with the current blue color value. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTextureAlphaMod + * \sa SDL_SetTextureColorMod + */ +extern DECLSPEC int SDLCALL SDL_GetTextureColorMod(SDL_Texture * texture, + Uint8 * r, Uint8 * g, + Uint8 * b); + +/** + * Set an additional alpha value multiplied into render copy operations. + * + * When this texture is rendered, during the copy operation the source alpha + * value is modulated by this alpha value according to the following formula: + * + * `srcA = srcA * (alpha / 255)` + * + * Alpha modulation is not always supported by the renderer; it will return -1 + * if alpha modulation is not supported. + * + * \param texture the texture to update. + * \param alpha the source alpha value multiplied into copy operations. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTextureAlphaMod + * \sa SDL_SetTextureColorMod + */ +extern DECLSPEC int SDLCALL SDL_SetTextureAlphaMod(SDL_Texture * texture, + Uint8 alpha); + +/** + * Get the additional alpha value multiplied into render copy operations. + * + * \param texture the texture to query. + * \param alpha a pointer filled in with the current alpha value. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTextureColorMod + * \sa SDL_SetTextureAlphaMod + */ +extern DECLSPEC int SDLCALL SDL_GetTextureAlphaMod(SDL_Texture * texture, + Uint8 * alpha); + +/** + * Set the blend mode for a texture, used by SDL_RenderCopy(). + * + * If the blend mode is not supported, the closest supported mode is chosen + * and this function returns -1. + * + * \param texture the texture to update. + * \param blendMode the SDL_BlendMode to use for texture blending. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTextureBlendMode + * \sa SDL_RenderCopy + */ +extern DECLSPEC int SDLCALL SDL_SetTextureBlendMode(SDL_Texture * texture, + SDL_BlendMode blendMode); + +/** + * Get the blend mode used for texture copy operations. + * + * \param texture the texture to query. + * \param blendMode a pointer filled in with the current SDL_BlendMode. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetTextureBlendMode + */ +extern DECLSPEC int SDLCALL SDL_GetTextureBlendMode(SDL_Texture * texture, + SDL_BlendMode *blendMode); + +/** + * Set the scale mode used for texture scale operations. + * + * If the scale mode is not supported, the closest supported mode is chosen. + * + * \param texture The texture to update. + * \param scaleMode the SDL_ScaleMode to use for texture scaling. + * \returns 0 on success, or -1 if the texture is not valid. + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_GetTextureScaleMode + */ +extern DECLSPEC int SDLCALL SDL_SetTextureScaleMode(SDL_Texture * texture, + SDL_ScaleMode scaleMode); + +/** + * Get the scale mode used for texture scale operations. + * + * \param texture the texture to query. + * \param scaleMode a pointer filled in with the current scale mode. + * \return 0 on success, or -1 if the texture is not valid. + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_SetTextureScaleMode + */ +extern DECLSPEC int SDLCALL SDL_GetTextureScaleMode(SDL_Texture * texture, + SDL_ScaleMode *scaleMode); + +/** + * Associate a user-specified pointer with a texture. + * + * \param texture the texture to update. + * \param userdata the pointer to associate with the texture. + * \returns 0 on success, or -1 if the texture is not valid. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_GetTextureUserData + */ +extern DECLSPEC int SDLCALL SDL_SetTextureUserData(SDL_Texture * texture, + void *userdata); + +/** + * Get the user-specified pointer associated with a texture + * + * \param texture the texture to query. + * \return the pointer associated with the texture, or NULL if the texture is + * not valid. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_SetTextureUserData + */ +extern DECLSPEC void * SDLCALL SDL_GetTextureUserData(SDL_Texture * texture); + +/** + * Update the given texture rectangle with new pixel data. + * + * The pixel data must be in the pixel format of the texture. Use + * SDL_QueryTexture() to query the pixel format of the texture. + * + * This is a fairly slow function, intended for use with static textures that + * do not change often. + * + * If the texture is intended to be updated often, it is preferred to create + * the texture as streaming and use the locking functions referenced below. + * While this function will work with streaming textures, for optimization + * reasons you may not get the pixels back if you lock the texture afterward. + * + * \param texture the texture to update. + * \param rect an SDL_Rect structure representing the area to update, or NULL + * to update the entire texture. + * \param pixels the raw pixel data in the format of the texture. + * \param pitch the number of bytes in a row of pixel data, including padding + * between lines. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateTexture + * \sa SDL_LockTexture + * \sa SDL_UnlockTexture + */ +extern DECLSPEC int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const void *pixels, int pitch); + +/** + * Update a rectangle within a planar YV12 or IYUV texture with new pixel + * data. + * + * You can use SDL_UpdateTexture() as long as your pixel data is a contiguous + * block of Y and U/V planes in the proper order, but this function is + * available if your pixel data is not contiguous. + * + * \param texture the texture to update. + * \param rect a pointer to the rectangle of pixels to update, or NULL to + * update the entire texture. + * \param Yplane the raw pixel data for the Y plane. + * \param Ypitch the number of bytes between rows of pixel data for the Y + * plane. + * \param Uplane the raw pixel data for the U plane. + * \param Upitch the number of bytes between rows of pixel data for the U + * plane. + * \param Vplane the raw pixel data for the V plane. + * \param Vpitch the number of bytes between rows of pixel data for the V + * plane. + * \returns 0 on success or -1 if the texture is not valid; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.1. + * + * \sa SDL_UpdateTexture + */ +extern DECLSPEC int SDLCALL SDL_UpdateYUVTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *Uplane, int Upitch, + const Uint8 *Vplane, int Vpitch); + +/** + * Update a rectangle within a planar NV12 or NV21 texture with new pixels. + * + * You can use SDL_UpdateTexture() as long as your pixel data is a contiguous + * block of NV12/21 planes in the proper order, but this function is available + * if your pixel data is not contiguous. + * + * \param texture the texture to update. + * \param rect a pointer to the rectangle of pixels to update, or NULL to + * update the entire texture. + * \param Yplane the raw pixel data for the Y plane. + * \param Ypitch the number of bytes between rows of pixel data for the Y + * plane. + * \param UVplane the raw pixel data for the UV plane. + * \param UVpitch the number of bytes between rows of pixel data for the UV + * plane. + * \return 0 on success, or -1 if the texture is not valid. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC int SDLCALL SDL_UpdateNVTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch); + +/** + * Lock a portion of the texture for **write-only** pixel access. + * + * As an optimization, the pixels made available for editing don't necessarily + * contain the old texture data. This is a write-only operation, and if you + * need to keep a copy of the texture data you should do that at the + * application level. + * + * You must use SDL_UnlockTexture() to unlock the pixels and apply any + * changes. + * + * \param texture the texture to lock for access, which was created with + * `SDL_TEXTUREACCESS_STREAMING`. + * \param rect an SDL_Rect structure representing the area to lock for access; + * NULL to lock the entire texture. + * \param pixels this is filled in with a pointer to the locked pixels, + * appropriately offset by the locked area. + * \param pitch this is filled in with the pitch of the locked pixels; the + * pitch is the length of one row in bytes. + * \returns 0 on success or a negative error code if the texture is not valid + * or was not created with `SDL_TEXTUREACCESS_STREAMING`; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_UnlockTexture + */ +extern DECLSPEC int SDLCALL SDL_LockTexture(SDL_Texture * texture, + const SDL_Rect * rect, + void **pixels, int *pitch); + +/** + * Lock a portion of the texture for **write-only** pixel access, and expose + * it as a SDL surface. + * + * Besides providing an SDL_Surface instead of raw pixel data, this function + * operates like SDL_LockTexture. + * + * As an optimization, the pixels made available for editing don't necessarily + * contain the old texture data. This is a write-only operation, and if you + * need to keep a copy of the texture data you should do that at the + * application level. + * + * You must use SDL_UnlockTexture() to unlock the pixels and apply any + * changes. + * + * The returned surface is freed internally after calling SDL_UnlockTexture() + * or SDL_DestroyTexture(). The caller should not free it. + * + * \param texture the texture to lock for access, which was created with + * `SDL_TEXTUREACCESS_STREAMING`. + * \param rect a pointer to the rectangle to lock for access. If the rect is + * NULL, the entire texture will be locked. + * \param surface this is filled in with an SDL surface representing the + * locked area. + * \returns 0 on success, or -1 if the texture is not valid or was not created + * with `SDL_TEXTUREACCESS_STREAMING`. + * + * \since This function is available since SDL 2.0.12. + * + * \sa SDL_LockTexture + * \sa SDL_UnlockTexture + */ +extern DECLSPEC int SDLCALL SDL_LockTextureToSurface(SDL_Texture *texture, + const SDL_Rect *rect, + SDL_Surface **surface); + +/** + * Unlock a texture, uploading the changes to video memory, if needed. + * + * **Warning**: Please note that SDL_LockTexture() is intended to be + * write-only; it will not guarantee the previous contents of the texture will + * be provided. You must fully initialize any area of a texture that you lock + * before unlocking it, as the pixels might otherwise be uninitialized memory. + * + * Which is to say: locking and immediately unlocking a texture can result in + * corrupted textures, depending on the renderer in use. + * + * \param texture a texture locked by SDL_LockTexture(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LockTexture + */ +extern DECLSPEC void SDLCALL SDL_UnlockTexture(SDL_Texture * texture); + +/** + * Determine whether a renderer supports the use of render targets. + * + * \param renderer the renderer that will be checked. + * \returns SDL_TRUE if supported or SDL_FALSE if not. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetRenderTarget + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RenderTargetSupported(SDL_Renderer *renderer); + +/** + * Set a texture as the current rendering target. + * + * Before using this function, you should check the + * `SDL_RENDERER_TARGETTEXTURE` bit in the flags of SDL_RendererInfo to see if + * render targets are supported. + * + * The default render target is the window for which the renderer was created. + * To stop rendering to a texture and render to the window again, call this + * function with a NULL `texture`. This will reset the renderer's viewport, + * clipping rectangle, and scaling settings to the state they were in before + * setting a non-NULL `texture` target, losing any changes made in the + * meantime. + * + * \param renderer the rendering context. + * \param texture the targeted texture, which must be created with the + * `SDL_TEXTUREACCESS_TARGET` flag, or NULL to render to the + * window instead of a texture. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRenderTarget + */ +extern DECLSPEC int SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer, + SDL_Texture *texture); + +/** + * Get the current render target. + * + * The default render target is the window for which the renderer was created, + * and is reported as NULL here. + * + * \param renderer the rendering context. + * \returns the current render target or NULL for the default render target. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetRenderTarget + */ +extern DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *renderer); + +/** + * Set a device independent resolution for rendering. + * + * This function uses the viewport and scaling functionality to allow a fixed + * logical resolution for rendering, regardless of the actual output + * resolution. If the actual output resolution doesn't have the same aspect + * ratio the output rendering will be centered within the output display. + * + * If the output display is a window, mouse and touch events in the window + * will be filtered and scaled so they seem to arrive within the logical + * resolution. The SDL_HINT_MOUSE_RELATIVE_SCALING hint controls whether + * relative motion events are also scaled. + * + * If this function results in scaling or subpixel drawing by the rendering + * backend, it will be handled using the appropriate quality hints. + * + * \param renderer the renderer for which resolution should be set. + * \param w the width of the logical resolution. + * \param h the height of the logical resolution. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderGetLogicalSize + */ +extern DECLSPEC int SDLCALL SDL_RenderSetLogicalSize(SDL_Renderer * renderer, int w, int h); + +/** + * Get device independent resolution for rendering. + * + * When using the main rendering target (eg no target texture is set): this + * may return 0 for `w` and `h` if the SDL_Renderer has never had its logical + * size set by SDL_RenderSetLogicalSize(). Otherwise it returns the logical + * width and height. + * + * When using a target texture: Never return 0 for `w` and `h` at first. Then + * it returns the logical width and height that are set. + * + * \param renderer a rendering context. + * \param w an int to be filled with the width. + * \param h an int to be filled with the height. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderSetLogicalSize + */ +extern DECLSPEC void SDLCALL SDL_RenderGetLogicalSize(SDL_Renderer * renderer, int *w, int *h); + +/** + * Set whether to force integer scales for resolution-independent rendering. + * + * This function restricts the logical viewport to integer values - that is, + * when a resolution is between two multiples of a logical size, the viewport + * size is rounded down to the lower multiple. + * + * \param renderer the renderer for which integer scaling should be set. + * \param enable enable or disable the integer scaling for rendering. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_RenderGetIntegerScale + * \sa SDL_RenderSetLogicalSize + */ +extern DECLSPEC int SDLCALL SDL_RenderSetIntegerScale(SDL_Renderer * renderer, + SDL_bool enable); + +/** + * Get whether integer scales are forced for resolution-independent rendering. + * + * \param renderer the renderer from which integer scaling should be queried. + * \returns SDL_TRUE if integer scales are forced or SDL_FALSE if not and on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_RenderSetIntegerScale + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RenderGetIntegerScale(SDL_Renderer * renderer); + +/** + * Set the drawing area for rendering on the current target. + * + * When the window is resized, the viewport is reset to fill the entire new + * window size. + * + * \param renderer the rendering context. + * \param rect the SDL_Rect structure representing the drawing area, or NULL + * to set the viewport to the entire target. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderGetViewport + */ +extern DECLSPEC int SDLCALL SDL_RenderSetViewport(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * Get the drawing area for the current target. + * + * \param renderer the rendering context. + * \param rect an SDL_Rect structure filled in with the current drawing area. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderSetViewport + */ +extern DECLSPEC void SDLCALL SDL_RenderGetViewport(SDL_Renderer * renderer, + SDL_Rect * rect); + +/** + * Set the clip rectangle for rendering on the specified target. + * + * \param renderer the rendering context for which clip rectangle should be + * set. + * \param rect an SDL_Rect structure representing the clip area, relative to + * the viewport, or NULL to disable clipping. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderGetClipRect + * \sa SDL_RenderIsClipEnabled + */ +extern DECLSPEC int SDLCALL SDL_RenderSetClipRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * Get the clip rectangle for the current target. + * + * \param renderer the rendering context from which clip rectangle should be + * queried. + * \param rect an SDL_Rect structure filled in with the current clipping area + * or an empty rectangle if clipping is disabled. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderIsClipEnabled + * \sa SDL_RenderSetClipRect + */ +extern DECLSPEC void SDLCALL SDL_RenderGetClipRect(SDL_Renderer * renderer, + SDL_Rect * rect); + +/** + * Get whether clipping is enabled on the given renderer. + * + * \param renderer the renderer from which clip state should be queried. + * \returns SDL_TRUE if clipping is enabled or SDL_FALSE if not; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_RenderGetClipRect + * \sa SDL_RenderSetClipRect + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RenderIsClipEnabled(SDL_Renderer * renderer); + + +/** + * Set the drawing scale for rendering on the current target. + * + * The drawing coordinates are scaled by the x/y scaling factors before they + * are used by the renderer. This allows resolution independent drawing with a + * single coordinate system. + * + * If this results in scaling or subpixel drawing by the rendering backend, it + * will be handled using the appropriate quality hints. For best results use + * integer scaling factors. + * + * \param renderer a rendering context. + * \param scaleX the horizontal scaling factor. + * \param scaleY the vertical scaling factor. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderGetScale + * \sa SDL_RenderSetLogicalSize + */ +extern DECLSPEC int SDLCALL SDL_RenderSetScale(SDL_Renderer * renderer, + float scaleX, float scaleY); + +/** + * Get the drawing scale for the current target. + * + * \param renderer the renderer from which drawing scale should be queried. + * \param scaleX a pointer filled in with the horizontal scaling factor. + * \param scaleY a pointer filled in with the vertical scaling factor. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderSetScale + */ +extern DECLSPEC void SDLCALL SDL_RenderGetScale(SDL_Renderer * renderer, + float *scaleX, float *scaleY); + +/** + * Get logical coordinates of point in renderer when given real coordinates of + * point in window. + * + * Logical coordinates will differ from real coordinates when render is scaled + * and logical renderer size set + * + * \param renderer the renderer from which the logical coordinates should be + * calculated. + * \param windowX the real X coordinate in the window. + * \param windowY the real Y coordinate in the window. + * \param logicalX the pointer filled with the logical x coordinate. + * \param logicalY the pointer filled with the logical y coordinate. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_RenderGetScale + * \sa SDL_RenderSetScale + * \sa SDL_RenderGetLogicalSize + * \sa SDL_RenderSetLogicalSize + */ +extern DECLSPEC void SDLCALL SDL_RenderWindowToLogical(SDL_Renderer * renderer, + int windowX, int windowY, + float *logicalX, float *logicalY); + + +/** + * Get real coordinates of point in window when given logical coordinates of + * point in renderer. + * + * Logical coordinates will differ from real coordinates when render is scaled + * and logical renderer size set + * + * \param renderer the renderer from which the window coordinates should be + * calculated. + * \param logicalX the logical x coordinate. + * \param logicalY the logical y coordinate. + * \param windowX the pointer filled with the real X coordinate in the window. + * \param windowY the pointer filled with the real Y coordinate in the window. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_RenderGetScale + * \sa SDL_RenderSetScale + * \sa SDL_RenderGetLogicalSize + * \sa SDL_RenderSetLogicalSize + */ +extern DECLSPEC void SDLCALL SDL_RenderLogicalToWindow(SDL_Renderer * renderer, + float logicalX, float logicalY, + int *windowX, int *windowY); + +/** + * Set the color used for drawing operations (Rect, Line and Clear). + * + * Set the color for drawing or filling rectangles, lines, and points, and for + * SDL_RenderClear(). + * + * \param renderer the rendering context. + * \param r the red value used to draw on the rendering target. + * \param g the green value used to draw on the rendering target. + * \param b the blue value used to draw on the rendering target. + * \param a the alpha value used to draw on the rendering target; usually + * `SDL_ALPHA_OPAQUE` (255). Use SDL_SetRenderDrawBlendMode to + * specify how the alpha channel is used. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRenderDrawColor + * \sa SDL_RenderClear + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + */ +extern DECLSPEC int SDLCALL SDL_SetRenderDrawColor(SDL_Renderer * renderer, + Uint8 r, Uint8 g, Uint8 b, + Uint8 a); + +/** + * Get the color used for drawing operations (Rect, Line and Clear). + * + * \param renderer the rendering context. + * \param r a pointer filled in with the red value used to draw on the + * rendering target. + * \param g a pointer filled in with the green value used to draw on the + * rendering target. + * \param b a pointer filled in with the blue value used to draw on the + * rendering target. + * \param a a pointer filled in with the alpha value used to draw on the + * rendering target; usually `SDL_ALPHA_OPAQUE` (255). + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_GetRenderDrawColor(SDL_Renderer * renderer, + Uint8 * r, Uint8 * g, Uint8 * b, + Uint8 * a); + +/** + * Set the blend mode used for drawing operations (Fill and Line). + * + * If the blend mode is not supported, the closest supported mode is chosen. + * + * \param renderer the rendering context. + * \param blendMode the SDL_BlendMode to use for blending. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRenderDrawBlendMode + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + */ +extern DECLSPEC int SDLCALL SDL_SetRenderDrawBlendMode(SDL_Renderer * renderer, + SDL_BlendMode blendMode); + +/** + * Get the blend mode used for drawing operations. + * + * \param renderer the rendering context. + * \param blendMode a pointer filled in with the current SDL_BlendMode. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetRenderDrawBlendMode + */ +extern DECLSPEC int SDLCALL SDL_GetRenderDrawBlendMode(SDL_Renderer * renderer, + SDL_BlendMode *blendMode); + +/** + * Clear the current rendering target with the drawing color. + * + * This function clears the entire rendering target, ignoring the viewport and + * the clip rectangle. + * + * \param renderer the rendering context. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderClear(SDL_Renderer * renderer); + +/** + * Draw a point on the current rendering target. + * + * SDL_RenderDrawPoint() draws a single point. If you want to draw multiple, + * use SDL_RenderDrawPoints() instead. + * + * \param renderer the rendering context. + * \param x the x coordinate of the point. + * \param y the y coordinate of the point. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawPoint(SDL_Renderer * renderer, + int x, int y); + +/** + * Draw multiple points on the current rendering target. + * + * \param renderer the rendering context. + * \param points an array of SDL_Point structures that represent the points to + * draw. + * \param count the number of points to draw. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawPoints(SDL_Renderer * renderer, + const SDL_Point * points, + int count); + +/** + * Draw a line on the current rendering target. + * + * SDL_RenderDrawLine() draws the line to include both end points. If you want + * to draw multiple, connecting lines use SDL_RenderDrawLines() instead. + * + * \param renderer the rendering context. + * \param x1 the x coordinate of the start point. + * \param y1 the y coordinate of the start point. + * \param x2 the x coordinate of the end point. + * \param y2 the y coordinate of the end point. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawLine(SDL_Renderer * renderer, + int x1, int y1, int x2, int y2); + +/** + * Draw a series of connected lines on the current rendering target. + * + * \param renderer the rendering context. + * \param points an array of SDL_Point structures representing points along + * the lines. + * \param count the number of points, drawing count-1 lines. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawLines(SDL_Renderer * renderer, + const SDL_Point * points, + int count); + +/** + * Draw a rectangle on the current rendering target. + * + * \param renderer the rendering context. + * \param rect an SDL_Rect structure representing the rectangle to draw, or + * NULL to outline the entire rendering target. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * Draw some number of rectangles on the current rendering target. + * + * \param renderer the rendering context. + * \param rects an array of SDL_Rect structures representing the rectangles to + * be drawn. + * \param count the number of rectangles. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawRects(SDL_Renderer * renderer, + const SDL_Rect * rects, + int count); + +/** + * Fill a rectangle on the current rendering target with the drawing color. + * + * The current drawing color is set by SDL_SetRenderDrawColor(), and the + * color's alpha value is ignored unless blending is enabled with the + * appropriate call to SDL_SetRenderDrawBlendMode(). + * + * \param renderer the rendering context. + * \param rect the SDL_Rect structure representing the rectangle to fill, or + * NULL for the entire rendering target. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRects + * \sa SDL_RenderPresent + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC int SDLCALL SDL_RenderFillRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * Fill some number of rectangles on the current rendering target with the + * drawing color. + * + * \param renderer the rendering context. + * \param rects an array of SDL_Rect structures representing the rectangles to + * be filled. + * \param count the number of rectangles. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderPresent + */ +extern DECLSPEC int SDLCALL SDL_RenderFillRects(SDL_Renderer * renderer, + const SDL_Rect * rects, + int count); + +/** + * Copy a portion of the texture to the current rendering target. + * + * The texture is blended with the destination based on its blend mode set + * with SDL_SetTextureBlendMode(). + * + * The texture color is affected based on its color modulation set by + * SDL_SetTextureColorMod(). + * + * The texture alpha is affected based on its alpha modulation set by + * SDL_SetTextureAlphaMod(). + * + * \param renderer the rendering context. + * \param texture the source texture. + * \param srcrect the source SDL_Rect structure or NULL for the entire + * texture. + * \param dstrect the destination SDL_Rect structure or NULL for the entire + * rendering target; the texture will be stretched to fill the + * given rectangle. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderCopyEx + * \sa SDL_SetTextureAlphaMod + * \sa SDL_SetTextureBlendMode + * \sa SDL_SetTextureColorMod + */ +extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_Rect * dstrect); + +/** + * Copy a portion of the texture to the current rendering, with optional + * rotation and flipping. + * + * Copy a portion of the texture to the current rendering target, optionally + * rotating it by angle around the given center and also flipping it + * top-bottom and/or left-right. + * + * The texture is blended with the destination based on its blend mode set + * with SDL_SetTextureBlendMode(). + * + * The texture color is affected based on its color modulation set by + * SDL_SetTextureColorMod(). + * + * The texture alpha is affected based on its alpha modulation set by + * SDL_SetTextureAlphaMod(). + * + * \param renderer the rendering context. + * \param texture the source texture. + * \param srcrect the source SDL_Rect structure or NULL for the entire + * texture. + * \param dstrect the destination SDL_Rect structure or NULL for the entire + * rendering target. + * \param angle an angle in degrees that indicates the rotation that will be + * applied to dstrect, rotating it in a clockwise direction. + * \param center a pointer to a point indicating the point around which + * dstrect will be rotated (if NULL, rotation will be done + * around `dstrect.w / 2`, `dstrect.h / 2`). + * \param flip a SDL_RendererFlip value stating which flipping actions should + * be performed on the texture. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RenderCopy + * \sa SDL_SetTextureAlphaMod + * \sa SDL_SetTextureBlendMode + * \sa SDL_SetTextureColorMod + */ +extern DECLSPEC int SDLCALL SDL_RenderCopyEx(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_Rect * dstrect, + const double angle, + const SDL_Point *center, + const SDL_RendererFlip flip); + + +/** + * Draw a point on the current rendering target at subpixel precision. + * + * \param renderer The renderer which should draw a point. + * \param x The x coordinate of the point. + * \param y The y coordinate of the point. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawPointF(SDL_Renderer * renderer, + float x, float y); + +/** + * Draw multiple points on the current rendering target at subpixel precision. + * + * \param renderer The renderer which should draw multiple points. + * \param points The points to draw. + * \param count The number of points to draw. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawPointsF(SDL_Renderer * renderer, + const SDL_FPoint * points, + int count); + +/** + * Draw a line on the current rendering target at subpixel precision. + * + * \param renderer The renderer which should draw a line. + * \param x1 The x coordinate of the start point. + * \param y1 The y coordinate of the start point. + * \param x2 The x coordinate of the end point. + * \param y2 The y coordinate of the end point. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawLineF(SDL_Renderer * renderer, + float x1, float y1, float x2, float y2); + +/** + * Draw a series of connected lines on the current rendering target at + * subpixel precision. + * + * \param renderer The renderer which should draw multiple lines. + * \param points The points along the lines. + * \param count The number of points, drawing count-1 lines. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawLinesF(SDL_Renderer * renderer, + const SDL_FPoint * points, + int count); + +/** + * Draw a rectangle on the current rendering target at subpixel precision. + * + * \param renderer The renderer which should draw a rectangle. + * \param rect A pointer to the destination rectangle, or NULL to outline the + * entire rendering target. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawRectF(SDL_Renderer * renderer, + const SDL_FRect * rect); + +/** + * Draw some number of rectangles on the current rendering target at subpixel + * precision. + * + * \param renderer The renderer which should draw multiple rectangles. + * \param rects A pointer to an array of destination rectangles. + * \param count The number of rectangles. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawRectsF(SDL_Renderer * renderer, + const SDL_FRect * rects, + int count); + +/** + * Fill a rectangle on the current rendering target with the drawing color at + * subpixel precision. + * + * \param renderer The renderer which should fill a rectangle. + * \param rect A pointer to the destination rectangle, or NULL for the entire + * rendering target. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderFillRectF(SDL_Renderer * renderer, + const SDL_FRect * rect); + +/** + * Fill some number of rectangles on the current rendering target with the + * drawing color at subpixel precision. + * + * \param renderer The renderer which should fill multiple rectangles. + * \param rects A pointer to an array of destination rectangles. + * \param count The number of rectangles. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderFillRectsF(SDL_Renderer * renderer, + const SDL_FRect * rects, + int count); + +/** + * Copy a portion of the texture to the current rendering target at subpixel + * precision. + * + * \param renderer The renderer which should copy parts of a texture. + * \param texture The source texture. + * \param srcrect A pointer to the source rectangle, or NULL for the entire + * texture. + * \param dstrect A pointer to the destination rectangle, or NULL for the + * entire rendering target. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderCopyF(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_FRect * dstrect); + +/** + * Copy a portion of the source texture to the current rendering target, with + * rotation and flipping, at subpixel precision. + * + * \param renderer The renderer which should copy parts of a texture. + * \param texture The source texture. + * \param srcrect A pointer to the source rectangle, or NULL for the entire + * texture. + * \param dstrect A pointer to the destination rectangle, or NULL for the + * entire rendering target. + * \param angle An angle in degrees that indicates the rotation that will be + * applied to dstrect, rotating it in a clockwise direction. + * \param center A pointer to a point indicating the point around which + * dstrect will be rotated (if NULL, rotation will be done + * around dstrect.w/2, dstrect.h/2). + * \param flip An SDL_RendererFlip value stating which flipping actions should + * be performed on the texture. + * \return 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderCopyExF(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_FRect * dstrect, + const double angle, + const SDL_FPoint *center, + const SDL_RendererFlip flip); + +/** + * Render a list of triangles, optionally using a texture and indices into the + * vertex array Color and alpha modulation is done per vertex + * (SDL_SetTextureColorMod and SDL_SetTextureAlphaMod are ignored). + * + * \param renderer The rendering context. + * \param texture (optional) The SDL texture to use. + * \param vertices Vertices. + * \param num_vertices Number of vertices. + * \param indices (optional) An array of integer indices into the 'vertices' + * array, if NULL all vertices will be rendered in sequential + * order. + * \param num_indices Number of indices. + * \return 0 on success, or -1 if the operation is not supported. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_RenderGeometryRaw + * \sa SDL_Vertex + */ +extern DECLSPEC int SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer, + SDL_Texture *texture, + const SDL_Vertex *vertices, int num_vertices, + const int *indices, int num_indices); + +/** + * Render a list of triangles, optionally using a texture and indices into the + * vertex arrays Color and alpha modulation is done per vertex + * (SDL_SetTextureColorMod and SDL_SetTextureAlphaMod are ignored). + * + * \param renderer The rendering context. + * \param texture (optional) The SDL texture to use. + * \param xy Vertex positions. + * \param xy_stride Byte size to move from one element to the next element. + * \param color Vertex colors (as SDL_Color). + * \param color_stride Byte size to move from one element to the next element. + * \param uv Vertex normalized texture coordinates. + * \param uv_stride Byte size to move from one element to the next element. + * \param num_vertices Number of vertices. + * \param indices (optional) An array of indices into the 'vertices' arrays, + * if NULL all vertices will be rendered in sequential order. + * \param num_indices Number of indices. + * \param size_indices Index size: 1 (byte), 2 (short), 4 (int). + * \return 0 on success, or -1 if the operation is not supported. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_RenderGeometry + * \sa SDL_Vertex + */ +extern DECLSPEC int SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, + SDL_Texture *texture, + const float *xy, int xy_stride, + const SDL_Color *color, int color_stride, + const float *uv, int uv_stride, + int num_vertices, + const void *indices, int num_indices, int size_indices); + +/** + * Read pixels from the current rendering target to an array of pixels. + * + * **WARNING**: This is a very slow operation, and should not be used + * frequently. If you're using this on the main rendering target, it should be + * called after rendering and before SDL_RenderPresent(). + * + * `pitch` specifies the number of bytes between rows in the destination + * `pixels` data. This allows you to write to a subrectangle or have padded + * rows in the destination. Generally, `pitch` should equal the number of + * pixels per row in the `pixels` data times the number of bytes per pixel, + * but it might contain additional padding (for example, 24bit RGB Windows + * Bitmap data pads all rows to multiples of 4 bytes). + * + * \param renderer the rendering context. + * \param rect an SDL_Rect structure representing the area to read, or NULL + * for the entire render target. + * \param format an SDL_PixelFormatEnum value of the desired format of the + * pixel data, or 0 to use the format of the rendering target. + * \param pixels a pointer to the pixel data to copy into. + * \param pitch the pitch of the `pixels` parameter. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_RenderReadPixels(SDL_Renderer * renderer, + const SDL_Rect * rect, + Uint32 format, + void *pixels, int pitch); + +/** + * Update the screen with any rendering performed since the previous call. + * + * SDL's rendering functions operate on a backbuffer; that is, calling a + * rendering function such as SDL_RenderDrawLine() does not directly put a + * line on the screen, but rather updates the backbuffer. As such, you compose + * your entire scene and *present* the composed backbuffer to the screen as a + * complete picture. + * + * Therefore, when using SDL's rendering API, one does all drawing intended + * for the frame, and then calls this function once per frame to present the + * final drawing to the user. + * + * The backbuffer should be considered invalidated after each present; do not + * assume that previous contents will exist between frames. You are strongly + * encouraged to call SDL_RenderClear() to initialize the backbuffer before + * starting each new frame's drawing, even if you plan to overwrite every + * pixel. + * + * \param renderer the rendering context. + * + * \threadsafety You may only call this function on the main thread. If this + * happens to work on a background thread on any given platform + * or backend, it's purely by luck and you should not rely on it + * to work next time. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + * \sa SDL_RenderClear + * \sa SDL_RenderDrawLine + * \sa SDL_RenderDrawLines + * \sa SDL_RenderDrawPoint + * \sa SDL_RenderDrawPoints + * \sa SDL_RenderDrawRect + * \sa SDL_RenderDrawRects + * \sa SDL_RenderFillRect + * \sa SDL_RenderFillRects + * \sa SDL_SetRenderDrawBlendMode + * \sa SDL_SetRenderDrawColor + */ +extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer); + +/** + * Destroy the specified texture. + * + * Passing NULL or an otherwise invalid texture will set the SDL error message + * to "Invalid texture". + * + * \param texture the texture to destroy. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateTexture + * \sa SDL_CreateTextureFromSurface + */ +extern DECLSPEC void SDLCALL SDL_DestroyTexture(SDL_Texture * texture); + +/** + * Destroy the rendering context for a window and free associated textures. + * + * If `renderer` is NULL, this function will return immediately after setting + * the SDL error message to "Invalid renderer". See SDL_GetError(). + * + * \param renderer the rendering context. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRenderer + */ +extern DECLSPEC void SDLCALL SDL_DestroyRenderer(SDL_Renderer * renderer); + +/** + * Force the rendering context to flush any pending commands to the underlying + * rendering API. + * + * You do not need to (and in fact, shouldn't) call this function unless you + * are planning to call into OpenGL/Direct3D/Metal/whatever directly in + * addition to using an SDL_Renderer. + * + * This is for a very-specific case: if you are using SDL's render API, you + * asked for a specific renderer backend (OpenGL, Direct3D, etc), you set + * SDL_HINT_RENDER_BATCHING to "1", and you plan to make OpenGL/D3D/whatever + * calls in addition to SDL render API calls. If all of this applies, you + * should call SDL_RenderFlush() between calls to SDL's render API and the + * low-level API you're using in cooperation. + * + * In all other cases, you can ignore this function. This is only here to get + * maximum performance out of a specific situation. In all other cases, SDL + * will do the right thing, perhaps at a performance loss. + * + * This function is first available in SDL 2.0.10, and is not needed in 2.0.9 + * and earlier, as earlier versions did not queue rendering commands at all, + * instead flushing them to the OS immediately. + * + * \param renderer the rendering context. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC int SDLCALL SDL_RenderFlush(SDL_Renderer * renderer); + + +/** + * Bind an OpenGL/ES/ES2 texture to the current context. + * + * This is for use with OpenGL instructions when rendering OpenGL primitives + * directly. + * + * If not NULL, `texw` and `texh` will be filled with the width and height + * values suitable for the provided texture. In most cases, both will be 1.0, + * however, on systems that support the GL_ARB_texture_rectangle extension, + * these values will actually be the pixel width and height used to create the + * texture, so this factor needs to be taken into account when providing + * texture coordinates to OpenGL. + * + * You need a renderer to create an SDL_Texture, therefore you can only use + * this function with an implicit OpenGL context from SDL_CreateRenderer(), + * not with your own OpenGL context. If you need control over your OpenGL + * context, you need to write your own texture-loading methods. + * + * Also note that SDL may upload RGB textures as BGR (or vice-versa), and + * re-order the color channels in the shaders phase, so the uploaded texture + * may have swapped color channels. + * + * \param texture the texture to bind to the current OpenGL/ES/ES2 context. + * \param texw a pointer to a float value which will be filled with the + * texture width or NULL if you don't need that value. + * \param texh a pointer to a float value which will be filled with the + * texture height or NULL if you don't need that value. + * \returns 0 on success, or -1 if the operation is not supported; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_MakeCurrent + * \sa SDL_GL_UnbindTexture + */ +extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw, float *texh); + +/** + * Unbind an OpenGL/ES/ES2 texture from the current context. + * + * See SDL_GL_BindTexture() for examples on how to use these functions + * + * \param texture the texture to unbind from the current OpenGL/ES/ES2 + * context. + * \returns 0 on success, or -1 if the operation is not supported. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_BindTexture + * \sa SDL_GL_MakeCurrent + */ +extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture); + +/** + * Get the CAMetalLayer associated with the given Metal renderer. + * + * This function returns `void *`, so SDL doesn't have to include Metal's + * headers, but it can be safely cast to a `CAMetalLayer *`. + * + * \param renderer The renderer to query. + * \returns a `CAMetalLayer *` on success, or NULL if the renderer isn't a + * Metal renderer. + * + * \since This function is available since SDL 2.0.8. + * + * \sa SDL_RenderGetMetalCommandEncoder + */ +extern DECLSPEC void *SDLCALL SDL_RenderGetMetalLayer(SDL_Renderer * renderer); + +/** + * Get the Metal command encoder for the current frame + * + * This function returns `void *`, so SDL doesn't have to include Metal's + * headers, but it can be safely cast to an `id`. + * + * Note that as of SDL 2.0.18, this will return NULL if Metal refuses to give + * SDL a drawable to render to, which might happen if the window is + * hidden/minimized/offscreen. This doesn't apply to command encoders for + * render targets, just the window's backbuffer. Check your return values! + * + * \param renderer The renderer to query. + * \returns an `id` on success, or NULL if the + * renderer isn't a Metal renderer or there was an error. + * + * \since This function is available since SDL 2.0.8. + * + * \sa SDL_RenderGetMetalLayer + */ +extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer); + +/** + * Toggle VSync of the given renderer. + * + * \param renderer The renderer to toggle. + * \param vsync 1 for on, 0 for off. All other values are reserved. + * \returns a 0 int on success, or non-zero on failure. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_RenderSetVSync(SDL_Renderer* renderer, int vsync); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_render_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_revision.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_revision.h new file mode 100644 index 00000000..e2aea6ed --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_revision.h @@ -0,0 +1,8 @@ +/* #undef SDL_VENDOR_INFO */ +#define SDL_REVISION_NUMBER 0 + +#ifdef SDL_VENDOR_INFO +#define SDL_REVISION "SDL-release-2.32.10-xbox-uwp (" SDL_VENDOR_INFO ")" +#else +#define SDL_REVISION "SDL-release-2.32.10-xbox-uwp" +#endif diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_rwops.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_rwops.h new file mode 100644 index 00000000..43c1b0ec --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_rwops.h @@ -0,0 +1,844 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: RWOPS */ + +/** + * # CategoryRWOPS + * + * This file provides a general interface for SDL to read and write data + * streams. It can easily be extended to files, memory, etc. + */ + +#ifndef SDL_rwops_h_ +#define SDL_rwops_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* RWops Types */ +#define SDL_RWOPS_UNKNOWN 0U /**< Unknown stream type */ +#define SDL_RWOPS_WINFILE 1U /**< Win32 file */ +#define SDL_RWOPS_STDFILE 2U /**< Stdio file */ +#define SDL_RWOPS_JNIFILE 3U /**< Android asset */ +#define SDL_RWOPS_MEMORY 4U /**< Memory stream */ +#define SDL_RWOPS_MEMORY_RO 5U /**< Read-Only memory stream */ + +/** + * This is the read/write operation structure -- very basic. + */ +typedef struct SDL_RWops +{ + /** + * Return the size of the file in this rwops, or -1 if unknown + */ + Sint64 (SDLCALL * size) (struct SDL_RWops * context); + + /** + * Seek to `offset` relative to `whence`, one of stdio's whence values: + * RW_SEEK_SET, RW_SEEK_CUR, RW_SEEK_END + * + * \return the final offset in the data stream, or -1 on error. + */ + Sint64 (SDLCALL * seek) (struct SDL_RWops * context, Sint64 offset, + int whence); + + /** + * Read up to `maxnum` objects each of size `size` from the data + * stream to the area pointed at by `ptr`. + * + * \return the number of objects read, or 0 at error or end of file. + */ + size_t (SDLCALL * read) (struct SDL_RWops * context, void *ptr, + size_t size, size_t maxnum); + + /** + * Write exactly `num` objects each of size `size` from the area + * pointed at by `ptr` to data stream. + * + * \return the number of objects written, or 0 at error or end of file. + */ + size_t (SDLCALL * write) (struct SDL_RWops * context, const void *ptr, + size_t size, size_t num); + + /** + * Close and free an allocated SDL_RWops structure. + * + * \return 0 if successful or -1 on write error when flushing data. + */ + int (SDLCALL * close) (struct SDL_RWops * context); + + Uint32 type; + union + { +#if defined(__ANDROID__) + struct + { + void *asset; + } androidio; +#elif defined(__WIN32__) || defined(__GDK__) + struct + { + SDL_bool append; + void *h; + struct + { + void *data; + size_t size; + size_t left; + } buffer; + } windowsio; +#endif + +#ifdef HAVE_STDIO_H + struct + { + SDL_bool autoclose; + FILE *fp; + } stdio; +#endif + struct + { + Uint8 *base; + Uint8 *here; + Uint8 *stop; + } mem; + struct + { + void *data1; + void *data2; + } unknown; + } hidden; + +} SDL_RWops; + + +/** + * \name RWFrom functions + * + * Functions to create SDL_RWops structures from various data streams. + */ +/* @{ */ + +/** + * Use this function to create a new SDL_RWops structure for reading from + * and/or writing to a named file. + * + * The `mode` string is treated roughly the same as in a call to the C + * library's fopen(), even if SDL doesn't happen to use fopen() behind the + * scenes. + * + * Available `mode` strings: + * + * - "r": Open a file for reading. The file must exist. + * - "w": Create an empty file for writing. If a file with the same name + * already exists its content is erased and the file is treated as a new + * empty file. + * - "a": Append to a file. Writing operations append data at the end of the + * file. The file is created if it does not exist. + * - "r+": Open a file for update both reading and writing. The file must + * exist. + * - "w+": Create an empty file for both reading and writing. If a file with + * the same name already exists its content is erased and the file is + * treated as a new empty file. + * - "a+": Open a file for reading and appending. All writing operations are + * performed at the end of the file, protecting the previous content to be + * overwritten. You can reposition (fseek, rewind) the internal pointer to + * anywhere in the file for reading, but writing operations will move it + * back to the end of file. The file is created if it does not exist. + * + * **NOTE**: In order to open a file as a binary file, a "b" character has to + * be included in the `mode` string. This additional "b" character can either + * be appended at the end of the string (thus making the following compound + * modes: "rb", "wb", "ab", "r+b", "w+b", "a+b") or be inserted between the + * letter and the "+" sign for the mixed modes ("rb+", "wb+", "ab+"). + * Additional characters may follow the sequence, although they should have no + * effect. For example, "t" is sometimes appended to make explicit the file is + * a text file. + * + * This function supports Unicode filenames, but they must be encoded in UTF-8 + * format, regardless of the underlying operating system. + * + * As a fallback, SDL_RWFromFile() will transparently open a matching filename + * in an Android app's `assets`. + * + * Closing the SDL_RWops will close the file handle SDL is holding internally. + * + * \param file a UTF-8 string representing the filename to open. + * \param mode an ASCII string representing the mode to be used for opening + * the file. + * \returns a pointer to the SDL_RWops structure that is created, or NULL on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWtell + * \sa SDL_RWwrite + */ +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFile(const char *file, + const char *mode); + +#ifdef HAVE_STDIO_H + +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(FILE * fp, SDL_bool autoclose); + +#else + +/** + * Use this function to create an SDL_RWops structure from a standard I/O file + * pointer (stdio.h's `FILE*`). + * + * This function is not available on Windows, since files opened in an + * application on that platform cannot be used by a dynamically linked + * library. + * + * On some platforms, the first parameter is a `void*`, on others, it's a + * `FILE*`, depending on what system headers are available to SDL. It is + * always intended to be the `FILE*` type from the C runtime's stdio.h. + * + * \param fp the `FILE*` that feeds the SDL_RWops stream. + * \param autoclose SDL_TRUE to close the `FILE*` when closing the SDL_RWops, + * SDL_FALSE to leave the `FILE*` open when the RWops is + * closed. + * \returns a pointer to the SDL_RWops structure that is created, or NULL on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWtell + * \sa SDL_RWwrite + */ +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(void * fp, + SDL_bool autoclose); +#endif + +/** + * Use this function to prepare a read-write memory buffer for use with + * SDL_RWops. + * + * This function sets up an SDL_RWops struct based on a memory area of a + * certain size, for both read and write access. + * + * This memory buffer is not copied by the RWops; the pointer you provide must + * remain valid until you close the stream. Closing the stream will not free + * the original buffer. + * + * If you need to make sure the RWops never writes to the memory buffer, you + * should use SDL_RWFromConstMem() with a read-only buffer of memory instead. + * + * \param mem a pointer to a buffer to feed an SDL_RWops stream. + * \param size the buffer size, in bytes. + * \returns a pointer to a new SDL_RWops structure, or NULL if it fails; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWtell + * \sa SDL_RWwrite + */ +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromMem(void *mem, int size); + +/** + * Use this function to prepare a read-only memory buffer for use with RWops. + * + * This function sets up an SDL_RWops struct based on a memory area of a + * certain size. It assumes the memory area is not writable. + * + * Attempting to write to this RWops stream will report an error without + * writing to the memory buffer. + * + * This memory buffer is not copied by the RWops; the pointer you provide must + * remain valid until you close the stream. Closing the stream will not free + * the original buffer. + * + * If you need to write to a memory buffer, you should use SDL_RWFromMem() + * with a writable buffer of memory instead. + * + * \param mem a pointer to a read-only buffer to feed an SDL_RWops stream. + * \param size the buffer size, in bytes. + * \returns a pointer to a new SDL_RWops structure, or NULL if it fails; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWtell + */ +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromConstMem(const void *mem, + int size); + +/* @} *//* RWFrom functions */ + + +/** + * Use this function to allocate an empty, unpopulated SDL_RWops structure. + * + * Applications do not need to use this function unless they are providing + * their own SDL_RWops implementation. If you just need a SDL_RWops to + * read/write a common data source, you should use the built-in + * implementations in SDL, like SDL_RWFromFile() or SDL_RWFromMem(), etc. + * + * You must free the returned pointer with SDL_FreeRW(). Depending on your + * operating system and compiler, there may be a difference between the + * malloc() and free() your program uses and the versions SDL calls + * internally. Trying to mix the two can cause crashing such as segmentation + * faults. Since all SDL_RWops must free themselves when their **close** + * method is called, all SDL_RWops must be allocated through this function, so + * they can all be freed correctly with SDL_FreeRW(). + * + * \returns a pointer to the allocated memory on success, or NULL on failure; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreeRW + */ +extern DECLSPEC SDL_RWops *SDLCALL SDL_AllocRW(void); + +/** + * Use this function to free an SDL_RWops structure allocated by + * SDL_AllocRW(). + * + * Applications do not need to use this function unless they are providing + * their own SDL_RWops implementation. If you just need a SDL_RWops to + * read/write a common data source, you should use the built-in + * implementations in SDL, like SDL_RWFromFile() or SDL_RWFromMem(), etc, and + * call the **close** method on those SDL_RWops pointers when you are done + * with them. + * + * Only use SDL_FreeRW() on pointers returned by SDL_AllocRW(). The pointer is + * invalid as soon as this function returns. Any extra memory allocated during + * creation of the SDL_RWops is not freed by SDL_FreeRW(); the programmer must + * be responsible for managing that memory in their **close** method. + * + * \param area the SDL_RWops structure to be freed. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocRW + */ +extern DECLSPEC void SDLCALL SDL_FreeRW(SDL_RWops * area); + +/* Possible `whence` values for SDL_RWops seeking... */ +#define RW_SEEK_SET 0 /**< Seek from the beginning of data */ +#define RW_SEEK_CUR 1 /**< Seek relative to current read point */ +#define RW_SEEK_END 2 /**< Seek relative to the end of data */ + +/** + * Use this function to get the size of the data stream in an SDL_RWops. + * + * Prior to SDL 2.0.10, this function was a macro. + * + * \param context the SDL_RWops to get the size of the data stream from. + * \returns the size of the data stream in the SDL_RWops on success, -1 if + * unknown or a negative error code on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC Sint64 SDLCALL SDL_RWsize(SDL_RWops *context); + +/** + * Seek within an SDL_RWops data stream. + * + * This function seeks to byte `offset`, relative to `whence`. + * + * `whence` may be any of the following values: + * + * - `RW_SEEK_SET`: seek from the beginning of data + * - `RW_SEEK_CUR`: seek relative to current read point + * - `RW_SEEK_END`: seek relative to the end of data + * + * If this stream can not seek, it will return -1. + * + * SDL_RWseek() is actually a wrapper function that calls the SDL_RWops's + * `seek` method appropriately, to simplify application development. + * + * Prior to SDL 2.0.10, this function was a macro. + * + * \param context a pointer to an SDL_RWops structure. + * \param offset an offset in bytes, relative to **whence** location; can be + * negative. + * \param whence any of `RW_SEEK_SET`, `RW_SEEK_CUR`, `RW_SEEK_END`. + * \returns the final offset in the data stream after the seek or -1 on error. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWtell + * \sa SDL_RWwrite + */ +extern DECLSPEC Sint64 SDLCALL SDL_RWseek(SDL_RWops *context, + Sint64 offset, int whence); + +/** + * Determine the current read/write offset in an SDL_RWops data stream. + * + * SDL_RWtell is actually a wrapper function that calls the SDL_RWops's `seek` + * method, with an offset of 0 bytes from `RW_SEEK_CUR`, to simplify + * application development. + * + * Prior to SDL 2.0.10, this function was a macro. + * + * \param context a SDL_RWops data stream object from which to get the current + * offset. + * \returns the current offset in the stream, or -1 if the information can not + * be determined. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWwrite + */ +extern DECLSPEC Sint64 SDLCALL SDL_RWtell(SDL_RWops *context); + +/** + * Read from a data source. + * + * This function reads up to `maxnum` objects each of size `size` from the + * data source to the area pointed at by `ptr`. This function may read less + * objects than requested. It will return zero when there has been an error or + * the data stream is completely read. + * + * SDL_RWread() is actually a function wrapper that calls the SDL_RWops's + * `read` method appropriately, to simplify application development. + * + * Prior to SDL 2.0.10, this function was a macro. + * + * \param context a pointer to an SDL_RWops structure. + * \param ptr a pointer to a buffer to read data into. + * \param size the size of each object to read, in bytes. + * \param maxnum the maximum number of objects to be read. + * \returns the number of objects read, or 0 at error or end of file; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWseek + * \sa SDL_RWwrite + */ +extern DECLSPEC size_t SDLCALL SDL_RWread(SDL_RWops *context, + void *ptr, size_t size, + size_t maxnum); + +/** + * Write to an SDL_RWops data stream. + * + * This function writes exactly `num` objects each of size `size` from the + * area pointed at by `ptr` to the stream. If this fails for any reason, it'll + * return less than `num` to demonstrate how far the write progressed. On + * success, it returns `num`. + * + * SDL_RWwrite is actually a function wrapper that calls the SDL_RWops's + * `write` method appropriately, to simplify application development. + * + * Prior to SDL 2.0.10, this function was a macro. + * + * \param context a pointer to an SDL_RWops structure. + * \param ptr a pointer to a buffer containing data to write. + * \param size the size of an object to write, in bytes. + * \param num the number of objects to write. + * \returns the number of objects written, which will be less than **num** on + * error; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_RWclose + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + */ +extern DECLSPEC size_t SDLCALL SDL_RWwrite(SDL_RWops *context, + const void *ptr, size_t size, + size_t num); + +/** + * Close and free an allocated SDL_RWops structure. + * + * SDL_RWclose() closes and cleans up the SDL_RWops stream. It releases any + * resources used by the stream and frees the SDL_RWops itself with + * SDL_FreeRW(). This returns 0 on success, or -1 if the stream failed to + * flush to its output (e.g. to disk). + * + * Note that if this fails to flush the stream to disk, this function reports + * an error, but the SDL_RWops is still invalid once this function returns. + * + * Prior to SDL 2.0.10, this function was a macro. + * + * \param context SDL_RWops structure to close. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.10. + * + * \sa SDL_RWFromConstMem + * \sa SDL_RWFromFile + * \sa SDL_RWFromFP + * \sa SDL_RWFromMem + * \sa SDL_RWread + * \sa SDL_RWseek + * \sa SDL_RWwrite + */ +extern DECLSPEC int SDLCALL SDL_RWclose(SDL_RWops *context); + +/** + * Load all the data from an SDL data stream. + * + * The data is allocated with a zero byte at the end (null terminated) for + * convenience. This extra byte is not included in the value reported via + * `datasize`. + * + * The data should be freed with SDL_free(). + * + * \param src the SDL_RWops to read all available data from. + * \param datasize if not NULL, will store the number of bytes read. + * \param freesrc if non-zero, calls SDL_RWclose() on `src` before returning. + * \returns the data, or NULL if there was an error. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC void *SDLCALL SDL_LoadFile_RW(SDL_RWops *src, + size_t *datasize, + int freesrc); + +/** + * Load all the data from a file path. + * + * The data is allocated with a zero byte at the end (null terminated) for + * convenience. This extra byte is not included in the value reported via + * `datasize`. + * + * The data should be freed with SDL_free(). + * + * Prior to SDL 2.0.10, this function was a macro wrapping around + * SDL_LoadFile_RW. + * + * \param file the path to read all available data from. + * \param datasize if not NULL, will store the number of bytes read. + * \returns the data, or NULL if there was an error. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC void *SDLCALL SDL_LoadFile(const char *file, size_t *datasize); + +/** + * \name Read endian functions + * + * Read an item of the specified endianness and return in native format. + */ +/* @{ */ + +/** + * Use this function to read a byte from an SDL_RWops. + * + * \param src the SDL_RWops to read from. + * \returns the read byte on success or 0 on failure; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteU8 + */ +extern DECLSPEC Uint8 SDLCALL SDL_ReadU8(SDL_RWops * src); + +/** + * Use this function to read 16 bits of little-endian data from an SDL_RWops + * and return in native format. + * + * SDL byteswaps the data only if necessary, so the data returned will be in + * the native byte order. + * + * \param src the stream from which to read data. + * \returns 16 bits of data in the native byte order of the platform. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadBE16 + */ +extern DECLSPEC Uint16 SDLCALL SDL_ReadLE16(SDL_RWops * src); + +/** + * Use this function to read 16 bits of big-endian data from an SDL_RWops and + * return in native format. + * + * SDL byteswaps the data only if necessary, so the data returned will be in + * the native byte order. + * + * \param src the stream from which to read data. + * \returns 16 bits of data in the native byte order of the platform. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadLE16 + */ +extern DECLSPEC Uint16 SDLCALL SDL_ReadBE16(SDL_RWops * src); + +/** + * Use this function to read 32 bits of little-endian data from an SDL_RWops + * and return in native format. + * + * SDL byteswaps the data only if necessary, so the data returned will be in + * the native byte order. + * + * \param src the stream from which to read data. + * \returns 32 bits of data in the native byte order of the platform. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadBE32 + */ +extern DECLSPEC Uint32 SDLCALL SDL_ReadLE32(SDL_RWops * src); + +/** + * Use this function to read 32 bits of big-endian data from an SDL_RWops and + * return in native format. + * + * SDL byteswaps the data only if necessary, so the data returned will be in + * the native byte order. + * + * \param src the stream from which to read data. + * \returns 32 bits of data in the native byte order of the platform. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadLE32 + */ +extern DECLSPEC Uint32 SDLCALL SDL_ReadBE32(SDL_RWops * src); + +/** + * Use this function to read 64 bits of little-endian data from an SDL_RWops + * and return in native format. + * + * SDL byteswaps the data only if necessary, so the data returned will be in + * the native byte order. + * + * \param src the stream from which to read data. + * \returns 64 bits of data in the native byte order of the platform. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadBE64 + */ +extern DECLSPEC Uint64 SDLCALL SDL_ReadLE64(SDL_RWops * src); + +/** + * Use this function to read 64 bits of big-endian data from an SDL_RWops and + * return in native format. + * + * SDL byteswaps the data only if necessary, so the data returned will be in + * the native byte order. + * + * \param src the stream from which to read data. + * \returns 64 bits of data in the native byte order of the platform. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadLE64 + */ +extern DECLSPEC Uint64 SDLCALL SDL_ReadBE64(SDL_RWops * src); +/* @} *//* Read endian functions */ + +/** + * \name Write endian functions + * + * Write an item of native format to the specified endianness. + */ +/* @{ */ + +/** + * Use this function to write a byte to an SDL_RWops. + * + * \param dst the SDL_RWops to write to. + * \param value the byte value to write. + * \returns 1 on success or 0 on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ReadU8 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteU8(SDL_RWops * dst, Uint8 value); + +/** + * Use this function to write 16 bits in native format to a SDL_RWops as + * little-endian data. + * + * SDL byteswaps the data only if necessary, so the application always + * specifies native format, and the data written will be in little-endian + * format. + * + * \param dst the stream to which data will be written. + * \param value the data to be written, in native format. + * \returns 1 on successful write, 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteBE16 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteLE16(SDL_RWops * dst, Uint16 value); + +/** + * Use this function to write 16 bits in native format to a SDL_RWops as + * big-endian data. + * + * SDL byteswaps the data only if necessary, so the application always + * specifies native format, and the data written will be in big-endian format. + * + * \param dst the stream to which data will be written. + * \param value the data to be written, in native format. + * \returns 1 on successful write, 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteLE16 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteBE16(SDL_RWops * dst, Uint16 value); + +/** + * Use this function to write 32 bits in native format to a SDL_RWops as + * little-endian data. + * + * SDL byteswaps the data only if necessary, so the application always + * specifies native format, and the data written will be in little-endian + * format. + * + * \param dst the stream to which data will be written. + * \param value the data to be written, in native format. + * \returns 1 on successful write, 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteBE32 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteLE32(SDL_RWops * dst, Uint32 value); + +/** + * Use this function to write 32 bits in native format to a SDL_RWops as + * big-endian data. + * + * SDL byteswaps the data only if necessary, so the application always + * specifies native format, and the data written will be in big-endian format. + * + * \param dst the stream to which data will be written. + * \param value the data to be written, in native format. + * \returns 1 on successful write, 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteLE32 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteBE32(SDL_RWops * dst, Uint32 value); + +/** + * Use this function to write 64 bits in native format to a SDL_RWops as + * little-endian data. + * + * SDL byteswaps the data only if necessary, so the application always + * specifies native format, and the data written will be in little-endian + * format. + * + * \param dst the stream to which data will be written. + * \param value the data to be written, in native format. + * \returns 1 on successful write, 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteBE64 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteLE64(SDL_RWops * dst, Uint64 value); + +/** + * Use this function to write 64 bits in native format to a SDL_RWops as + * big-endian data. + * + * SDL byteswaps the data only if necessary, so the application always + * specifies native format, and the data written will be in big-endian format. + * + * \param dst the stream to which data will be written. + * \param value the data to be written, in native format. + * \returns 1 on successful write, 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WriteLE64 + */ +extern DECLSPEC size_t SDLCALL SDL_WriteBE64(SDL_RWops * dst, Uint64 value); +/* @} *//* Write endian functions */ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_rwops_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_scancode.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_scancode.h new file mode 100644 index 00000000..0652d7ef --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_scancode.h @@ -0,0 +1,438 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryScancode + * + * Defines keyboard scancodes. + */ + +#ifndef SDL_scancode_h_ +#define SDL_scancode_h_ + +#include "SDL_stdinc.h" + +/** + * The SDL keyboard scancode representation. + * + * Values of this type are used to represent keyboard keys, among other places + * in the SDL_Keysym::scancode key.keysym.scancode field of the SDL_Event + * structure. + * + * The values in this enumeration are based on the USB usage page standard: + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ +typedef enum SDL_Scancode +{ + SDL_SCANCODE_UNKNOWN = 0, + + /** + * \name Usage page 0x07 + * + * These values are from usage page 0x07 (USB keyboard page). + */ + /* @{ */ + + SDL_SCANCODE_A = 4, + SDL_SCANCODE_B = 5, + SDL_SCANCODE_C = 6, + SDL_SCANCODE_D = 7, + SDL_SCANCODE_E = 8, + SDL_SCANCODE_F = 9, + SDL_SCANCODE_G = 10, + SDL_SCANCODE_H = 11, + SDL_SCANCODE_I = 12, + SDL_SCANCODE_J = 13, + SDL_SCANCODE_K = 14, + SDL_SCANCODE_L = 15, + SDL_SCANCODE_M = 16, + SDL_SCANCODE_N = 17, + SDL_SCANCODE_O = 18, + SDL_SCANCODE_P = 19, + SDL_SCANCODE_Q = 20, + SDL_SCANCODE_R = 21, + SDL_SCANCODE_S = 22, + SDL_SCANCODE_T = 23, + SDL_SCANCODE_U = 24, + SDL_SCANCODE_V = 25, + SDL_SCANCODE_W = 26, + SDL_SCANCODE_X = 27, + SDL_SCANCODE_Y = 28, + SDL_SCANCODE_Z = 29, + + SDL_SCANCODE_1 = 30, + SDL_SCANCODE_2 = 31, + SDL_SCANCODE_3 = 32, + SDL_SCANCODE_4 = 33, + SDL_SCANCODE_5 = 34, + SDL_SCANCODE_6 = 35, + SDL_SCANCODE_7 = 36, + SDL_SCANCODE_8 = 37, + SDL_SCANCODE_9 = 38, + SDL_SCANCODE_0 = 39, + + SDL_SCANCODE_RETURN = 40, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_BACKSPACE = 42, + SDL_SCANCODE_TAB = 43, + SDL_SCANCODE_SPACE = 44, + + SDL_SCANCODE_MINUS = 45, + SDL_SCANCODE_EQUALS = 46, + SDL_SCANCODE_LEFTBRACKET = 47, + SDL_SCANCODE_RIGHTBRACKET = 48, + SDL_SCANCODE_BACKSLASH = 49, /**< Located at the lower left of the return + * key on ISO keyboards and at the right end + * of the QWERTY row on ANSI keyboards. + * Produces REVERSE SOLIDUS (backslash) and + * VERTICAL LINE in a US layout, REVERSE + * SOLIDUS and VERTICAL LINE in a UK Mac + * layout, NUMBER SIGN and TILDE in a UK + * Windows layout, DOLLAR SIGN and POUND SIGN + * in a Swiss German layout, NUMBER SIGN and + * APOSTROPHE in a German layout, GRAVE + * ACCENT and POUND SIGN in a French Mac + * layout, and ASTERISK and MICRO SIGN in a + * French Windows layout. + */ + SDL_SCANCODE_NONUSHASH = 50, /**< ISO USB keyboards actually use this code + * instead of 49 for the same key, but all + * OSes I've seen treat the two codes + * identically. So, as an implementor, unless + * your keyboard generates both of those + * codes and your OS treats them differently, + * you should generate SDL_SCANCODE_BACKSLASH + * instead of this code. As a user, you + * should not rely on this code because SDL + * will never generate it with most (all?) + * keyboards. + */ + SDL_SCANCODE_SEMICOLON = 51, + SDL_SCANCODE_APOSTROPHE = 52, + SDL_SCANCODE_GRAVE = 53, /**< Located in the top left corner (on both ANSI + * and ISO keyboards). Produces GRAVE ACCENT and + * TILDE in a US Windows layout and in US and UK + * Mac layouts on ANSI keyboards, GRAVE ACCENT + * and NOT SIGN in a UK Windows layout, SECTION + * SIGN and PLUS-MINUS SIGN in US and UK Mac + * layouts on ISO keyboards, SECTION SIGN and + * DEGREE SIGN in a Swiss German layout (Mac: + * only on ISO keyboards), CIRCUMFLEX ACCENT and + * DEGREE SIGN in a German layout (Mac: only on + * ISO keyboards), SUPERSCRIPT TWO and TILDE in a + * French Windows layout, COMMERCIAL AT and + * NUMBER SIGN in a French Mac layout on ISO + * keyboards, and LESS-THAN SIGN and GREATER-THAN + * SIGN in a Swiss German, German, or French Mac + * layout on ANSI keyboards. + */ + SDL_SCANCODE_COMMA = 54, + SDL_SCANCODE_PERIOD = 55, + SDL_SCANCODE_SLASH = 56, + + SDL_SCANCODE_CAPSLOCK = 57, + + SDL_SCANCODE_F1 = 58, + SDL_SCANCODE_F2 = 59, + SDL_SCANCODE_F3 = 60, + SDL_SCANCODE_F4 = 61, + SDL_SCANCODE_F5 = 62, + SDL_SCANCODE_F6 = 63, + SDL_SCANCODE_F7 = 64, + SDL_SCANCODE_F8 = 65, + SDL_SCANCODE_F9 = 66, + SDL_SCANCODE_F10 = 67, + SDL_SCANCODE_F11 = 68, + SDL_SCANCODE_F12 = 69, + + SDL_SCANCODE_PRINTSCREEN = 70, + SDL_SCANCODE_SCROLLLOCK = 71, + SDL_SCANCODE_PAUSE = 72, + SDL_SCANCODE_INSERT = 73, /**< insert on PC, help on some Mac keyboards (but + does send code 73, not 117) */ + SDL_SCANCODE_HOME = 74, + SDL_SCANCODE_PAGEUP = 75, + SDL_SCANCODE_DELETE = 76, + SDL_SCANCODE_END = 77, + SDL_SCANCODE_PAGEDOWN = 78, + SDL_SCANCODE_RIGHT = 79, + SDL_SCANCODE_LEFT = 80, + SDL_SCANCODE_DOWN = 81, + SDL_SCANCODE_UP = 82, + + SDL_SCANCODE_NUMLOCKCLEAR = 83, /**< num lock on PC, clear on Mac keyboards + */ + SDL_SCANCODE_KP_DIVIDE = 84, + SDL_SCANCODE_KP_MULTIPLY = 85, + SDL_SCANCODE_KP_MINUS = 86, + SDL_SCANCODE_KP_PLUS = 87, + SDL_SCANCODE_KP_ENTER = 88, + SDL_SCANCODE_KP_1 = 89, + SDL_SCANCODE_KP_2 = 90, + SDL_SCANCODE_KP_3 = 91, + SDL_SCANCODE_KP_4 = 92, + SDL_SCANCODE_KP_5 = 93, + SDL_SCANCODE_KP_6 = 94, + SDL_SCANCODE_KP_7 = 95, + SDL_SCANCODE_KP_8 = 96, + SDL_SCANCODE_KP_9 = 97, + SDL_SCANCODE_KP_0 = 98, + SDL_SCANCODE_KP_PERIOD = 99, + + SDL_SCANCODE_NONUSBACKSLASH = 100, /**< This is the additional key that ISO + * keyboards have over ANSI ones, + * located between left shift and Y. + * Produces GRAVE ACCENT and TILDE in a + * US or UK Mac layout, REVERSE SOLIDUS + * (backslash) and VERTICAL LINE in a + * US or UK Windows layout, and + * LESS-THAN SIGN and GREATER-THAN SIGN + * in a Swiss German, German, or French + * layout. */ + SDL_SCANCODE_APPLICATION = 101, /**< windows contextual menu, compose */ + SDL_SCANCODE_POWER = 102, /**< The USB document says this is a status flag, + * not a physical key - but some Mac keyboards + * do have a power key. */ + SDL_SCANCODE_KP_EQUALS = 103, + SDL_SCANCODE_F13 = 104, + SDL_SCANCODE_F14 = 105, + SDL_SCANCODE_F15 = 106, + SDL_SCANCODE_F16 = 107, + SDL_SCANCODE_F17 = 108, + SDL_SCANCODE_F18 = 109, + SDL_SCANCODE_F19 = 110, + SDL_SCANCODE_F20 = 111, + SDL_SCANCODE_F21 = 112, + SDL_SCANCODE_F22 = 113, + SDL_SCANCODE_F23 = 114, + SDL_SCANCODE_F24 = 115, + SDL_SCANCODE_EXECUTE = 116, + SDL_SCANCODE_HELP = 117, /**< AL Integrated Help Center */ + SDL_SCANCODE_MENU = 118, /**< Menu (show menu) */ + SDL_SCANCODE_SELECT = 119, + SDL_SCANCODE_STOP = 120, /**< AC Stop */ + SDL_SCANCODE_AGAIN = 121, /**< AC Redo/Repeat */ + SDL_SCANCODE_UNDO = 122, /**< AC Undo */ + SDL_SCANCODE_CUT = 123, /**< AC Cut */ + SDL_SCANCODE_COPY = 124, /**< AC Copy */ + SDL_SCANCODE_PASTE = 125, /**< AC Paste */ + SDL_SCANCODE_FIND = 126, /**< AC Find */ + SDL_SCANCODE_MUTE = 127, + SDL_SCANCODE_VOLUMEUP = 128, + SDL_SCANCODE_VOLUMEDOWN = 129, +/* not sure whether there's a reason to enable these */ +/* SDL_SCANCODE_LOCKINGCAPSLOCK = 130, */ +/* SDL_SCANCODE_LOCKINGNUMLOCK = 131, */ +/* SDL_SCANCODE_LOCKINGSCROLLLOCK = 132, */ + SDL_SCANCODE_KP_COMMA = 133, + SDL_SCANCODE_KP_EQUALSAS400 = 134, + + SDL_SCANCODE_INTERNATIONAL1 = 135, /**< used on Asian keyboards, see + footnotes in USB doc */ + SDL_SCANCODE_INTERNATIONAL2 = 136, + SDL_SCANCODE_INTERNATIONAL3 = 137, /**< Yen */ + SDL_SCANCODE_INTERNATIONAL4 = 138, + SDL_SCANCODE_INTERNATIONAL5 = 139, + SDL_SCANCODE_INTERNATIONAL6 = 140, + SDL_SCANCODE_INTERNATIONAL7 = 141, + SDL_SCANCODE_INTERNATIONAL8 = 142, + SDL_SCANCODE_INTERNATIONAL9 = 143, + SDL_SCANCODE_LANG1 = 144, /**< Hangul/English toggle */ + SDL_SCANCODE_LANG2 = 145, /**< Hanja conversion */ + SDL_SCANCODE_LANG3 = 146, /**< Katakana */ + SDL_SCANCODE_LANG4 = 147, /**< Hiragana */ + SDL_SCANCODE_LANG5 = 148, /**< Zenkaku/Hankaku */ + SDL_SCANCODE_LANG6 = 149, /**< reserved */ + SDL_SCANCODE_LANG7 = 150, /**< reserved */ + SDL_SCANCODE_LANG8 = 151, /**< reserved */ + SDL_SCANCODE_LANG9 = 152, /**< reserved */ + + SDL_SCANCODE_ALTERASE = 153, /**< Erase-Eaze */ + SDL_SCANCODE_SYSREQ = 154, + SDL_SCANCODE_CANCEL = 155, /**< AC Cancel */ + SDL_SCANCODE_CLEAR = 156, + SDL_SCANCODE_PRIOR = 157, + SDL_SCANCODE_RETURN2 = 158, + SDL_SCANCODE_SEPARATOR = 159, + SDL_SCANCODE_OUT = 160, + SDL_SCANCODE_OPER = 161, + SDL_SCANCODE_CLEARAGAIN = 162, + SDL_SCANCODE_CRSEL = 163, + SDL_SCANCODE_EXSEL = 164, + + SDL_SCANCODE_KP_00 = 176, + SDL_SCANCODE_KP_000 = 177, + SDL_SCANCODE_THOUSANDSSEPARATOR = 178, + SDL_SCANCODE_DECIMALSEPARATOR = 179, + SDL_SCANCODE_CURRENCYUNIT = 180, + SDL_SCANCODE_CURRENCYSUBUNIT = 181, + SDL_SCANCODE_KP_LEFTPAREN = 182, + SDL_SCANCODE_KP_RIGHTPAREN = 183, + SDL_SCANCODE_KP_LEFTBRACE = 184, + SDL_SCANCODE_KP_RIGHTBRACE = 185, + SDL_SCANCODE_KP_TAB = 186, + SDL_SCANCODE_KP_BACKSPACE = 187, + SDL_SCANCODE_KP_A = 188, + SDL_SCANCODE_KP_B = 189, + SDL_SCANCODE_KP_C = 190, + SDL_SCANCODE_KP_D = 191, + SDL_SCANCODE_KP_E = 192, + SDL_SCANCODE_KP_F = 193, + SDL_SCANCODE_KP_XOR = 194, + SDL_SCANCODE_KP_POWER = 195, + SDL_SCANCODE_KP_PERCENT = 196, + SDL_SCANCODE_KP_LESS = 197, + SDL_SCANCODE_KP_GREATER = 198, + SDL_SCANCODE_KP_AMPERSAND = 199, + SDL_SCANCODE_KP_DBLAMPERSAND = 200, + SDL_SCANCODE_KP_VERTICALBAR = 201, + SDL_SCANCODE_KP_DBLVERTICALBAR = 202, + SDL_SCANCODE_KP_COLON = 203, + SDL_SCANCODE_KP_HASH = 204, + SDL_SCANCODE_KP_SPACE = 205, + SDL_SCANCODE_KP_AT = 206, + SDL_SCANCODE_KP_EXCLAM = 207, + SDL_SCANCODE_KP_MEMSTORE = 208, + SDL_SCANCODE_KP_MEMRECALL = 209, + SDL_SCANCODE_KP_MEMCLEAR = 210, + SDL_SCANCODE_KP_MEMADD = 211, + SDL_SCANCODE_KP_MEMSUBTRACT = 212, + SDL_SCANCODE_KP_MEMMULTIPLY = 213, + SDL_SCANCODE_KP_MEMDIVIDE = 214, + SDL_SCANCODE_KP_PLUSMINUS = 215, + SDL_SCANCODE_KP_CLEAR = 216, + SDL_SCANCODE_KP_CLEARENTRY = 217, + SDL_SCANCODE_KP_BINARY = 218, + SDL_SCANCODE_KP_OCTAL = 219, + SDL_SCANCODE_KP_DECIMAL = 220, + SDL_SCANCODE_KP_HEXADECIMAL = 221, + + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, /**< alt, option */ + SDL_SCANCODE_LGUI = 227, /**< windows, command (apple), meta */ + SDL_SCANCODE_RCTRL = 228, + SDL_SCANCODE_RSHIFT = 229, + SDL_SCANCODE_RALT = 230, /**< alt gr, option */ + SDL_SCANCODE_RGUI = 231, /**< windows, command (apple), meta */ + + SDL_SCANCODE_MODE = 257, /**< I'm not sure if this is really not covered + * by any of the above, but since there's a + * special KMOD_MODE for it I'm adding it here + */ + + /* @} *//* Usage page 0x07 */ + + /** + * \name Usage page 0x0C + * + * These values are mapped from usage page 0x0C (USB consumer page). + * See https://usb.org/sites/default/files/hut1_2.pdf + * + * There are way more keys in the spec than we can represent in the + * current scancode range, so pick the ones that commonly come up in + * real world usage. + */ + /* @{ */ + + SDL_SCANCODE_AUDIONEXT = 258, + SDL_SCANCODE_AUDIOPREV = 259, + SDL_SCANCODE_AUDIOSTOP = 260, + SDL_SCANCODE_AUDIOPLAY = 261, + SDL_SCANCODE_AUDIOMUTE = 262, + SDL_SCANCODE_MEDIASELECT = 263, + SDL_SCANCODE_WWW = 264, /**< AL Internet Browser */ + SDL_SCANCODE_MAIL = 265, + SDL_SCANCODE_CALCULATOR = 266, /**< AL Calculator */ + SDL_SCANCODE_COMPUTER = 267, + SDL_SCANCODE_AC_SEARCH = 268, /**< AC Search */ + SDL_SCANCODE_AC_HOME = 269, /**< AC Home */ + SDL_SCANCODE_AC_BACK = 270, /**< AC Back */ + SDL_SCANCODE_AC_FORWARD = 271, /**< AC Forward */ + SDL_SCANCODE_AC_STOP = 272, /**< AC Stop */ + SDL_SCANCODE_AC_REFRESH = 273, /**< AC Refresh */ + SDL_SCANCODE_AC_BOOKMARKS = 274, /**< AC Bookmarks */ + + /* @} *//* Usage page 0x0C */ + + /** + * \name Walther keys + * + * These are values that Christian Walther added (for mac keyboard?). + */ + /* @{ */ + + SDL_SCANCODE_BRIGHTNESSDOWN = 275, + SDL_SCANCODE_BRIGHTNESSUP = 276, + SDL_SCANCODE_DISPLAYSWITCH = 277, /**< display mirroring/dual display + switch, video mode switch */ + SDL_SCANCODE_KBDILLUMTOGGLE = 278, + SDL_SCANCODE_KBDILLUMDOWN = 279, + SDL_SCANCODE_KBDILLUMUP = 280, + SDL_SCANCODE_EJECT = 281, + SDL_SCANCODE_SLEEP = 282, /**< SC System Sleep */ + + SDL_SCANCODE_APP1 = 283, + SDL_SCANCODE_APP2 = 284, + + /* @} *//* Walther keys */ + + /** + * \name Usage page 0x0C (additional media keys) + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /* @{ */ + + SDL_SCANCODE_AUDIOREWIND = 285, + SDL_SCANCODE_AUDIOFASTFORWARD = 286, + + /* @} *//* Usage page 0x0C (additional media keys) */ + + /** + * \name Mobile keys + * + * These are values that are often used on mobile phones. + */ + /* @{ */ + + SDL_SCANCODE_SOFTLEFT = 287, /**< Usually situated below the display on phones and + used as a multi-function feature key for selecting + a software defined function shown on the bottom left + of the display. */ + SDL_SCANCODE_SOFTRIGHT = 288, /**< Usually situated below the display on phones and + used as a multi-function feature key for selecting + a software defined function shown on the bottom right + of the display. */ + SDL_SCANCODE_CALL = 289, /**< Used for accepting phone calls. */ + SDL_SCANCODE_ENDCALL = 290, /**< Used for rejecting phone calls. */ + + /* @} *//* Mobile keys */ + + /* Add any other keys here. */ + + SDL_NUM_SCANCODES = 512 /**< not a key, just marks the number of scancodes + for array bounds */ +} SDL_Scancode; + +#endif /* SDL_scancode_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_sensor.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_sensor.h new file mode 100644 index 00000000..d4b1c511 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_sensor.h @@ -0,0 +1,329 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategorySensor + * + * Include file for SDL sensor event handling + */ + +#ifndef SDL_sensor_h_ +#define SDL_sensor_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief SDL_sensor.h + * + * In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_SENSOR flag. This causes SDL to scan the system + * for sensors, and load appropriate drivers. + */ + +struct _SDL_Sensor; +typedef struct _SDL_Sensor SDL_Sensor; + +/** + * This is a unique ID for a sensor for the time it is connected to the + * system, and is never reused for the lifetime of the application. + * + * The ID value starts at 0 and increments from there. The value -1 is an + * invalid ID. + */ +typedef Sint32 SDL_SensorID; + +/** + * The different sensors defined by SDL. + * + * Additional sensors may be available, using platform dependent semantics. + * + * Here are the additional Android sensors: + * + * https://developer.android.com/reference/android/hardware/SensorEvent.html#values + * + * Accelerometer sensor notes: + * + * The accelerometer returns the current acceleration in SI meters per second + * squared. This measurement includes the force of gravity, so a device at + * rest will have an value of SDL_STANDARD_GRAVITY away from the center of the + * earth, which is a positive Y value. + * + * - `values[0]`: Acceleration on the x axis + * - `values[1]`: Acceleration on the y axis + * - `values[2]`: Acceleration on the z axis + * + * For phones and tablets held in natural orientation and game controllers + * held in front of you, the axes are defined as follows: + * + * - -X ... +X : left ... right + * - -Y ... +Y : bottom ... top + * - -Z ... +Z : farther ... closer + * + * The accelerometer axis data is not changed when the device is rotated. + * + * Gyroscope sensor notes: + * + * The gyroscope returns the current rate of rotation in radians per second. + * The rotation is positive in the counter-clockwise direction. That is, an + * observer looking from a positive location on one of the axes would see + * positive rotation on that axis when it appeared to be rotating + * counter-clockwise. + * + * - `values[0]`: Angular speed around the x axis (pitch) + * - `values[1]`: Angular speed around the y axis (yaw) + * - `values[2]`: Angular speed around the z axis (roll) + * + * For phones and tablets held in natural orientation and game controllers + * held in front of you, the axes are defined as follows: + * + * - -X ... +X : left ... right + * - -Y ... +Y : bottom ... top + * - -Z ... +Z : farther ... closer + * + * The gyroscope axis data is not changed when the device is rotated. + * + * \sa SDL_GetDisplayOrientation + */ +typedef enum SDL_SensorType +{ + SDL_SENSOR_INVALID = -1, /**< Returned for an invalid sensor */ + SDL_SENSOR_UNKNOWN, /**< Unknown sensor type */ + SDL_SENSOR_ACCEL, /**< Accelerometer */ + SDL_SENSOR_GYRO, /**< Gyroscope */ + SDL_SENSOR_ACCEL_L, /**< Accelerometer for left Joy-Con controller and Wii nunchuk */ + SDL_SENSOR_GYRO_L, /**< Gyroscope for left Joy-Con controller */ + SDL_SENSOR_ACCEL_R, /**< Accelerometer for right Joy-Con controller */ + SDL_SENSOR_GYRO_R /**< Gyroscope for right Joy-Con controller */ +} SDL_SensorType; + +/** + * A constant to represent standard gravity for accelerometer sensors. + * + * The accelerometer returns the current acceleration in SI meters per second + * squared. This measurement includes the force of gravity, so a device at + * rest will have an value of SDL_STANDARD_GRAVITY away from the center of the + * earth, which is a positive Y value. + */ +#define SDL_STANDARD_GRAVITY 9.80665f + +/* Function prototypes */ + +/** + * Locking for multi-threaded access to the sensor API + * + * If you are using the sensor API or handling events from multiple threads + * you should use these locking functions to protect access to the sensors. + * + * In particular, you are guaranteed that the sensor list won't change, so the + * API functions that take a sensor index will be valid, and sensor events + * will not be delivered. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC void SDLCALL SDL_LockSensors(void); +extern DECLSPEC void SDLCALL SDL_UnlockSensors(void); + +/** + * Count the number of sensors attached to the system right now. + * + * \returns the number of sensors detected. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_NumSensors(void); + +/** + * Get the implementation dependent name of a sensor. + * + * \param device_index The sensor to obtain name from. + * \returns the sensor name, or NULL if `device_index` is out of range. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC const char *SDLCALL SDL_SensorGetDeviceName(int device_index); + +/** + * Get the type of a sensor. + * + * \param device_index The sensor to get the type from. + * \returns the SDL_SensorType, or `SDL_SENSOR_INVALID` if `device_index` is + * out of range. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetDeviceType(int device_index); + +/** + * Get the platform dependent type of a sensor. + * + * \param device_index The sensor to check. + * \returns the sensor platform dependent type, or -1 if `device_index` is out + * of range. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_SensorGetDeviceNonPortableType(int device_index); + +/** + * Get the instance ID of a sensor. + * + * \param device_index The sensor to get instance id from. + * \returns the sensor instance ID, or -1 if `device_index` is out of range. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetDeviceInstanceID(int device_index); + +/** + * Open a sensor for use. + * + * \param device_index The sensor to open. + * \returns an SDL_Sensor sensor object, or NULL if an error occurred. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorOpen(int device_index); + +/** + * Return the SDL_Sensor associated with an instance id. + * + * \param instance_id The sensor from instance id. + * \returns an SDL_Sensor object. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorFromInstanceID(SDL_SensorID instance_id); + +/** + * Get the implementation dependent name of a sensor + * + * \param sensor The SDL_Sensor object. + * \returns the sensor name, or NULL if `sensor` is NULL. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC const char *SDLCALL SDL_SensorGetName(SDL_Sensor *sensor); + +/** + * Get the type of a sensor. + * + * \param sensor The SDL_Sensor object to inspect. + * \returns the SDL_SensorType type, or `SDL_SENSOR_INVALID` if `sensor` is + * NULL. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetType(SDL_Sensor *sensor); + +/** + * Get the platform dependent type of a sensor. + * + * \param sensor The SDL_Sensor object to inspect. + * \returns the sensor platform dependent type, or -1 if `sensor` is NULL. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_SensorGetNonPortableType(SDL_Sensor *sensor); + +/** + * Get the instance ID of a sensor. + * + * \param sensor The SDL_Sensor object to inspect. + * \returns the sensor instance ID, or -1 if `sensor` is NULL. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor); + +/** + * Get the current state of an opened sensor. + * + * The number of values and interpretation of the data is sensor dependent. + * + * \param sensor The SDL_Sensor object to query. + * \param data A pointer filled with the current sensor state. + * \param num_values The number of values to write to data. + * \returns 0 or -1 if an error occurred. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values); + +/** + * Get the current state of an opened sensor with the timestamp of the last + * update. + * + * The number of values and interpretation of the data is sensor dependent. + * + * \param sensor The SDL_Sensor object to query. + * \param timestamp A pointer filled with the timestamp in microseconds of the + * current sensor reading if available, or 0 if not. + * \param data A pointer filled with the current sensor state. + * \param num_values The number of values to write to data. + * \returns 0 or -1 if an error occurred. + * + * \since This function is available since SDL 2.26.0. + */ +extern DECLSPEC int SDLCALL SDL_SensorGetDataWithTimestamp(SDL_Sensor *sensor, Uint64 *timestamp, float *data, int num_values); + +/** + * Close a sensor previously opened with SDL_SensorOpen(). + * + * \param sensor The SDL_Sensor object to close. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor *sensor); + +/** + * Update the current state of the open sensors. + * + * This is called automatically by the event loop if sensor events are + * enabled. + * + * This needs to be called from the thread that initialized the sensor + * subsystem. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC void SDLCALL SDL_SensorUpdate(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* SDL_sensor_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_shape.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_shape.h new file mode 100644 index 00000000..d3560845 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_shape.h @@ -0,0 +1,155 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_shape_h_ +#define SDL_shape_h_ + +#include "SDL_stdinc.h" +#include "SDL_pixels.h" +#include "SDL_rect.h" +#include "SDL_surface.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** \file SDL_shape.h + * + * Header file for the shaped window API. + */ + +#define SDL_NONSHAPEABLE_WINDOW -1 +#define SDL_INVALID_SHAPE_ARGUMENT -2 +#define SDL_WINDOW_LACKS_SHAPE -3 + +/** + * Create a window that can be shaped with the specified position, dimensions, + * and flags. + * + * \param title The title of the window, in UTF-8 encoding. + * \param x The x position of the window, SDL_WINDOWPOS_CENTERED, or + * SDL_WINDOWPOS_UNDEFINED. + * \param y The y position of the window, SDL_WINDOWPOS_CENTERED, or + * SDL_WINDOWPOS_UNDEFINED. + * \param w The width of the window. + * \param h The height of the window. + * \param flags The flags for the window, a mask of SDL_WINDOW_BORDERLESS with + * any of the following: SDL_WINDOW_OPENGL, + * SDL_WINDOW_INPUT_GRABBED, SDL_WINDOW_HIDDEN, + * SDL_WINDOW_RESIZABLE, SDL_WINDOW_MAXIMIZED, + * SDL_WINDOW_MINIMIZED, SDL_WINDOW_BORDERLESS is always set, and + * SDL_WINDOW_FULLSCREEN is always unset. + * \return the window created, or NULL if window creation failed. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DestroyWindow + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateShapedWindow(const char *title,unsigned int x,unsigned int y,unsigned int w,unsigned int h,Uint32 flags); + +/** + * Return whether the given window is a shaped window. + * + * \param window The window to query for being shaped. + * \return SDL_TRUE if the window is a window that can be shaped, SDL_FALSE if + * the window is unshaped or NULL. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateShapedWindow + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsShapedWindow(const SDL_Window *window); + +/** \brief An enum denoting the specific type of contents present in an SDL_WindowShapeParams union. */ +typedef enum { + /** \brief The default mode, a binarized alpha cutoff of 1. */ + ShapeModeDefault, + /** \brief A binarized alpha cutoff with a given integer value. */ + ShapeModeBinarizeAlpha, + /** \brief A binarized alpha cutoff with a given integer value, but with the opposite comparison. */ + ShapeModeReverseBinarizeAlpha, + /** \brief A color key is applied. */ + ShapeModeColorKey +} WindowShapeMode; + +#define SDL_SHAPEMODEALPHA(mode) (mode == ShapeModeDefault || mode == ShapeModeBinarizeAlpha || mode == ShapeModeReverseBinarizeAlpha) + +/** \brief A union containing parameters for shaped windows. */ +typedef union { + /** \brief A cutoff alpha value for binarization of the window shape's alpha channel. */ + Uint8 binarizationCutoff; + SDL_Color colorKey; +} SDL_WindowShapeParams; + +/** \brief A struct that tags the SDL_WindowShapeParams union with an enum describing the type of its contents. */ +typedef struct SDL_WindowShapeMode { + /** \brief The mode of these window-shape parameters. */ + WindowShapeMode mode; + /** \brief Window-shape parameters. */ + SDL_WindowShapeParams parameters; +} SDL_WindowShapeMode; + +/** + * Set the shape and parameters of a shaped window. + * + * \param window The shaped window whose parameters should be set. + * \param shape A surface encoding the desired shape for the window. + * \param shape_mode The parameters to set for the shaped window. + * \return 0 on success, SDL_INVALID_SHAPE_ARGUMENT on an invalid shape + * argument, or SDL_NONSHAPEABLE_WINDOW if the SDL_Window given does + * not reference a valid shaped window. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WindowShapeMode + * \sa SDL_GetShapedWindowMode + */ +extern DECLSPEC int SDLCALL SDL_SetWindowShape(SDL_Window *window,SDL_Surface *shape,SDL_WindowShapeMode *shape_mode); + +/** + * Get the shape parameters of a shaped window. + * + * \param window The shaped window whose parameters should be retrieved. + * \param shape_mode An empty shape-mode structure to fill, or NULL to check + * whether the window has a shape. + * \return 0 if the window has a shape and, provided shape_mode was not NULL, + * shape_mode has been filled with the mode data, + * SDL_NONSHAPEABLE_WINDOW if the SDL_Window given is not a shaped + * window, or SDL_WINDOW_LACKS_SHAPE if the SDL_Window given is a + * shapeable window currently lacking a shape. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_WindowShapeMode + * \sa SDL_SetWindowShape + */ +extern DECLSPEC int SDLCALL SDL_GetShapedWindowMode(SDL_Window *window,SDL_WindowShapeMode *shape_mode); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_shape_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_stdinc.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_stdinc.h new file mode 100644 index 00000000..1854698b --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_stdinc.h @@ -0,0 +1,873 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: StdInc */ + +/** + * # CategoryStdInc + * + * This is a general header that includes C language support. + */ + +#ifndef SDL_stdinc_h_ +#define SDL_stdinc_h_ + +#include "SDL_config.h" + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#if defined(STDC_HEADERS) +# include +# include +# include +#else +# if defined(HAVE_STDLIB_H) +# include +# elif defined(HAVE_MALLOC_H) +# include +# endif +# if defined(HAVE_STDDEF_H) +# include +# endif +# if defined(HAVE_STDARG_H) +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_WCHAR_H +# include +#endif +#if defined(HAVE_INTTYPES_H) +# include +#elif defined(HAVE_STDINT_H) +# include +#endif +#ifdef HAVE_CTYPE_H +# include +#endif +#ifdef HAVE_MATH_H +# if defined(_MSC_VER) +/* Defining _USE_MATH_DEFINES is required to get M_PI to be defined on + Visual Studio. See http://msdn.microsoft.com/en-us/library/4hwaceh6.aspx + for more information. +*/ +# ifndef _USE_MATH_DEFINES +# define _USE_MATH_DEFINES +# endif +# endif +# include +#endif +#ifdef HAVE_FLOAT_H +# include +#endif +#if defined(HAVE_ALLOCA) && !defined(alloca) +# if defined(HAVE_ALLOCA_H) +# include +# elif defined(__GNUC__) +# define alloca __builtin_alloca +# elif defined(_MSC_VER) +# include +# define alloca _alloca +# elif defined(__WATCOMC__) +# include +# elif defined(__BORLANDC__) +# include +# elif defined(__DMC__) +# include +# elif defined(__AIX__) +#pragma alloca +# elif defined(__MRC__) +void *alloca(unsigned); +# else +void *alloca(size_t); +# endif +#endif + +#ifdef SIZE_MAX +# define SDL_SIZE_MAX SIZE_MAX +#else +# define SDL_SIZE_MAX ((size_t) -1) +#endif + +/** + * Check if the compiler supports a given builtin. + * Supported by virtually all clang versions and recent gcc. Use this + * instead of checking the clang version if possible. + */ +#ifdef __has_builtin +#define _SDL_HAS_BUILTIN(x) __has_builtin(x) +#else +#define _SDL_HAS_BUILTIN(x) 0 +#endif + +/** + * The number of elements in an array. + */ +#define SDL_arraysize(array) (sizeof(array)/sizeof(array[0])) +#define SDL_TABLESIZE(table) SDL_arraysize(table) + +/** + * Macro useful for building other macros with strings in them + * + * e.g: + * + * ```c + * #define LOG_ERROR(X) OutputDebugString(SDL_STRINGIFY_ARG(__FUNCTION__) ": " X "\n") + * ``` + */ +#define SDL_STRINGIFY_ARG(arg) #arg + +/** + * \name Cast operators + * + * Use proper C++ casts when compiled as C++ to be compatible with the option + * -Wold-style-cast of GCC (and -Werror=old-style-cast in GCC 4.2 and above). + */ +/* @{ */ +#ifdef __cplusplus +#define SDL_reinterpret_cast(type, expression) reinterpret_cast(expression) +#define SDL_static_cast(type, expression) static_cast(expression) +#define SDL_const_cast(type, expression) const_cast(expression) +#else +#define SDL_reinterpret_cast(type, expression) ((type)(expression)) +#define SDL_static_cast(type, expression) ((type)(expression)) +#define SDL_const_cast(type, expression) ((type)(expression)) +#endif +/* @} *//* Cast operators */ + +/* Define a four character code as a Uint32 */ +#define SDL_FOURCC(A, B, C, D) \ + ((SDL_static_cast(Uint32, SDL_static_cast(Uint8, (A))) << 0) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (B))) << 8) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (C))) << 16) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (D))) << 24)) + +/** + * \name Basic data types + */ +/* @{ */ + +#ifdef __CC_ARM +/* ARM's compiler throws warnings if we use an enum: like "SDL_bool x = a < b;" */ +#define SDL_FALSE 0 +#define SDL_TRUE 1 +typedef int SDL_bool; +#else +typedef enum +{ + SDL_FALSE = 0, + SDL_TRUE = 1 +} SDL_bool; +#endif + +/** + * A signed 8-bit integer type. + */ +typedef int8_t Sint8; +#define SDL_MAX_SINT8 ((Sint8)0x7F) /* 127 */ +#define SDL_MIN_SINT8 ((Sint8)(~0x7F)) /* -128 */ + +/** + * An unsigned 8-bit integer type. + */ +typedef uint8_t Uint8; +#define SDL_MAX_UINT8 ((Uint8)0xFF) /* 255 */ +#define SDL_MIN_UINT8 ((Uint8)0x00) /* 0 */ + +/** + * A signed 16-bit integer type. + */ +typedef int16_t Sint16; +#define SDL_MAX_SINT16 ((Sint16)0x7FFF) /* 32767 */ +#define SDL_MIN_SINT16 ((Sint16)(~0x7FFF)) /* -32768 */ + +/** + * An unsigned 16-bit integer type. + */ +typedef uint16_t Uint16; +#define SDL_MAX_UINT16 ((Uint16)0xFFFF) /* 65535 */ +#define SDL_MIN_UINT16 ((Uint16)0x0000) /* 0 */ + +/** + * A signed 32-bit integer type. + */ +typedef int32_t Sint32; +#define SDL_MAX_SINT32 ((Sint32)0x7FFFFFFF) /* 2147483647 */ +#define SDL_MIN_SINT32 ((Sint32)(~0x7FFFFFFF)) /* -2147483648 */ + +/** + * An unsigned 32-bit integer type. + */ +typedef uint32_t Uint32; +#define SDL_MAX_UINT32 ((Uint32)0xFFFFFFFFu) /* 4294967295 */ +#define SDL_MIN_UINT32 ((Uint32)0x00000000) /* 0 */ + +/** + * A signed 64-bit integer type. + */ +typedef int64_t Sint64; +#define SDL_MAX_SINT64 ((Sint64)0x7FFFFFFFFFFFFFFFll) /* 9223372036854775807 */ +#define SDL_MIN_SINT64 ((Sint64)(~0x7FFFFFFFFFFFFFFFll)) /* -9223372036854775808 */ + +/** + * An unsigned 64-bit integer type. + */ +typedef uint64_t Uint64; +#define SDL_MAX_UINT64 ((Uint64)0xFFFFFFFFFFFFFFFFull) /* 18446744073709551615 */ +#define SDL_MIN_UINT64 ((Uint64)(0x0000000000000000ull)) /* 0 */ + + +/* @} *//* Basic data types */ + +/** + * \name Floating-point constants + */ +/* @{ */ + +#ifdef FLT_EPSILON +#define SDL_FLT_EPSILON FLT_EPSILON +#else +#define SDL_FLT_EPSILON 1.1920928955078125e-07F /* 0x0.000002p0 */ +#endif + +/* @} *//* Floating-point constants */ + +/* Make sure we have macros for printing width-based integers. + * should define these but this is not true all platforms. + * (for example win32) */ +#ifndef SDL_PRIs64 +#if defined(__WIN32__) || defined(__GDK__) +#define SDL_PRIs64 "I64d" +#elif defined(PRId64) +#define SDL_PRIs64 PRId64 +#elif defined(__LP64__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) +#define SDL_PRIs64 "ld" +#else +#define SDL_PRIs64 "lld" +#endif +#endif +#ifndef SDL_PRIu64 +#if defined(__WIN32__) || defined(__GDK__) +#define SDL_PRIu64 "I64u" +#elif defined(PRIu64) +#define SDL_PRIu64 PRIu64 +#elif defined(__LP64__) && !defined(__APPLE__) +#define SDL_PRIu64 "lu" +#else +#define SDL_PRIu64 "llu" +#endif +#endif +#ifndef SDL_PRIx64 +#if defined(__WIN32__) || defined(__GDK__) +#define SDL_PRIx64 "I64x" +#elif defined(PRIx64) +#define SDL_PRIx64 PRIx64 +#elif defined(__LP64__) && !defined(__APPLE__) +#define SDL_PRIx64 "lx" +#else +#define SDL_PRIx64 "llx" +#endif +#endif +#ifndef SDL_PRIX64 +#if defined(__WIN32__) || defined(__GDK__) +#define SDL_PRIX64 "I64X" +#elif defined(PRIX64) +#define SDL_PRIX64 PRIX64 +#elif defined(__LP64__) && !defined(__APPLE__) +#define SDL_PRIX64 "lX" +#else +#define SDL_PRIX64 "llX" +#endif +#endif +#ifndef SDL_PRIs32 +#ifdef PRId32 +#define SDL_PRIs32 PRId32 +#else +#define SDL_PRIs32 "d" +#endif +#endif +#ifndef SDL_PRIu32 +#ifdef PRIu32 +#define SDL_PRIu32 PRIu32 +#else +#define SDL_PRIu32 "u" +#endif +#endif +#ifndef SDL_PRIx32 +#ifdef PRIx32 +#define SDL_PRIx32 PRIx32 +#else +#define SDL_PRIx32 "x" +#endif +#endif +#ifndef SDL_PRIX32 +#ifdef PRIX32 +#define SDL_PRIX32 PRIX32 +#else +#define SDL_PRIX32 "X" +#endif +#endif + +/* Annotations to help code analysis tools */ +#ifdef SDL_DISABLE_ANALYZE_MACROS +#define SDL_IN_BYTECAP(x) +#define SDL_INOUT_Z_CAP(x) +#define SDL_OUT_Z_CAP(x) +#define SDL_OUT_CAP(x) +#define SDL_OUT_BYTECAP(x) +#define SDL_OUT_Z_BYTECAP(x) +#define SDL_PRINTF_FORMAT_STRING +#define SDL_SCANF_FORMAT_STRING +#define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) +#define SDL_PRINTF_VARARG_FUNCV( fmtargnumber ) +#define SDL_SCANF_VARARG_FUNC( fmtargnumber ) +#define SDL_SCANF_VARARG_FUNCV( fmtargnumber ) +#else +#if defined(_MSC_VER) && (_MSC_VER >= 1600) /* VS 2010 and above */ +#include + +#define SDL_IN_BYTECAP(x) _In_bytecount_(x) +#define SDL_INOUT_Z_CAP(x) _Inout_z_cap_(x) +#define SDL_OUT_Z_CAP(x) _Out_z_cap_(x) +#define SDL_OUT_CAP(x) _Out_cap_(x) +#define SDL_OUT_BYTECAP(x) _Out_bytecap_(x) +#define SDL_OUT_Z_BYTECAP(x) _Out_z_bytecap_(x) + +#define SDL_PRINTF_FORMAT_STRING _Printf_format_string_ +#define SDL_SCANF_FORMAT_STRING _Scanf_format_string_impl_ +#else +#define SDL_IN_BYTECAP(x) +#define SDL_INOUT_Z_CAP(x) +#define SDL_OUT_Z_CAP(x) +#define SDL_OUT_CAP(x) +#define SDL_OUT_BYTECAP(x) +#define SDL_OUT_Z_BYTECAP(x) +#define SDL_PRINTF_FORMAT_STRING +#define SDL_SCANF_FORMAT_STRING +#endif +#if defined(__GNUC__) +#define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __printf__, fmtargnumber, fmtargnumber+1 ))) +#define SDL_PRINTF_VARARG_FUNCV( fmtargnumber ) __attribute__(( format( __printf__, fmtargnumber, 0 ))) +#define SDL_SCANF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __scanf__, fmtargnumber, fmtargnumber+1 ))) +#define SDL_SCANF_VARARG_FUNCV( fmtargnumber ) __attribute__(( format( __scanf__, fmtargnumber, 0 ))) +#else +#define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) +#define SDL_PRINTF_VARARG_FUNCV( fmtargnumber ) +#define SDL_SCANF_VARARG_FUNC( fmtargnumber ) +#define SDL_SCANF_VARARG_FUNCV( fmtargnumber ) +#endif +#endif /* SDL_DISABLE_ANALYZE_MACROS */ + +#ifndef SDL_COMPILE_TIME_ASSERT +#if defined(__cplusplus) +/* Keep C++ case alone: Some versions of gcc will define __STDC_VERSION__ even when compiling in C++ mode. */ +#if (__cplusplus >= 201103L) +#define SDL_COMPILE_TIME_ASSERT(name, x) static_assert(x, #x) +#endif +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L) +#define SDL_COMPILE_TIME_ASSERT(name, x) static_assert(x, #x) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#define SDL_COMPILE_TIME_ASSERT(name, x) _Static_assert(x, #x) +#endif +#endif /* !SDL_COMPILE_TIME_ASSERT */ + +#ifndef SDL_COMPILE_TIME_ASSERT +/* universal, but may trigger -Wunused-local-typedefs */ +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_compile_time_assert_ ## name[(x) * 2 - 1] +#endif + +/** \cond */ +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); +SDL_COMPILE_TIME_ASSERT(sint8, sizeof(Sint8) == 1); +SDL_COMPILE_TIME_ASSERT(uint16, sizeof(Uint16) == 2); +SDL_COMPILE_TIME_ASSERT(sint16, sizeof(Sint16) == 2); +SDL_COMPILE_TIME_ASSERT(uint32, sizeof(Uint32) == 4); +SDL_COMPILE_TIME_ASSERT(sint32, sizeof(Sint32) == 4); +SDL_COMPILE_TIME_ASSERT(uint64, sizeof(Uint64) == 8); +SDL_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8); +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ +/** \endcond */ + +/* Check to make sure enums are the size of ints, for structure packing. + For both Watcom C/C++ and Borland C/C++ the compiler option that makes + enums having the size of an int must be enabled. + This is "-b" for Borland C/C++ and "-ei" for Watcom C/C++ (v11). +*/ + +/** \cond */ +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +#if !defined(__VITA__) && !defined(__3DS__) +/* TODO: include/SDL_stdinc.h:422: error: size of array 'SDL_dummy_enum' is negative */ +typedef enum +{ + DUMMY_ENUM_VALUE +} SDL_DUMMY_ENUM; + +SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); +#endif +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ +/** \endcond */ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_ALLOCA +#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) +#define SDL_stack_free(data) +#else +#define SDL_stack_alloc(type, count) (type*)SDL_malloc(sizeof(type)*(count)) +#define SDL_stack_free(data) SDL_free(data) +#endif + +extern DECLSPEC void *SDLCALL SDL_malloc(size_t size); +extern DECLSPEC void *SDLCALL SDL_calloc(size_t nmemb, size_t size); +extern DECLSPEC void *SDLCALL SDL_realloc(void *mem, size_t size); +extern DECLSPEC void SDLCALL SDL_free(void *mem); + +typedef void *(SDLCALL *SDL_malloc_func)(size_t size); +typedef void *(SDLCALL *SDL_calloc_func)(size_t nmemb, size_t size); +typedef void *(SDLCALL *SDL_realloc_func)(void *mem, size_t size); +typedef void (SDLCALL *SDL_free_func)(void *mem); + +/** + * Get the original set of SDL memory functions + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC void SDLCALL SDL_GetOriginalMemoryFunctions(SDL_malloc_func *malloc_func, + SDL_calloc_func *calloc_func, + SDL_realloc_func *realloc_func, + SDL_free_func *free_func); + +/** + * Get the current set of SDL memory functions + * + * \since This function is available since SDL 2.0.7. + */ +extern DECLSPEC void SDLCALL SDL_GetMemoryFunctions(SDL_malloc_func *malloc_func, + SDL_calloc_func *calloc_func, + SDL_realloc_func *realloc_func, + SDL_free_func *free_func); + +/** + * Replace SDL's memory allocation functions with a custom set + * + * \since This function is available since SDL 2.0.7. + */ +extern DECLSPEC int SDLCALL SDL_SetMemoryFunctions(SDL_malloc_func malloc_func, + SDL_calloc_func calloc_func, + SDL_realloc_func realloc_func, + SDL_free_func free_func); + +/** + * Get the number of outstanding (unfreed) allocations + * + * \since This function is available since SDL 2.0.7. + */ +extern DECLSPEC int SDLCALL SDL_GetNumAllocations(void); + +extern DECLSPEC char *SDLCALL SDL_getenv(const char *name); +extern DECLSPEC int SDLCALL SDL_setenv(const char *name, const char *value, int overwrite); + +typedef int (SDLCALL *SDL_CompareCallback)(const void *, const void *); +extern DECLSPEC void SDLCALL SDL_qsort(void *base, size_t nmemb, size_t size, SDL_CompareCallback compare); +extern DECLSPEC void * SDLCALL SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, SDL_CompareCallback compare); + +extern DECLSPEC int SDLCALL SDL_abs(int x); + +/* NOTE: these double-evaluate their arguments, so you should never have side effects in the parameters */ +#define SDL_min(x, y) (((x) < (y)) ? (x) : (y)) +#define SDL_max(x, y) (((x) > (y)) ? (x) : (y)) +#define SDL_clamp(x, a, b) (((x) < (a)) ? (a) : (((x) > (b)) ? (b) : (x))) + +extern DECLSPEC int SDLCALL SDL_isalpha(int x); +extern DECLSPEC int SDLCALL SDL_isalnum(int x); +extern DECLSPEC int SDLCALL SDL_isblank(int x); +extern DECLSPEC int SDLCALL SDL_iscntrl(int x); +extern DECLSPEC int SDLCALL SDL_isdigit(int x); +extern DECLSPEC int SDLCALL SDL_isxdigit(int x); +extern DECLSPEC int SDLCALL SDL_ispunct(int x); +extern DECLSPEC int SDLCALL SDL_isspace(int x); +extern DECLSPEC int SDLCALL SDL_isupper(int x); +extern DECLSPEC int SDLCALL SDL_islower(int x); +extern DECLSPEC int SDLCALL SDL_isprint(int x); +extern DECLSPEC int SDLCALL SDL_isgraph(int x); +extern DECLSPEC int SDLCALL SDL_toupper(int x); +extern DECLSPEC int SDLCALL SDL_tolower(int x); + +extern DECLSPEC Uint16 SDLCALL SDL_crc16(Uint16 crc, const void *data, size_t len); +extern DECLSPEC Uint32 SDLCALL SDL_crc32(Uint32 crc, const void *data, size_t len); + +extern DECLSPEC void *SDLCALL SDL_memset(SDL_OUT_BYTECAP(len) void *dst, int c, size_t len); + +/* Some safe(r) macros for zero'ing structures... */ +#define SDL_zero(x) SDL_memset(&(x), 0, sizeof((x))) +#define SDL_zerop(x) SDL_memset((x), 0, sizeof(*(x))) +#define SDL_zeroa(x) SDL_memset((x), 0, sizeof((x))) + +#define SDL_copyp(dst, src) \ + { SDL_COMPILE_TIME_ASSERT(SDL_copyp, sizeof (*(dst)) == sizeof (*(src))); } \ + SDL_memcpy((dst), (src), sizeof (*(src))) + + +/* Note that memset() is a byte assignment and this is a 32-bit assignment, so they're not directly equivalent. */ +SDL_FORCE_INLINE void SDL_memset4(void *dst, Uint32 val, size_t dwords) +{ +#if defined(__GNUC__) && defined(__i386__) + int u0, u1, u2; + __asm__ __volatile__ ( + "cld \n\t" + "rep ; stosl \n\t" + : "=&D" (u0), "=&a" (u1), "=&c" (u2) + : "0" (dst), "1" (val), "2" (SDL_static_cast(Uint32, dwords)) + : "memory" + ); +#else + size_t _n = (dwords + 3) / 4; + Uint32 *_p = SDL_static_cast(Uint32 *, dst); + Uint32 _val = (val); + if (dwords == 0) { + return; + } + switch (dwords % 4) { + case 0: do { *_p++ = _val; SDL_FALLTHROUGH; + case 3: *_p++ = _val; SDL_FALLTHROUGH; + case 2: *_p++ = _val; SDL_FALLTHROUGH; + case 1: *_p++ = _val; + } while ( --_n ); + } +#endif +} + +extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len); + +extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len); +extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len); + +extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr); +extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen); +extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen); +extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr); +extern DECLSPEC wchar_t *SDLCALL SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle); + +extern DECLSPEC int SDLCALL SDL_wcscmp(const wchar_t *str1, const wchar_t *str2); +extern DECLSPEC int SDLCALL SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen); +extern DECLSPEC int SDLCALL SDL_wcscasecmp(const wchar_t *str1, const wchar_t *str2); +extern DECLSPEC int SDLCALL SDL_wcsncasecmp(const wchar_t *str1, const wchar_t *str2, size_t len); + +extern DECLSPEC size_t SDLCALL SDL_strlen(const char *str); +extern DECLSPEC size_t SDLCALL SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen); +extern DECLSPEC size_t SDLCALL SDL_utf8strlcpy(SDL_OUT_Z_CAP(dst_bytes) char *dst, const char *src, size_t dst_bytes); +extern DECLSPEC size_t SDLCALL SDL_strlcat(SDL_INOUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen); +extern DECLSPEC char *SDLCALL SDL_strdup(const char *str); +extern DECLSPEC char *SDLCALL SDL_strrev(char *str); +extern DECLSPEC char *SDLCALL SDL_strupr(char *str); +extern DECLSPEC char *SDLCALL SDL_strlwr(char *str); +extern DECLSPEC char *SDLCALL SDL_strchr(const char *str, int c); +extern DECLSPEC char *SDLCALL SDL_strrchr(const char *str, int c); +extern DECLSPEC char *SDLCALL SDL_strstr(const char *haystack, const char *needle); +extern DECLSPEC char *SDLCALL SDL_strcasestr(const char *haystack, const char *needle); +extern DECLSPEC char *SDLCALL SDL_strtokr(char *s1, const char *s2, char **saveptr); +extern DECLSPEC size_t SDLCALL SDL_utf8strlen(const char *str); +extern DECLSPEC size_t SDLCALL SDL_utf8strnlen(const char *str, size_t bytes); + +extern DECLSPEC char *SDLCALL SDL_itoa(int value, char *str, int radix); +extern DECLSPEC char *SDLCALL SDL_uitoa(unsigned int value, char *str, int radix); +extern DECLSPEC char *SDLCALL SDL_ltoa(long value, char *str, int radix); +extern DECLSPEC char *SDLCALL SDL_ultoa(unsigned long value, char *str, int radix); +extern DECLSPEC char *SDLCALL SDL_lltoa(Sint64 value, char *str, int radix); +extern DECLSPEC char *SDLCALL SDL_ulltoa(Uint64 value, char *str, int radix); + +extern DECLSPEC int SDLCALL SDL_atoi(const char *str); +extern DECLSPEC double SDLCALL SDL_atof(const char *str); +extern DECLSPEC long SDLCALL SDL_strtol(const char *str, char **endp, int base); +extern DECLSPEC unsigned long SDLCALL SDL_strtoul(const char *str, char **endp, int base); +extern DECLSPEC Sint64 SDLCALL SDL_strtoll(const char *str, char **endp, int base); +extern DECLSPEC Uint64 SDLCALL SDL_strtoull(const char *str, char **endp, int base); +extern DECLSPEC double SDLCALL SDL_strtod(const char *str, char **endp); + +extern DECLSPEC int SDLCALL SDL_strcmp(const char *str1, const char *str2); +extern DECLSPEC int SDLCALL SDL_strncmp(const char *str1, const char *str2, size_t maxlen); +extern DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str2); +extern DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t len); + +extern DECLSPEC int SDLCALL SDL_sscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, ...) SDL_SCANF_VARARG_FUNC(2); +extern DECLSPEC int SDLCALL SDL_vsscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, va_list ap) SDL_SCANF_VARARG_FUNCV(2); +extern DECLSPEC int SDLCALL SDL_snprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, ... ) SDL_PRINTF_VARARG_FUNC(3); +extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(3); +extern DECLSPEC int SDLCALL SDL_asprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); +extern DECLSPEC int SDLCALL SDL_vasprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(2); + +#ifndef HAVE_M_PI +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 /**< pi */ +#endif +#endif + +/** + * Use this function to compute arc cosine of `x`. + * + * The definition of `y = acos(x)` is `x = cos(y)`. + * + * Domain: `-1 <= x <= 1` + * + * Range: `0 <= y <= Pi` + * + * \param x floating point value, in radians. + * \returns arc cosine of `x`. + * + * \since This function is available since SDL 2.0.2. + */ +extern DECLSPEC double SDLCALL SDL_acos(double x); +extern DECLSPEC float SDLCALL SDL_acosf(float x); +extern DECLSPEC double SDLCALL SDL_asin(double x); +extern DECLSPEC float SDLCALL SDL_asinf(float x); +extern DECLSPEC double SDLCALL SDL_atan(double x); +extern DECLSPEC float SDLCALL SDL_atanf(float x); +extern DECLSPEC double SDLCALL SDL_atan2(double y, double x); +extern DECLSPEC float SDLCALL SDL_atan2f(float y, float x); +extern DECLSPEC double SDLCALL SDL_ceil(double x); +extern DECLSPEC float SDLCALL SDL_ceilf(float x); +extern DECLSPEC double SDLCALL SDL_copysign(double x, double y); +extern DECLSPEC float SDLCALL SDL_copysignf(float x, float y); +extern DECLSPEC double SDLCALL SDL_cos(double x); +extern DECLSPEC float SDLCALL SDL_cosf(float x); +extern DECLSPEC double SDLCALL SDL_exp(double x); +extern DECLSPEC float SDLCALL SDL_expf(float x); +extern DECLSPEC double SDLCALL SDL_fabs(double x); +extern DECLSPEC float SDLCALL SDL_fabsf(float x); +extern DECLSPEC double SDLCALL SDL_floor(double x); +extern DECLSPEC float SDLCALL SDL_floorf(float x); +extern DECLSPEC double SDLCALL SDL_trunc(double x); +extern DECLSPEC float SDLCALL SDL_truncf(float x); +extern DECLSPEC double SDLCALL SDL_fmod(double x, double y); +extern DECLSPEC float SDLCALL SDL_fmodf(float x, float y); +extern DECLSPEC double SDLCALL SDL_log(double x); +extern DECLSPEC float SDLCALL SDL_logf(float x); +extern DECLSPEC double SDLCALL SDL_log10(double x); +extern DECLSPEC float SDLCALL SDL_log10f(float x); +extern DECLSPEC double SDLCALL SDL_pow(double x, double y); +extern DECLSPEC float SDLCALL SDL_powf(float x, float y); +extern DECLSPEC double SDLCALL SDL_round(double x); +extern DECLSPEC float SDLCALL SDL_roundf(float x); +extern DECLSPEC long SDLCALL SDL_lround(double x); +extern DECLSPEC long SDLCALL SDL_lroundf(float x); +extern DECLSPEC double SDLCALL SDL_scalbn(double x, int n); +extern DECLSPEC float SDLCALL SDL_scalbnf(float x, int n); +extern DECLSPEC double SDLCALL SDL_sin(double x); +extern DECLSPEC float SDLCALL SDL_sinf(float x); +extern DECLSPEC double SDLCALL SDL_sqrt(double x); +extern DECLSPEC float SDLCALL SDL_sqrtf(float x); +extern DECLSPEC double SDLCALL SDL_tan(double x); +extern DECLSPEC float SDLCALL SDL_tanf(float x); + +/* The SDL implementation of iconv() returns these error codes */ +#define SDL_ICONV_ERROR (size_t)-1 +#define SDL_ICONV_E2BIG (size_t)-2 +#define SDL_ICONV_EILSEQ (size_t)-3 +#define SDL_ICONV_EINVAL (size_t)-4 + +/* SDL_iconv_* are now always real symbols/types, not macros or inlined. */ +typedef struct _SDL_iconv_t *SDL_iconv_t; +extern DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, + const char *fromcode); +extern DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); +extern DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, + size_t * inbytesleft, char **outbuf, + size_t * outbytesleft); + +/** + * This function converts a buffer or string between encodings in one pass, + * returning a string that must be freed with SDL_free() or NULL on error. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC char *SDLCALL SDL_iconv_string(const char *tocode, + const char *fromcode, + const char *inbuf, + size_t inbytesleft); + +/* Some helper macros for common cases... */ +#define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_wchar_utf8(S) SDL_iconv_string("UTF-8", "WCHAR_T", (char *)S, (SDL_wcslen(S)+1)*sizeof(wchar_t)) + +/* force builds using Clang's static analysis tools to use literal C runtime + here, since there are possibly tests that are ineffective otherwise. */ +#if defined(__clang_analyzer__) && !defined(SDL_DISABLE_ANALYZE_MACROS) + +/* The analyzer knows about strlcpy even when the system doesn't provide it */ +#ifndef HAVE_STRLCPY +size_t strlcpy(char* dst, const char* src, size_t size); +#endif + +/* The analyzer knows about strlcat even when the system doesn't provide it */ +#ifndef HAVE_STRLCAT +size_t strlcat(char* dst, const char* src, size_t size); +#endif + +#ifndef HAVE_WCSLCPY +size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t size); +#endif + +#ifndef HAVE_WCSLCAT +size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t size); +#endif + +#ifndef _WIN32 +/* strdup is not ANSI but POSIX, and its prototype might be hidden... */ +/* not for windows: might conflict with string.h where strdup may have + * dllimport attribute: https://github.com/libsdl-org/SDL/issues/12948 */ +char *strdup(const char *str); +#endif + +/* Starting LLVM 16, the analyser errors out if these functions do not have + their prototype defined (clang-diagnostic-implicit-function-declaration) */ +#include +#include +#include + +#define SDL_malloc malloc +#define SDL_calloc calloc +#define SDL_realloc realloc +#define SDL_free free +#define SDL_memset memset +#define SDL_memcpy memcpy +#define SDL_memmove memmove +#define SDL_memcmp memcmp +#define SDL_strlcpy strlcpy +#define SDL_strlcat strlcat +#define SDL_strlen strlen +#define SDL_wcslen wcslen +#define SDL_wcslcpy wcslcpy +#define SDL_wcslcat wcslcat +#define SDL_strdup strdup +#define SDL_wcsdup wcsdup +#define SDL_strchr strchr +#define SDL_strrchr strrchr +#define SDL_strstr strstr +#define SDL_wcsstr wcsstr +#define SDL_strtokr strtok_r +#define SDL_strcmp strcmp +#define SDL_wcscmp wcscmp +#define SDL_strncmp strncmp +#define SDL_wcsncmp wcsncmp +#define SDL_strcasecmp strcasecmp +#define SDL_strncasecmp strncasecmp +#define SDL_sscanf sscanf +#define SDL_vsscanf vsscanf +#define SDL_snprintf snprintf +#define SDL_vsnprintf vsnprintf +#endif + +SDL_FORCE_INLINE void *SDL_memcpy4(SDL_OUT_BYTECAP(dwords*4) void *dst, SDL_IN_BYTECAP(dwords*4) const void *src, size_t dwords) +{ + return SDL_memcpy(dst, src, dwords * 4); +} + +/** + * If a * b would overflow, return -1. + * + * Otherwise store a * b via ret and return 0. + * + * \since This function is available since SDL 2.24.0. + */ +SDL_FORCE_INLINE int SDL_size_mul_overflow (size_t a, + size_t b, + size_t *ret) +{ + if (a != 0 && b > SDL_SIZE_MAX / a) { + return -1; + } + *ret = a * b; + return 0; +} + +#if _SDL_HAS_BUILTIN(__builtin_mul_overflow) +/* This needs to be wrapped in an inline rather than being a direct #define, + * because __builtin_mul_overflow() is type-generic, but we want to be + * consistent about interpreting a and b as size_t. */ +SDL_FORCE_INLINE int _SDL_size_mul_overflow_builtin (size_t a, + size_t b, + size_t *ret) +{ + return __builtin_mul_overflow(a, b, ret) == 0 ? 0 : -1; +} +#define SDL_size_mul_overflow(a, b, ret) (_SDL_size_mul_overflow_builtin(a, b, ret)) +#endif + +/** + * If a + b would overflow, return -1. + * + * Otherwise store a + b via ret and return 0. + * + * \since This function is available since SDL 2.24.0. + */ +SDL_FORCE_INLINE int SDL_size_add_overflow (size_t a, + size_t b, + size_t *ret) +{ + if (b > SDL_SIZE_MAX - a) { + return -1; + } + *ret = a + b; + return 0; +} + +#if _SDL_HAS_BUILTIN(__builtin_add_overflow) +/* This needs to be wrapped in an inline rather than being a direct #define, + * the same as the call to __builtin_mul_overflow() above. */ +SDL_FORCE_INLINE int _SDL_size_add_overflow_builtin (size_t a, + size_t b, + size_t *ret) +{ + return __builtin_add_overflow(a, b, ret) == 0 ? 0 : -1; +} +#define SDL_size_add_overflow(a, b, ret) (_SDL_size_add_overflow_builtin(a, b, ret)) +#endif + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_stdinc_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_surface.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_surface.h new file mode 100644 index 00000000..42ea5919 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_surface.h @@ -0,0 +1,1001 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategorySurface + * + * Header file for SDL_Surface definition and management functions. + */ + +#ifndef SDL_surface_h_ +#define SDL_surface_h_ + +#include "SDL_stdinc.h" +#include "SDL_pixels.h" +#include "SDL_rect.h" +#include "SDL_blendmode.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \name Surface flags + * + * These are the currently supported flags for the SDL_Surface. + * + * \internal + * Used internally (read-only). + */ +/* @{ */ +#define SDL_SWSURFACE 0 /**< Just here for compatibility */ +#define SDL_PREALLOC 0x00000001 /**< Surface uses preallocated memory */ +#define SDL_RLEACCEL 0x00000002 /**< Surface is RLE encoded */ +#define SDL_DONTFREE 0x00000004 /**< Surface is referenced internally */ +#define SDL_SIMD_ALIGNED 0x00000008 /**< Surface uses aligned memory */ +/* @} *//* Surface flags */ + +/** + * Evaluates to true if the surface needs to be locked before access. + */ +#define SDL_MUSTLOCK(S) (((S)->flags & SDL_RLEACCEL) != 0) + +typedef struct SDL_BlitMap SDL_BlitMap; /* this is an opaque type. */ + +/** + * A collection of pixels used in software blitting. + * + * This structure should be treated as read-only, except for `pixels`, which, + * if not NULL, contains the raw pixel data for the surface. + */ +typedef struct SDL_Surface +{ + Uint32 flags; /**< Read-only */ + SDL_PixelFormat *format; /**< Read-only */ + int w, h; /**< Read-only */ + int pitch; /**< Read-only */ + void *pixels; /**< Read-write */ + + /** Application data associated with the surface */ + void *userdata; /**< Read-write */ + + /** information needed for surfaces requiring locks */ + int locked; /**< Read-only */ + + /** list of BlitMap that hold a reference to this surface */ + void *list_blitmap; /**< Private */ + + /** clipping information */ + SDL_Rect clip_rect; /**< Read-only */ + + /** info for fast blit mapping to other surfaces */ + SDL_BlitMap *map; /**< Private */ + + /** Reference count -- used when freeing surface */ + int refcount; /**< Read-mostly */ +} SDL_Surface; + +/** + * The type of function used for surface blitting functions. + */ +typedef int (SDLCALL *SDL_blit) (struct SDL_Surface * src, SDL_Rect * srcrect, + struct SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * The formula used for converting between YUV and RGB + */ +typedef enum SDL_YUV_CONVERSION_MODE +{ + SDL_YUV_CONVERSION_JPEG, /**< Full range JPEG */ + SDL_YUV_CONVERSION_BT601, /**< BT.601 (the default) */ + SDL_YUV_CONVERSION_BT709, /**< BT.709 */ + SDL_YUV_CONVERSION_AUTOMATIC /**< BT.601 for SD content, BT.709 for HD content */ +} SDL_YUV_CONVERSION_MODE; + +/** + * Allocate a new RGB surface. + * + * If `depth` is 4 or 8 bits, an empty palette is allocated for the surface. + * If `depth` is greater than 8 bits, the pixel format is set using the + * [RGBA]mask parameters. + * + * The [RGBA]mask parameters are the bitmasks used to extract that color from + * a pixel. For instance, `Rmask` being 0xFF000000 means the red data is + * stored in the most significant byte. Using zeros for the RGB masks sets a + * default value, based on the depth. For example: + * + * ```c++ + * SDL_CreateRGBSurface(0,w,h,32,0,0,0,0); + * ``` + * + * However, using zero for the Amask results in an Amask of 0. + * + * By default surfaces with an alpha mask are set up for blending as with: + * + * ```c++ + * SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND) + * ``` + * + * You can change this by calling SDL_SetSurfaceBlendMode() and selecting a + * different `blendMode`. + * + * \param flags the flags are unused and should be set to 0. + * \param width the width of the surface. + * \param height the height of the surface. + * \param depth the depth of the surface in bits. + * \param Rmask the red mask for the pixels. + * \param Gmask the green mask for the pixels. + * \param Bmask the blue mask for the pixels. + * \param Amask the alpha mask for the pixels. + * \returns the new SDL_Surface structure that is created or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRGBSurfaceFrom + * \sa SDL_CreateRGBSurfaceWithFormat + * \sa SDL_FreeSurface + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurface + (Uint32 flags, int width, int height, int depth, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); + + +/* !!! FIXME for 2.1: why does this ask for depth? Format provides that. */ + +/** + * Allocate a new RGB surface with a specific pixel format. + * + * This function operates mostly like SDL_CreateRGBSurface(), except instead + * of providing pixel color masks, you provide it with a predefined format + * from SDL_PixelFormatEnum. + * + * \param flags the flags are unused and should be set to 0. + * \param width the width of the surface. + * \param height the height of the surface. + * \param depth the depth of the surface in bits. + * \param format the SDL_PixelFormatEnum for the new surface's pixel format. + * \returns the new SDL_Surface structure that is created or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_CreateRGBSurface + * \sa SDL_CreateRGBSurfaceFrom + * \sa SDL_FreeSurface + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurfaceWithFormat + (Uint32 flags, int width, int height, int depth, Uint32 format); + +/** + * Allocate a new RGB surface with existing pixel data. + * + * This function operates mostly like SDL_CreateRGBSurface(), except it does + * not allocate memory for the pixel data, instead the caller provides an + * existing buffer of data for the surface to use. + * + * No copy is made of the pixel data. Pixel data is not managed automatically; + * you must free the surface before you free the pixel data. + * + * \param pixels a pointer to existing pixel data. + * \param width the width of the surface. + * \param height the height of the surface. + * \param depth the depth of the surface in bits. + * \param pitch the pitch of the surface in bytes. + * \param Rmask the red mask for the pixels. + * \param Gmask the green mask for the pixels. + * \param Bmask the blue mask for the pixels. + * \param Amask the alpha mask for the pixels. + * \returns the new SDL_Surface structure that is created or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRGBSurface + * \sa SDL_CreateRGBSurfaceWithFormat + * \sa SDL_CreateRGBSurfaceWithFormatFrom + * \sa SDL_FreeSurface + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurfaceFrom(void *pixels, + int width, + int height, + int depth, + int pitch, + Uint32 Rmask, + Uint32 Gmask, + Uint32 Bmask, + Uint32 Amask); + +/* !!! FIXME for 2.1: why does this ask for depth? Format provides that. */ + +/** + * Allocate a new RGB surface with with a specific pixel format and existing + * pixel data. + * + * This function operates mostly like SDL_CreateRGBSurfaceFrom(), except + * instead of providing pixel color masks, you provide it with a predefined + * format from SDL_PixelFormatEnum. + * + * No copy is made of the pixel data. Pixel data is not managed automatically; + * you must free the surface before you free the pixel data. + * + * \param pixels a pointer to existing pixel data. + * \param width the width of the surface. + * \param height the height of the surface. + * \param depth the depth of the surface in bits. + * \param pitch the pitch of the surface in bytes. + * \param format the SDL_PixelFormatEnum for the new surface's pixel format. + * \returns the new SDL_Surface structure that is created or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_CreateRGBSurfaceFrom + * \sa SDL_CreateRGBSurfaceWithFormat + * \sa SDL_FreeSurface + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurfaceWithFormatFrom + (void *pixels, int width, int height, int depth, int pitch, Uint32 format); + +/** + * Free an RGB surface. + * + * It is safe to pass NULL to this function. + * + * \param surface the SDL_Surface to free. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateRGBSurface + * \sa SDL_CreateRGBSurfaceFrom + * \sa SDL_LoadBMP + * \sa SDL_LoadBMP_RW + */ +extern DECLSPEC void SDLCALL SDL_FreeSurface(SDL_Surface * surface); + +/** + * Set the palette used by a surface. + * + * A single palette can be shared with many surfaces. + * + * \param surface the SDL_Surface structure to update. + * \param palette the SDL_Palette structure to use. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_SetSurfacePalette(SDL_Surface * surface, + SDL_Palette * palette); + +/** + * Set up a surface for directly accessing the pixels. + * + * Between calls to SDL_LockSurface() / SDL_UnlockSurface(), you can write to + * and read from `surface->pixels`, using the pixel format stored in + * `surface->format`. Once you are done accessing the surface, you should use + * SDL_UnlockSurface() to release it. + * + * Not all surfaces require locking. If `SDL_MUSTLOCK(surface)` evaluates to + * 0, then you can read and write to the surface at any time, and the pixel + * format of the surface will not change. + * + * \param surface the SDL_Surface structure to be locked. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_MUSTLOCK + * \sa SDL_UnlockSurface + */ +extern DECLSPEC int SDLCALL SDL_LockSurface(SDL_Surface * surface); + +/** + * Release a surface after directly accessing the pixels. + * + * \param surface the SDL_Surface structure to be unlocked. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LockSurface + */ +extern DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface * surface); + +/** + * Load a BMP image from a seekable SDL data stream. + * + * The new surface should be freed with SDL_FreeSurface(). Not doing so will + * result in a memory leak. + * + * src is an open SDL_RWops buffer, typically loaded with SDL_RWFromFile. + * Alternatively, you might also use the macro SDL_LoadBMP to load a bitmap + * from a file, convert it to an SDL_Surface and then close the file. + * + * \param src the data stream for the surface. + * \param freesrc non-zero to close the stream after being read. + * \returns a pointer to a new SDL_Surface structure or NULL if there was an + * error; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FreeSurface + * \sa SDL_RWFromFile + * \sa SDL_LoadBMP + * \sa SDL_SaveBMP_RW + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_LoadBMP_RW(SDL_RWops * src, + int freesrc); + +/** + * Load a surface from a file. + * + * Convenience macro. + */ +#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1) + +/** + * Save a surface to a seekable SDL data stream in BMP format. + * + * Surfaces with a 24-bit, 32-bit and paletted 8-bit format get saved in the + * BMP directly. Other RGB formats with 8-bit or higher get converted to a + * 24-bit surface or, if they have an alpha mask or a colorkey, to a 32-bit + * surface before they are saved. YUV and paletted 1-bit and 4-bit formats are + * not supported. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param dst a data stream to save to. + * \param freedst non-zero to close the stream after being written. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_LoadBMP_RW + * \sa SDL_SaveBMP + */ +extern DECLSPEC int SDLCALL SDL_SaveBMP_RW + (SDL_Surface * surface, SDL_RWops * dst, int freedst); + +/** + * Save a surface to a file. + * + * Convenience macro. + */ +#define SDL_SaveBMP(surface, file) \ + SDL_SaveBMP_RW(surface, SDL_RWFromFile(file, "wb"), 1) + +/** + * Set the RLE acceleration hint for a surface. + * + * If RLE is enabled, color key and alpha blending blits are much faster, but + * the surface must be locked before directly accessing the pixels. + * + * \param surface the SDL_Surface structure to optimize. + * \param flag 0 to disable, non-zero to enable RLE acceleration. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + * \sa SDL_LockSurface + * \sa SDL_UnlockSurface + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface, + int flag); + +/** + * Returns whether the surface is RLE enabled + * + * It is safe to pass a NULL `surface` here; it will return SDL_FALSE. + * + * \param surface the SDL_Surface structure to query. + * \returns SDL_TRUE if the surface is RLE enabled, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + * + * \sa SDL_SetSurfaceRLE + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSurfaceRLE(SDL_Surface * surface); + +/** + * Set the color key (transparent pixel) in a surface. + * + * The color key defines a pixel value that will be treated as transparent in + * a blit. For example, one can use this to specify that cyan pixels should be + * considered transparent, and therefore not rendered. + * + * It is a pixel of the format used by the surface, as generated by + * SDL_MapRGB(). + * + * RLE acceleration can substantially speed up blitting of images with large + * horizontal runs of transparent pixels. See SDL_SetSurfaceRLE() for details. + * + * \param surface the SDL_Surface structure to update. + * \param flag SDL_TRUE to enable color key, SDL_FALSE to disable color key. + * \param key the transparent pixel. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + * \sa SDL_GetColorKey + */ +extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface, + int flag, Uint32 key); + +/** + * Returns whether the surface has a color key + * + * It is safe to pass a NULL `surface` here; it will return SDL_FALSE. + * + * \param surface the SDL_Surface structure to query. + * \return SDL_TRUE if the surface has a color key, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.9. + * + * \sa SDL_SetColorKey + * \sa SDL_GetColorKey + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasColorKey(SDL_Surface * surface); + +/** + * Get the color key (transparent pixel) for a surface. + * + * The color key is a pixel of the format used by the surface, as generated by + * SDL_MapRGB(). + * + * If the surface doesn't have color key enabled this function returns -1. + * + * \param surface the SDL_Surface structure to query. + * \param key a pointer filled in with the transparent pixel. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + * \sa SDL_SetColorKey + */ +extern DECLSPEC int SDLCALL SDL_GetColorKey(SDL_Surface * surface, + Uint32 * key); + +/** + * Set an additional color value multiplied into blit operations. + * + * When this surface is blitted, during the blit operation each source color + * channel is modulated by the appropriate color value according to the + * following formula: + * + * `srcC = srcC * (color / 255)` + * + * \param surface the SDL_Surface structure to update. + * \param r the red color value multiplied into blit operations. + * \param g the green color value multiplied into blit operations. + * \param b the blue color value multiplied into blit operations. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetSurfaceColorMod + * \sa SDL_SetSurfaceAlphaMod + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceColorMod(SDL_Surface * surface, + Uint8 r, Uint8 g, Uint8 b); + + +/** + * Get the additional color value multiplied into blit operations. + * + * \param surface the SDL_Surface structure to query. + * \param r a pointer filled in with the current red color value. + * \param g a pointer filled in with the current green color value. + * \param b a pointer filled in with the current blue color value. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetSurfaceAlphaMod + * \sa SDL_SetSurfaceColorMod + */ +extern DECLSPEC int SDLCALL SDL_GetSurfaceColorMod(SDL_Surface * surface, + Uint8 * r, Uint8 * g, + Uint8 * b); + +/** + * Set an additional alpha value used in blit operations. + * + * When this surface is blitted, during the blit operation the source alpha + * value is modulated by this alpha value according to the following formula: + * + * `srcA = srcA * (alpha / 255)` + * + * \param surface the SDL_Surface structure to update. + * \param alpha the alpha value multiplied into blit operations. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetSurfaceAlphaMod + * \sa SDL_SetSurfaceColorMod + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface * surface, + Uint8 alpha); + +/** + * Get the additional alpha value used in blit operations. + * + * \param surface the SDL_Surface structure to query. + * \param alpha a pointer filled in with the current alpha value. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetSurfaceColorMod + * \sa SDL_SetSurfaceAlphaMod + */ +extern DECLSPEC int SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface * surface, + Uint8 * alpha); + +/** + * Set the blend mode used for blit operations. + * + * To copy a surface to another surface (or texture) without blending with the + * existing data, the blendmode of the SOURCE surface should be set to + * `SDL_BLENDMODE_NONE`. + * + * \param surface the SDL_Surface structure to update. + * \param blendMode the SDL_BlendMode to use for blit blending. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetSurfaceBlendMode + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface * surface, + SDL_BlendMode blendMode); + +/** + * Get the blend mode used for blit operations. + * + * \param surface the SDL_Surface structure to query. + * \param blendMode a pointer filled in with the current SDL_BlendMode. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetSurfaceBlendMode + */ +extern DECLSPEC int SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface * surface, + SDL_BlendMode *blendMode); + +/** + * Set the clipping rectangle for a surface. + * + * When `surface` is the destination of a blit, only the area within the clip + * rectangle is drawn into. + * + * Note that blits are automatically clipped to the edges of the source and + * destination surfaces. + * + * \param surface the SDL_Surface structure to be clipped. + * \param rect the SDL_Rect structure representing the clipping rectangle, or + * NULL to disable clipping. + * \returns SDL_TRUE if the rectangle intersects the surface, otherwise + * SDL_FALSE and blits will be completely clipped. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + * \sa SDL_GetClipRect + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetClipRect(SDL_Surface * surface, + const SDL_Rect * rect); + +/** + * Get the clipping rectangle for a surface. + * + * When `surface` is the destination of a blit, only the area within the clip + * rectangle is drawn into. + * + * \param surface the SDL_Surface structure representing the surface to be + * clipped. + * \param rect an SDL_Rect structure filled in with the clipping rectangle for + * the surface. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + * \sa SDL_SetClipRect + */ +extern DECLSPEC void SDLCALL SDL_GetClipRect(SDL_Surface * surface, + SDL_Rect * rect); + +/* + * Creates a new surface identical to the existing surface. + * + * The returned surface should be freed with SDL_FreeSurface(). + * + * \param surface the surface to duplicate. + * \returns a copy of the surface, or NULL on failure; call SDL_GetError() for + * more information. + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_DuplicateSurface(SDL_Surface * surface); + +/** + * Copy an existing surface to a new surface of the specified format. + * + * This function is used to optimize images for faster *repeat* blitting. This + * is accomplished by converting the original and storing the result as a new + * surface. The new, optimized surface can then be used as the source for + * future blits, making them faster. + * + * \param src the existing SDL_Surface structure to convert. + * \param fmt the SDL_PixelFormat structure that the new surface is optimized + * for. + * \param flags the flags are unused and should be set to 0; this is a + * leftover from SDL 1.2's API. + * \returns the new SDL_Surface structure that is created or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocFormat + * \sa SDL_ConvertSurfaceFormat + * \sa SDL_CreateRGBSurface + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface + (SDL_Surface * src, const SDL_PixelFormat * fmt, Uint32 flags); + +/** + * Copy an existing surface to a new surface of the specified format enum. + * + * This function operates just like SDL_ConvertSurface(), but accepts an + * SDL_PixelFormatEnum value instead of an SDL_PixelFormat structure. As such, + * it might be easier to call but it doesn't have access to palette + * information for the destination surface, in case that would be important. + * + * \param src the existing SDL_Surface structure to convert. + * \param pixel_format the SDL_PixelFormatEnum that the new surface is + * optimized for. + * \param flags the flags are unused and should be set to 0; this is a + * leftover from SDL 1.2's API. + * \returns the new SDL_Surface structure that is created or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AllocFormat + * \sa SDL_ConvertSurface + * \sa SDL_CreateRGBSurface + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormat + (SDL_Surface * src, Uint32 pixel_format, Uint32 flags); + +/** + * Copy a block of pixels of one format to another format. + * + * \param width the width of the block to copy, in pixels. + * \param height the height of the block to copy, in pixels. + * \param src_format an SDL_PixelFormatEnum value of the `src` pixels format. + * \param src a pointer to the source pixels. + * \param src_pitch the pitch of the source pixels, in bytes. + * \param dst_format an SDL_PixelFormatEnum value of the `dst` pixels format. + * \param dst a pointer to be filled in with new pixel data. + * \param dst_pitch the pitch of the destination pixels, in bytes. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_ConvertPixels(int width, int height, + Uint32 src_format, + const void * src, int src_pitch, + Uint32 dst_format, + void * dst, int dst_pitch); + +/** + * Premultiply the alpha on a block of pixels. + * + * This is safe to use with src == dst, but not for other overlapping areas. + * + * This function is currently only implemented for SDL_PIXELFORMAT_ARGB8888. + * + * \param width the width of the block to convert, in pixels. + * \param height the height of the block to convert, in pixels. + * \param src_format an SDL_PixelFormatEnum value of the `src` pixels format. + * \param src a pointer to the source pixels. + * \param src_pitch the pitch of the source pixels, in bytes. + * \param dst_format an SDL_PixelFormatEnum value of the `dst` pixels format. + * \param dst a pointer to be filled in with premultiplied pixel data. + * \param dst_pitch the pitch of the destination pixels, in bytes. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_PremultiplyAlpha(int width, int height, + Uint32 src_format, + const void * src, int src_pitch, + Uint32 dst_format, + void * dst, int dst_pitch); + +/** + * Perform a fast fill of a rectangle with a specific color. + * + * `color` should be a pixel of the format used by the surface, and can be + * generated by SDL_MapRGB() or SDL_MapRGBA(). If the color value contains an + * alpha component then the destination is simply filled with that alpha + * information, no blending takes place. + * + * If there is a clip rectangle set on the destination (set via + * SDL_SetClipRect()), then this function will fill based on the intersection + * of the clip rectangle and `rect`. + * + * \param dst the SDL_Surface structure that is the drawing target. + * \param rect the SDL_Rect structure representing the rectangle to fill, or + * NULL to fill the entire surface. + * \param color the color to fill with. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FillRects + */ +extern DECLSPEC int SDLCALL SDL_FillRect + (SDL_Surface * dst, const SDL_Rect * rect, Uint32 color); + +/** + * Perform a fast fill of a set of rectangles with a specific color. + * + * `color` should be a pixel of the format used by the surface, and can be + * generated by SDL_MapRGB() or SDL_MapRGBA(). If the color value contains an + * alpha component then the destination is simply filled with that alpha + * information, no blending takes place. + * + * If there is a clip rectangle set on the destination (set via + * SDL_SetClipRect()), then this function will fill based on the intersection + * of the clip rectangle and `rect`. + * + * \param dst the SDL_Surface structure that is the drawing target. + * \param rects an array of SDL_Rect representing the rectangles to fill. + * \param count the number of rectangles in the array. + * \param color the color to fill with. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_FillRect + */ +extern DECLSPEC int SDLCALL SDL_FillRects + (SDL_Surface * dst, const SDL_Rect * rects, int count, Uint32 color); + +/* !!! FIXME: merge this documentation with the wiki */ + +/** + * Performs a fast blit from the source surface to the destination surface. + * + * This assumes that the source and destination rectangles are the same size. + * If either `srcrect` or `dstrect` are NULL, the entire surface (`src` or + * `dst`) is copied. The final blit rectangle is saved in `dstrect` after + * all clipping is performed. + * + * The blit function should not be called on a locked surface. + * + * The blit semantics for surfaces with and without blending and colorkey are + * defined as follows: + * + * ``` + * RGBA->RGB: + * Source surface blend mode set to SDL_BLENDMODE_BLEND: + * alpha-blend (using the source alpha-channel and per-surface alpha) + * SDL_SRCCOLORKEY ignored. + * Source surface blend mode set to SDL_BLENDMODE_NONE: + * copy RGB. + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * RGB values of the source color key, ignoring alpha in the + * comparison. + * + * RGB->RGBA: + * Source surface blend mode set to SDL_BLENDMODE_BLEND: + * alpha-blend (using the source per-surface alpha) + * Source surface blend mode set to SDL_BLENDMODE_NONE: + * copy RGB, set destination alpha to source per-surface alpha value. + * both: + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * source color key. + * + * RGBA->RGBA: + * Source surface blend mode set to SDL_BLENDMODE_BLEND: + * alpha-blend (using the source alpha-channel and per-surface alpha) + * SDL_SRCCOLORKEY ignored. + * Source surface blend mode set to SDL_BLENDMODE_NONE: + * copy all of RGBA to the destination. + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * RGB values of the source color key, ignoring alpha in the + * comparison. + * + * RGB->RGB: + * Source surface blend mode set to SDL_BLENDMODE_BLEND: + * alpha-blend (using the source per-surface alpha) + * Source surface blend mode set to SDL_BLENDMODE_NONE: + * copy RGB. + * both: + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * source color key. + * ``` + * + * You should call SDL_BlitSurface() unless you know exactly how SDL blitting + * works internally and how to use the other blit functions. + * + * \returns 0 if the blit is successful, otherwise it returns -1. + */ +#define SDL_BlitSurface SDL_UpperBlit + +/** + * Perform a fast blit from the source surface to the destination surface. + * + * SDL_UpperBlit() has been replaced by SDL_BlitSurface(), which is merely a + * macro for this function with a less confusing name. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + */ +extern DECLSPEC int SDLCALL SDL_UpperBlit + (SDL_Surface * src, const SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * Perform low-level surface blitting only. + * + * This is a semi-private blit function and it performs low-level surface + * blitting, assuming the input rectangles have already been clipped. + * + * Unless you know what you're doing, you should be using SDL_BlitSurface() + * instead. + * + * \param src the SDL_Surface structure to be copied from. + * \param srcrect the SDL_Rect structure representing the rectangle to be + * copied, or NULL to copy the entire surface. + * \param dst the SDL_Surface structure that is the blit target. + * \param dstrect the SDL_Rect structure representing the rectangle that is + * copied into. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitSurface + */ +extern DECLSPEC int SDLCALL SDL_LowerBlit + (SDL_Surface * src, SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + + +/** + * Perform a fast, low quality, stretch blit between two surfaces of the same + * format. + * + * Please use SDL_BlitScaled() instead. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface * src, + const SDL_Rect * srcrect, + SDL_Surface * dst, + const SDL_Rect * dstrect); + +/** + * Perform bilinear scaling between two surfaces of the same format, 32BPP. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC int SDLCALL SDL_SoftStretchLinear(SDL_Surface * src, + const SDL_Rect * srcrect, + SDL_Surface * dst, + const SDL_Rect * dstrect); + + +/** + * Perform a scaled surface copy to a destination surface. + * + * SDL_UpperBlitScaled() has been replaced by SDL_BlitScaled(), which is + * merely a macro for this function with a less confusing name. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitScaled + */ +extern DECLSPEC int SDLCALL SDL_UpperBlitScaled + (SDL_Surface * src, const SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +#define SDL_BlitScaled SDL_UpperBlitScaled + + +/** + * Perform low-level surface scaled blitting only. + * + * This is a semi-private function and it performs low-level surface blitting, + * assuming the input rectangles have already been clipped. + * + * \param src the SDL_Surface structure to be copied from. + * \param srcrect the SDL_Rect structure representing the rectangle to be + * copied. + * \param dst the SDL_Surface structure that is the blit target. + * \param dstrect the SDL_Rect structure representing the rectangle that is + * copied into. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_BlitScaled + */ +extern DECLSPEC int SDLCALL SDL_LowerBlitScaled + (SDL_Surface * src, SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * Set the YUV conversion mode + * + * \since This function is available since SDL 2.0.8. + */ +extern DECLSPEC void SDLCALL SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_MODE mode); + +/** + * Get the YUV conversion mode + * + * \since This function is available since SDL 2.0.8. + */ +extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionMode(void); + +/** + * Get the YUV conversion mode, returning the correct mode for the resolution + * when the current conversion mode is SDL_YUV_CONVERSION_AUTOMATIC + * + * \since This function is available since SDL 2.0.8. + */ +extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionModeForResolution(int width, int height); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_surface_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_system.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_system.h new file mode 100644 index 00000000..2f7a236f --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_system.h @@ -0,0 +1,642 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategorySystem + * + * Include file for platform specific SDL API functions + */ + +#ifndef SDL_system_h_ +#define SDL_system_h_ + +#include "SDL_stdinc.h" +#include "SDL_keyboard.h" +#include "SDL_render.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/* Platform specific functions for Windows */ +#if defined(__WIN32__) || defined(__GDK__) + +typedef void (SDLCALL * SDL_WindowsMessageHook)(void *userdata, void *hWnd, unsigned int message, Uint64 wParam, Sint64 lParam); + +/** + * Set a callback for every Windows message, run before TranslateMessage(). + * + * \param callback The SDL_WindowsMessageHook function to call. + * \param userdata a pointer to pass to every iteration of `callback`. + * + * \since This function is available since SDL 2.0.4. + */ +extern DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata); + +#endif /* defined(__WIN32__) || defined(__GDK__) */ + +#if defined(__WIN32__) || defined(__WINGDK__) + +/** + * Get the D3D9 adapter index that matches the specified display index. + * + * The returned adapter index can be passed to `IDirect3D9::CreateDevice` and + * controls on which monitor a full screen application will appear. + * + * \param displayIndex the display index for which to get the D3D9 adapter + * index. + * \returns the D3D9 adapter index on success or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.1. + */ +extern DECLSPEC int SDLCALL SDL_Direct3D9GetAdapterIndex( int displayIndex ); + +typedef struct IDirect3DDevice9 IDirect3DDevice9; + +/** + * Get the D3D9 device associated with a renderer. + * + * Once you are done using the device, you should release it to avoid a + * resource leak. + * + * \param renderer the renderer from which to get the associated D3D device. + * \returns the D3D9 device associated with given renderer or NULL if it is + * not a D3D9 renderer; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.1. + */ +extern DECLSPEC IDirect3DDevice9* SDLCALL SDL_RenderGetD3D9Device(SDL_Renderer * renderer); + +typedef struct ID3D11Device ID3D11Device; + +/** + * Get the D3D11 device associated with a renderer. + * + * Once you are done using the device, you should release it to avoid a + * resource leak. + * + * \param renderer the renderer from which to get the associated D3D11 device. + * \returns the D3D11 device associated with given renderer or NULL if it is + * not a D3D11 renderer; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC ID3D11Device* SDLCALL SDL_RenderGetD3D11Device(SDL_Renderer * renderer); + +#endif /* defined(__WIN32__) || defined(__WINGDK__) */ + +#if defined(__WIN32__) || defined(__GDK__) + +typedef struct ID3D12Device ID3D12Device; + +/** + * Get the D3D12 device associated with a renderer. + * + * Once you are done using the device, you should release it to avoid a + * resource leak. + * + * \param renderer the renderer from which to get the associated D3D12 device. + * \returns the D3D12 device associated with given renderer or NULL if it is + * not a D3D12 renderer; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC ID3D12Device* SDLCALL SDL_RenderGetD3D12Device(SDL_Renderer* renderer); + +#endif /* defined(__WIN32__) || defined(__GDK__) */ + +#if defined(__WIN32__) || defined(__WINGDK__) + +/** + * Get the DXGI Adapter and Output indices for the specified display index. + * + * The DXGI Adapter and Output indices can be passed to `EnumAdapters` and + * `EnumOutputs` respectively to get the objects required to create a DX10 or + * DX11 device and swap chain. + * + * Before SDL 2.0.4 this function did not return a value. Since SDL 2.0.4 it + * returns an SDL_bool. + * + * \param displayIndex the display index for which to get both indices. + * \param adapterIndex a pointer to be filled in with the adapter index. + * \param outputIndex a pointer to be filled in with the output index. + * \returns SDL_TRUE on success or SDL_FALSE on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.2. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo( int displayIndex, int *adapterIndex, int *outputIndex ); + +#endif /* defined(__WIN32__) || defined(__WINGDK__) */ + +/* Platform specific functions for Linux */ +#ifdef __LINUX__ + +/** + * Sets the UNIX nice value for a thread. + * + * This uses setpriority() if possible, and RealtimeKit if available. + * + * \param threadID the Unix thread ID to change priority of. + * \param priority The new, Unix-specific, priority value. + * \returns 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority); + +/** + * Sets the priority (not nice level) and scheduling policy for a thread. + * + * This uses setpriority() if possible, and RealtimeKit if available. + * + * \param threadID The Unix thread ID to change priority of. + * \param sdlPriority The new SDL_ThreadPriority value. + * \param schedPolicy The new scheduling policy (SCHED_FIFO, SCHED_RR, + * SCHED_OTHER, etc...). + * \returns 0 on success, or -1 on error. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); + +#endif /* __LINUX__ */ + +/* Platform specific functions for iOS */ +#ifdef __IPHONEOS__ + +typedef void (SDLCALL *SDL_iOSAnimationCallback)(void*); + +/** + * Use this function to set the animation callback on Apple iOS. + * + * The function prototype for `callback` is: + * + * ```c + * void callback(void* callbackParam); + * ``` + * + * Where its parameter, `callbackParam`, is what was passed as `callbackParam` + * to SDL_iPhoneSetAnimationCallback(). + * + * This function is only available on Apple iOS. + * + * For more information see: + * https://github.com/libsdl-org/SDL/blob/main/docs/README-ios.md + * + * This functions is also accessible using the macro + * SDL_iOSSetAnimationCallback() since SDL 2.0.4. + * + * \param window the window for which the animation callback should be set. + * \param interval the number of frames after which **callback** will be + * called. + * \param callback the function to call for every frame. + * \param callbackParam a pointer that is passed to `callback`. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_iPhoneSetEventPump + */ +extern DECLSPEC int SDLCALL SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, SDL_iOSAnimationCallback callback, void *callbackParam); + +#define SDL_iOSSetAnimationCallback(window, interval, callback, callbackParam) SDL_iPhoneSetAnimationCallback(window, interval, callback, callbackParam) + + +/** + * Use this function to enable or disable the SDL event pump on Apple iOS. + * + * This function is only available on Apple iOS. + * + * This functions is also accessible using the macro SDL_iOSSetEventPump() + * since SDL 2.0.4. + * + * \param enabled SDL_TRUE to enable the event pump, SDL_FALSE to disable it. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_iPhoneSetAnimationCallback + */ +extern DECLSPEC void SDLCALL SDL_iPhoneSetEventPump(SDL_bool enabled); + +#define SDL_iOSSetEventPump(enabled) SDL_iPhoneSetEventPump(enabled) + +/* end of iOS-specific functions. */ +#endif /* __IPHONEOS__ */ + + +/* Platform specific functions for Android */ +#ifdef __ANDROID__ + +/** + * Get the Android Java Native Interface Environment of the current thread. + * + * This is the JNIEnv one needs to access the Java virtual machine from native + * code, and is needed for many Android APIs to be usable from C. + * + * The prototype of the function in SDL's code actually declare a void* return + * type, even if the implementation returns a pointer to a JNIEnv. The + * rationale being that the SDL headers can avoid including jni.h. + * + * \returns a pointer to Java native interface object (JNIEnv) to which the + * current thread is attached, or 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AndroidGetActivity + */ +extern DECLSPEC void * SDLCALL SDL_AndroidGetJNIEnv(void); + +/** + * Retrieve the Java instance of the Android activity class. + * + * The prototype of the function in SDL's code actually declares a void* + * return type, even if the implementation returns a jobject. The rationale + * being that the SDL headers can avoid including jni.h. + * + * The jobject returned by the function is a local reference and must be + * released by the caller. See the PushLocalFrame() and PopLocalFrame() or + * DeleteLocalRef() functions of the Java native interface: + * + * https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html + * + * \returns the jobject representing the instance of the Activity class of the + * Android application, or NULL on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AndroidGetJNIEnv + */ +extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(void); + +/** + * Query Android API level of the current device. + * + * - API level 31: Android 12 + * - API level 30: Android 11 + * - API level 29: Android 10 + * - API level 28: Android 9 + * - API level 27: Android 8.1 + * - API level 26: Android 8.0 + * - API level 25: Android 7.1 + * - API level 24: Android 7.0 + * - API level 23: Android 6.0 + * - API level 22: Android 5.1 + * - API level 21: Android 5.0 + * - API level 20: Android 4.4W + * - API level 19: Android 4.4 + * - API level 18: Android 4.3 + * - API level 17: Android 4.2 + * - API level 16: Android 4.1 + * - API level 15: Android 4.0.3 + * - API level 14: Android 4.0 + * - API level 13: Android 3.2 + * - API level 12: Android 3.1 + * - API level 11: Android 3.0 + * - API level 10: Android 2.3.3 + * + * \returns the Android API level. + * + * \since This function is available since SDL 2.0.12. + */ +extern DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void); + +/** + * Query if the application is running on Android TV. + * + * \returns SDL_TRUE if this is Android TV, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.8. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void); + +/** + * Query if the application is running on a Chromebook. + * + * \returns SDL_TRUE if this is a Chromebook, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void); + +/** + * Query if the application is running on a Samsung DeX docking station. + * + * \returns SDL_TRUE if this is a DeX docking station, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void); + +/** + * Trigger the Android system back button behavior. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC void SDLCALL SDL_AndroidBackButton(void); + +/** + * See the official Android developer guide for more information: + * http://developer.android.com/guide/topics/data/data-storage.html + */ +#define SDL_ANDROID_EXTERNAL_STORAGE_READ 0x01 +#define SDL_ANDROID_EXTERNAL_STORAGE_WRITE 0x02 + +/** + * Get the path used for internal storage for this application. + * + * This path is unique to your application and cannot be written to by other + * applications. + * + * Your internal storage path is typically: + * `/data/data/your.app.package/files`. + * + * \returns the path used for internal storage or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AndroidGetExternalStorageState + */ +extern DECLSPEC const char * SDLCALL SDL_AndroidGetInternalStoragePath(void); + +/** + * Get the current state of external storage. + * + * The current state of external storage, a bitmask of these values: + * `SDL_ANDROID_EXTERNAL_STORAGE_READ`, `SDL_ANDROID_EXTERNAL_STORAGE_WRITE`. + * + * If external storage is currently unavailable, this will return 0. + * + * \returns the current state of external storage on success or 0 on failure; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AndroidGetExternalStoragePath + */ +extern DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(void); + +/** + * Get the path used for external storage for this application. + * + * This path is unique to your application, but is public and can be written + * to by other applications. + * + * Your external storage path is typically: + * `/storage/sdcard0/Android/data/your.app.package/files`. + * + * \returns the path used for external storage for this application on success + * or NULL on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AndroidGetExternalStorageState + */ +extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(void); + +/** + * Request permissions at runtime. + * + * This blocks the calling thread until the permission is granted or denied. + * + * \param permission The permission to request. + * \returns SDL_TRUE if the permission was granted, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.14. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_AndroidRequestPermission(const char *permission); + +/** + * Shows an Android toast notification. + * + * Toasts are a sort of lightweight notification that are unique to Android. + * + * https://developer.android.com/guide/topics/ui/notifiers/toasts + * + * Shows toast in UI thread. + * + * For the `gravity` parameter, choose a value from here, or -1 if you don't + * have a preference: + * + * https://developer.android.com/reference/android/view/Gravity + * + * \param message text message to be shown. + * \param duration 0=short, 1=long. + * \param gravity where the notification should appear on the screen. + * \param xoffset set this parameter only when gravity >=0. + * \param yoffset set this parameter only when gravity >=0. + * \returns 0 if success, -1 if any error occurs. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC int SDLCALL SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset); + +/** + * Send a user command to SDLActivity. + * + * Override "boolean onUnhandledMessage(Message msg)" to handle the message. + * + * \param command user command that must be greater or equal to 0x8000. + * \param param user parameter. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param); + +#endif /* __ANDROID__ */ + +/* Platform specific functions for WinRT */ +#ifdef __WINRT__ + +/** + * WinRT / Windows Phone path types + */ +typedef enum SDL_WinRT_Path +{ + /** \brief The installed app's root directory. + Files here are likely to be read-only. */ + SDL_WINRT_PATH_INSTALLED_LOCATION, + + /** \brief The app's local data store. Files may be written here */ + SDL_WINRT_PATH_LOCAL_FOLDER, + + /** \brief The app's roaming data store. Unsupported on Windows Phone. + Files written here may be copied to other machines via a network + connection. + */ + SDL_WINRT_PATH_ROAMING_FOLDER, + + /** \brief The app's temporary data store. Unsupported on Windows Phone. + Files written here may be deleted at any time. */ + SDL_WINRT_PATH_TEMP_FOLDER +} SDL_WinRT_Path; + + +/** + * WinRT Device Family + */ +typedef enum SDL_WinRT_DeviceFamily +{ + /** \brief Unknown family */ + SDL_WINRT_DEVICEFAMILY_UNKNOWN, + + /** \brief Desktop family*/ + SDL_WINRT_DEVICEFAMILY_DESKTOP, + + /** \brief Mobile family (for example smartphone) */ + SDL_WINRT_DEVICEFAMILY_MOBILE, + + /** \brief XBox family */ + SDL_WINRT_DEVICEFAMILY_XBOX, +} SDL_WinRT_DeviceFamily; + + +/** + * Retrieve a WinRT defined path on the local file system. + * + * Not all paths are available on all versions of Windows. This is especially + * true on Windows Phone. Check the documentation for the given SDL_WinRT_Path + * for more information on which path types are supported where. + * + * Documentation on most app-specific path types on WinRT can be found on + * MSDN, at the URL: + * + * https://msdn.microsoft.com/en-us/library/windows/apps/hh464917.aspx + * + * \param pathType the type of path to retrieve, one of SDL_WinRT_Path. + * \returns a UCS-2 string (16-bit, wide-char) containing the path, or NULL if + * the path is not available for any reason; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.3. + * + * \sa SDL_WinRTGetFSPathUTF8 + */ +extern DECLSPEC const wchar_t * SDLCALL SDL_WinRTGetFSPathUNICODE(SDL_WinRT_Path pathType); + +/** + * Retrieve a WinRT defined path on the local file system. + * + * Not all paths are available on all versions of Windows. This is especially + * true on Windows Phone. Check the documentation for the given SDL_WinRT_Path + * for more information on which path types are supported where. + * + * Documentation on most app-specific path types on WinRT can be found on + * MSDN, at the URL: + * + * https://msdn.microsoft.com/en-us/library/windows/apps/hh464917.aspx + * + * \param pathType the type of path to retrieve, one of SDL_WinRT_Path. + * \returns a UTF-8 string (8-bit, multi-byte) containing the path, or NULL if + * the path is not available for any reason; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 2.0.3. + * + * \sa SDL_WinRTGetFSPathUNICODE + */ +extern DECLSPEC const char * SDLCALL SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType); + +/** + * Detects the device family of WinRT platform at runtime. + * + * \returns a value from the SDL_WinRT_DeviceFamily enum. + * + * \since This function is available since SDL 2.0.8. + */ +extern DECLSPEC SDL_WinRT_DeviceFamily SDLCALL SDL_WinRTGetDeviceFamily(); + +#endif /* __WINRT__ */ + +/** + * Query if the current device is a tablet. + * + * If SDL can't determine this, it will return SDL_FALSE. + * + * \returns SDL_TRUE if the device is a tablet, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.9. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsTablet(void); + +/* Functions used by iOS application delegates to notify SDL about state changes */ +extern DECLSPEC void SDLCALL SDL_OnApplicationWillTerminate(void); +extern DECLSPEC void SDLCALL SDL_OnApplicationDidReceiveMemoryWarning(void); +extern DECLSPEC void SDLCALL SDL_OnApplicationWillResignActive(void); +extern DECLSPEC void SDLCALL SDL_OnApplicationDidEnterBackground(void); +extern DECLSPEC void SDLCALL SDL_OnApplicationWillEnterForeground(void); +extern DECLSPEC void SDLCALL SDL_OnApplicationDidBecomeActive(void); +#ifdef __IPHONEOS__ +extern DECLSPEC void SDLCALL SDL_OnApplicationDidChangeStatusBarOrientation(void); +#endif + +/* Functions used only by GDK */ +#if defined(__GDK__) +typedef struct XTaskQueueObject *XTaskQueueHandle; +typedef struct XUser *XUserHandle; + +/** + * Gets a reference to the global async task queue handle for GDK, + * initializing if needed. + * + * Once you are done with the task queue, you should call + * XTaskQueueCloseHandle to reduce the reference count to avoid a resource + * leak. + * + * \param outTaskQueue a pointer to be filled in with task queue handle. + * \returns 0 if success, -1 if any error occurs. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC int SDLCALL SDL_GDKGetTaskQueue(XTaskQueueHandle * outTaskQueue); + +/** + * Gets a reference to the default user handle for GDK. + * + * This is effectively a synchronous version of XUserAddAsync, which always + * prefers the default user and allows a sign-in UI. + * + * \param outUserHandle a pointer to be filled in with the default user + * handle. + * \returns 0 if success, -1 if any error occurs. + * + * \since This function is available since SDL 2.28.0. + */ +extern DECLSPEC int SDLCALL SDL_GDKGetDefaultUser(XUserHandle * outUserHandle); + +#endif + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_system_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_syswm.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_syswm.h new file mode 100644 index 00000000..18f68732 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_syswm.h @@ -0,0 +1,386 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: SYSWM */ + +/* + * # CategorySYSWM + * + * Include file for SDL custom system window manager hooks. + * + * Your application has access to a special type of event SDL_SYSWMEVENT, + * which contains window-manager specific information and arrives whenever + * an unhandled window event occurs. This event is ignored by default, but + * you can enable it with SDL_EventState(). + */ + +#ifndef SDL_syswm_h_ +#define SDL_syswm_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" +#include "SDL_version.h" + +struct SDL_SysWMinfo; + +#if !defined(SDL_PROTOTYPES_ONLY) + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX /* don't define min() and max(). */ +#define NOMINMAX +#endif +#include +#endif + +#if defined(SDL_VIDEO_DRIVER_WINRT) +#include +#endif + +/* This is the structure for custom window manager events */ +#if defined(SDL_VIDEO_DRIVER_X11) +#if defined(__APPLE__) && defined(__MACH__) +/* conflicts with Quickdraw.h */ +#define Cursor X11Cursor +#endif + +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +/* matches the re-define above */ +#undef Cursor +#endif + +#endif /* defined(SDL_VIDEO_DRIVER_X11) */ + +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) +#include +#endif + +#if defined(SDL_VIDEO_DRIVER_COCOA) +#ifdef __OBJC__ +@class NSWindow; +#else +typedef struct _NSWindow NSWindow; +#endif +#endif + +#if defined(SDL_VIDEO_DRIVER_UIKIT) +#ifdef __OBJC__ +#include +#else +typedef struct _UIWindow UIWindow; +typedef struct _UIViewController UIViewController; +#endif +typedef Uint32 GLuint; +#endif + +#if defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL) +#define SDL_METALVIEW_TAG 255 +#endif + +#if defined(SDL_VIDEO_DRIVER_ANDROID) +typedef struct ANativeWindow ANativeWindow; +typedef void *EGLSurface; +#endif + +#if defined(SDL_VIDEO_DRIVER_VIVANTE) +#include "SDL_egl.h" +#endif + +#if defined(SDL_VIDEO_DRIVER_OS2) +#define INCL_WIN +#include +#endif +#endif /* SDL_PROTOTYPES_ONLY */ + +#if defined(SDL_VIDEO_DRIVER_KMSDRM) +struct gbm_device; +#endif + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(SDL_PROTOTYPES_ONLY) + +/** + * These are the various supported windowing subsystems + */ +typedef enum SDL_SYSWM_TYPE +{ + SDL_SYSWM_UNKNOWN, + SDL_SYSWM_WINDOWS, + SDL_SYSWM_X11, + SDL_SYSWM_DIRECTFB, + SDL_SYSWM_COCOA, + SDL_SYSWM_UIKIT, + SDL_SYSWM_WAYLAND, + SDL_SYSWM_MIR, /* no longer available, left for API/ABI compatibility. Remove in 2.1! */ + SDL_SYSWM_WINRT, + SDL_SYSWM_ANDROID, + SDL_SYSWM_VIVANTE, + SDL_SYSWM_OS2, + SDL_SYSWM_HAIKU, + SDL_SYSWM_KMSDRM, + SDL_SYSWM_RISCOS +} SDL_SYSWM_TYPE; + +/** + * The custom event structure. + */ +struct SDL_SysWMmsg +{ + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union + { +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + struct { + HWND hwnd; /**< The window for the message */ + UINT msg; /**< The type of message */ + WPARAM wParam; /**< WORD message parameter */ + LPARAM lParam; /**< LONG message parameter */ + } win; +#endif +#if defined(SDL_VIDEO_DRIVER_X11) + struct { + XEvent event; + } x11; +#endif +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) + struct { + DFBEvent event; + } dfb; +#endif +#if defined(SDL_VIDEO_DRIVER_COCOA) + struct + { + /* Latest version of Xcode clang complains about empty structs in C v. C++: + error: empty struct has size 0 in C, size 1 in C++ + */ + int dummy; + /* No Cocoa window events yet */ + } cocoa; +#endif +#if defined(SDL_VIDEO_DRIVER_UIKIT) + struct + { + int dummy; + /* No UIKit window events yet */ + } uikit; +#endif +#if defined(SDL_VIDEO_DRIVER_VIVANTE) + struct + { + int dummy; + /* No Vivante window events yet */ + } vivante; +#endif +#if defined(SDL_VIDEO_DRIVER_OS2) + struct + { + BOOL fFrame; /**< TRUE if hwnd is a frame window */ + HWND hwnd; /**< The window receiving the message */ + ULONG msg; /**< The message identifier */ + MPARAM mp1; /**< The first first message parameter */ + MPARAM mp2; /**< The second first message parameter */ + } os2; +#endif + /* Can't have an empty union */ + int dummy; + } msg; +}; + +/** + * The custom window manager information structure. + * + * When this structure is returned, it holds information about which low level + * system it is using, and will be one of SDL_SYSWM_TYPE. + */ +struct SDL_SysWMinfo +{ + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union + { +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + struct + { + HWND window; /**< The window handle */ + HDC hdc; /**< The window device context */ + HINSTANCE hinstance; /**< The instance handle */ + } win; +#endif +#if defined(SDL_VIDEO_DRIVER_WINRT) + struct + { + IInspectable * window; /**< The WinRT CoreWindow */ + } winrt; +#endif +#if defined(SDL_VIDEO_DRIVER_X11) + struct + { + Display *display; /**< The X11 display */ + Window window; /**< The X11 window */ + } x11; +#endif +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) + struct + { + IDirectFB *dfb; /**< The directfb main interface */ + IDirectFBWindow *window; /**< The directfb window handle */ + IDirectFBSurface *surface; /**< The directfb client surface */ + } dfb; +#endif +#if defined(SDL_VIDEO_DRIVER_COCOA) + struct + { +#if defined(__OBJC__) && defined(__has_feature) + #if __has_feature(objc_arc) + NSWindow __unsafe_unretained *window; /**< The Cocoa window */ + #else + NSWindow *window; /**< The Cocoa window */ + #endif +#else + NSWindow *window; /**< The Cocoa window */ +#endif + } cocoa; +#endif +#if defined(SDL_VIDEO_DRIVER_UIKIT) + struct + { +#if defined(__OBJC__) && defined(__has_feature) + #if __has_feature(objc_arc) + UIWindow __unsafe_unretained *window; /**< The UIKit window */ + #else + UIWindow *window; /**< The UIKit window */ + #endif +#else + UIWindow *window; /**< The UIKit window */ +#endif + GLuint framebuffer; /**< The GL view's Framebuffer Object. It must be bound when rendering to the screen using GL. */ + GLuint colorbuffer; /**< The GL view's color Renderbuffer Object. It must be bound when SDL_GL_SwapWindow is called. */ + GLuint resolveFramebuffer; /**< The Framebuffer Object which holds the resolve color Renderbuffer, when MSAA is used. */ + } uikit; +#endif +#if defined(SDL_VIDEO_DRIVER_WAYLAND) + struct + { + struct wl_display *display; /**< Wayland display */ + struct wl_surface *surface; /**< Wayland surface */ + void *shell_surface; /**< DEPRECATED Wayland shell_surface (window manager handle) */ + struct wl_egl_window *egl_window; /**< Wayland EGL window (native window) */ + struct xdg_surface *xdg_surface; /**< Wayland xdg surface (window manager handle) */ + struct xdg_toplevel *xdg_toplevel; /**< Wayland xdg toplevel role */ + struct xdg_popup *xdg_popup; /**< Wayland xdg popup role */ + struct xdg_positioner *xdg_positioner; /**< Wayland xdg positioner, for popup */ + } wl; +#endif +#if defined(SDL_VIDEO_DRIVER_MIR) /* no longer available, left for API/ABI compatibility. Remove in 2.1! */ + struct + { + void *connection; /**< Mir display server connection */ + void *surface; /**< Mir surface */ + } mir; +#endif + +#if defined(SDL_VIDEO_DRIVER_ANDROID) + struct + { + ANativeWindow *window; + EGLSurface surface; + } android; +#endif + +#if defined(SDL_VIDEO_DRIVER_OS2) + struct + { + HWND hwnd; /**< The window handle */ + HWND hwndFrame; /**< The frame window handle */ + } os2; +#endif + +#if defined(SDL_VIDEO_DRIVER_VIVANTE) + struct + { + EGLNativeDisplayType display; + EGLNativeWindowType window; + } vivante; +#endif + +#if defined(SDL_VIDEO_DRIVER_KMSDRM) + struct + { + int dev_index; /**< Device index (ex: the X in /dev/dri/cardX) */ + int drm_fd; /**< DRM FD (unavailable on Vulkan windows) */ + struct gbm_device *gbm_dev; /**< GBM device (unavailable on Vulkan windows) */ + } kmsdrm; +#endif + + /* Make sure this union is always 64 bytes (8 64-bit pointers). */ + /* Be careful not to overflow this if you add a new target! */ + Uint8 dummy[64]; + } info; +}; + +#endif /* SDL_PROTOTYPES_ONLY */ + +typedef struct SDL_SysWMinfo SDL_SysWMinfo; + + +/** + * Get driver-specific information about a window. + * + * You must include SDL_syswm.h for the declaration of SDL_SysWMinfo. + * + * The caller must initialize the `info` structure's version by using + * `SDL_VERSION(&info.version)`, and then this function will fill in the rest + * of the structure with information about the given window. + * + * \param window the window about which information is being requested. + * \param info an SDL_SysWMinfo structure filled in with window information. + * \returns SDL_TRUE if the function is implemented and the `version` member + * of the `info` struct is valid, or SDL_FALSE if the information + * could not be retrieved; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowWMInfo(SDL_Window * window, + SDL_SysWMinfo * info); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_syswm_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test.h new file mode 100644 index 00000000..78a7e623 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test.h @@ -0,0 +1,69 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +#ifndef SDL_test_h_ +#define SDL_test_h_ + +#include "SDL.h" +#include "SDL_test_assert.h" +#include "SDL_test_common.h" +#include "SDL_test_compare.h" +#include "SDL_test_crc32.h" +#include "SDL_test_font.h" +#include "SDL_test_fuzzer.h" +#include "SDL_test_harness.h" +#include "SDL_test_images.h" +#include "SDL_test_log.h" +#include "SDL_test_md5.h" +#include "SDL_test_memory.h" +#include "SDL_test_random.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Global definitions */ + +/* + * Note: Maximum size of SDLTest log message is less than SDL's limit + * to ensure we can fit additional information such as the timestamp. + */ +#define SDLTEST_MAX_LOGMESSAGE_LENGTH 3584 + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_assert.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_assert.h new file mode 100644 index 00000000..ff3b6b6b --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_assert.h @@ -0,0 +1,105 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_assert.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + * + * Assert API for test code and test cases + * + */ + +#ifndef SDL_test_assert_h_ +#define SDL_test_assert_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * \brief Fails the assert. + */ +#define ASSERT_FAIL 0 + +/* + * \brief Passes the assert. + */ +#define ASSERT_PASS 1 + +/* + * \brief Assert that logs and break execution flow on failures. + * + * \param assertCondition Evaluated condition or variable to assert; fail (==0) or pass (!=0). + * \param assertDescription Message to log with the assert describing it. + */ +void SDLTest_Assert(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) SDL_PRINTF_VARARG_FUNC(2); + +/* + * \brief Assert for test cases that logs but does not break execution flow on failures. Updates assertion counters. + * + * \param assertCondition Evaluated condition or variable to assert; fail (==0) or pass (!=0). + * \param assertDescription Message to log with the assert describing it. + * + * \returns the assertCondition so it can be used to externally to break execution flow if desired. + */ +int SDLTest_AssertCheck(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) SDL_PRINTF_VARARG_FUNC(2); + +/* + * \brief Explicitly pass without checking an assertion condition. Updates assertion counter. + * + * \param assertDescription Message to log with the assert describing it. + */ +void SDLTest_AssertPass(SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) SDL_PRINTF_VARARG_FUNC(1); + +/* + * \brief Resets the assert summary counters to zero. + */ +void SDLTest_ResetAssertSummary(void); + +/* + * \brief Logs summary of all assertions (total, pass, fail) since last reset as INFO or ERROR. + */ +void SDLTest_LogAssertSummary(void); + + +/* + * \brief Converts the current assert summary state to a test result. + * + * \returns TEST_RESULT_PASSED, TEST_RESULT_FAILED, or TEST_RESULT_NO_ASSERT + */ +int SDLTest_AssertSummaryToTestResult(void); + +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_assert_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_common.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_common.h new file mode 100644 index 00000000..64b5f83e --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_common.h @@ -0,0 +1,236 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_common.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* Ported from original test\common.h file. */ + +#ifndef SDL_test_common_h_ +#define SDL_test_common_h_ + +#include "SDL.h" + +#if defined(__PSP__) +#define DEFAULT_WINDOW_WIDTH 480 +#define DEFAULT_WINDOW_HEIGHT 272 +#elif defined(__VITA__) +#define DEFAULT_WINDOW_WIDTH 960 +#define DEFAULT_WINDOW_HEIGHT 544 +#else +#define DEFAULT_WINDOW_WIDTH 640 +#define DEFAULT_WINDOW_HEIGHT 480 +#endif + +#define VERBOSE_VIDEO 0x00000001 +#define VERBOSE_MODES 0x00000002 +#define VERBOSE_RENDER 0x00000004 +#define VERBOSE_EVENT 0x00000008 +#define VERBOSE_AUDIO 0x00000010 +#define VERBOSE_MOTION 0x00000020 + +typedef struct +{ + /* SDL init flags */ + char **argv; + Uint32 flags; + Uint32 verbose; + + /* Video info */ + const char *videodriver; + int display; + const char *window_title; + const char *window_icon; + Uint32 window_flags; + SDL_bool flash_on_focus_loss; + int window_x; + int window_y; + int window_w; + int window_h; + int window_minW; + int window_minH; + int window_maxW; + int window_maxH; + int logical_w; + int logical_h; + float scale; + int depth; + int refresh_rate; + int num_windows; + SDL_Window **windows; + + /* Renderer info */ + const char *renderdriver; + Uint32 render_flags; + SDL_bool skip_renderer; + SDL_Renderer **renderers; + SDL_Texture **targets; + + /* Audio info */ + const char *audiodriver; + SDL_AudioSpec audiospec; + + /* GL settings */ + int gl_red_size; + int gl_green_size; + int gl_blue_size; + int gl_alpha_size; + int gl_buffer_size; + int gl_depth_size; + int gl_stencil_size; + int gl_double_buffer; + int gl_accum_red_size; + int gl_accum_green_size; + int gl_accum_blue_size; + int gl_accum_alpha_size; + int gl_stereo; + int gl_multisamplebuffers; + int gl_multisamplesamples; + int gl_retained_backing; + int gl_accelerated; + int gl_major_version; + int gl_minor_version; + int gl_debug; + int gl_profile_mask; + + /* Additional fields added in 2.0.18 */ + SDL_Rect confine; + +} SDLTest_CommonState; + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Function prototypes */ + +/* + * \brief Parse command line parameters and create common state. + * + * \param argv Array of command line parameters + * \param flags Flags indicating which subsystem to initialize (i.e. SDL_INIT_VIDEO | SDL_INIT_AUDIO) + * + * \returns a newly allocated common state object. + */ +SDLTest_CommonState *SDLTest_CommonCreateState(char **argv, Uint32 flags); + +/* + * \brief Process one common argument. + * + * \param state The common state describing the test window to create. + * \param index The index of the argument to process in argv[]. + * + * \returns the number of arguments processed (i.e. 1 for --fullscreen, 2 for --video [videodriver], or -1 on error. + */ +int SDLTest_CommonArg(SDLTest_CommonState * state, int index); + + +/* + * \brief Logs command line usage info. + * + * This logs the appropriate command line options for the subsystems in use + * plus other common options, and then any application-specific options. + * This uses the SDL_Log() function and splits up output to be friendly to + * 80-character-wide terminals. + * + * \param state The common state describing the test window for the app. + * \param argv0 argv[0], as passed to main/SDL_main. + * \param options an array of strings for application specific options. The last element of the array should be NULL. + */ +void SDLTest_CommonLogUsage(SDLTest_CommonState * state, const char *argv0, const char **options); + +/* + * \brief Returns common usage information + * + * You should (probably) be using SDLTest_CommonLogUsage() instead, but this + * function remains for binary compatibility. Strings returned from this + * function are valid until SDLTest_CommonQuit() is called, in which case + * those strings' memory is freed and can no longer be used. + * + * \param state The common state describing the test window to create. + * \returns a string with usage information + */ +const char *SDLTest_CommonUsage(SDLTest_CommonState * state); + +/* + * \brief Open test window. + * + * \param state The common state describing the test window to create. + * + * \returns SDL_TRUE if initialization succeeded, false otherwise + */ +SDL_bool SDLTest_CommonInit(SDLTest_CommonState * state); + +/* + * \brief Easy argument handling when test app doesn't need any custom args. + * + * \param state The common state describing the test window to create. + * \param argc argc, as supplied to SDL_main + * \param argv argv, as supplied to SDL_main + * + * \returns SDL_FALSE if app should quit, true otherwise. + */ +SDL_bool SDLTest_CommonDefaultArgs(SDLTest_CommonState * state, const int argc, char **argv); + +/* + * \brief Common event handler for test windows. + * + * \param state The common state used to create test window. + * \param event The event to handle. + * \param done Flag indicating we are done. + * + */ +void SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done); + +/* + * \brief Close test window. + * + * \param state The common state used to create test window. + * + */ +void SDLTest_CommonQuit(SDLTest_CommonState * state); + +/* + * \brief Draws various window information (position, size, etc.) to the renderer. + * + * \param renderer The renderer to draw to. + * \param window The window whose information should be displayed. + * \param usedHeight Returns the height used, so the caller can draw more below. + * + */ +void SDLTest_CommonDrawWindowInfo(SDL_Renderer * renderer, SDL_Window * window, int * usedHeight); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_common_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_compare.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_compare.h new file mode 100644 index 00000000..3fcb9359 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_compare.h @@ -0,0 +1,69 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_compare.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Defines comparison functions (i.e. for surfaces). + +*/ + +#ifndef SDL_test_compare_h_ +#define SDL_test_compare_h_ + +#include "SDL.h" + +#include "SDL_test_images.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * \brief Compares a surface and with reference image data for equality + * + * \param surface Surface used in comparison + * \param referenceSurface Test Surface used in comparison + * \param allowable_error Allowable difference (=sum of squared difference for each RGB component) in blending accuracy. + * + * \returns 0 if comparison succeeded, >0 (=number of pixels for which the comparison failed) if comparison failed, -1 if any of the surfaces were NULL, -2 if the surface sizes differ. + */ +int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_compare_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_crc32.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_crc32.h new file mode 100644 index 00000000..1dbeef27 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_crc32.h @@ -0,0 +1,124 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_crc32.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Implements CRC32 calculations (default output is Perl String::CRC32 compatible). + +*/ + +#ifndef SDL_test_crc32_h_ +#define SDL_test_crc32_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/* ------------ Definitions --------- */ + +/* Definition shared by all CRC routines */ + +#ifndef CrcUint32 + #define CrcUint32 unsigned int +#endif +#ifndef CrcUint8 + #define CrcUint8 unsigned char +#endif + +#ifdef ORIGINAL_METHOD + #define CRC32_POLY 0x04c11db7 /* AUTODIN II, Ethernet, & FDDI */ +#else + #define CRC32_POLY 0xEDB88320 /* Perl String::CRC32 compatible */ +#endif + +/* + * Data structure for CRC32 (checksum) computation + */ + typedef struct { + CrcUint32 crc32_table[256]; /* CRC table */ + } SDLTest_Crc32Context; + +/* ---------- Function Prototypes ------------- */ + +/* + * \brief Initialize the CRC context + * + * Note: The function initializes the crc table required for all crc calculations. + * + * \param crcContext pointer to context variable + * + * \returns 0 for OK, -1 on error + * + */ + int SDLTest_Crc32Init(SDLTest_Crc32Context * crcContext); + + +/* + * \brief calculate a crc32 from a data block + * + * \param crcContext pointer to context variable + * \param inBuf input buffer to checksum + * \param inLen length of input buffer + * \param crc32 pointer to Uint32 to store the final CRC into + * + * \returns 0 for OK, -1 on error + * + */ +int SDLTest_Crc32Calc(SDLTest_Crc32Context * crcContext, CrcUint8 *inBuf, CrcUint32 inLen, CrcUint32 *crc32); + +/* Same routine broken down into three steps */ +int SDLTest_Crc32CalcStart(SDLTest_Crc32Context * crcContext, CrcUint32 *crc32); +int SDLTest_Crc32CalcEnd(SDLTest_Crc32Context * crcContext, CrcUint32 *crc32); +int SDLTest_Crc32CalcBuffer(SDLTest_Crc32Context * crcContext, CrcUint8 *inBuf, CrcUint32 inLen, CrcUint32 *crc32); + + +/* + * \brief clean up CRC context + * + * \param crcContext pointer to context variable + * + * \returns 0 for OK, -1 on error + * +*/ + +int SDLTest_Crc32Done(SDLTest_Crc32Context * crcContext); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_crc32_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_font.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_font.h new file mode 100644 index 00000000..0eade923 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_font.h @@ -0,0 +1,168 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_font.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +#ifndef SDL_test_font_h_ +#define SDL_test_font_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Function prototypes */ + +#define FONT_CHARACTER_SIZE 8 +#define FONT_LINE_HEIGHT (FONT_CHARACTER_SIZE + 2) + +/* + * \brief Draw a string in the currently set font. + * + * \param renderer The renderer to draw on. + * \param x The X coordinate of the upper left corner of the character. + * \param y The Y coordinate of the upper left corner of the character. + * \param c The character to draw. + * + * \returns 0 on success, -1 on failure. + */ +int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, Uint32 c); + +/* + * \brief Draw a UTF-8 string in the currently set font. + * + * The font currently only supports characters in the Basic Latin and Latin-1 Supplement sets. + * + * \param renderer The renderer to draw on. + * \param x The X coordinate of the upper left corner of the string. + * \param y The Y coordinate of the upper left corner of the string. + * \param s The string to draw. + * + * \returns 0 on success, -1 on failure. + */ +int SDLTest_DrawString(SDL_Renderer *renderer, int x, int y, const char *s); + +/* + * \brief Data used for multi-line text output + */ +typedef struct SDLTest_TextWindow +{ + SDL_Rect rect; + int current; + int numlines; + char **lines; +} SDLTest_TextWindow; + +/* + * \brief Create a multi-line text output window + * + * \param x The X coordinate of the upper left corner of the window. + * \param y The Y coordinate of the upper left corner of the window. + * \param w The width of the window (currently ignored) + * \param h The height of the window (currently ignored) + * + * \returns the new window, or NULL on failure. + * + * \since This function is available since SDL 2.24.0 + */ +SDLTest_TextWindow *SDLTest_TextWindowCreate(int x, int y, int w, int h); + +/* + * \brief Display a multi-line text output window + * + * This function should be called every frame to display the text + * + * \param textwin The text output window + * \param renderer The renderer to use for display + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowDisplay(SDLTest_TextWindow *textwin, SDL_Renderer *renderer); + +/* + * \brief Add text to a multi-line text output window + * + * Adds UTF-8 text to the end of the current text. The newline character starts a + * new line of text. The backspace character deletes the last character or, if the + * line is empty, deletes the line and goes to the end of the previous line. + * + * \param textwin The text output window + * \param fmt A printf() style format string + * \param ... additional parameters matching % tokens in the `fmt` string, if any + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowAddText(SDLTest_TextWindow *textwin, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/* + * \brief Add text to a multi-line text output window + * + * Adds UTF-8 text to the end of the current text. The newline character starts a + * new line of text. The backspace character deletes the last character or, if the + * line is empty, deletes the line and goes to the end of the previous line. + * + * \param textwin The text output window + * \param text The text to add to the window + * \param len The length, in bytes, of the text to add to the window + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowAddTextWithLength(SDLTest_TextWindow *textwin, const char *text, size_t len); + +/* + * \brief Clear the text in a multi-line text output window + * + * \param textwin The text output window + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowClear(SDLTest_TextWindow *textwin); + +/* + * \brief Free the storage associated with a multi-line text output window + * + * \param textwin The text output window + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowDestroy(SDLTest_TextWindow *textwin); + +/* + * \brief Cleanup textures used by font drawing functions. + */ +void SDLTest_CleanupTextDrawing(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_font_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_fuzzer.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_fuzzer.h new file mode 100644 index 00000000..c6978ac8 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_fuzzer.h @@ -0,0 +1,387 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_fuzzer.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Data generators for fuzzing test data in a reproducible way. + +*/ + +#ifndef SDL_test_fuzzer_h_ +#define SDL_test_fuzzer_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/* + Based on GSOC code by Markus Kauppila +*/ + + +/* + * \file + * Note: The fuzzer implementation uses a static instance of random context + * internally which makes it thread-UNsafe. + */ + +/* + * Initializes the fuzzer for a test + * + * \param execKey Execution "Key" that initializes the random number generator uniquely for the test. + * + */ +void SDLTest_FuzzerInit(Uint64 execKey); + + +/* + * Returns a random Uint8 + * + * \returns a generated integer + */ +Uint8 SDLTest_RandomUint8(void); + +/* + * Returns a random Sint8 + * + * \returns a generated signed integer + */ +Sint8 SDLTest_RandomSint8(void); + + +/* + * Returns a random Uint16 + * + * \returns a generated integer + */ +Uint16 SDLTest_RandomUint16(void); + +/* + * Returns a random Sint16 + * + * \returns a generated signed integer + */ +Sint16 SDLTest_RandomSint16(void); + + +/* + * Returns a random integer + * + * \returns a generated integer + */ +Sint32 SDLTest_RandomSint32(void); + + +/* + * Returns a random positive integer + * + * \returns a generated integer + */ +Uint32 SDLTest_RandomUint32(void); + +/* + * Returns random Uint64. + * + * \returns a generated integer + */ +Uint64 SDLTest_RandomUint64(void); + + +/* + * Returns random Sint64. + * + * \returns a generated signed integer + */ +Sint64 SDLTest_RandomSint64(void); + +/* + * \returns a random float in range [0.0 - 1.0] + */ +float SDLTest_RandomUnitFloat(void); + +/* + * \returns a random double in range [0.0 - 1.0] + */ +double SDLTest_RandomUnitDouble(void); + +/* + * \returns a random float. + * + */ +float SDLTest_RandomFloat(void); + +/* + * \returns a random double. + * + */ +double SDLTest_RandomDouble(void); + +/* + * Returns a random boundary value for Uint8 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint8BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint8BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint8BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint8BoundaryValue(0, 255, SDL_FALSE) returns 0 (error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or 0 with error set + */ +Uint8 SDLTest_RandomUint8BoundaryValue(Uint8 boundary1, Uint8 boundary2, SDL_bool validDomain); + +/* + * Returns a random boundary value for Uint16 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint16BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint16BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint16BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint16BoundaryValue(0, 0xFFFF, SDL_FALSE) returns 0 (error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or 0 with error set + */ +Uint16 SDLTest_RandomUint16BoundaryValue(Uint16 boundary1, Uint16 boundary2, SDL_bool validDomain); + +/* + * Returns a random boundary value for Uint32 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint32BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint32BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint32BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint32BoundaryValue(0, 0xFFFFFFFF, SDL_FALSE) returns 0 (with error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or 0 with error set + */ +Uint32 SDLTest_RandomUint32BoundaryValue(Uint32 boundary1, Uint32 boundary2, SDL_bool validDomain); + +/* + * Returns a random boundary value for Uint64 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint64BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint64BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint64BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint64BoundaryValue(0, 0xFFFFFFFFFFFFFFFF, SDL_FALSE) returns 0 (with error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or 0 with error set + */ +Uint64 SDLTest_RandomUint64BoundaryValue(Uint64 boundary1, Uint64 boundary2, SDL_bool validDomain); + +/* + * Returns a random boundary value for Sint8 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint8BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint8BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint8BoundaryValue(SINT8_MIN, 99, SDL_FALSE) returns 100 + * RandomSint8BoundaryValue(SINT8_MIN, SINT8_MAX, SDL_FALSE) returns SINT8_MIN (== error value) with error set + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or SINT8_MIN with error set + */ +Sint8 SDLTest_RandomSint8BoundaryValue(Sint8 boundary1, Sint8 boundary2, SDL_bool validDomain); + + +/* + * Returns a random boundary value for Sint16 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint16BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint16BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint16BoundaryValue(SINT16_MIN, 99, SDL_FALSE) returns 100 + * RandomSint16BoundaryValue(SINT16_MIN, SINT16_MAX, SDL_FALSE) returns SINT16_MIN (== error value) with error set + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or SINT16_MIN with error set + */ +Sint16 SDLTest_RandomSint16BoundaryValue(Sint16 boundary1, Sint16 boundary2, SDL_bool validDomain); + +/* + * Returns a random boundary value for Sint32 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint32BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint32BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint32BoundaryValue(SINT32_MIN, 99, SDL_FALSE) returns 100 + * RandomSint32BoundaryValue(SINT32_MIN, SINT32_MAX, SDL_FALSE) returns SINT32_MIN (== error value) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or SINT32_MIN with error set + */ +Sint32 SDLTest_RandomSint32BoundaryValue(Sint32 boundary1, Sint32 boundary2, SDL_bool validDomain); + +/* + * Returns a random boundary value for Sint64 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint64BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint64BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint64BoundaryValue(SINT64_MIN, 99, SDL_FALSE) returns 100 + * RandomSint64BoundaryValue(SINT64_MIN, SINT64_MAX, SDL_FALSE) returns SINT64_MIN (== error value) and error set + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns a random boundary value for the given range and domain or SINT64_MIN with error set + */ +Sint64 SDLTest_RandomSint64BoundaryValue(Sint64 boundary1, Sint64 boundary2, SDL_bool validDomain); + + +/* + * Returns integer in range [min, max] (inclusive). + * Min and max values can be negative values. + * If Max in smaller than min, then the values are swapped. + * Min and max are the same value, that value will be returned. + * + * \param min Minimum inclusive value of returned random number + * \param max Maximum inclusive value of returned random number + * + * \returns a generated random integer in range + */ +Sint32 SDLTest_RandomIntegerInRange(Sint32 min, Sint32 max); + + +/* + * Generates random null-terminated string. The minimum length for + * the string is 1 character, maximum length for the string is 255 + * characters and it can contain ASCII characters from 32 to 126. + * + * Note: Returned string needs to be deallocated. + * + * \returns a newly allocated random string; or NULL if length was invalid or string could not be allocated. + */ +char * SDLTest_RandomAsciiString(void); + + +/* + * Generates random null-terminated string. The maximum length for + * the string is defined by the maxLength parameter. + * String can contain ASCII characters from 32 to 126. + * + * Note: Returned string needs to be deallocated. + * + * \param maxLength The maximum length of the generated string. + * + * \returns a newly allocated random string; or NULL if maxLength was invalid or string could not be allocated. + */ +char * SDLTest_RandomAsciiStringWithMaximumLength(int maxLength); + + +/* + * Generates random null-terminated string. The length for + * the string is defined by the size parameter. + * String can contain ASCII characters from 32 to 126. + * + * Note: Returned string needs to be deallocated. + * + * \param size The length of the generated string + * + * \returns a newly allocated random string; or NULL if size was invalid or string could not be allocated. + */ +char * SDLTest_RandomAsciiStringOfSize(int size); + + +/* + * Get the invocation count for the fuzzer since last ...FuzzerInit. + * + * \returns the invocation count. + */ +int SDLTest_GetFuzzerInvocationCount(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_fuzzer_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_harness.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_harness.h new file mode 100644 index 00000000..cfd62e84 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_harness.h @@ -0,0 +1,134 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_harness.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + Defines types for test case definitions and the test execution harness API. + + Based on original GSOC code by Markus Kauppila +*/ + +#ifndef SDL_test_h_arness_h +#define SDL_test_h_arness_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/* ! Definitions for test case structures */ +#define TEST_ENABLED 1 +#define TEST_DISABLED 0 + +/* ! Definition of all the possible test return values of the test case method */ +#define TEST_ABORTED -1 +#define TEST_STARTED 0 +#define TEST_COMPLETED 1 +#define TEST_SKIPPED 2 + +/* ! Definition of all the possible test results for the harness */ +#define TEST_RESULT_PASSED 0 +#define TEST_RESULT_FAILED 1 +#define TEST_RESULT_NO_ASSERT 2 +#define TEST_RESULT_SKIPPED 3 +#define TEST_RESULT_SETUP_FAILURE 4 + +/* !< Function pointer to a test case setup function (run before every test) */ +typedef void (*SDLTest_TestCaseSetUpFp)(void *arg); + +/* !< Function pointer to a test case function */ +typedef int (*SDLTest_TestCaseFp)(void *arg); + +/* !< Function pointer to a test case teardown function (run after every test) */ +typedef void (*SDLTest_TestCaseTearDownFp)(void *arg); + +/* + * Holds information about a single test case. + */ +typedef struct SDLTest_TestCaseReference { + /* !< Func2Stress */ + SDLTest_TestCaseFp testCase; + /* !< Short name (or function name) "Func2Stress" */ + const char *name; + /* !< Long name or full description "This test pushes func2() to the limit." */ + const char *description; + /* !< Set to TEST_ENABLED or TEST_DISABLED (test won't be run) */ + int enabled; +} SDLTest_TestCaseReference; + +/* + * Holds information about a test suite (multiple test cases). + */ +typedef struct SDLTest_TestSuiteReference { + /* !< "PlatformSuite" */ + const char *name; + /* !< The function that is run before each test. NULL skips. */ + SDLTest_TestCaseSetUpFp testSetUp; + /* !< The test cases that are run as part of the suite. Last item should be NULL. */ + const SDLTest_TestCaseReference **testCases; + /* !< The function that is run after each test. NULL skips. */ + SDLTest_TestCaseTearDownFp testTearDown; +} SDLTest_TestSuiteReference; + + +/* + * \brief Generates a random run seed string for the harness. The generated seed will contain alphanumeric characters (0-9A-Z). + * + * Note: The returned string needs to be deallocated by the caller. + * + * \param length The length of the seed string to generate + * + * \returns the generated seed string + */ +char *SDLTest_GenerateRunSeed(const int length); + +/* + * \brief Execute a test suite using the given run seed and execution key. + * + * \param testSuites Suites containing the test case. + * \param userRunSeed Custom run seed provided by user, or NULL to autogenerate one. + * \param userExecKey Custom execution key provided by user, or 0 to autogenerate one. + * \param filter Filter specification. NULL disables. Case sensitive. + * \param testIterations Number of iterations to run each test case. + * + * \returns the test run result: 0 when all tests passed, 1 if any tests failed. + */ +int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *userRunSeed, Uint64 userExecKey, const char *filter, int testIterations); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_h_arness_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_images.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_images.h new file mode 100644 index 00000000..d593a315 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_images.h @@ -0,0 +1,78 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_images.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Defines some images for tests. + +*/ + +#ifndef SDL_test_images_h_ +#define SDL_test_images_h_ + +#include "SDL.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* + *Type for test images. + */ +typedef struct SDLTest_SurfaceImage_s { + int width; + int height; + unsigned int bytes_per_pixel; /* 3:RGB, 4:RGBA */ + const char *pixel_data; +} SDLTest_SurfaceImage_t; + +/* Test images */ +SDL_Surface *SDLTest_ImageBlit(void); +SDL_Surface *SDLTest_ImageBlitColor(void); +SDL_Surface *SDLTest_ImageBlitAlpha(void); +SDL_Surface *SDLTest_ImageBlitBlendAdd(void); +SDL_Surface *SDLTest_ImageBlitBlend(void); +SDL_Surface *SDLTest_ImageBlitBlendMod(void); +SDL_Surface *SDLTest_ImageBlitBlendNone(void); +SDL_Surface *SDLTest_ImageBlitBlendAll(void); +SDL_Surface *SDLTest_ImageFace(void); +SDL_Surface *SDLTest_ImagePrimitives(void); +SDL_Surface *SDLTest_ImagePrimitivesBlend(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_images_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_log.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_log.h new file mode 100644 index 00000000..6f7f6614 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_log.h @@ -0,0 +1,67 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_log.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + * + * Wrapper to log in the TEST category + * + */ + +#ifndef SDL_test_log_h_ +#define SDL_test_log_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * \brief Prints given message with a timestamp in the TEST category and INFO priority. + * + * \param fmt Message to be logged + */ +void SDLTest_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); + +/* + * \brief Prints given message with a timestamp in the TEST category and the ERROR priority. + * + * \param fmt Message to be logged + */ +void SDLTest_LogError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_log_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_md5.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_md5.h new file mode 100644 index 00000000..edd79451 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_md5.h @@ -0,0 +1,129 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_md5.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + *********************************************************************** + ** Header file for implementation of MD5 ** + ** RSA Data Security, Inc. MD5 Message-Digest Algorithm ** + ** Created: 2/17/90 RLR ** + ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version ** + ** Revised (for MD5): RLR 4/27/91 ** + ** -- G modified to have y&~z instead of y&z ** + ** -- FF, GG, HH modified to add in last register done ** + ** -- Access pattern: round 2 works mod 5, round 3 works mod 3 ** + ** -- distinct additive constant for each step ** + ** -- round 4 added, working mod 7 ** + *********************************************************************** +*/ + +/* + *********************************************************************** + ** Message-digest routines: ** + ** To form the message digest for a message M ** + ** (1) Initialize a context buffer mdContext using MD5Init ** + ** (2) Call MD5Update on mdContext and M ** + ** (3) Call MD5Final on mdContext ** + ** The message digest is now in mdContext->digest[0...15] ** + *********************************************************************** +*/ + +#ifndef SDL_test_md5_h_ +#define SDL_test_md5_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------ Definitions --------- */ + +/* typedef a 32-bit type */ + typedef unsigned long int MD5UINT4; + +/* Data structure for MD5 (Message-Digest) computation */ + typedef struct { + MD5UINT4 i[2]; /* number of _bits_ handled mod 2^64 */ + MD5UINT4 buf[4]; /* scratch buffer */ + unsigned char in[64]; /* input buffer */ + unsigned char digest[16]; /* actual digest after Md5Final call */ + } SDLTest_Md5Context; + +/* ---------- Function Prototypes ------------- */ + +/* + * \brief initialize the context + * + * \param mdContext pointer to context variable + * + * Note: The function initializes the message-digest context + * mdContext. Call before each new use of the context - + * all fields are set to zero. + */ + void SDLTest_Md5Init(SDLTest_Md5Context * mdContext); + + +/* + * \brief update digest from variable length data + * + * \param mdContext pointer to context variable + * \param inBuf pointer to data array/string + * \param inLen length of data array/string + * + * Note: The function updates the message-digest context to account + * for the presence of each of the characters inBuf[0..inLen-1] + * in the message whose digest is being computed. +*/ + + void SDLTest_Md5Update(SDLTest_Md5Context * mdContext, unsigned char *inBuf, + unsigned int inLen); + + +/* + * \brief complete digest computation + * + * \param mdContext pointer to context variable + * + * Note: The function terminates the message-digest computation and + * ends with the desired message digest in mdContext.digest[0..15]. + * Always call before using the digest[] variable. +*/ + + void SDLTest_Md5Final(SDLTest_Md5Context * mdContext); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_md5_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_memory.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_memory.h new file mode 100644 index 00000000..e789fa80 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_memory.h @@ -0,0 +1,63 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_memory.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +#ifndef SDL_test_memory_h_ +#define SDL_test_memory_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * \brief Start tracking SDL memory allocations + * + * \note This should be called before any other SDL functions for complete tracking coverage + */ +int SDLTest_TrackAllocations(void); + +/* + * \brief Print a log of any outstanding allocations + * + * \note This can be called after SDL_Quit() + */ +void SDLTest_LogAllocations(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_memory_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_random.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_random.h new file mode 100644 index 00000000..05d6d3ee --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_test_random.h @@ -0,0 +1,115 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * \file SDL_test_random.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + A "32-bit Multiply with carry random number generator. Very fast. + Includes a list of recommended multipliers. + + multiply-with-carry generator: x(n) = a*x(n-1) + carry mod 2^32. + period: (a*2^31)-1 + +*/ + +#ifndef SDL_test_random_h_ +#define SDL_test_random_h_ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* --- Definitions */ + +/* + * Macros that return a random number in a specific format. + */ +#define SDLTest_RandomInt(c) ((int)SDLTest_Random(c)) + +/* + * Context structure for the random number generator state. + */ + typedef struct { + unsigned int a; + unsigned int x; + unsigned int c; + unsigned int ah; + unsigned int al; + } SDLTest_RandomContext; + + +/* --- Function prototypes */ + +/* + * \brief Initialize random number generator with two integers. + * + * Note: The random sequence of numbers returned by ...Random() is the + * same for the same two integers and has a period of 2^31. + * + * \param rndContext pointer to context structure + * \param xi integer that defines the random sequence + * \param ci integer that defines the random sequence + * + */ + void SDLTest_RandomInit(SDLTest_RandomContext * rndContext, unsigned int xi, + unsigned int ci); + +/* + * \brief Initialize random number generator based on current system time. + * + * \param rndContext pointer to context structure + * + */ + void SDLTest_RandomInitTime(SDLTest_RandomContext *rndContext); + + +/* + * \brief Initialize random number generator based on current system time. + * + * Note: ...RandomInit() or ...RandomInitTime() must have been called + * before using this function. + * + * \param rndContext pointer to context structure + * + * \returns a random number (32bit unsigned integer) + * + */ + unsigned int SDLTest_Random(SDLTest_RandomContext *rndContext); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_test_random_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_thread.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_thread.h new file mode 100644 index 00000000..ac405d85 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_thread.h @@ -0,0 +1,468 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_thread_h_ +#define SDL_thread_h_ + +/** + * # CategoryThread + * + * Header for the SDL thread management routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/* Thread synchronization primitives */ +#include "SDL_atomic.h" +#include "SDL_mutex.h" + +#if (defined(__WIN32__) || defined(__GDK__)) && !defined(__WINRT__) +#include /* _beginthreadex() and _endthreadex() */ +#endif +#if defined(__OS2__) /* for _beginthread() and _endthread() */ +#ifndef __EMX__ +#include +#else +#include +#endif +#endif + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* The SDL thread structure, defined in SDL_thread.c */ +struct SDL_Thread; +typedef struct SDL_Thread SDL_Thread; + +/* The SDL thread ID */ +typedef unsigned long SDL_threadID; + +/* Thread local storage ID, 0 is the invalid ID */ +typedef unsigned int SDL_TLSID; + +/** + * The SDL thread priority. + * + * SDL will make system changes as necessary in order to apply the thread + * priority. Code which attempts to control thread state related to priority + * should be aware that calling SDL_SetThreadPriority may alter such state. + * SDL_HINT_THREAD_PRIORITY_POLICY can be used to control aspects of this + * behavior. + * + * On many systems you require special privileges to set high or time critical + * priority. + */ +typedef enum SDL_ThreadPriority { + SDL_THREAD_PRIORITY_LOW, + SDL_THREAD_PRIORITY_NORMAL, + SDL_THREAD_PRIORITY_HIGH, + SDL_THREAD_PRIORITY_TIME_CRITICAL +} SDL_ThreadPriority; + +/** + * The function passed to SDL_CreateThread(). + * + * \param data what was passed as `data` to SDL_CreateThread(). + * \returns a value that can be reported through SDL_WaitThread(). + */ +typedef int (SDLCALL * SDL_ThreadFunction) (void *data); + + +#if (defined(__WIN32__) || defined(__GDK__)) && !defined(__WINRT__) +/** + * \file SDL_thread.h + * + * We compile SDL into a DLL. This means, that it's the DLL which + * creates a new thread for the calling process with the SDL_CreateThread() + * API. There is a problem with this, that only the RTL of the SDL2.DLL will + * be initialized for those threads, and not the RTL of the calling + * application! + * + * To solve this, we make a little hack here. + * + * We'll always use the caller's _beginthread() and _endthread() APIs to + * start a new thread. This way, if it's the SDL2.DLL which uses this API, + * then the RTL of SDL2.DLL will be used to create the new thread, and if it's + * the application, then the RTL of the application will be used. + * + * So, in short: + * Always use the _beginthread() and _endthread() of the calling runtime + * library! + */ +#define SDL_PASSED_BEGINTHREAD_ENDTHREAD + +typedef uintptr_t (__cdecl * pfnSDL_CurrentBeginThread) + (void *, unsigned, unsigned (__stdcall *func)(void *), + void * /*arg*/, unsigned, unsigned * /* threadID */); +typedef void (__cdecl * pfnSDL_CurrentEndThread) (unsigned code); + +#ifndef SDL_beginthread +#define SDL_beginthread _beginthreadex +#endif +#ifndef SDL_endthread +#define SDL_endthread _endthreadex +#endif + +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); + +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, + const char *name, const size_t stacksize, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); + + +#if defined(SDL_CreateThread) && SDL_DYNAMIC_API +#undef SDL_CreateThread +#define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#undef SDL_CreateThreadWithStackSize +#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#else +#define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#endif + +#elif defined(__OS2__) +/* + * just like the windows case above: We compile SDL2 + * into a dll with Watcom's runtime statically linked. + */ +#define SDL_PASSED_BEGINTHREAD_ENDTHREAD + +typedef int (*pfnSDL_CurrentBeginThread)(void (*func)(void *), void *, unsigned, void * /*arg*/); +typedef void (*pfnSDL_CurrentEndThread)(void); + +#ifndef SDL_beginthread +#define SDL_beginthread _beginthread +#endif +#ifndef SDL_endthread +#define SDL_endthread _endthread +#endif + +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); + +#if defined(SDL_CreateThread) && SDL_DYNAMIC_API +#undef SDL_CreateThread +#define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#undef SDL_CreateThreadWithStackSize +#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#else +#define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)SDL_beginthread, (pfnSDL_CurrentEndThread)SDL_endthread) +#endif + +#else + +/** + * Create a new thread with a default stack size. + * + * This is equivalent to calling: + * + * ```c + * SDL_CreateThreadWithStackSize(fn, name, 0, data); + * ``` + * + * \param fn the SDL_ThreadFunction function to call in the new thread. + * \param name the name of the thread. + * \param data a pointer that is passed to `fn`. + * \returns an opaque pointer to the new thread object on success, NULL if the + * new thread could not be created; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateThreadWithStackSize + * \sa SDL_WaitThread + */ +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data); + +/** + * Create a new thread with a specific stack size. + * + * SDL makes an attempt to report `name` to the system, so that debuggers can + * display it. Not all platforms support this. + * + * Thread naming is a little complicated: Most systems have very small limits + * for the string length (Haiku has 32 bytes, Linux currently has 16, Visual + * C++ 6.0 has _nine_!), and possibly other arbitrary rules. You'll have to + * see what happens with your system's debugger. The name should be UTF-8 (but + * using the naming limits of C identifiers is a better bet). There are no + * requirements for thread naming conventions, so long as the string is + * null-terminated UTF-8, but these guidelines are helpful in choosing a name: + * + * https://stackoverflow.com/questions/149932/naming-conventions-for-threads + * + * If a system imposes requirements, SDL will try to munge the string for it + * (truncate, etc), but the original string contents will be available from + * SDL_GetThreadName(). + * + * The size (in bytes) of the new stack can be specified. Zero means "use the + * system default" which might be wildly different between platforms. x86 + * Linux generally defaults to eight megabytes, an embedded device might be a + * few kilobytes instead. You generally need to specify a stack that is a + * multiple of the system's page size (in many cases, this is 4 kilobytes, but + * check your system documentation). + * + * In SDL 2.1, stack size will be folded into the original SDL_CreateThread + * function, but for backwards compatibility, this is currently a separate + * function. + * + * \param fn the SDL_ThreadFunction function to call in the new thread. + * \param name the name of the thread. + * \param stacksize the size, in bytes, to allocate for the new thread stack. + * \param data a pointer that is passed to `fn`. + * \returns an opaque pointer to the new thread object on success, NULL if the + * new thread could not be created; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.9. + * + * \sa SDL_WaitThread + */ +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data); + +#endif + +/** + * Get the thread name as it was specified in SDL_CreateThread(). + * + * This is internal memory, not to be freed by the caller, and remains valid + * until the specified thread is cleaned up by SDL_WaitThread(). + * + * \param thread the thread to query. + * \returns a pointer to a UTF-8 string that names the specified thread, or + * NULL if it doesn't have a name. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateThread + */ +extern DECLSPEC const char *SDLCALL SDL_GetThreadName(SDL_Thread *thread); + +/** + * Get the thread identifier for the current thread. + * + * This thread identifier is as reported by the underlying operating system. + * If SDL is running on a platform that does not support threads the return + * value will always be zero. + * + * This function also returns a valid thread ID when called from the main + * thread. + * + * \returns the ID of the current thread. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetThreadID + */ +extern DECLSPEC SDL_threadID SDLCALL SDL_ThreadID(void); + +/** + * Get the thread identifier for the specified thread. + * + * This thread identifier is as reported by the underlying operating system. + * If SDL is running on a platform that does not support threads the return + * value will always be zero. + * + * \param thread the thread to query. + * \returns the ID of the specified thread, or the ID of the current thread if + * `thread` is NULL. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ThreadID + */ +extern DECLSPEC SDL_threadID SDLCALL SDL_GetThreadID(SDL_Thread * thread); + +/** + * Set the priority for the current thread. + * + * Note that some platforms will not let you alter the priority (or at least, + * promote the thread to a higher priority) at all, and some require you to be + * an administrator account. Be prepared for this to fail. + * + * \param priority the SDL_ThreadPriority to set. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC int SDLCALL SDL_SetThreadPriority(SDL_ThreadPriority priority); + +/** + * Wait for a thread to finish. + * + * Threads that haven't been detached will remain (as a "zombie") until this + * function cleans them up. Not doing so is a resource leak. + * + * Once a thread has been cleaned up through this function, the SDL_Thread + * that references it becomes invalid and should not be referenced again. As + * such, only one thread may call SDL_WaitThread() on another. + * + * The return code for the thread function is placed in the area pointed to by + * `status`, if `status` is not NULL. + * + * You may not wait on a thread that has been used in a call to + * SDL_DetachThread(). Use either that function or this one, but not both, or + * behavior is undefined. + * + * It is safe to pass a NULL thread to this function; it is a no-op. + * + * Note that the thread pointer is freed by this function and is not valid + * afterward. + * + * \param thread the SDL_Thread pointer that was returned from the + * SDL_CreateThread() call that started this thread. + * \param status pointer to an integer that will receive the value returned + * from the thread function by its 'return', or NULL to not + * receive such value back. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateThread + * \sa SDL_DetachThread + */ +extern DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread * thread, int *status); + +/** + * Let a thread clean up on exit without intervention. + * + * A thread may be "detached" to signify that it should not remain until + * another thread has called SDL_WaitThread() on it. Detaching a thread is + * useful for long-running threads that nothing needs to synchronize with or + * further manage. When a detached thread is done, it simply goes away. + * + * There is no way to recover the return code of a detached thread. If you + * need this, don't detach the thread and instead use SDL_WaitThread(). + * + * Once a thread is detached, you should usually assume the SDL_Thread isn't + * safe to reference again, as it will become invalid immediately upon the + * detached thread's exit, instead of remaining until someone has called + * SDL_WaitThread() to finally clean it up. As such, don't detach the same + * thread more than once. + * + * If a thread has already exited when passed to SDL_DetachThread(), it will + * stop waiting for a call to SDL_WaitThread() and clean up immediately. It is + * not safe to detach a thread that might be used with SDL_WaitThread(). + * + * You may not call SDL_WaitThread() on a thread that has been detached. Use + * either that function or this one, but not both, or behavior is undefined. + * + * It is safe to pass NULL to this function; it is a no-op. + * + * \param thread the SDL_Thread pointer that was returned from the + * SDL_CreateThread() call that started this thread. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_CreateThread + * \sa SDL_WaitThread + */ +extern DECLSPEC void SDLCALL SDL_DetachThread(SDL_Thread * thread); + +/** + * Create a piece of thread-local storage. + * + * This creates an identifier that is globally visible to all threads but + * refers to data that is thread-specific. + * + * \returns the newly created thread local storage identifier or 0 on error. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_TLSGet + * \sa SDL_TLSSet + */ +extern DECLSPEC SDL_TLSID SDLCALL SDL_TLSCreate(void); + +/** + * Get the current thread's value associated with a thread local storage ID. + * + * \param id the thread local storage ID. + * \returns the value associated with the ID for the current thread or NULL if + * no value has been set; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_TLSCreate + * \sa SDL_TLSSet + */ +extern DECLSPEC void * SDLCALL SDL_TLSGet(SDL_TLSID id); + +typedef void (SDLCALL *SDL_TLSDestructorCallback)(void*); + +/** + * Set the current thread's value associated with a thread local storage ID. + * + * The function prototype for `destructor` is: + * + * ```c + * void destructor(void *value) + * ``` + * + * where its parameter `value` is what was passed as `value` to SDL_TLSSet(). + * + * \param id the thread local storage ID. + * \param value the value to associate with the ID for the current thread. + * \param destructor a function called when the thread exits, to free the + * value. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_TLSCreate + * \sa SDL_TLSGet + */ +extern DECLSPEC int SDLCALL SDL_TLSSet(SDL_TLSID id, const void *value, SDL_TLSDestructorCallback destructor); + +/** + * Cleanup all TLS data for this thread. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC void SDLCALL SDL_TLSCleanup(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_thread_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_timer.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_timer.h new file mode 100644 index 00000000..60969690 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_timer.h @@ -0,0 +1,222 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_timer_h_ +#define SDL_timer_h_ + +/** + * # CategoryTimer + * + * Header for the SDL time management routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get the number of milliseconds since SDL library initialization. + * + * This value wraps if the program runs for more than ~49 days. + * + * This function is not recommended as of SDL 2.0.18; use SDL_GetTicks64() + * instead, where the value doesn't wrap every ~49 days. There are places in + * SDL where we provide a 32-bit timestamp that can not change without + * breaking binary compatibility, though, so this function isn't officially + * deprecated. + * + * \returns an unsigned 32-bit value representing the number of milliseconds + * since the SDL library initialized. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_TICKS_PASSED + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetTicks(void); + +/** + * Get the number of milliseconds since SDL library initialization. + * + * Note that you should not use the SDL_TICKS_PASSED macro with values + * returned by this function, as that macro does clever math to compensate for + * the 32-bit overflow every ~49 days that SDL_GetTicks() suffers from. 64-bit + * values from this function can be safely compared directly. + * + * For example, if you want to wait 100 ms, you could do this: + * + * ```c + * const Uint64 timeout = SDL_GetTicks64() + 100; + * while (SDL_GetTicks64() < timeout) { + * // ... do work until timeout has elapsed + * } + * ``` + * + * \returns an unsigned 64-bit value representing the number of milliseconds + * since the SDL library initialized. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC Uint64 SDLCALL SDL_GetTicks64(void); + +/** + * Compare 32-bit SDL ticks values, and return true if `A` has passed `B`. + * + * This should be used with results from SDL_GetTicks(), as this macro + * attempts to deal with the 32-bit counter wrapping back to zero every ~49 + * days, but should _not_ be used with SDL_GetTicks64(), which does not have + * that problem. + * + * For example, with SDL_GetTicks(), if you want to wait 100 ms, you could do + * this: + * + * ```c + * const Uint32 timeout = SDL_GetTicks() + 100; + * while (!SDL_TICKS_PASSED(SDL_GetTicks(), timeout)) { + * // ... do work until timeout has elapsed + * } + * ``` + * + * Note that this does not handle tick differences greater than 2^31 so take + * care when using the above kind of code with large timeout delays (tens of + * days). + */ +#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) + +/** + * Get the current value of the high resolution counter. + * + * This function is typically used for profiling. + * + * The counter values are only meaningful relative to each other. Differences + * between values can be converted to times by using + * SDL_GetPerformanceFrequency(). + * + * \returns the current counter value. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetPerformanceFrequency + */ +extern DECLSPEC Uint64 SDLCALL SDL_GetPerformanceCounter(void); + +/** + * Get the count per second of the high resolution counter. + * + * \returns a platform-specific count per second. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetPerformanceCounter + */ +extern DECLSPEC Uint64 SDLCALL SDL_GetPerformanceFrequency(void); + +/** + * Wait a specified number of milliseconds before returning. + * + * This function waits a specified number of milliseconds before returning. It + * waits at least the specified time, but possibly longer due to OS + * scheduling. + * + * \param ms the number of milliseconds to delay. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_Delay(Uint32 ms); + +/** + * Function prototype for the timer callback function. + * + * The callback function is passed the current timer interval and returns the + * next timer interval. If the returned value is the same as the one passed + * in, the periodic alarm continues, otherwise a new alarm is scheduled. If + * the callback returns 0, the periodic alarm is cancelled. + */ +typedef Uint32 (SDLCALL * SDL_TimerCallback) (Uint32 interval, void *param); + +/** + * Definition of the timer ID type. + */ +typedef int SDL_TimerID; + +/** + * Call a callback function at a future time. + * + * If you use this function, you must pass `SDL_INIT_TIMER` to SDL_Init(). + * + * The callback function is passed the current timer interval and the user + * supplied parameter from the SDL_AddTimer() call and should return the next + * timer interval. If the value returned from the callback is 0, the timer is + * canceled. + * + * The callback is run on a separate thread. + * + * Timers take into account the amount of time it took to execute the + * callback. For example, if the callback took 250 ms to execute and returned + * 1000 (ms), the timer would only wait another 750 ms before its next + * iteration. + * + * Timing may be inexact due to OS scheduling. Be sure to note the current + * time with SDL_GetTicks() or SDL_GetPerformanceCounter() in case your + * callback needs to adjust for variances. + * + * \param interval the timer delay, in milliseconds, passed to `callback`. + * \param callback the SDL_TimerCallback function to call when the specified + * `interval` elapses. + * \param param a pointer that is passed to `callback`. + * \returns a timer ID or 0 if an error occurs; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RemoveTimer + */ +extern DECLSPEC SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, + SDL_TimerCallback callback, + void *param); + +/** + * Remove a timer created with SDL_AddTimer(). + * + * \param id the ID of the timer to remove. + * \returns SDL_TRUE if the timer is removed or SDL_FALSE if the timer wasn't + * found. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_AddTimer + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RemoveTimer(SDL_TimerID id); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_timer_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_touch.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_touch.h new file mode 100644 index 00000000..80a0fef8 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_touch.h @@ -0,0 +1,150 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryTouch + * + * Include file for SDL touch event handling. + */ + +#ifndef SDL_touch_h_ +#define SDL_touch_h_ + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef Sint64 SDL_TouchID; +typedef Sint64 SDL_FingerID; + +typedef enum +{ + SDL_TOUCH_DEVICE_INVALID = -1, + SDL_TOUCH_DEVICE_DIRECT, /* touch screen with window-relative coordinates */ + SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE, /* trackpad with absolute device coordinates */ + SDL_TOUCH_DEVICE_INDIRECT_RELATIVE /* trackpad with screen cursor-relative coordinates */ +} SDL_TouchDeviceType; + +typedef struct SDL_Finger +{ + SDL_FingerID id; + float x; + float y; + float pressure; +} SDL_Finger; + +/* Used as the device ID for mouse events simulated with touch input */ +#define SDL_TOUCH_MOUSEID ((Uint32)-1) + +/* Used as the SDL_TouchID for touch events simulated with mouse input */ +#define SDL_MOUSE_TOUCHID ((Sint64)-1) + + +/** + * Get the number of registered touch devices. + * + * On some platforms SDL first sees the touch device if it was actually used. + * Therefore SDL_GetNumTouchDevices() may return 0 although devices are + * available. After using all devices at least once the number will be + * correct. + * + * This was fixed for Android in SDL 2.0.1. + * + * \returns the number of registered touch devices. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTouchDevice + */ +extern DECLSPEC int SDLCALL SDL_GetNumTouchDevices(void); + +/** + * Get the touch ID with the given index. + * + * \param index the touch device index. + * \returns the touch ID with the given index on success or 0 if the index is + * invalid; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumTouchDevices + */ +extern DECLSPEC SDL_TouchID SDLCALL SDL_GetTouchDevice(int index); + +/** + * Get the touch device name as reported from the driver or NULL if the index + * is invalid. + * + * \since This function is available since SDL 2.0.22. + */ +extern DECLSPEC const char* SDLCALL SDL_GetTouchName(int index); + +/** + * Get the type of the given touch device. + * + * \since This function is available since SDL 2.0.10. + */ +extern DECLSPEC SDL_TouchDeviceType SDLCALL SDL_GetTouchDeviceType(SDL_TouchID touchID); + +/** + * Get the number of active fingers for a given touch device. + * + * \param touchID the ID of a touch device. + * \returns the number of active fingers for a given touch device on success + * or 0 on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetTouchFinger + */ +extern DECLSPEC int SDLCALL SDL_GetNumTouchFingers(SDL_TouchID touchID); + +/** + * Get the finger object for specified touch device ID and finger index. + * + * The returned resource is owned by SDL and should not be deallocated. + * + * \param touchID the ID of the requested touch device. + * \param index the index of the requested finger. + * \returns a pointer to the SDL_Finger object or NULL if no object at the + * given ID and index could be found. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_RecordGesture + */ +extern DECLSPEC SDL_Finger * SDLCALL SDL_GetTouchFinger(SDL_TouchID touchID, int index); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_touch_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_types.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_types.h new file mode 100644 index 00000000..cb3b4a8d --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_types.h @@ -0,0 +1,24 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* DEPRECATED */ + +#include "SDL_stdinc.h" diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_version.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_version.h new file mode 100644 index 00000000..b94e6d8b --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_version.h @@ -0,0 +1,205 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryVersion + * + * This header defines the current SDL version. + */ + +#ifndef SDL_version_h_ +#define SDL_version_h_ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Information about the version of SDL in use. + * + * Represents the library's version as three levels: major revision + * (increments with massive changes, additions, and enhancements), minor + * revision (increments with backwards-compatible changes to the major + * revision), and patchlevel (increments with fixes to the minor revision). + * + * \sa SDL_VERSION + * \sa SDL_GetVersion + */ +typedef struct SDL_version +{ + Uint8 major; /**< major version */ + Uint8 minor; /**< minor version */ + Uint8 patch; /**< update version */ +} SDL_version; + +/* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL +*/ +#define SDL_MAJOR_VERSION 2 +#define SDL_MINOR_VERSION 32 +#define SDL_PATCHLEVEL 10 + +/** + * Macro to determine SDL version program was compiled against. + * + * This macro fills in a SDL_version structure with the version of the library + * you compiled against. This is determined by what header the compiler uses. + * Note that if you dynamically linked the library, you might have a slightly + * newer or older version at runtime. That version can be determined with + * SDL_GetVersion(), which, unlike SDL_VERSION(), is not a macro. + * + * \param x A pointer to a SDL_version struct to initialize. + * + * \sa SDL_version + * \sa SDL_GetVersion + */ +#define SDL_VERSION(x) \ +{ \ + (x)->major = SDL_MAJOR_VERSION; \ + (x)->minor = SDL_MINOR_VERSION; \ + (x)->patch = SDL_PATCHLEVEL; \ +} + +/* TODO: Remove this whole block in SDL 3 */ +#if SDL_MAJOR_VERSION < 3 + +/** + * This macro turns the version numbers into a numeric value: + * + * ``` + * (1,2,3) -> (1203) + * ``` + * + * This assumes that there will never be more than 100 patchlevels. + * + * In versions higher than 2.9.0, the minor version overflows into the + * thousands digit: for example, 2.23.0 is encoded as 4300, and 2.255.99 would + * be encoded as 25799. + * + * This macro will not be available in SDL 3.x. + */ +#define SDL_VERSIONNUM(X, Y, Z) \ + ((X)*1000 + (Y)*100 + (Z)) + +/** + * This is the version number macro for the current SDL version. + * + * In versions higher than 2.9.0, the minor version overflows into the + * thousands digit: for example, 2.23.0 is encoded as 4300. This macro will + * not be available in SDL 3.x. + * + * Deprecated, use SDL_VERSION_ATLEAST or SDL_VERSION instead. + */ +#define SDL_COMPILEDVERSION \ + SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL) +#endif /* SDL_MAJOR_VERSION < 3 */ + +/** + * This macro will evaluate to true if compiled with SDL at least X.Y.Z. + */ +#define SDL_VERSION_ATLEAST(X, Y, Z) \ + ((SDL_MAJOR_VERSION >= X) && \ + (SDL_MAJOR_VERSION > X || SDL_MINOR_VERSION >= Y) && \ + (SDL_MAJOR_VERSION > X || SDL_MINOR_VERSION > Y || SDL_PATCHLEVEL >= Z)) + +/** + * Get the version of SDL that is linked against your program. + * + * If you are linking to SDL dynamically, then it is possible that the current + * version will be different than the version you compiled against. This + * function returns the current version, while SDL_VERSION() is a macro that + * tells you what version you compiled with. + * + * This function may be called safely at any time, even before SDL_Init(). + * + * \param ver the SDL_version structure that contains the version information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRevision + */ +extern DECLSPEC void SDLCALL SDL_GetVersion(SDL_version * ver); + +/** + * Get the code revision of SDL that is linked against your program. + * + * This value is the revision of the code you are linked with and may be + * different from the code you are compiling with, which is found in the + * constant SDL_REVISION. + * + * The revision is arbitrary string (a hash value) uniquely identifying the + * exact revision of the SDL library in use, and is only useful in comparing + * against other revisions. It is NOT an incrementing number. + * + * If SDL wasn't built from a git repository with the appropriate tools, this + * will return an empty string. + * + * Prior to SDL 2.0.16, before development moved to GitHub, this returned a + * hash for a Mercurial repository. + * + * You shouldn't use this function for anything but logging it for debugging + * purposes. The string is not intended to be reliable in any way. + * + * \returns an arbitrary string, uniquely identifying the exact revision of + * the SDL library in use. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetVersion + */ +extern DECLSPEC const char *SDLCALL SDL_GetRevision(void); + +/** + * Obsolete function, do not use. + * + * When SDL was hosted in a Mercurial repository, and was built carefully, + * this would return the revision number that the build was created from. This + * number was not reliable for several reasons, but more importantly, SDL is + * now hosted in a git repository, which does not offer numbers at all, only + * hashes. This function only ever returns zero now. Don't use it. + * + * Before SDL 2.0.16, this might have returned an unreliable, but non-zero + * number. + * + * \deprecated Use SDL_GetRevision() instead; if SDL was carefully built, it + * will return a git hash. + * + * \returns zero, always, in modern SDL releases. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetRevision + */ +extern SDL_DEPRECATED DECLSPEC int SDLCALL SDL_GetRevisionNumber(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_version_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_video.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_video.h new file mode 100644 index 00000000..2db5552f --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_video.h @@ -0,0 +1,2228 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryVideo + * + * Header file for SDL video functions. + */ + +#ifndef SDL_video_h_ +#define SDL_video_h_ + +#include "SDL_stdinc.h" +#include "SDL_pixels.h" +#include "SDL_rect.h" +#include "SDL_surface.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The structure that defines a display mode + * + * \sa SDL_GetNumDisplayModes + * \sa SDL_GetDisplayMode + * \sa SDL_GetDesktopDisplayMode + * \sa SDL_GetCurrentDisplayMode + * \sa SDL_GetClosestDisplayMode + * \sa SDL_SetWindowDisplayMode + * \sa SDL_GetWindowDisplayMode + */ +typedef struct SDL_DisplayMode +{ + Uint32 format; /**< pixel format */ + int w; /**< width, in screen coordinates */ + int h; /**< height, in screen coordinates */ + int refresh_rate; /**< refresh rate (or zero for unspecified) */ + void *driverdata; /**< driver-specific data, initialize to 0 */ +} SDL_DisplayMode; + +/** + * The opaque type used to identify a window. + * + * \sa SDL_CreateWindow + * \sa SDL_CreateWindowFrom + * \sa SDL_DestroyWindow + * \sa SDL_FlashWindow + * \sa SDL_GetWindowData + * \sa SDL_GetWindowFlags + * \sa SDL_GetWindowGrab + * \sa SDL_GetWindowKeyboardGrab + * \sa SDL_GetWindowMouseGrab + * \sa SDL_GetWindowPosition + * \sa SDL_GetWindowSize + * \sa SDL_GetWindowTitle + * \sa SDL_HideWindow + * \sa SDL_MaximizeWindow + * \sa SDL_MinimizeWindow + * \sa SDL_RaiseWindow + * \sa SDL_RestoreWindow + * \sa SDL_SetWindowData + * \sa SDL_SetWindowFullscreen + * \sa SDL_SetWindowGrab + * \sa SDL_SetWindowKeyboardGrab + * \sa SDL_SetWindowMouseGrab + * \sa SDL_SetWindowIcon + * \sa SDL_SetWindowPosition + * \sa SDL_SetWindowSize + * \sa SDL_SetWindowBordered + * \sa SDL_SetWindowResizable + * \sa SDL_SetWindowTitle + * \sa SDL_ShowWindow + */ +typedef struct SDL_Window SDL_Window; + +/** + * The flags on a window + * + * \sa SDL_GetWindowFlags + */ +typedef enum SDL_WindowFlags +{ + SDL_WINDOW_FULLSCREEN = 0x00000001, /**< fullscreen window */ + SDL_WINDOW_OPENGL = 0x00000002, /**< window usable with OpenGL context */ + SDL_WINDOW_SHOWN = 0x00000004, /**< window is visible */ + SDL_WINDOW_HIDDEN = 0x00000008, /**< window is not visible */ + SDL_WINDOW_BORDERLESS = 0x00000010, /**< no window decoration */ + SDL_WINDOW_RESIZABLE = 0x00000020, /**< window can be resized */ + SDL_WINDOW_MINIMIZED = 0x00000040, /**< window is minimized */ + SDL_WINDOW_MAXIMIZED = 0x00000080, /**< window is maximized */ + SDL_WINDOW_MOUSE_GRABBED = 0x00000100, /**< window has grabbed mouse input */ + SDL_WINDOW_INPUT_FOCUS = 0x00000200, /**< window has input focus */ + SDL_WINDOW_MOUSE_FOCUS = 0x00000400, /**< window has mouse focus */ + SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ), + SDL_WINDOW_FOREIGN = 0x00000800, /**< window not created by SDL */ + SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000, /**< window should be created in high-DPI mode if supported. + On macOS NSHighResolutionCapable must be set true in the + application's Info.plist for this to have any effect. */ + SDL_WINDOW_MOUSE_CAPTURE = 0x00004000, /**< window has mouse captured (unrelated to MOUSE_GRABBED) */ + SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000, /**< window should always be above others */ + SDL_WINDOW_SKIP_TASKBAR = 0x00010000, /**< window should not be added to the taskbar */ + SDL_WINDOW_UTILITY = 0x00020000, /**< window should be treated as a utility window */ + SDL_WINDOW_TOOLTIP = 0x00040000, /**< window should be treated as a tooltip */ + SDL_WINDOW_POPUP_MENU = 0x00080000, /**< window should be treated as a popup menu */ + SDL_WINDOW_KEYBOARD_GRABBED = 0x00100000, /**< window has grabbed keyboard input */ + SDL_WINDOW_VULKAN = 0x10000000, /**< window usable for Vulkan surface */ + SDL_WINDOW_METAL = 0x20000000, /**< window usable for Metal view */ + + SDL_WINDOW_INPUT_GRABBED = SDL_WINDOW_MOUSE_GRABBED /**< equivalent to SDL_WINDOW_MOUSE_GRABBED for compatibility */ +} SDL_WindowFlags; + +/** + * Used to indicate that you don't care what the window position is. + */ +#define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000u +#define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) +#define SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED_DISPLAY(0) +#define SDL_WINDOWPOS_ISUNDEFINED(X) \ + (((X)&0xFFFF0000) == SDL_WINDOWPOS_UNDEFINED_MASK) + +/** + * Used to indicate that the window position should be centered. + */ +#define SDL_WINDOWPOS_CENTERED_MASK 0x2FFF0000u +#define SDL_WINDOWPOS_CENTERED_DISPLAY(X) (SDL_WINDOWPOS_CENTERED_MASK|(X)) +#define SDL_WINDOWPOS_CENTERED SDL_WINDOWPOS_CENTERED_DISPLAY(0) +#define SDL_WINDOWPOS_ISCENTERED(X) \ + (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) + +/** + * Event subtype for window events + */ +typedef enum SDL_WindowEventID +{ + SDL_WINDOWEVENT_NONE, /**< Never used */ + SDL_WINDOWEVENT_SHOWN, /**< Window has been shown */ + SDL_WINDOWEVENT_HIDDEN, /**< Window has been hidden */ + SDL_WINDOWEVENT_EXPOSED, /**< Window has been exposed and should be + redrawn */ + SDL_WINDOWEVENT_MOVED, /**< Window has been moved to data1, data2 + */ + SDL_WINDOWEVENT_RESIZED, /**< Window has been resized to data1xdata2 */ + SDL_WINDOWEVENT_SIZE_CHANGED, /**< The window size has changed, either as + a result of an API call or through the + system or user changing the window size. */ + SDL_WINDOWEVENT_MINIMIZED, /**< Window has been minimized */ + SDL_WINDOWEVENT_MAXIMIZED, /**< Window has been maximized */ + SDL_WINDOWEVENT_RESTORED, /**< Window has been restored to normal size + and position */ + SDL_WINDOWEVENT_ENTER, /**< Window has gained mouse focus */ + SDL_WINDOWEVENT_LEAVE, /**< Window has lost mouse focus */ + SDL_WINDOWEVENT_FOCUS_GAINED, /**< Window has gained keyboard focus */ + SDL_WINDOWEVENT_FOCUS_LOST, /**< Window has lost keyboard focus */ + SDL_WINDOWEVENT_CLOSE, /**< The window manager requests that the window be closed */ + SDL_WINDOWEVENT_TAKE_FOCUS, /**< Window is being offered a focus (should SetWindowInputFocus() on itself or a subwindow, or ignore) */ + SDL_WINDOWEVENT_HIT_TEST, /**< Window had a hit test that wasn't SDL_HITTEST_NORMAL. */ + SDL_WINDOWEVENT_ICCPROF_CHANGED,/**< The ICC profile of the window's display has changed. */ + SDL_WINDOWEVENT_DISPLAY_CHANGED /**< Window has been moved to display data1. */ +} SDL_WindowEventID; + +/** + * Event subtype for display events + */ +typedef enum SDL_DisplayEventID +{ + SDL_DISPLAYEVENT_NONE, /**< Never used */ + SDL_DISPLAYEVENT_ORIENTATION, /**< Display orientation has changed to data1 */ + SDL_DISPLAYEVENT_CONNECTED, /**< Display has been added to the system */ + SDL_DISPLAYEVENT_DISCONNECTED, /**< Display has been removed from the system */ + SDL_DISPLAYEVENT_MOVED /**< Display has changed position */ +} SDL_DisplayEventID; + +/** + * Display orientation + */ +typedef enum SDL_DisplayOrientation +{ + SDL_ORIENTATION_UNKNOWN, /**< The display orientation can't be determined */ + SDL_ORIENTATION_LANDSCAPE, /**< The display is in landscape mode, with the right side up, relative to portrait mode */ + SDL_ORIENTATION_LANDSCAPE_FLIPPED, /**< The display is in landscape mode, with the left side up, relative to portrait mode */ + SDL_ORIENTATION_PORTRAIT, /**< The display is in portrait mode */ + SDL_ORIENTATION_PORTRAIT_FLIPPED /**< The display is in portrait mode, upside down */ +} SDL_DisplayOrientation; + +/** + * Window flash operation + */ +typedef enum SDL_FlashOperation +{ + SDL_FLASH_CANCEL, /**< Cancel any window flash state */ + SDL_FLASH_BRIEFLY, /**< Flash the window briefly to get attention */ + SDL_FLASH_UNTIL_FOCUSED /**< Flash the window until it gets focus */ +} SDL_FlashOperation; + +/** + * An opaque handle to an OpenGL context. + * + * \sa SDL_GL_CreateContext + */ +typedef void *SDL_GLContext; + +/** + * OpenGL configuration attributes. + * + * While you can set most OpenGL attributes normally, the attributes listed + * above must be known before SDL creates the window that will be used with + * the OpenGL context. These attributes are set and read with + * SDL_GL_SetAttribute and SDL_GL_GetAttribute. + * + * In some cases, these attributes are minimum requests; the GL does not + * promise to give you exactly what you asked for. It's possible to ask for a + * 16-bit depth buffer and get a 24-bit one instead, for example, or to ask + * for no stencil buffer and still have one available. Context creation should + * fail if the GL can't provide your requested attributes at a minimum, but + * you should check to see exactly what you got. + * + * + * [Multisample anti-aliasing](http://en.wikipedia.org/wiki/Multisample_anti-aliasing) + * is a type of full screen anti-aliasing. Multipsampling defaults to off but + * can be turned on by setting SDL_GL_MULTISAMPLEBUFFERS to 1 and + * SDL_GL_MULTISAMPLESAMPLES to a value greater than 0. Typical values are 2 + * and 4. + * + * SDL_GL_CONTEXT_PROFILE_MASK determines the type of context created, while + * both SDL_GL_CONTEXT_MAJOR_VERSION and SDL_GL_CONTEXT_MINOR_VERSION + * determine which version. All three attributes must be set prior to creating + * the first window, and in general you can't change the value of + * SDL_GL_CONTEXT_PROFILE_MASK without first destroying all windows created + * with the previous setting. + * + * SDL_GL_CONTEXT_RELEASE_BEHAVIOR can be set to + * SDL_GL_CONTEXT_RELEASE_BEHAVIOR_NONE or + * SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH. + */ +typedef enum SDL_GLattr +{ + SDL_GL_RED_SIZE, /**< the minimum number of bits for the red channel of the color buffer; defaults to 3. */ + SDL_GL_GREEN_SIZE, /**< the minimum number of bits for the green channel of the color buffer; defaults to 3. */ + SDL_GL_BLUE_SIZE, /**< the minimum number of bits for the blue channel of the color buffer; defaults to 2. */ + SDL_GL_ALPHA_SIZE, /**< the minimum number of bits for the alpha channel of the color buffer; defaults to 0. */ + SDL_GL_BUFFER_SIZE, /**< the minimum number of bits for frame buffer size; defaults to 0. */ + SDL_GL_DOUBLEBUFFER, /**< whether the output is single or double buffered; defaults to double buffering on. */ + SDL_GL_DEPTH_SIZE, /**< the minimum number of bits in the depth buffer; defaults to 16. */ + SDL_GL_STENCIL_SIZE, /**< the minimum number of bits in the stencil buffer; defaults to 0. */ + SDL_GL_ACCUM_RED_SIZE, /**< the minimum number of bits for the red channel of the accumulation buffer; defaults to 0. */ + SDL_GL_ACCUM_GREEN_SIZE, /**< the minimum number of bits for the green channel of the accumulation buffer; defaults to 0. */ + SDL_GL_ACCUM_BLUE_SIZE, /**< the minimum number of bits for the blue channel of the accumulation buffer; defaults to 0. */ + SDL_GL_ACCUM_ALPHA_SIZE, /**< the minimum number of bits for the alpha channel of the accumulation buffer; defaults to 0. */ + SDL_GL_STEREO, /**< whether the output is stereo 3D; defaults to off. */ + SDL_GL_MULTISAMPLEBUFFERS, /**< the number of buffers used for multisample anti-aliasing; defaults to 0. */ + SDL_GL_MULTISAMPLESAMPLES, /**< the number of samples used around the current pixel used for multisample anti-aliasing. */ + SDL_GL_ACCELERATED_VISUAL, /**< set to 1 to require hardware acceleration, set to 0 to force software rendering; defaults to allow either. */ + SDL_GL_RETAINED_BACKING, /**< not used (deprecated). */ + SDL_GL_CONTEXT_MAJOR_VERSION, /**< OpenGL context major version. */ + SDL_GL_CONTEXT_MINOR_VERSION, /**< OpenGL context minor version. */ + SDL_GL_CONTEXT_EGL, /**< deprecated: set SDL_GL_CONTEXT_PROFILE_MASK to SDL_GL_CONTEXT_PROFILE_ES to enable instead. */ + SDL_GL_CONTEXT_FLAGS, /**< some combination of 0 or more of elements of the SDL_GLcontextFlag enumeration; defaults to 0. */ + SDL_GL_CONTEXT_PROFILE_MASK, /**< type of GL context (Core, Compatibility, ES). See SDL_GLprofile; default value depends on platform. */ + SDL_GL_SHARE_WITH_CURRENT_CONTEXT, /**< OpenGL context sharing; defaults to 0. */ + SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, /**< requests sRGB capable visual; defaults to 0. (>= SDL 2.0.1) */ + SDL_GL_CONTEXT_RELEASE_BEHAVIOR, /**< sets context the release behavior; defaults to 1. (>= SDL 2.0.4) */ + SDL_GL_CONTEXT_RESET_NOTIFICATION, + SDL_GL_CONTEXT_NO_ERROR, + SDL_GL_FLOATBUFFERS +} SDL_GLattr; + +typedef enum SDL_GLprofile +{ + SDL_GL_CONTEXT_PROFILE_CORE = 0x0001, + SDL_GL_CONTEXT_PROFILE_COMPATIBILITY = 0x0002, + SDL_GL_CONTEXT_PROFILE_ES = 0x0004 /**< GLX_CONTEXT_ES2_PROFILE_BIT_EXT */ +} SDL_GLprofile; + +typedef enum SDL_GLcontextFlag +{ + SDL_GL_CONTEXT_DEBUG_FLAG = 0x0001, + SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002, + SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG = 0x0004, + SDL_GL_CONTEXT_RESET_ISOLATION_FLAG = 0x0008 +} SDL_GLcontextFlag; + +typedef enum SDL_GLcontextReleaseFlag +{ + SDL_GL_CONTEXT_RELEASE_BEHAVIOR_NONE = 0x0000, + SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH = 0x0001 +} SDL_GLcontextReleaseFlag; + +typedef enum SDL_GLContextResetNotification +{ + SDL_GL_CONTEXT_RESET_NO_NOTIFICATION = 0x0000, + SDL_GL_CONTEXT_RESET_LOSE_CONTEXT = 0x0001 +} SDL_GLContextResetNotification; + +/* Function prototypes */ + +/** + * Get the number of video drivers compiled into SDL. + * + * \returns a number >= 1 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetVideoDriver + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoDrivers(void); + +/** + * Get the name of a built in video driver. + * + * The video drivers are presented in the order in which they are normally + * checked during initialization. + * + * \param index the index of a video driver. + * \returns the name of the video driver with the given **index**. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumVideoDrivers + */ +extern DECLSPEC const char *SDLCALL SDL_GetVideoDriver(int index); + +/** + * Initialize the video subsystem, optionally specifying a video driver. + * + * This function initializes the video subsystem, setting up a connection to + * the window manager, etc, and determines the available display modes and + * pixel formats, but does not initialize a window or graphics mode. + * + * If you use this function and you haven't used the SDL_INIT_VIDEO flag with + * either SDL_Init() or SDL_InitSubSystem(), you should call SDL_VideoQuit() + * before calling SDL_Quit(). + * + * It is safe to call this function multiple times. SDL_VideoInit() will call + * SDL_VideoQuit() itself if the video subsystem has already been initialized. + * + * You can use SDL_GetNumVideoDrivers() and SDL_GetVideoDriver() to find a + * specific `driver_name`. + * + * \param driver_name the name of a video driver to initialize, or NULL for + * the default driver. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumVideoDrivers + * \sa SDL_GetVideoDriver + * \sa SDL_InitSubSystem + * \sa SDL_VideoQuit + */ +extern DECLSPEC int SDLCALL SDL_VideoInit(const char *driver_name); + +/** + * Shut down the video subsystem, if initialized with SDL_VideoInit(). + * + * This function closes all windows, and restores the original video mode. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_VideoInit + */ +extern DECLSPEC void SDLCALL SDL_VideoQuit(void); + +/** + * Get the name of the currently initialized video driver. + * + * \returns the name of the current video driver or NULL if no driver has been + * initialized. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumVideoDrivers + * \sa SDL_GetVideoDriver + */ +extern DECLSPEC const char *SDLCALL SDL_GetCurrentVideoDriver(void); + +/** + * Get the number of available video displays. + * + * \returns a number >= 1 or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetDisplayBounds + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoDisplays(void); + +/** + * Get the name of a display in UTF-8 encoding. + * + * \param displayIndex the index of display from which the name should be + * queried. + * \returns the name of a display or NULL for an invalid display index or + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC const char * SDLCALL SDL_GetDisplayName(int displayIndex); + +/** + * Get the desktop area represented by a display. + * + * The primary display (`displayIndex` zero) is always located at 0,0. + * + * \param displayIndex the index of the display to query. + * \param rect the SDL_Rect structure filled in with the display bounds. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetDisplayBounds(int displayIndex, SDL_Rect * rect); + +/** + * Get the usable desktop area represented by a display. + * + * The primary display (`displayIndex` zero) is always located at 0,0. + * + * This is the same area as SDL_GetDisplayBounds() reports, but with portions + * reserved by the system removed. For example, on Apple's macOS, this + * subtracts the area occupied by the menu bar and dock. + * + * Setting a window to be fullscreen generally bypasses these unusable areas, + * so these are good guidelines for the maximum space available to a + * non-fullscreen window. + * + * The parameter `rect` is ignored if it is NULL. + * + * This function also returns -1 if the parameter `displayIndex` is out of + * range. + * + * \param displayIndex the index of the display to query the usable bounds + * from. + * \param rect the SDL_Rect structure filled in with the display bounds. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_GetDisplayBounds + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetDisplayUsableBounds(int displayIndex, SDL_Rect * rect); + +/** + * Get the dots/pixels-per-inch for a display. + * + * Diagonal, horizontal and vertical DPI can all be optionally returned if the + * appropriate parameter is non-NULL. + * + * A failure of this function usually means that either no DPI information is + * available or the `displayIndex` is out of range. + * + * **WARNING**: This reports the DPI that the hardware reports, and it is not + * always reliable! It is almost always better to use SDL_GetWindowSize() to + * find the window size, which might be in logical points instead of pixels, + * and then SDL_GL_GetDrawableSize(), SDL_Vulkan_GetDrawableSize(), + * SDL_Metal_GetDrawableSize(), or SDL_GetRendererOutputSize(), and compare + * the two values to get an actual scaling value between the two. We will be + * rethinking how high-dpi details should be managed in SDL3 to make things + * more consistent, reliable, and clear. + * + * \param displayIndex the index of the display from which DPI information + * should be queried. + * \param ddpi a pointer filled in with the diagonal DPI of the display; may + * be NULL. + * \param hdpi a pointer filled in with the horizontal DPI of the display; may + * be NULL. + * \param vdpi a pointer filled in with the vertical DPI of the display; may + * be NULL. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi); + +/** + * Get the orientation of a display. + * + * \param displayIndex the index of the display to query. + * \returns The SDL_DisplayOrientation enum value of the display, or + * `SDL_ORIENTATION_UNKNOWN` if it isn't available. + * + * \since This function is available since SDL 2.0.9. + * + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC SDL_DisplayOrientation SDLCALL SDL_GetDisplayOrientation(int displayIndex); + +/** + * Get the number of available display modes. + * + * The `displayIndex` needs to be in the range from 0 to + * SDL_GetNumVideoDisplays() - 1. + * + * \param displayIndex the index of the display to query. + * \returns a number >= 1 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetDisplayMode + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetNumDisplayModes(int displayIndex); + +/** + * Get information about a specific display mode. + * + * The display modes are sorted in this priority: + * + * - width -> largest to smallest + * - height -> largest to smallest + * - bits per pixel -> more colors to fewer colors + * - packed pixel layout -> largest to smallest + * - refresh rate -> highest to lowest + * + * \param displayIndex the index of the display to query. + * \param modeIndex the index of the display mode to query. + * \param mode an SDL_DisplayMode structure filled in with the mode at + * `modeIndex`. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetNumDisplayModes + */ +extern DECLSPEC int SDLCALL SDL_GetDisplayMode(int displayIndex, int modeIndex, + SDL_DisplayMode * mode); + +/** + * Get information about the desktop's display mode. + * + * There's a difference between this function and SDL_GetCurrentDisplayMode() + * when SDL runs fullscreen and has changed the resolution. In that case this + * function will return the previous native display mode, and not the current + * display mode. + * + * \param displayIndex the index of the display to query. + * \param mode an SDL_DisplayMode structure filled in with the current display + * mode. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetCurrentDisplayMode + * \sa SDL_GetDisplayMode + * \sa SDL_SetWindowDisplayMode + */ +extern DECLSPEC int SDLCALL SDL_GetDesktopDisplayMode(int displayIndex, SDL_DisplayMode * mode); + +/** + * Get information about the current display mode. + * + * There's a difference between this function and SDL_GetDesktopDisplayMode() + * when SDL runs fullscreen and has changed the resolution. In that case this + * function will return the current display mode, and not the previous native + * display mode. + * + * \param displayIndex the index of the display to query. + * \param mode an SDL_DisplayMode structure filled in with the current display + * mode. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetDesktopDisplayMode + * \sa SDL_GetDisplayMode + * \sa SDL_GetNumVideoDisplays + * \sa SDL_SetWindowDisplayMode + */ +extern DECLSPEC int SDLCALL SDL_GetCurrentDisplayMode(int displayIndex, SDL_DisplayMode * mode); + + +/** + * Get the closest match to the requested display mode. + * + * The available display modes are scanned and `closest` is filled in with the + * closest mode matching the requested mode and returned. The mode format and + * refresh rate default to the desktop mode if they are set to 0. The modes + * are scanned with size being first priority, format being second priority, + * and finally checking the refresh rate. If all the available modes are too + * small, then NULL is returned. + * + * \param displayIndex the index of the display to query. + * \param mode an SDL_DisplayMode structure containing the desired display + * mode. + * \param closest an SDL_DisplayMode structure filled in with the closest + * match of the available display modes. + * \returns the passed in value `closest` or NULL if no matching video mode + * was available; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetDisplayMode + * \sa SDL_GetNumDisplayModes + */ +extern DECLSPEC SDL_DisplayMode * SDLCALL SDL_GetClosestDisplayMode(int displayIndex, const SDL_DisplayMode * mode, SDL_DisplayMode * closest); + +/** + * Get the index of the display containing a point + * + * \param point the point to query. + * \returns the index of the display containing the point or a negative error + * code on failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GetDisplayBounds + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetPointDisplayIndex(const SDL_Point * point); + +/** + * Get the index of the display primarily containing a rect + * + * \param rect the rect to query. + * \returns the index of the display entirely containing the rect or closest + * to the center of the rect on success or a negative error code on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GetDisplayBounds + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetRectDisplayIndex(const SDL_Rect * rect); + +/** + * Get the index of the display associated with a window. + * + * \param window the window to query. + * \returns the index of the display containing the center of the window on + * success or a negative error code on failure; call SDL_GetError() + * for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetDisplayBounds + * \sa SDL_GetNumVideoDisplays + */ +extern DECLSPEC int SDLCALL SDL_GetWindowDisplayIndex(SDL_Window * window); + +/** + * Set the display mode to use when a window is visible at fullscreen. + * + * This only affects the display mode used when the window is fullscreen. To + * change the window size when the window is not fullscreen, use + * SDL_SetWindowSize(). + * + * \param window the window to affect. + * \param mode the SDL_DisplayMode structure representing the mode to use, or + * NULL to use the window's dimensions and the desktop's format + * and refresh rate. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowDisplayMode + * \sa SDL_SetWindowFullscreen + */ +extern DECLSPEC int SDLCALL SDL_SetWindowDisplayMode(SDL_Window * window, + const SDL_DisplayMode * mode); + +/** + * Query the display mode to use when a window is visible at fullscreen. + * + * \param window the window to query. + * \param mode an SDL_DisplayMode structure filled in with the fullscreen + * display mode. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowDisplayMode + * \sa SDL_SetWindowFullscreen + */ +extern DECLSPEC int SDLCALL SDL_GetWindowDisplayMode(SDL_Window * window, + SDL_DisplayMode * mode); + +/** + * Get the raw ICC profile data for the screen the window is currently on. + * + * Data returned should be freed with SDL_free. + * + * \param window the window to query. + * \param size the size of the ICC profile. + * \returns the raw ICC profile data on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.18. + */ +extern DECLSPEC void* SDLCALL SDL_GetWindowICCProfile(SDL_Window * window, size_t* size); + +/** + * Get the pixel format associated with the window. + * + * \param window the window to query. + * \returns the pixel format of the window on success or + * SDL_PIXELFORMAT_UNKNOWN on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window * window); + +/** + * Create a window with the specified position, dimensions, and flags. + * + * `flags` may be any of the following OR'd together: + * + * - `SDL_WINDOW_FULLSCREEN`: fullscreen window + * - `SDL_WINDOW_FULLSCREEN_DESKTOP`: fullscreen window at desktop resolution + * - `SDL_WINDOW_OPENGL`: window usable with an OpenGL context + * - `SDL_WINDOW_VULKAN`: window usable with a Vulkan instance + * - `SDL_WINDOW_METAL`: window usable with a Metal instance + * - `SDL_WINDOW_HIDDEN`: window is not visible + * - `SDL_WINDOW_BORDERLESS`: no window decoration + * - `SDL_WINDOW_RESIZABLE`: window can be resized + * - `SDL_WINDOW_MINIMIZED`: window is minimized + * - `SDL_WINDOW_MAXIMIZED`: window is maximized + * - `SDL_WINDOW_INPUT_GRABBED`: window has grabbed input focus + * - `SDL_WINDOW_ALLOW_HIGHDPI`: window should be created in high-DPI mode if + * supported (>= SDL 2.0.1) + * + * `SDL_WINDOW_SHOWN` is ignored by SDL_CreateWindow(). The SDL_Window is + * implicitly shown if SDL_WINDOW_HIDDEN is not set. `SDL_WINDOW_SHOWN` may be + * queried later using SDL_GetWindowFlags(). + * + * On Apple's macOS, you **must** set the NSHighResolutionCapable Info.plist + * property to YES, otherwise you will not receive a High-DPI OpenGL canvas. + * + * If the window is created with the `SDL_WINDOW_ALLOW_HIGHDPI` flag, its size + * in pixels may differ from its size in screen coordinates on platforms with + * high-DPI support (e.g. iOS and macOS). Use SDL_GetWindowSize() to query the + * client area's size in screen coordinates, and SDL_GL_GetDrawableSize() or + * SDL_GetRendererOutputSize() to query the drawable size in pixels. Note that + * when this flag is set, the drawable size can vary after the window is + * created and should be queried after major window events such as when the + * window is resized or moved between displays. + * + * If the window is set fullscreen, the width and height parameters `w` and + * `h` will not be used. However, invalid size parameters (e.g. too large) may + * still fail. Window size is actually limited to 16384 x 16384 for all + * platforms at window creation. + * + * If the window is created with any of the SDL_WINDOW_OPENGL or + * SDL_WINDOW_VULKAN flags, then the corresponding LoadLibrary function + * (SDL_GL_LoadLibrary or SDL_Vulkan_LoadLibrary) is called and the + * corresponding UnloadLibrary function is called by SDL_DestroyWindow(). + * + * If SDL_WINDOW_VULKAN is specified and there isn't a working Vulkan driver, + * SDL_CreateWindow() will fail because SDL_Vulkan_LoadLibrary() will fail. + * + * If SDL_WINDOW_METAL is specified on an OS that does not support Metal, + * SDL_CreateWindow() will fail. + * + * On non-Apple devices, SDL requires you to either not link to the Vulkan + * loader or link to a dynamic library version. This limitation may be removed + * in a future version of SDL. + * + * \param title the title of the window, in UTF-8 encoding. + * \param x the x position of the window, `SDL_WINDOWPOS_CENTERED`, or + * `SDL_WINDOWPOS_UNDEFINED`. + * \param y the y position of the window, `SDL_WINDOWPOS_CENTERED`, or + * `SDL_WINDOWPOS_UNDEFINED`. + * \param w the width of the window, in screen coordinates. + * \param h the height of the window, in screen coordinates. + * \param flags 0, or one or more SDL_WindowFlags OR'd together. + * \returns the `SDL_Window` that was created or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateWindowFrom + * \sa SDL_DestroyWindow + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, + int x, int y, int w, + int h, Uint32 flags); + +/** + * Create an SDL window from an existing native window. + * + * In some cases (e.g. OpenGL) and on some platforms (e.g. Microsoft Windows) + * the hint `SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT` needs to be configured + * before using SDL_CreateWindowFrom(). + * + * \param data a pointer to driver-dependent window creation data, typically + * your native window cast to a void*. + * \returns the window that was created or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateWindow + * \sa SDL_DestroyWindow + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowFrom(const void *data); + +/** + * Get the numeric ID of a window. + * + * The numeric ID is what SDL_WindowEvent references, and is necessary to map + * these events to specific SDL_Window objects. + * + * \param window the window to query. + * \returns the ID of the window on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowFromID + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowID(SDL_Window * window); + +/** + * Get a window from a stored ID. + * + * The numeric ID is what SDL_WindowEvent references, and is necessary to map + * these events to specific SDL_Window objects. + * + * \param id the ID of the window. + * \returns the window associated with `id` or NULL if it doesn't exist; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowID + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetWindowFromID(Uint32 id); + +/** + * Get the window flags. + * + * \param window the window to query. + * \returns a mask of the SDL_WindowFlags associated with `window`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateWindow + * \sa SDL_HideWindow + * \sa SDL_MaximizeWindow + * \sa SDL_MinimizeWindow + * \sa SDL_SetWindowFullscreen + * \sa SDL_SetWindowGrab + * \sa SDL_ShowWindow + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowFlags(SDL_Window * window); + +/** + * Set the title of a window. + * + * This string is expected to be in UTF-8 encoding. + * + * \param window the window to change. + * \param title the desired window title in UTF-8 format. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowTitle + */ +extern DECLSPEC void SDLCALL SDL_SetWindowTitle(SDL_Window * window, + const char *title); + +/** + * Get the title of a window. + * + * \param window the window to query. + * \returns the title of the window in UTF-8 format or "" if there is no + * title. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowTitle + */ +extern DECLSPEC const char *SDLCALL SDL_GetWindowTitle(SDL_Window * window); + +/** + * Set the icon for a window. + * + * \param window the window to change. + * \param icon an SDL_Surface structure containing the icon for the window. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_SetWindowIcon(SDL_Window * window, + SDL_Surface * icon); + +/** + * Associate an arbitrary named pointer with a window. + * + * `name` is case-sensitive. + * + * \param window the window to associate with the pointer. + * \param name the name of the pointer. + * \param userdata the associated pointer. + * \returns the previous value associated with `name`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowData + */ +extern DECLSPEC void* SDLCALL SDL_SetWindowData(SDL_Window * window, + const char *name, + void *userdata); + +/** + * Retrieve the data pointer associated with a window. + * + * \param window the window to query. + * \param name the name of the pointer. + * \returns the value associated with `name`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowData + */ +extern DECLSPEC void *SDLCALL SDL_GetWindowData(SDL_Window * window, + const char *name); + +/** + * Set the position of a window. + * + * The window coordinate origin is the upper left of the display. + * + * \param window the window to reposition. + * \param x the x coordinate of the window in screen coordinates, or + * `SDL_WINDOWPOS_CENTERED` or `SDL_WINDOWPOS_UNDEFINED`. + * \param y the y coordinate of the window in screen coordinates, or + * `SDL_WINDOWPOS_CENTERED` or `SDL_WINDOWPOS_UNDEFINED`. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowPosition + */ +extern DECLSPEC void SDLCALL SDL_SetWindowPosition(SDL_Window * window, + int x, int y); + +/** + * Get the position of a window. + * + * If you do not need the value for one of the positions a NULL may be passed + * in the `x` or `y` parameter. + * + * \param window the window to query. + * \param x a pointer filled in with the x position of the window, in screen + * coordinates, may be NULL. + * \param y a pointer filled in with the y position of the window, in screen + * coordinates, may be NULL. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowPosition + */ +extern DECLSPEC void SDLCALL SDL_GetWindowPosition(SDL_Window * window, + int *x, int *y); + +/** + * Set the size of a window's client area. + * + * The window size in screen coordinates may differ from the size in pixels, + * if the window was created with `SDL_WINDOW_ALLOW_HIGHDPI` on a platform + * with high-dpi support (e.g. iOS or macOS). Use SDL_GL_GetDrawableSize() or + * SDL_GetRendererOutputSize() to get the real client area size in pixels. + * + * Fullscreen windows automatically match the size of the display mode, and + * you should use SDL_SetWindowDisplayMode() to change their size. + * + * \param window the window to change. + * \param w the width of the window in pixels, in screen coordinates, must be + * > 0. + * \param h the height of the window in pixels, in screen coordinates, must be + * > 0. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowSize + * \sa SDL_SetWindowDisplayMode + */ +extern DECLSPEC void SDLCALL SDL_SetWindowSize(SDL_Window * window, int w, + int h); + +/** + * Get the size of a window's client area. + * + * NULL can safely be passed as the `w` or `h` parameter if the width or + * height value is not desired. + * + * The window size in screen coordinates may differ from the size in pixels, + * if the window was created with `SDL_WINDOW_ALLOW_HIGHDPI` on a platform + * with high-dpi support (e.g. iOS or macOS). Use SDL_GL_GetDrawableSize(), + * SDL_Vulkan_GetDrawableSize(), or SDL_GetRendererOutputSize() to get the + * real client area size in pixels. + * + * \param window the window to query the width and height from. + * \param w a pointer filled in with the width of the window, in screen + * coordinates, may be NULL. + * \param h a pointer filled in with the height of the window, in screen + * coordinates, may be NULL. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_GetDrawableSize + * \sa SDL_Vulkan_GetDrawableSize + * \sa SDL_SetWindowSize + */ +extern DECLSPEC void SDLCALL SDL_GetWindowSize(SDL_Window * window, int *w, + int *h); + +/** + * Get the size of a window's borders (decorations) around the client area. + * + * Note: If this function fails (returns -1), the size values will be + * initialized to 0, 0, 0, 0 (if a non-NULL pointer is provided), as if the + * window in question was borderless. + * + * Note: This function may fail on systems where the window has not yet been + * decorated by the display server (for example, immediately after calling + * SDL_CreateWindow). It is recommended that you wait at least until the + * window has been presented and composited, so that the window system has a + * chance to decorate the window and provide the border dimensions to SDL. + * + * This function also returns -1 if getting the information is not supported. + * + * \param window the window to query the size values of the border + * (decorations) from. + * \param top pointer to variable for storing the size of the top border; NULL + * is permitted. + * \param left pointer to variable for storing the size of the left border; + * NULL is permitted. + * \param bottom pointer to variable for storing the size of the bottom + * border; NULL is permitted. + * \param right pointer to variable for storing the size of the right border; + * NULL is permitted. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_GetWindowSize + */ +extern DECLSPEC int SDLCALL SDL_GetWindowBordersSize(SDL_Window * window, + int *top, int *left, + int *bottom, int *right); + +/** + * Get the size of a window in pixels. + * + * This may differ from SDL_GetWindowSize() if we're rendering to a high-DPI + * drawable, i.e. the window was created with `SDL_WINDOW_ALLOW_HIGHDPI` on a + * platform with high-DPI support (Apple calls this "Retina"), and not + * disabled by the `SDL_HINT_VIDEO_HIGHDPI_DISABLED` hint. + * + * \param window the window from which the drawable size should be queried. + * \param w a pointer to variable for storing the width in pixels, may be + * NULL. + * \param h a pointer to variable for storing the height in pixels, may be + * NULL. + * + * \since This function is available since SDL 2.26.0. + * + * \sa SDL_CreateWindow + * \sa SDL_GetWindowSize + */ +extern DECLSPEC void SDLCALL SDL_GetWindowSizeInPixels(SDL_Window * window, + int *w, int *h); + +/** + * Set the minimum size of a window's client area. + * + * \param window the window to change. + * \param min_w the minimum width of the window in pixels. + * \param min_h the minimum height of the window in pixels. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowMinimumSize + * \sa SDL_SetWindowMaximumSize + */ +extern DECLSPEC void SDLCALL SDL_SetWindowMinimumSize(SDL_Window * window, + int min_w, int min_h); + +/** + * Get the minimum size of a window's client area. + * + * \param window the window to query. + * \param w a pointer filled in with the minimum width of the window, may be + * NULL. + * \param h a pointer filled in with the minimum height of the window, may be + * NULL. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowMaximumSize + * \sa SDL_SetWindowMinimumSize + */ +extern DECLSPEC void SDLCALL SDL_GetWindowMinimumSize(SDL_Window * window, + int *w, int *h); + +/** + * Set the maximum size of a window's client area. + * + * \param window the window to change. + * \param max_w the maximum width of the window in pixels. + * \param max_h the maximum height of the window in pixels. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowMaximumSize + * \sa SDL_SetWindowMinimumSize + */ +extern DECLSPEC void SDLCALL SDL_SetWindowMaximumSize(SDL_Window * window, + int max_w, int max_h); + +/** + * Get the maximum size of a window's client area. + * + * \param window the window to query. + * \param w a pointer filled in with the maximum width of the window, may be + * NULL. + * \param h a pointer filled in with the maximum height of the window, may be + * NULL. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowMinimumSize + * \sa SDL_SetWindowMaximumSize + */ +extern DECLSPEC void SDLCALL SDL_GetWindowMaximumSize(SDL_Window * window, + int *w, int *h); + +/** + * Set the border state of a window. + * + * This will add or remove the window's `SDL_WINDOW_BORDERLESS` flag and add + * or remove the border from the actual window. This is a no-op if the + * window's border already matches the requested state. + * + * You can't change the border state of a fullscreen window. + * + * \param window the window of which to change the border state. + * \param bordered SDL_FALSE to remove border, SDL_TRUE to add border. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowFlags + */ +extern DECLSPEC void SDLCALL SDL_SetWindowBordered(SDL_Window * window, + SDL_bool bordered); + +/** + * Set the user-resizable state of a window. + * + * This will add or remove the window's `SDL_WINDOW_RESIZABLE` flag and + * allow/disallow user resizing of the window. This is a no-op if the window's + * resizable state already matches the requested state. + * + * You can't change the resizable state of a fullscreen window. + * + * \param window the window of which to change the resizable state. + * \param resizable SDL_TRUE to allow resizing, SDL_FALSE to disallow. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_GetWindowFlags + */ +extern DECLSPEC void SDLCALL SDL_SetWindowResizable(SDL_Window * window, + SDL_bool resizable); + +/** + * Set the window to always be above the others. + * + * This will add or remove the window's `SDL_WINDOW_ALWAYS_ON_TOP` flag. This + * will bring the window to the front and keep the window above the rest. + * + * \param window The window of which to change the always on top state. + * \param on_top SDL_TRUE to set the window always on top, SDL_FALSE to + * disable. + * + * \since This function is available since SDL 2.0.16. + * + * \sa SDL_GetWindowFlags + */ +extern DECLSPEC void SDLCALL SDL_SetWindowAlwaysOnTop(SDL_Window * window, + SDL_bool on_top); + +/** + * Show a window. + * + * \param window the window to show. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_HideWindow + * \sa SDL_RaiseWindow + */ +extern DECLSPEC void SDLCALL SDL_ShowWindow(SDL_Window * window); + +/** + * Hide a window. + * + * \param window the window to hide. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_ShowWindow + */ +extern DECLSPEC void SDLCALL SDL_HideWindow(SDL_Window * window); + +/** + * Raise a window above other windows and set the input focus. + * + * \param window the window to raise. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_RaiseWindow(SDL_Window * window); + +/** + * Make a window as large as possible. + * + * \param window the window to maximize. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_MinimizeWindow + * \sa SDL_RestoreWindow + */ +extern DECLSPEC void SDLCALL SDL_MaximizeWindow(SDL_Window * window); + +/** + * Minimize a window to an iconic representation. + * + * \param window the window to minimize. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_MaximizeWindow + * \sa SDL_RestoreWindow + */ +extern DECLSPEC void SDLCALL SDL_MinimizeWindow(SDL_Window * window); + +/** + * Restore the size and position of a minimized or maximized window. + * + * \param window the window to restore. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_MaximizeWindow + * \sa SDL_MinimizeWindow + */ +extern DECLSPEC void SDLCALL SDL_RestoreWindow(SDL_Window * window); + +/** + * Set a window's fullscreen state. + * + * `flags` may be `SDL_WINDOW_FULLSCREEN`, for "real" fullscreen with a + * videomode change; `SDL_WINDOW_FULLSCREEN_DESKTOP` for "fake" fullscreen + * that takes the size of the desktop; and 0 for windowed mode. + * + * Note that for some renderers, this function may trigger an + * SDL_RENDER_TARGETS_RESET event. Your application should be prepared to + * handle this event by reuploading textures! + * + * \param window the window to change. + * \param flags `SDL_WINDOW_FULLSCREEN`, `SDL_WINDOW_FULLSCREEN_DESKTOP` or 0. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowDisplayMode + * \sa SDL_SetWindowDisplayMode + */ +extern DECLSPEC int SDLCALL SDL_SetWindowFullscreen(SDL_Window * window, + Uint32 flags); + +/** + * Return whether the window has a surface associated with it. + * + * \returns SDL_TRUE if there is a surface associated with the window, or + * SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.28.0. + * + * \sa SDL_GetWindowSurface + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasWindowSurface(SDL_Window *window); + +/** + * Get the SDL surface associated with the window. + * + * A new surface will be created with the optimal format for the window, if + * necessary. This surface will be freed when the window is destroyed. Do not + * free this surface. + * + * This surface will be invalidated if the window is resized. After resizing a + * window this function must be called again to return a valid surface. + * + * Note that on some platforms the pixels pointer of the surface may be + * modified after each call to SDL_UpdateWindowSurface(), so that the platform + * code can implement efficient double or triple buffering. + * + * You may not combine this with 3D or the rendering API on this window. + * + * This function is affected by `SDL_HINT_FRAMEBUFFER_ACCELERATION`. + * + * \param window the window to query. + * \returns the surface associated with the window, or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DestroyWindowSurface + * \sa SDL_HasWindowSurface + * \sa SDL_UpdateWindowSurface + * \sa SDL_UpdateWindowSurfaceRects + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_GetWindowSurface(SDL_Window * window); + +/** + * Copy the window surface to the screen. + * + * This is the function you use to reflect any changes to the surface on the + * screen. + * + * This function is equivalent to the SDL 1.2 API SDL_Flip(). + * + * \param window the window to update. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowSurface + * \sa SDL_UpdateWindowSurfaceRects + */ +extern DECLSPEC int SDLCALL SDL_UpdateWindowSurface(SDL_Window * window); + +/** + * Copy areas of the window surface to the screen. + * + * This is the function you use to reflect changes to portions of the surface + * on the screen. + * + * This function is equivalent to the SDL 1.2 API SDL_UpdateRects(). + * + * Note that this function will update _at least_ the rectangles specified, + * but this is only intended as an optimization; in practice, this might + * update more of the screen (or all of the screen!), depending on what method + * SDL uses to send pixels to the system. + * + * \param window the window to update. + * \param rects an array of SDL_Rect structures representing areas of the + * surface to copy, in pixels. + * \param numrects the number of rectangles. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowSurface + * \sa SDL_UpdateWindowSurface + */ +extern DECLSPEC int SDLCALL SDL_UpdateWindowSurfaceRects(SDL_Window * window, + const SDL_Rect * rects, + int numrects); + +/** + * Destroy the surface associated with the window. + * + * \param window the window to update. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.28.0. + * + * \sa SDL_GetWindowSurface + * \sa SDL_HasWindowSurface + */ +extern DECLSPEC int SDLCALL SDL_DestroyWindowSurface(SDL_Window *window); + +/** + * Set a window's input grab mode. + * + * When input is grabbed, the mouse is confined to the window. This function + * will also grab the keyboard if `SDL_HINT_GRAB_KEYBOARD` is set. To grab the + * keyboard without also grabbing the mouse, use SDL_SetWindowKeyboardGrab(). + * + * If the caller enables a grab while another window is currently grabbed, the + * other window loses its grab in favor of the caller's window. + * + * \param window the window for which the input grab mode should be set. + * \param grabbed SDL_TRUE to grab input or SDL_FALSE to release input. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetGrabbedWindow + * \sa SDL_GetWindowGrab + */ +extern DECLSPEC void SDLCALL SDL_SetWindowGrab(SDL_Window * window, + SDL_bool grabbed); + +/** + * Set a window's keyboard grab mode. + * + * Keyboard grab enables capture of system keyboard shortcuts like Alt+Tab or + * the Meta/Super key. Note that not all system keyboard shortcuts can be + * captured by applications (one example is Ctrl+Alt+Del on Windows). + * + * This is primarily intended for specialized applications such as VNC clients + * or VM frontends. Normal games should not use keyboard grab. + * + * When keyboard grab is enabled, SDL will continue to handle Alt+Tab when the + * window is full-screen to ensure the user is not trapped in your + * application. If you have a custom keyboard shortcut to exit fullscreen + * mode, you may suppress this behavior with + * `SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED`. + * + * If the caller enables a grab while another window is currently grabbed, the + * other window loses its grab in favor of the caller's window. + * + * \param window The window for which the keyboard grab mode should be set. + * \param grabbed This is SDL_TRUE to grab keyboard, and SDL_FALSE to release. + * + * \since This function is available since SDL 2.0.16. + * + * \sa SDL_GetWindowKeyboardGrab + * \sa SDL_SetWindowMouseGrab + * \sa SDL_SetWindowGrab + */ +extern DECLSPEC void SDLCALL SDL_SetWindowKeyboardGrab(SDL_Window * window, + SDL_bool grabbed); + +/** + * Set a window's mouse grab mode. + * + * Mouse grab confines the mouse cursor to the window. + * + * \param window The window for which the mouse grab mode should be set. + * \param grabbed This is SDL_TRUE to grab mouse, and SDL_FALSE to release. + * + * \since This function is available since SDL 2.0.16. + * + * \sa SDL_GetWindowMouseGrab + * \sa SDL_SetWindowKeyboardGrab + * \sa SDL_SetWindowGrab + */ +extern DECLSPEC void SDLCALL SDL_SetWindowMouseGrab(SDL_Window * window, + SDL_bool grabbed); + +/** + * Get a window's input grab mode. + * + * \param window the window to query. + * \returns SDL_TRUE if input is grabbed, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowGrab + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowGrab(SDL_Window * window); + +/** + * Get a window's keyboard grab mode. + * + * \param window the window to query. + * \returns SDL_TRUE if keyboard is grabbed, and SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.16. + * + * \sa SDL_SetWindowKeyboardGrab + * \sa SDL_GetWindowGrab + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowKeyboardGrab(SDL_Window * window); + +/** + * Get a window's mouse grab mode. + * + * \param window the window to query. + * \returns SDL_TRUE if mouse is grabbed, and SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.16. + * + * \sa SDL_SetWindowKeyboardGrab + * \sa SDL_GetWindowGrab + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowMouseGrab(SDL_Window * window); + +/** + * Get the window that currently has an input grab enabled. + * + * \returns the window if input is grabbed or NULL otherwise. + * + * \since This function is available since SDL 2.0.4. + * + * \sa SDL_GetWindowGrab + * \sa SDL_SetWindowGrab + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetGrabbedWindow(void); + +/** + * Confines the cursor to the specified area of a window. + * + * Note that this does NOT grab the cursor, it only defines the area a cursor + * is restricted to when the window has mouse focus. + * + * \param window The window that will be associated with the barrier. + * \param rect A rectangle area in window-relative coordinates. If NULL the + * barrier for the specified window will be destroyed. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_GetWindowMouseRect + * \sa SDL_SetWindowMouseGrab + */ +extern DECLSPEC int SDLCALL SDL_SetWindowMouseRect(SDL_Window * window, const SDL_Rect * rect); + +/** + * Get the mouse confinement rectangle of a window. + * + * \param window The window to query. + * \returns A pointer to the mouse confinement rectangle of a window, or NULL + * if there isn't one. + * + * \since This function is available since SDL 2.0.18. + * + * \sa SDL_SetWindowMouseRect + */ +extern DECLSPEC const SDL_Rect * SDLCALL SDL_GetWindowMouseRect(SDL_Window * window); + +/** + * Set the brightness (gamma multiplier) for a given window's display. + * + * Despite the name and signature, this method sets the brightness of the + * entire display, not an individual window. A window is considered to be + * owned by the display that contains the window's center pixel. (The index of + * this display can be retrieved using SDL_GetWindowDisplayIndex().) The + * brightness set will not follow the window if it is moved to another + * display. + * + * Many platforms will refuse to set the display brightness in modern times. + * You are better off using a shader to adjust gamma during rendering, or + * something similar. + * + * \param window the window used to select the display whose brightness will + * be changed. + * \param brightness the brightness (gamma multiplier) value to set where 0.0 + * is completely dark and 1.0 is normal brightness. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowBrightness + * \sa SDL_SetWindowGammaRamp + */ +extern DECLSPEC int SDLCALL SDL_SetWindowBrightness(SDL_Window * window, float brightness); + +/** + * Get the brightness (gamma multiplier) for a given window's display. + * + * Despite the name and signature, this method retrieves the brightness of the + * entire display, not an individual window. A window is considered to be + * owned by the display that contains the window's center pixel. (The index of + * this display can be retrieved using SDL_GetWindowDisplayIndex().) + * + * \param window the window used to select the display whose brightness will + * be queried. + * \returns the brightness for the display where 0.0 is completely dark and + * 1.0 is normal brightness. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowBrightness + */ +extern DECLSPEC float SDLCALL SDL_GetWindowBrightness(SDL_Window * window); + +/** + * Set the opacity for a window. + * + * The parameter `opacity` will be clamped internally between 0.0f + * (transparent) and 1.0f (opaque). + * + * This function also returns -1 if setting the opacity isn't supported. + * + * \param window the window which will be made transparent or opaque. + * \param opacity the opacity value (0.0f - transparent, 1.0f - opaque). + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_GetWindowOpacity + */ +extern DECLSPEC int SDLCALL SDL_SetWindowOpacity(SDL_Window * window, float opacity); + +/** + * Get the opacity of a window. + * + * If transparency isn't supported on this platform, opacity will be reported + * as 1.0f without error. + * + * The parameter `opacity` is ignored if it is NULL. + * + * This function also returns -1 if an invalid window was provided. + * + * \param window the window to get the current opacity value from. + * \param out_opacity the float filled in (0.0f - transparent, 1.0f - opaque). + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_SetWindowOpacity + */ +extern DECLSPEC int SDLCALL SDL_GetWindowOpacity(SDL_Window * window, float * out_opacity); + +/** + * Set the window as a modal for another window. + * + * \param modal_window the window that should be set modal. + * \param parent_window the parent window for the modal window. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + */ +extern DECLSPEC int SDLCALL SDL_SetWindowModalFor(SDL_Window * modal_window, SDL_Window * parent_window); + +/** + * Explicitly set input focus to the window. + * + * You almost certainly want SDL_RaiseWindow() instead of this function. Use + * this with caution, as you might give focus to a window that is completely + * obscured by other windows. + * + * \param window the window that should get the input focus. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.5. + * + * \sa SDL_RaiseWindow + */ +extern DECLSPEC int SDLCALL SDL_SetWindowInputFocus(SDL_Window * window); + +/** + * Set the gamma ramp for the display that owns a given window. + * + * Set the gamma translation table for the red, green, and blue channels of + * the video hardware. Each table is an array of 256 16-bit quantities, + * representing a mapping between the input and output for that channel. The + * input is the index into the array, and the output is the 16-bit gamma value + * at that index, scaled to the output color precision. + * + * Despite the name and signature, this method sets the gamma ramp of the + * entire display, not an individual window. A window is considered to be + * owned by the display that contains the window's center pixel. (The index of + * this display can be retrieved using SDL_GetWindowDisplayIndex().) The gamma + * ramp set will not follow the window if it is moved to another display. + * + * \param window the window used to select the display whose gamma ramp will + * be changed. + * \param red a 256 element array of 16-bit quantities representing the + * translation table for the red channel, or NULL. + * \param green a 256 element array of 16-bit quantities representing the + * translation table for the green channel, or NULL. + * \param blue a 256 element array of 16-bit quantities representing the + * translation table for the blue channel, or NULL. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GetWindowGammaRamp + */ +extern DECLSPEC int SDLCALL SDL_SetWindowGammaRamp(SDL_Window * window, + const Uint16 * red, + const Uint16 * green, + const Uint16 * blue); + +/** + * Get the gamma ramp for a given window's display. + * + * Despite the name and signature, this method retrieves the gamma ramp of the + * entire display, not an individual window. A window is considered to be + * owned by the display that contains the window's center pixel. (The index of + * this display can be retrieved using SDL_GetWindowDisplayIndex().) + * + * \param window the window used to select the display whose gamma ramp will + * be queried. + * \param red a 256 element array of 16-bit quantities filled in with the + * translation table for the red channel, or NULL. + * \param green a 256 element array of 16-bit quantities filled in with the + * translation table for the green channel, or NULL. + * \param blue a 256 element array of 16-bit quantities filled in with the + * translation table for the blue channel, or NULL. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_SetWindowGammaRamp + */ +extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window, + Uint16 * red, + Uint16 * green, + Uint16 * blue); + +/** + * Possible return values from the SDL_HitTest callback. + * + * \sa SDL_HitTest + */ +typedef enum SDL_HitTestResult +{ + SDL_HITTEST_NORMAL, /**< Region is normal. No special properties. */ + SDL_HITTEST_DRAGGABLE, /**< Region can drag entire window. */ + SDL_HITTEST_RESIZE_TOPLEFT, + SDL_HITTEST_RESIZE_TOP, + SDL_HITTEST_RESIZE_TOPRIGHT, + SDL_HITTEST_RESIZE_RIGHT, + SDL_HITTEST_RESIZE_BOTTOMRIGHT, + SDL_HITTEST_RESIZE_BOTTOM, + SDL_HITTEST_RESIZE_BOTTOMLEFT, + SDL_HITTEST_RESIZE_LEFT +} SDL_HitTestResult; + +/** + * Callback used for hit-testing. + * + * \param win the SDL_Window where hit-testing was set on. + * \param area an SDL_Point which should be hit-tested. + * \param data what was passed as `callback_data` to SDL_SetWindowHitTest(). + * \return an SDL_HitTestResult value. + * + * \sa SDL_SetWindowHitTest + */ +typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(SDL_Window *win, + const SDL_Point *area, + void *data); + +/** + * Provide a callback that decides if a window region has special properties. + * + * Normally windows are dragged and resized by decorations provided by the + * system window manager (a title bar, borders, etc), but for some apps, it + * makes sense to drag them from somewhere else inside the window itself; for + * example, one might have a borderless window that wants to be draggable from + * any part, or simulate its own title bar, etc. + * + * This function lets the app provide a callback that designates pieces of a + * given window as special. This callback is run during event processing if we + * need to tell the OS to treat a region of the window specially; the use of + * this callback is known as "hit testing." + * + * Mouse input may not be delivered to your application if it is within a + * special area; the OS will often apply that input to moving the window or + * resizing the window and not deliver it to the application. + * + * Specifying NULL for a callback disables hit-testing. Hit-testing is + * disabled by default. + * + * Platforms that don't support this functionality will return -1 + * unconditionally, even if you're attempting to disable hit-testing. + * + * Your callback may fire at any time, and its firing does not indicate any + * specific behavior (for example, on Windows, this certainly might fire when + * the OS is deciding whether to drag your window, but it fires for lots of + * other reasons, too, some unrelated to anything you probably care about _and + * when the mouse isn't actually at the location it is testing_). Since this + * can fire at any time, you should try to keep your callback efficient, + * devoid of allocations, etc. + * + * \param window the window to set hit-testing on. + * \param callback the function to call when doing a hit-test. + * \param callback_data an app-defined void pointer passed to **callback**. + * \returns 0 on success or -1 on error (including unsupported); call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.4. + */ +extern DECLSPEC int SDLCALL SDL_SetWindowHitTest(SDL_Window * window, + SDL_HitTest callback, + void *callback_data); + +/** + * Request a window to demand attention from the user. + * + * \param window the window to be flashed. + * \param operation the flash operation. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.16. + */ +extern DECLSPEC int SDLCALL SDL_FlashWindow(SDL_Window * window, SDL_FlashOperation operation); + +/** + * Destroy a window. + * + * If `window` is NULL, this function will return immediately after setting + * the SDL error message to "Invalid window". See SDL_GetError(). + * + * \param window the window to destroy. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_CreateWindow + * \sa SDL_CreateWindowFrom + */ +extern DECLSPEC void SDLCALL SDL_DestroyWindow(SDL_Window * window); + + +/** + * Check whether the screensaver is currently enabled. + * + * The screensaver is disabled by default since SDL 2.0.2. Before SDL 2.0.2 + * the screensaver was enabled by default. + * + * The default can also be changed using `SDL_HINT_VIDEO_ALLOW_SCREENSAVER`. + * + * \returns SDL_TRUE if the screensaver is enabled, SDL_FALSE if it is + * disabled. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DisableScreenSaver + * \sa SDL_EnableScreenSaver + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsScreenSaverEnabled(void); + +/** + * Allow the screen to be blanked by a screen saver. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_DisableScreenSaver + * \sa SDL_IsScreenSaverEnabled + */ +extern DECLSPEC void SDLCALL SDL_EnableScreenSaver(void); + +/** + * Prevent the screen from being blanked by a screen saver. + * + * If you disable the screensaver, it is automatically re-enabled when SDL + * quits. + * + * The screensaver is disabled by default since SDL 2.0.2. Before SDL 2.0.2 + * the screensaver was enabled by default. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_EnableScreenSaver + * \sa SDL_IsScreenSaverEnabled + */ +extern DECLSPEC void SDLCALL SDL_DisableScreenSaver(void); + + +/** + * \name OpenGL support functions + */ +/* @{ */ + +/** + * Dynamically load an OpenGL library. + * + * This should be done after initializing the video driver, but before + * creating any OpenGL windows. If no OpenGL library is loaded, the default + * library will be loaded upon creation of the first OpenGL window. + * + * If you do this, you need to retrieve all of the GL functions used in your + * program from the dynamic library using SDL_GL_GetProcAddress(). + * + * \param path the platform dependent OpenGL library name, or NULL to open the + * default OpenGL library. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_GetProcAddress + * \sa SDL_GL_UnloadLibrary + */ +extern DECLSPEC int SDLCALL SDL_GL_LoadLibrary(const char *path); + +/** + * Get an OpenGL function by name. + * + * If the GL library is loaded at runtime with SDL_GL_LoadLibrary(), then all + * GL functions must be retrieved this way. Usually this is used to retrieve + * function pointers to OpenGL extensions. + * + * There are some quirks to looking up OpenGL functions that require some + * extra care from the application. If you code carefully, you can handle + * these quirks without any platform-specific code, though: + * + * - On Windows, function pointers are specific to the current GL context; + * this means you need to have created a GL context and made it current + * before calling SDL_GL_GetProcAddress(). If you recreate your context or + * create a second context, you should assume that any existing function + * pointers aren't valid to use with it. This is (currently) a + * Windows-specific limitation, and in practice lots of drivers don't suffer + * this limitation, but it is still the way the wgl API is documented to + * work and you should expect crashes if you don't respect it. Store a copy + * of the function pointers that comes and goes with context lifespan. + * - On X11, function pointers returned by this function are valid for any + * context, and can even be looked up before a context is created at all. + * This means that, for at least some common OpenGL implementations, if you + * look up a function that doesn't exist, you'll get a non-NULL result that + * is _NOT_ safe to call. You must always make sure the function is actually + * available for a given GL context before calling it, by checking for the + * existence of the appropriate extension with SDL_GL_ExtensionSupported(), + * or verifying that the version of OpenGL you're using offers the function + * as core functionality. + * - Some OpenGL drivers, on all platforms, *will* return NULL if a function + * isn't supported, but you can't count on this behavior. Check for + * extensions you use, and if you get a NULL anyway, act as if that + * extension wasn't available. This is probably a bug in the driver, but you + * can code defensively for this scenario anyhow. + * - Just because you're on Linux/Unix, don't assume you'll be using X11. + * Next-gen display servers are waiting to replace it, and may or may not + * make the same promises about function pointers. + * - OpenGL function pointers must be declared `APIENTRY` as in the example + * code. This will ensure the proper calling convention is followed on + * platforms where this matters (Win32) thereby avoiding stack corruption. + * + * \param proc the name of an OpenGL function. + * \returns a pointer to the named OpenGL function. The returned pointer + * should be cast to the appropriate function signature. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_ExtensionSupported + * \sa SDL_GL_LoadLibrary + * \sa SDL_GL_UnloadLibrary + */ +extern DECLSPEC void *SDLCALL SDL_GL_GetProcAddress(const char *proc); + +/** + * Unload the OpenGL library previously loaded by SDL_GL_LoadLibrary(). + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_LoadLibrary + */ +extern DECLSPEC void SDLCALL SDL_GL_UnloadLibrary(void); + +/** + * Check if an OpenGL extension is supported for the current context. + * + * This function operates on the current GL context; you must have created a + * context and it must be current before calling this function. Do not assume + * that all contexts you create will have the same set of extensions + * available, or that recreating an existing context will offer the same + * extensions again. + * + * While it's probably not a massive overhead, this function is not an O(1) + * operation. Check the extensions you care about after creating the GL + * context and save that information somewhere instead of calling the function + * every time you need to know. + * + * \param extension the name of the extension to check. + * \returns SDL_TRUE if the extension is supported, SDL_FALSE otherwise. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GL_ExtensionSupported(const char + *extension); + +/** + * Reset all previously set OpenGL context attributes to their default values. + * + * \since This function is available since SDL 2.0.2. + * + * \sa SDL_GL_GetAttribute + * \sa SDL_GL_SetAttribute + */ +extern DECLSPEC void SDLCALL SDL_GL_ResetAttributes(void); + +/** + * Set an OpenGL window attribute before window creation. + * + * This function sets the OpenGL attribute `attr` to `value`. The requested + * attributes should be set before creating an OpenGL window. You should use + * SDL_GL_GetAttribute() to check the values after creating the OpenGL + * context, since the values obtained can differ from the requested ones. + * + * \param attr an SDL_GLattr enum value specifying the OpenGL attribute to + * set. + * \param value the desired value for the attribute. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_GetAttribute + * \sa SDL_GL_ResetAttributes + */ +extern DECLSPEC int SDLCALL SDL_GL_SetAttribute(SDL_GLattr attr, int value); + +/** + * Get the actual value for an attribute from the current context. + * + * \param attr an SDL_GLattr enum value specifying the OpenGL attribute to + * get. + * \param value a pointer filled in with the current value of `attr`. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_ResetAttributes + * \sa SDL_GL_SetAttribute + */ +extern DECLSPEC int SDLCALL SDL_GL_GetAttribute(SDL_GLattr attr, int *value); + +/** + * Create an OpenGL context for an OpenGL window, and make it current. + * + * Windows users new to OpenGL should note that, for historical reasons, GL + * functions added after OpenGL version 1.1 are not available by default. + * Those functions must be loaded at run-time, either with an OpenGL + * extension-handling library or with SDL_GL_GetProcAddress() and its related + * functions. + * + * SDL_GLContext is an alias for `void *`. It's opaque to the application. + * + * \param window the window to associate with the context. + * \returns the OpenGL context associated with `window` or NULL on error; call + * SDL_GetError() for more details. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_DeleteContext + * \sa SDL_GL_MakeCurrent + */ +extern DECLSPEC SDL_GLContext SDLCALL SDL_GL_CreateContext(SDL_Window * + window); + +/** + * Set up an OpenGL context for rendering into an OpenGL window. + * + * The context must have been created with a compatible window. + * + * \param window the window to associate with the context. + * \param context the OpenGL context to associate with the window. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_CreateContext + */ +extern DECLSPEC int SDLCALL SDL_GL_MakeCurrent(SDL_Window * window, + SDL_GLContext context); + +/** + * Get the currently active OpenGL window. + * + * \returns the currently active OpenGL window on success or NULL on failure; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC SDL_Window* SDLCALL SDL_GL_GetCurrentWindow(void); + +/** + * Get the currently active OpenGL context. + * + * \returns the currently active OpenGL context or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_MakeCurrent + */ +extern DECLSPEC SDL_GLContext SDLCALL SDL_GL_GetCurrentContext(void); + +/** + * Get the size of a window's underlying drawable in pixels. + * + * This returns info useful for calling glViewport(). + * + * This may differ from SDL_GetWindowSize() if we're rendering to a high-DPI + * drawable, i.e. the window was created with `SDL_WINDOW_ALLOW_HIGHDPI` on a + * platform with high-DPI support (Apple calls this "Retina"), and not + * disabled by the `SDL_HINT_VIDEO_HIGHDPI_DISABLED` hint. + * + * \param window the window from which the drawable size should be queried. + * \param w a pointer to variable for storing the width in pixels, may be + * NULL. + * \param h a pointer to variable for storing the height in pixels, may be + * NULL. + * + * \since This function is available since SDL 2.0.1. + * + * \sa SDL_CreateWindow + * \sa SDL_GetWindowSize + */ +extern DECLSPEC void SDLCALL SDL_GL_GetDrawableSize(SDL_Window * window, int *w, + int *h); + +/** + * Set the swap interval for the current OpenGL context. + * + * Some systems allow specifying -1 for the interval, to enable adaptive + * vsync. Adaptive vsync works the same as vsync, but if you've already missed + * the vertical retrace for a given frame, it swaps buffers immediately, which + * might be less jarring for the user during occasional framerate drops. If an + * application requests adaptive vsync and the system does not support it, + * this function will fail and return -1. In such a case, you should probably + * retry the call with 1 for the interval. + * + * Adaptive vsync is implemented for some glX drivers with + * GLX_EXT_swap_control_tear, and for some Windows drivers with + * WGL_EXT_swap_control_tear. + * + * Read more on the Khronos wiki: + * https://www.khronos.org/opengl/wiki/Swap_Interval#Adaptive_Vsync + * + * \param interval 0 for immediate updates, 1 for updates synchronized with + * the vertical retrace, -1 for adaptive vsync. + * \returns 0 on success or -1 if setting the swap interval is not supported; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_GetSwapInterval + */ +extern DECLSPEC int SDLCALL SDL_GL_SetSwapInterval(int interval); + +/** + * Get the swap interval for the current OpenGL context. + * + * If the system can't determine the swap interval, or there isn't a valid + * current context, this function will return 0 as a safe default. + * + * \returns 0 if there is no vertical retrace synchronization, 1 if the buffer + * swap is synchronized with the vertical retrace, and -1 if late + * swaps happen immediately instead of waiting for the next retrace; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_SetSwapInterval + */ +extern DECLSPEC int SDLCALL SDL_GL_GetSwapInterval(void); + +/** + * Update a window with OpenGL rendering. + * + * This is used with double-buffered OpenGL contexts, which are the default. + * + * On macOS, make sure you bind 0 to the draw framebuffer before swapping the + * window, otherwise nothing will happen. If you aren't using + * glBindFramebuffer(), this is the default and you won't have to do anything + * extra. + * + * \param window the window to change. + * + * \since This function is available since SDL 2.0.0. + */ +extern DECLSPEC void SDLCALL SDL_GL_SwapWindow(SDL_Window * window); + +/** + * Delete an OpenGL context. + * + * \param context the OpenGL context to be deleted. + * + * \since This function is available since SDL 2.0.0. + * + * \sa SDL_GL_CreateContext + */ +extern DECLSPEC void SDLCALL SDL_GL_DeleteContext(SDL_GLContext context); + +/* @} *//* OpenGL support functions */ + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_video_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/SDL_vulkan.h b/ports/uwp/third_party/sdl2/include/SDL2/SDL_vulkan.h new file mode 100644 index 00000000..e005ed37 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/SDL_vulkan.h @@ -0,0 +1,219 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2017, Mark Callow + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * # CategoryVulkan + * + * Header file for functions to creating Vulkan surfaces on SDL windows. + */ + +#ifndef SDL_vulkan_h_ +#define SDL_vulkan_h_ + +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Avoid including vulkan.h, don't define VkInstance if it's already included */ +#ifdef VULKAN_H_ +#define NO_SDL_VULKAN_TYPEDEFS +#endif +#ifndef NO_SDL_VULKAN_TYPEDEFS +#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; + +#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; +#else +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#endif + +VK_DEFINE_HANDLE(VkInstance) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR) + +/* Make sure to undef to avoid issues in case of later vulkan include */ +#undef VK_DEFINE_HANDLE +#undef VK_DEFINE_NON_DISPATCHABLE_HANDLE + +#endif /* !NO_SDL_VULKAN_TYPEDEFS */ + +typedef VkInstance SDL_vulkanInstance; +typedef VkSurfaceKHR SDL_vulkanSurface; /* for compatibility with Tizen */ + +/** + * \name Vulkan support functions + * + * \note SDL_Vulkan_GetInstanceExtensions & SDL_Vulkan_CreateSurface API + * is compatable with Tizen's implementation of Vulkan in SDL. + */ +/* @{ */ + +/** + * Dynamically load the Vulkan loader library. + * + * This should be called after initializing the video driver, but before + * creating any Vulkan windows. If no Vulkan loader library is loaded, the + * default library will be loaded upon creation of the first Vulkan window. + * + * It is fairly common for Vulkan applications to link with libvulkan instead + * of explicitly loading it at run time. This will work with SDL provided the + * application links to a dynamic library and both it and SDL use the same + * search path. + * + * If you specify a non-NULL `path`, an application should retrieve all of the + * Vulkan functions it uses from the dynamic library using + * SDL_Vulkan_GetVkGetInstanceProcAddr unless you can guarantee `path` points + * to the same vulkan loader library the application linked to. + * + * On Apple devices, if `path` is NULL, SDL will attempt to find the + * `vkGetInstanceProcAddr` address within all the Mach-O images of the current + * process. This is because it is fairly common for Vulkan applications to + * link with libvulkan (and historically MoltenVK was provided as a static + * library). If it is not found, on macOS, SDL will attempt to load + * `vulkan.framework/vulkan`, `libvulkan.1.dylib`, + * `MoltenVK.framework/MoltenVK`, and `libMoltenVK.dylib`, in that order. On + * iOS, SDL will attempt to load `libMoltenVK.dylib`. Applications using a + * dynamic framework or .dylib must ensure it is included in its application + * bundle. + * + * On non-Apple devices, application linking with a static libvulkan is not + * supported. Either do not link to the Vulkan loader or link to a dynamic + * library version. + * + * \param path The platform dependent Vulkan loader library name or NULL. + * \returns 0 on success or -1 if the library couldn't be loaded; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 2.0.6. + * + * \sa SDL_Vulkan_GetVkGetInstanceProcAddr + * \sa SDL_Vulkan_UnloadLibrary + */ +extern DECLSPEC int SDLCALL SDL_Vulkan_LoadLibrary(const char *path); + +/** + * Get the address of the `vkGetInstanceProcAddr` function. + * + * This should be called after either calling SDL_Vulkan_LoadLibrary() or + * creating an SDL_Window with the `SDL_WINDOW_VULKAN` flag. + * + * \returns the function pointer for `vkGetInstanceProcAddr` or NULL on error. + * + * \since This function is available since SDL 2.0.6. + */ +extern DECLSPEC void *SDLCALL SDL_Vulkan_GetVkGetInstanceProcAddr(void); + +/** + * Unload the Vulkan library previously loaded by SDL_Vulkan_LoadLibrary() + * + * \since This function is available since SDL 2.0.6. + * + * \sa SDL_Vulkan_LoadLibrary + */ +extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void); + +/** + * Get the names of the Vulkan instance extensions needed to create a surface + * with SDL_Vulkan_CreateSurface. + * + * If `pNames` is NULL, then the number of required Vulkan instance extensions + * is returned in `pCount`. Otherwise, `pCount` must point to a variable set + * to the number of elements in the `pNames` array, and on return the variable + * is overwritten with the number of names actually written to `pNames`. If + * `pCount` is less than the number of required extensions, at most `pCount` + * structures will be written. If `pCount` is smaller than the number of + * required extensions, SDL_FALSE will be returned instead of SDL_TRUE, to + * indicate that not all the required extensions were returned. + * + * The `window` parameter is currently needed to be valid as of SDL 2.0.8, + * however, this parameter will likely be removed in future releases + * + * \param window A window for which the required Vulkan instance extensions + * should be retrieved (will be deprecated in a future release). + * \param pCount A pointer to an unsigned int corresponding to the number of + * extensions to be returned. + * \param pNames NULL or a pointer to an array to be filled with required + * Vulkan instance extensions. + * \returns SDL_TRUE on success, SDL_FALSE on error. + * + * \since This function is available since SDL 2.0.6. + * + * \sa SDL_Vulkan_CreateSurface + */ +extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(SDL_Window *window, + unsigned int *pCount, + const char **pNames); + +/** + * Create a Vulkan rendering surface for a window. + * + * The `window` must have been created with the `SDL_WINDOW_VULKAN` flag and + * `instance` must have been created with extensions returned by + * SDL_Vulkan_GetInstanceExtensions() enabled. + * + * \param window The window to which to attach the Vulkan surface. + * \param instance The Vulkan instance handle. + * \param surface A pointer to a VkSurfaceKHR handle to output the newly + * created surface. + * \returns SDL_TRUE on success, SDL_FALSE on error. + * + * \since This function is available since SDL 2.0.6. + * + * \sa SDL_Vulkan_GetInstanceExtensions + * \sa SDL_Vulkan_GetDrawableSize + */ +extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(SDL_Window *window, + VkInstance instance, + VkSurfaceKHR* surface); + +/** + * Get the size of the window's underlying drawable dimensions in pixels. + * + * This may differ from SDL_GetWindowSize() if we're rendering to a high-DPI + * drawable, i.e. the window was created with `SDL_WINDOW_ALLOW_HIGHDPI` on a + * platform with high-DPI support (Apple calls this "Retina"), and not + * disabled by the `SDL_HINT_VIDEO_HIGHDPI_DISABLED` hint. + * + * \param window an SDL_Window for which the size is to be queried. + * \param w Pointer to the variable to write the width to or NULL. + * \param h Pointer to the variable to write the height to or NULL. + * + * \since This function is available since SDL 2.0.6. + * + * \sa SDL_GetWindowSize + * \sa SDL_CreateWindow + * \sa SDL_Vulkan_CreateSurface + */ +extern DECLSPEC void SDLCALL SDL_Vulkan_GetDrawableSize(SDL_Window * window, + int *w, int *h); + +/* @} *//* Vulkan support functions */ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* SDL_vulkan_h_ */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/begin_code.h b/ports/uwp/third_party/sdl2/include/SDL2/begin_code.h new file mode 100644 index 00000000..2044e5cb --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/begin_code.h @@ -0,0 +1,189 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* WIKI CATEGORY: BeginCode */ + +/** + * begin_code.h sets things up for C dynamic library function definitions, + * static inlined functions, and structures aligned at 4-byte alignment. + * If you don't like ugly C preprocessor code, don't look at this file. :) + */ + +/* This shouldn't be nested -- included it around code only. */ +#ifdef SDL_begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define SDL_begin_code_h + +#ifndef SDL_DEPRECATED +# if defined(__GNUC__) && (__GNUC__ >= 4) /* technically, this arrived in gcc 3.1, but oh well. */ +# define SDL_DEPRECATED __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define SDL_DEPRECATED __declspec(deprecated) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef SDL_UNUSED +# ifdef __GNUC__ +# define SDL_UNUSED __attribute__((unused)) +# else +# define SDL_UNUSED +# endif +#endif + +/* Some compilers use a special export keyword */ +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) || defined(__CYGWIN__) || defined(__GDK__) +# ifdef DLL_EXPORT +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# elif defined(__OS2__) +# ifdef BUILD_SDL +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# else +# define DECLSPEC +# endif +# endif +#endif + +/* By default SDL uses the C calling convention */ +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__) || defined(__GDK__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#elif defined(__OS2__) || defined(__EMX__) +#define SDLCALL _System +# if defined (__GNUC__) && !defined(_System) +# define _System /* for old EMX/GCC compat. */ +# endif +#else +#define SDLCALL +#endif +#endif /* SDLCALL */ + +/* Removed DECLSPEC on Symbian OS because SDL cannot be a DLL in EPOC */ +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif /* __SYMBIAN32__ */ + +/* Force structure packing at 4 byte alignment. + This is necessary if the header is included in code which has structure + packing set to an alternate value, say for loading structures from disk. + The packing is reset to the previous value in close_code.h + */ +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpragma-pack" +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _WIN64 +/* Use 8-byte alignment on 64-bit architectures, so pointers are aligned */ +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif /* Compiler needs structure packing set */ + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) || defined(__CC_ARM) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif /* SDL_INLINE not defined */ + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif /* SDL_FORCE_INLINE not defined */ + +#ifndef SDL_NORETURN +#if defined(__GNUC__) +#define SDL_NORETURN __attribute__((noreturn)) +#elif defined(_MSC_VER) +#define SDL_NORETURN __declspec(noreturn) +#else +#define SDL_NORETURN +#endif +#endif /* SDL_NORETURN not defined */ + +/* Apparently this is needed by several Windows compilers */ +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif /* NULL */ +#endif /* ! Mac OS X - breaks precompiled headers */ + +#ifndef SDL_FALLTHROUGH +#if (defined(__cplusplus) && __cplusplus >= 201703L) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L) +#define SDL_FALLTHROUGH [[fallthrough]] +#else +#if defined(__has_attribute) && !defined(__SUNPRO_C) && !defined(__SUNPRO_CC) +#define SDL_HAS_FALLTHROUGH __has_attribute(__fallthrough__) +#else +#define SDL_HAS_FALLTHROUGH 0 +#endif /* __has_attribute */ +#if SDL_HAS_FALLTHROUGH && \ + ((defined(__GNUC__) && __GNUC__ >= 7) || \ + (defined(__clang_major__) && __clang_major__ >= 10)) +#define SDL_FALLTHROUGH __attribute__((__fallthrough__)) +#else +#define SDL_FALLTHROUGH do {} while (0) /* fallthrough */ +#endif /* SDL_HAS_FALLTHROUGH */ +#undef SDL_HAS_FALLTHROUGH +#endif /* C++17 or C2x */ +#endif /* SDL_FALLTHROUGH not defined */ diff --git a/ports/uwp/third_party/sdl2/include/SDL2/close_code.h b/ports/uwp/third_party/sdl2/include/SDL2/close_code.h new file mode 100644 index 00000000..f991f458 --- /dev/null +++ b/ports/uwp/third_party/sdl2/include/SDL2/close_code.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file close_code.h + * + * This file reverses the effects of begin_code.h and should be included + * after you finish any function and structure declarations in your headers + */ + +#ifndef SDL_begin_code_h +#error close_code.h included without matching begin_code.h +#endif +#undef SDL_begin_code_h + +/* Reset structure packing at previous byte alignment */ +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif /* Compiler needs structure packing set */ diff --git a/ports/uwp/third_party/sdl2/lib/SDL2.lib b/ports/uwp/third_party/sdl2/lib/SDL2.lib new file mode 100644 index 00000000..bd6dcbc7 Binary files /dev/null and b/ports/uwp/third_party/sdl2/lib/SDL2.lib differ diff --git a/ports/uwp/third_party/sdl2/patches/xbox-wgi-controller.patch b/ports/uwp/third_party/sdl2/patches/xbox-wgi-controller.patch new file mode 100644 index 00000000..0e5dbde2 --- /dev/null +++ b/ports/uwp/third_party/sdl2/patches/xbox-wgi-controller.patch @@ -0,0 +1,112 @@ +diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c +index 36e521e..4d1f171 100644 +--- a/src/joystick/SDL_gamecontroller.c ++++ b/src/joystick/SDL_gamecontroller.c +@@ -682,0 +683,4 @@ static ControllerMapping_t *SDL_CreateMappingForWGIController(SDL_JoystickGUID g ++#ifdef __WINRT__ ++ /* Xbox WGI exposes the canonical SDL axis and hat layout. */ ++ SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,", sizeof(mapping_string)); ++#else +@@ -683,0 +688 @@ static ControllerMapping_t *SDL_CreateMappingForWGIController(SDL_JoystickGUID g ++#endif +diff --git a/src/joystick/windows/SDL_windows_gaming_input.c b/src/joystick/windows/SDL_windows_gaming_input.c +index 9299ac5..9fce0a5 100644 +--- a/src/joystick/windows/SDL_windows_gaming_input.c ++++ b/src/joystick/windows/SDL_windows_gaming_input.c +@@ -793,4 +793,13 @@ static int WGI_JoystickOpen(SDL_Joystick *joystick, int device_index) +- /* Initialize the joystick capabilities */ +- joystick->nbuttons = state->nbuttons; +- joystick->naxes = state->naxes; +- joystick->nhats = state->nhats; ++#ifdef __WINRT__ ++ if (hwdata->gamepad) { ++ joystick->nbuttons = 10; ++ joystick->naxes = 6; ++ joystick->nhats = 1; ++ } else ++#endif ++ { ++ /* Initialize the joystick capabilities */ ++ joystick->nbuttons = state->nbuttons; ++ joystick->naxes = state->naxes; ++ joystick->nhats = state->nhats; ++ } +@@ -933,0 +943,45 @@ static void WGI_JoystickUpdate(SDL_Joystick *joystick) ++ ++#ifdef __WINRT__ ++ if (hwdata->gamepad) { ++ struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading reading; ++ static const __x_ABI_CWindows_CGaming_CInput_CGamepadButtons button_masks[] = { ++ GamepadButtons_A, GamepadButtons_B, GamepadButtons_X, GamepadButtons_Y, ++ GamepadButtons_LeftShoulder, GamepadButtons_RightShoulder, ++ GamepadButtons_View, GamepadButtons_Menu, ++ GamepadButtons_LeftThumbstick, GamepadButtons_RightThumbstick ++ }; ++ Uint8 hat = SDL_HAT_CENTERED; ++ Uint8 i; ++ ++ hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(hwdata->gamepad, &reading); ++ if (SUCCEEDED(hr) && (!reading.Timestamp || reading.Timestamp != hwdata->timestamp)) { ++ const DOUBLE left_x = SDL_max(-1.0, SDL_min(1.0, reading.LeftThumbstickX)); ++ const DOUBLE left_y = SDL_max(-1.0, SDL_min(1.0, reading.LeftThumbstickY)); ++ const DOUBLE right_x = SDL_max(-1.0, SDL_min(1.0, reading.RightThumbstickX)); ++ const DOUBLE right_y = SDL_max(-1.0, SDL_min(1.0, reading.RightThumbstickY)); ++ const DOUBLE left_trigger = SDL_max(0.0, SDL_min(1.0, reading.LeftTrigger)); ++ const DOUBLE right_trigger = SDL_max(0.0, SDL_min(1.0, reading.RightTrigger)); ++ ++ SDL_PrivateJoystickAxis(joystick, 0, (Sint16)(left_x * 32767.0)); ++ SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-left_y * 32767.0)); ++ SDL_PrivateJoystickAxis(joystick, 2, (Sint16)(left_trigger * 65535.0 - 32768.0)); ++ SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(right_x * 32767.0)); ++ SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(-right_y * 32767.0)); ++ SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(right_trigger * 65535.0 - 32768.0)); ++ ++ for (i = 0; i < (Uint8)SDL_arraysize(button_masks); ++i) { ++ SDL_PrivateJoystickButton(joystick, i, ++ (reading.Buttons & button_masks[i]) ? SDL_PRESSED : SDL_RELEASED); ++ } ++ ++ if (reading.Buttons & GamepadButtons_DPadUp) hat |= SDL_HAT_UP; ++ if (reading.Buttons & GamepadButtons_DPadDown) hat |= SDL_HAT_DOWN; ++ if (reading.Buttons & GamepadButtons_DPadLeft) hat |= SDL_HAT_LEFT; ++ if (reading.Buttons & GamepadButtons_DPadRight) hat |= SDL_HAT_RIGHT; ++ SDL_PrivateJoystickHat(joystick, 0, hat); ++ hwdata->timestamp = reading.Timestamp; ++ } ++ return; ++ } ++#endif ++ +@@ -1055,0 +1110,30 @@ static SDL_bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMappi ++#ifdef __WINRT__ ++ WindowsGamingInputControllerState *state = &wgi.controllers[device_index]; ++ ++ if (state->type != SDL_JOYSTICK_TYPE_GAMECONTROLLER) { ++ return SDL_FALSE; ++ } ++ ++ SDL_zero(*out); ++ out->a = (SDL_InputMapping){ EMappingKind_Button, 0 }; ++ out->b = (SDL_InputMapping){ EMappingKind_Button, 1 }; ++ out->x = (SDL_InputMapping){ EMappingKind_Button, 2 }; ++ out->y = (SDL_InputMapping){ EMappingKind_Button, 3 }; ++ out->leftshoulder = (SDL_InputMapping){ EMappingKind_Button, 4 }; ++ out->rightshoulder = (SDL_InputMapping){ EMappingKind_Button, 5 }; ++ out->back = (SDL_InputMapping){ EMappingKind_Button, 6 }; ++ out->start = (SDL_InputMapping){ EMappingKind_Button, 7 }; ++ out->leftstick = (SDL_InputMapping){ EMappingKind_Button, 8 }; ++ out->rightstick = (SDL_InputMapping){ EMappingKind_Button, 9 }; ++ out->dpup = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_UP }; ++ out->dpdown = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_DOWN }; ++ out->dpleft = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_LEFT }; ++ out->dpright = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_RIGHT }; ++ out->leftx = (SDL_InputMapping){ EMappingKind_Axis, 0 }; ++ out->lefty = (SDL_InputMapping){ EMappingKind_Axis, 1 }; ++ out->lefttrigger = (SDL_InputMapping){ EMappingKind_Axis, 2 }; ++ out->rightx = (SDL_InputMapping){ EMappingKind_Axis, 3 }; ++ out->righty = (SDL_InputMapping){ EMappingKind_Axis, 4 }; ++ out->righttrigger = (SDL_InputMapping){ EMappingKind_Axis, 5 }; ++ return SDL_TRUE; ++#else +@@ -1056,0 +1141 @@ static SDL_bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMappi ++#endif diff --git a/scripts/build.sh b/scripts/build.sh index 9573f5b9..5ae82de6 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -65,15 +65,20 @@ mkdir -p "$CACHE" "$WORK" "$DIST/mac" "$DIST/win" "$DIST/linux" say "packing game.love" LOVE_FILE="$WORK/game.love" rm -f "$LOVE_FILE" -# libs/ carries the vendored FlexLove toolkit the launcher UI is built on -# (src/import/LauncherView.lua); a build without it dies on the first frame. +# The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); +# the vendored libs/flexlove tree it replaced is gone. (cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \ - main.lua conf.lua src libs data assets tools/save-editor \ + main.lua conf.lua src data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json \ -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') -if unzip -Z1 "$LOVE_FILE" \ - | grep -Eq '^(data|assets)/generated/[^/]+|^(data|assets)/generated/.+/'; then +# Materialize the listing once and grep the file: piping unzip straight into +# grep -q under `set -o pipefail` SIGPIPEs unzip when grep exits early on a +# match, and the pipeline's failure reads as "missing " for whichever +# entry happened to match first (see the same fix in pack_love.sh). +LOVE_LISTING="$WORK/love-listing.txt" +unzip -Z1 "$LOVE_FILE" > "$LOVE_LISTING" +if grep -Eq '^(data|assets)/generated/[^/]+|^(data|assets)/generated/.+/' "$LOVE_LISTING"; then fail "game.love unexpectedly contains generated ROM data" fi # The editor is only reachable if its entry point and both module directories @@ -83,10 +88,10 @@ fi # way once). for required in tools/save-editor/App.lua tools/save-editor/Kit.lua \ tools/save-editor/panels/Party.lua \ - libs/flexlove/FlexLove.lua \ + src/ui/kit/Kit.lua \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json; do - unzip -Z1 "$LOVE_FILE" | grep -qx "$required" \ + grep -qxF "$required" "$LOVE_LISTING" \ || fail "game.love is missing $required" done say "game.love: $(du -h "$LOVE_FILE" | cut -f1)" @@ -116,6 +121,32 @@ else say "version '$VERSION' is not X.Y.Z, shipping default engine (no stamp)" fi +# --------------------------------------------------------------- app icon +# One source of truth for every platform's launcher icon; iOS resizes the +# same file in scripts/build_ios.sh (apply_ios_icon) and the Android res/ +# drawables are generated from it too. +ICON_SRC="$ROOT/assets/logo/gen1recomp_cover.png" + +# pipx installs peresed (Windows exe icon patcher) here, off the default PATH. +PATH="$PATH:$HOME/.local/bin" + +make_icns() { # $1 = output .icns path + [ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC" + local iconset="$WORK/GameIcon.iconset" size scaled + rm -rf "$iconset"; mkdir -p "$iconset" + for size in 16 32 128 256 512; do + sips -z "$size" "$size" "$ICON_SRC" --out "$iconset/icon_${size}x${size}.png" >/dev/null + scaled=$((size * 2)) + sips -z "$scaled" "$scaled" "$ICON_SRC" --out "$iconset/icon_${size}x${size}@2x.png" >/dev/null + done + iconutil -c icns "$iconset" -o "$1" +} + +make_ico() { # $1 = output .ico path + [ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC" + magick "$ICON_SRC" -define icon:auto-resize=256,128,64,48,32,16 "$1" +} + # --------------------------------------------------------------- macOS build_mac() { say "building macOS app" @@ -142,9 +173,21 @@ build_mac() { /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$plist" 2>/dev/null \ || /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $VERSION" "$plist" - if [ -f "$ROOT/assets/icon.icns" ]; then - cp "$ROOT/assets/icon.icns" "$out_app/Contents/Resources/GameIcon.icns" + # Brand the app icon. LÖVE.app resolves its icon through CFBundleIconName -> + # Assets.car first, so overwriting the loose .icns files alone changes + # nothing; the compiled asset catalog has to go and the plist has to fall + # back to CFBundleIconFile. + local icns="$ROOT/assets/icon.icns" + if [ ! -f "$icns" ]; then + icns="$WORK/GameIcon.icns" + make_icns "$icns" fi + cp "$icns" "$out_app/Contents/Resources/GameIcon.icns" + cp "$icns" "$out_app/Contents/Resources/OS X AppIcon.icns" + rm -f "$out_app/Contents/Resources/Assets.car" + /usr/libexec/PlistBuddy -c "Delete :CFBundleIconName" "$plist" 2>/dev/null || true + /usr/libexec/PlistBuddy -c "Set :CFBundleIconFile OS X AppIcon" "$plist" 2>/dev/null \ + || /usr/libexec/PlistBuddy -c "Add :CFBundleIconFile string 'OS X AppIcon'" "$plist" local id="$IDENTITY" if [ -z "$id" ]; then @@ -218,7 +261,47 @@ build_win() { cp "$love_dir"/*.dll "$out_dir"/ cp "$love_dir"/license.txt "$out_dir"/ 2>/dev/null || true - cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe" + # The exe's icon lives in love.exe's PE resources, so it must be patched + # BEFORE the .love is appended: peresed rewrites the whole file and would + # drop the fused bytes. peresed (pipx install pe_tools) has no .ico input, + # only raw --set-resource, so split the .ico into RT_ICON blobs plus a + # GRPICONDIR that reuses love.exe's existing resource ids (1..N, lang 1033). + local ico="$WORK/$APP_NAME.ico" + make_ico "$ico" + if command -v peresed >/dev/null 2>&1; then + local ico_parts="$WORK/ico-parts" + rm -rf "$ico_parts"; mkdir -p "$ico_parts" + python3 - "$ico" "$ico_parts" <<'PY' +import struct, sys +data = open(sys.argv[1], "rb").read() +outdir = sys.argv[2] +count = struct.unpack_from("/dev/null; then + cat "$exe_branded" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe" + else + warn "peresed failed to patch the exe icon, shipping stock LÖVE icon" + cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe" + fi + else + warn "peresed not found (pipx install pe_tools), shipping stock LÖVE exe icon" + cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe" + fi local zip_out="$DIST/win/$APP_NAME-win64.zip" rm -f "$zip_out" @@ -273,6 +356,46 @@ build_linux() { unsquashfs -q -no-xattrs -o "$sfs_offset" -d "$appdir" "$love_appimage" >/dev/null cp "$LOVE_FILE" "$appdir/game.love" + + # Replace LÖVE's own desktop entry rather than keeping it: it says + # Name=LÖVE / Icon=love, which is what appimaged, app menus and file + # managers displayed this image as. Same file as the arm64 build writes, + # so both architectures integrate under the game's name. + local stock_desktop + stock_desktop="$(find "$appdir" -maxdepth 1 -name '*.desktop' | wc -l | tr -d ' ')" + [ "$stock_desktop" = 1 ] \ + || fail "expected exactly one .desktop at the AppDir root, found $stock_desktop" + rm -f "$appdir"/*.desktop + + # share/ carries a second, NoDisplay copy of the same entry plus the .love + # file-type icons and mime rule, all left over from LÖVE's `make install` + # (its Exec even points at the CI runner that built it). Nothing at runtime + # reads them -- only share/lua and share/luajit-* are on LUA_PATH -- but + # AppRun puts $APPDIR/share on XDG_DATA_DIRS, so anyone extracting the image + # gets a "LÖVE" entry back. The arm64 AppDir never had them. + rm -rf "$appdir/share/applications" "$appdir/share/pixmaps" \ + "$appdir/share/mime" "$appdir/share/icons" + + cat > "$appdir/$APP_NAME.desktop" </dev/null + cp "$appdir/$APP_NAME.png" "$appdir/.DirIcon" + sed -i '' 's|^#FUSE_PATH="$APPDIR/my_game.love"$|FUSE_PATH="$APPDIR/game.love"|' "$appdir/AppRun" grep -q '^FUSE_PATH="\$APPDIR/game.love"$' "$appdir/AppRun" \ || fail "failed to enable FUSE_PATH in AppRun (upstream AppRun changed?)" diff --git a/scripts/build_android.sh b/scripts/build_android.sh index bdb535ba..df49f4c3 100755 --- a/scripts/build_android.sh +++ b/scripts/build_android.sh @@ -202,34 +202,39 @@ pack_game_love() { # APK, so the mod manager's Delete can't remove it and it reappears every # launch. Pokewalker ships as an importable .zip instead, which gives it # a real install/upgrade/delete lifecycle. - # libs/ carries the vendored FlexLove toolkit the launcher UI is built on - # (src/import/LauncherView.lua requires it at the top level, and RomImporter - # calls into that view from both update and draw), so an archive without it - # dies on the first frame with nothing left to fall back to. + # The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); + # the vendored libs/flexlove tree it replaced is gone. (cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \ - main.lua conf.lua src libs data assets tools/save-editor \ + main.lua conf.lua src data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json \ -x '*.DS_Store' -x '*/.git/*' -x '*/.DS_Store' \ -x 'data/generated/*' -x 'assets/generated/*') - if unzip -Z1 "$LOVE_FILE" \ - | grep -Eq '^(data|assets)/generated/[^/]+|^(data|assets)/generated/.+/'; then - fail "game.love unexpectedly contains generated ROM data" - fi - # Do not pipe unzip straight into grep here: on a large archive grep can - # finish early and make unzip report SIGPIPE under `set -o pipefail`. + # List once and match against the captured text: piping unzip straight into + # grep under `set -o pipefail` SIGPIPEs unzip as soon as grep exits early, + # and the pipeline's 141 outranks grep's own status. For the generated-data + # guard that inverted the test -- an archive that really did carry generated + # ROM data made grep match, killed unzip, and the `if` read the 141 as "no + # match" and let the build through (#774). Same listing feeds the + # required-file gates below, as in scripts/build.sh and scripts/pack_love.sh. local archive_entries archive_entries="$(unzip -Z1 "$LOVE_FILE")" + if grep -Eq '^(data|assets)/generated/[^/]+|^(data|assets)/generated/.+/' \ + <<< "$archive_entries"; then + fail "game.love unexpectedly contains generated ROM data" + fi grep -qx 'tools/save-editor/App.lua' <<< "$archive_entries" \ || fail "game.love is missing the save editor (Edit on a save row would crash)" grep -qx "$YELLOW_MANIFEST_RELATIVE" <<< "$archive_entries" \ || fail "game.love is missing the Yellow ROM import manifest" - # This gate exists because libs/ was added to scripts/build.sh's payload and - # to no other packager, so Android and iOS built an APK/IPA whose launcher - # threw on require("libs.flexlove.FlexLove") before drawing anything. Source - # runs read libs/ off the working tree, so only a build can catch it. - grep -qx 'libs/flexlove/FlexLove.lua' <<< "$archive_entries" \ - || fail "game.love is missing the FlexLove UI toolkit (launcher dies on frame 1)" + # This gate exists because the launcher's UI toolkit once lived outside + # src/ (libs/flexlove) and was added to scripts/build.sh's payload and to + # no other packager, so Android and iOS built an APK/IPA whose launcher + # threw before drawing anything. The kit now lives inside src/, but the + # gate stays: source runs read the working tree, so only a build can catch + # a packaging miss. + grep -qx 'src/ui/kit/Kit.lua' <<< "$archive_entries" \ + || fail "game.love is missing the launcher UI kit (launcher dies on frame 1)" say "game.love: $(du -h "$LOVE_FILE" | cut -f1) -> $LOVE_FILE" # This script packs its own game.love (it does not reuse build.sh's), so it diff --git a/scripts/build_ios.sh b/scripts/build_ios.sh index ddc6fb1b..3a87dce9 100755 --- a/scripts/build_ios.sh +++ b/scripts/build_ios.sh @@ -217,6 +217,14 @@ apply_ios_branding() { cp "$OVERLAY_PLIST" "$dest" } +verify_documents_overlay() { + local sharing in_place + sharing="$(/usr/libexec/PlistBuddy -c 'Print :UIFileSharingEnabled' "$OVERLAY_PLIST" 2>/dev/null || true)" + in_place="$(/usr/libexec/PlistBuddy -c 'Print :LSSupportsOpeningDocumentsInPlace' "$OVERLAY_PLIST" 2>/dev/null || true)" + [ "$sharing" = "true" ] && [ "$in_place" = "true" ] \ + || fail "iOS plist overlay must enable UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace" +} + apply_ios_icon() { local source="$ROOT/assets/logo/gen1recomp_cover.png" local target="$XCODE_DIR/Images.xcassets/iOS AppIcon.appiconset" @@ -315,13 +323,11 @@ pack_game_love() { # it reappears every launch. Mods install as .zips at runtime instead # (launcher -> MODS -> Import mod .zip), the same lifecycle as every # other platform. - # libs/ carries the vendored FlexLove toolkit the launcher UI is built on - # (src/import/LauncherView.lua requires it at the top level, and RomImporter - # calls into that view from both update and draw), so an archive without it - # dies on the first frame with nothing left to fall back to. + # The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); + # the vendored libs/flexlove tree it replaced is gone. # shellcheck disable=SC2086 # MANIFESTS is a deliberate word list (cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \ - main.lua conf.lua src libs data assets tools/save-editor \ + main.lua conf.lua src data assets tools/save-editor \ $MANIFESTS \ -x '*.DS_Store' -x '*/.git/*' -x '*/.DS_Store' \ -x 'data/generated/*' -x 'assets/generated/*') @@ -337,14 +343,15 @@ pack_game_love() { # in 0.1.45 through 0.1.47: decodeManifest (src/import/RomImporter.lua) errors # outright when a version's manifest is absent, so Import ROM on Yellow died # in the built app while dev, which reads the source tree, stayed green. - # libs/flexlove/FlexLove.lua is on the list for the same reason: it was added - # to build.sh's payload and to no other packager, so the mobile builds shipped - # a launcher that threw before drawing its first frame. + # src/ui/kit/Kit.lua is on the list for the same reason: the launcher's UI + # toolkit once lived outside src/ (libs/flexlove) and shipped missing from + # the mobile packagers, so the launcher threw before drawing its first + # frame. The kit is inside src/ now; the gate stays to catch a repeat. archive_entries="$(unzip -Z1 "$LOVE_FILE")" # shellcheck disable=SC2086 # MANIFESTS is a deliberate word list for required in src/update/Boot.lua tools/save-editor/App.lua \ tools/save-editor/Kit.lua tools/save-editor/panels/Party.lua \ - libs/flexlove/FlexLove.lua \ + src/ui/kit/Kit.lua \ $MANIFESTS; do printf '%s\n' "$archive_entries" | grep -qx "$required" \ || fail "game.love is missing $required" @@ -579,6 +586,25 @@ verify_native_bridge() { say "native bridge present (pickFile, createFile)" } +verify_documents_configuration() { + local app="$1" + local plist="$app/Info.plist" + local sharing in_place + [ -f "$plist" ] || fail "built iOS app is missing Info.plist: $plist" + sharing="$(/usr/libexec/PlistBuddy -c 'Print :UIFileSharingEnabled' "$plist" 2>/dev/null || true)" + in_place="$(/usr/libexec/PlistBuddy -c 'Print :LSSupportsOpeningDocumentsInPlace' "$plist" 2>/dev/null || true)" + [ "$sharing" = "true" ] && [ "$in_place" = "true" ] \ + || fail "built iOS app does not expose its Documents folder in $(basename "$app")" + say "public Documents exposure present (file sharing + in-place access)" +} + +verify_game_payload() { + local app="$1" + [ -s "$app/game.love" ] \ + || fail "built iOS app is missing game.love: $app" + say "game.love present ($(du -h "$app/game.love" | cut -f1))" +} + run_xcodebuild() { local config sdk destination if $RELEASE; then @@ -619,6 +645,8 @@ run_xcodebuild() { PRODUCT_BUNDLE_IDENTIFIER="$BUNDLE_ID" MARKETING_VERSION="$marketing_version" CURRENT_PROJECT_VERSION="$project_version" + INFOPLIST_KEY_UIFileSharingEnabled=YES + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace=YES ONLY_ACTIVE_ARCH=NO DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING=YES ) @@ -694,12 +722,19 @@ run_xcodebuild() { fi fi + verify_documents_configuration "$app" + # Fuse even if the pbxproj wire-up failed, LÖVE runs any bundled *.love. - if [ ! -f "$app/game.love" ]; then + # Byte-compare, never just existence: xcodebuild's incremental Copy Bundle + # Resources can leave a previous build's game.love in a surviving .app, and + # an existence check shipped that stale payload in the .ipa (today's Lua + # fixes present in ios/resources/ but absent from the installed app). + if ! cmp -s "$LOVE_FILE" "$app/game.love"; then say "fusing game.love into $(basename "$app")" cp "$LOVE_FILE" "$app/game.love" fi + verify_game_payload "$app" verify_native_bridge "$app" local dist_dir="$DIST/${config}-${sdk}" @@ -771,6 +806,7 @@ install_to_device() { # --------------------------------------------------------------- main apply_ios_branding +verify_documents_overlay apply_ios_icon say "applying iOS native bridge patches (picker/Files support)" python3 "$IOS_DIR/patch_love_src.py" || fail "patch_love_src.py failed" diff --git a/scripts/build_linux_arm64.sh b/scripts/build_linux_arm64.sh new file mode 100755 index 00000000..4df2ff00 --- /dev/null +++ b/scripts/build_linux_arm64.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# Builds the aarch64 (arm64) Linux AppImage. +# +# scripts/build.sh's `linux` target only produces x86_64: it unpacks LÖVE's +# official x86_64 AppImage and re-fuses it, and no aarch64 equivalent is +# published. This script compiles LÖVE 11.5 from the official linux-src +# tarball inside a Debian bullseye arm64 container and fuses game.love into a +# type-2 AppImage, so one artifact covers Raspberry Pi OS, Armbian, Ubuntu +# arm64 and the aarch64 handhelds. +# +# Usage: +# scripts/build_linux_arm64.sh [--version X.Y.Z] [--game-love PATH] +# [--rebuild-image] [--clean-cache] +# +# Output: +# dist/linux-arm64/gen1recomp--linux-arm64.AppImage +# dist/linux-arm64/gen1recomp--linux-arm64.AppImage.sha256 +# +# Requirements: docker or podman on an aarch64 host (a Raspberry Pi 5, an +# ubuntu-24.04-arm runner or Apple Silicon Docker all work). Nothing is +# cross-compiled and no qemu emulation is involved. + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +. "$ROOT/scripts/linux-arm64/common.sh" + +HERE="$ROOT/.bazinga" +CACHE="$HERE/cache/linux-arm64" +WORK="$HERE/work/linux-arm64" +DIST="$ROOT/dist/linux-arm64" + +VERSION="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo dev)" +GAME_LOVE="" +REBUILD_IMAGE=0 + +while [ $# -gt 0 ]; do + case "$1" in + --version) VERSION="${2:?--version needs a value}"; shift ;; + --game-love) GAME_LOVE="${2:?--game-love needs a path}"; shift ;; + --rebuild-image) REBUILD_IMAGE=1 ;; + --clean-cache) rm -rf "$CACHE" ;; + -h|--help) + sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) fail "unknown argument: $1" ;; + esac + shift +done + +# --------------------------------------------------------------- host checks +# aarch64 only. The container is arch-native; running it under qemu-user on an +# x86_64 host "works" but takes hours and has produced miscompiled LuaJIT +# before, so refuse rather than hand back a build nobody can trust. +host_arch="$(uname -m)" +case "$host_arch" in + aarch64|arm64) ;; + *) fail "this build must run on an aarch64 host (found: $host_arch). + Use a Raspberry Pi 5 / arm64 VM / Apple Silicon, or the ubuntu-24.04-arm CI runner." ;; +esac + +RUNTIME="$(container_runtime)" || fail_need_container +say "container runtime: $RUNTIME" + +mkdir -p "$CACHE" "$WORK" "$DIST" + +# --------------------------------------------------------------- game.love +# Shared packer, same include/exclude set and the same verification gates as +# every other platform, so this artifact can never drift from the desktop one. +if [ -n "$GAME_LOVE" ]; then + [ -f "$GAME_LOVE" ] || fail "--game-love: no such file: $GAME_LOVE" + say "using prebuilt payload: $GAME_LOVE" +else + GAME_LOVE="$WORK/game.love" + "$ROOT/scripts/pack_love.sh" \ + --output "$GAME_LOVE" \ + --listing "$WORK/love-listing.txt" \ + --version "$VERSION" +fi + +# --------------------------------------------------------------- icon +# One source of truth for every platform's launcher icon (scripts/build.sh +# resizes the same file with sips on macOS). Pillow is already a project +# dependency via tools/build_data.py; without it, ship the 1024px original +# rather than failing the build over an icon. +IN_DIR="$WORK/in" +rm -rf "$IN_DIR"; mkdir -p "$IN_DIR" +ICON_SRC="$ROOT/assets/logo/gen1recomp_cover.png" +[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC" +if ! python3 - "$ICON_SRC" "$IN_DIR/icon.png" <<'PY' 2>/dev/null +import sys +from PIL import Image +with Image.open(sys.argv[1]) as image: + image.convert("RGBA").resize((512, 512), Image.LANCZOS).save(sys.argv[2]) +PY +then + warn "Pillow not available, shipping the unresized icon" + cp "$ICON_SRC" "$IN_DIR/icon.png" +fi +cp "$GAME_LOVE" "$IN_DIR/game.love" + +# --------------------------------------------------------------- downloads +# Fetched on the host and checksum-pinned here so the container never needs +# network access and every input is verified in exactly one place. +download_pinned "$LOVE_SRC_URL" "$CACHE/$LOVE_SRC_TARBALL" "$LOVE_SRC_SHA256" +download_pinned "$SDL2_URL" "$CACHE/$SDL2_TARBALL" "$SDL2_SHA256" +download_pinned "$OPENAL_URL" "$CACHE/$OPENAL_TARBALL" "$OPENAL_SHA256" +download_pinned "$THEORA_URL" "$CACHE/$THEORA_TARBALL" "$THEORA_SHA256" +download_pinned "$OGG_URL" "$CACHE/$OGG_TARBALL" "$OGG_SHA256" +download_pinned "$VORBIS_URL" "$CACHE/$VORBIS_TARBALL" "$VORBIS_SHA256" +download_pinned "$MPG123_URL" "$CACHE/$MPG123_TARBALL" "$MPG123_SHA256" +download_pinned "$APPIMAGE_RUNTIME_URL" "$CACHE/$APPIMAGE_RUNTIME_NAME" \ + "$APPIMAGE_RUNTIME_SHA256" + +# --------------------------------------------------------------- builder image +if [ "$REBUILD_IMAGE" = 1 ] || ! "$RUNTIME" image inspect "$BUILDER_IMAGE" >/dev/null 2>&1; then + say "building $BUILDER_IMAGE ($BUILDER_BASE_IMAGE)" + "$RUNTIME" build -t "$BUILDER_IMAGE" \ + -f "$ROOT/scripts/linux-arm64/Dockerfile" "$ROOT/scripts/linux-arm64" \ + || fail "failed to build the $BUILDER_BASE_IMAGE builder image" +fi + +# --------------------------------------------------------------- build +OUT_DIR="$WORK/out" +rm -rf "$OUT_DIR"; mkdir -p "$OUT_DIR" + +# --user keeps the AppImage owned by the invoking user instead of root; podman +# maps root in the container to the host user already, so only docker needs it. +user_args=() +if [ "$RUNTIME" = "docker" ]; then + user_args=(--user "$(id -u):$(id -g)") +fi + +say "compiling and packaging inside $BUILDER_BASE_IMAGE" +"$RUNTIME" run --rm ${user_args[@]+"${user_args[@]}"} \ + -e LOVE_VERSION="$LOVE_VERSION" \ + -e SDL2_VERSION="$SDL2_VERSION" \ + -e SDL2_TARBALL="$SDL2_TARBALL" \ + -e OPENAL_VERSION="$OPENAL_VERSION" \ + -e OPENAL_TARBALL="$OPENAL_TARBALL" \ + -e THEORA_VERSION="$THEORA_VERSION" \ + -e THEORA_TARBALL="$THEORA_TARBALL" \ + -e OGG_VERSION="$OGG_VERSION" \ + -e OGG_TARBALL="$OGG_TARBALL" \ + -e VORBIS_VERSION="$VORBIS_VERSION" \ + -e VORBIS_TARBALL="$VORBIS_TARBALL" \ + -e MPG123_VERSION="$MPG123_VERSION" \ + -e MPG123_TARBALL="$MPG123_TARBALL" \ + -e APP_NAME="$APP_NAME" \ + -e VERSION="$VERSION" \ + -v "$CACHE:/cache" \ + -v "$IN_DIR:/in:ro" \ + -v "$OUT_DIR:/out" \ + -v "$ROOT/scripts/linux-arm64:/scripts:ro" \ + "$BUILDER_IMAGE" bash /scripts/build_appimage.sh + +# --------------------------------------------------------------- publish +built="$OUT_DIR/$APP_NAME-$VERSION-linux-arm64.AppImage" +[ -f "$built" ] || fail "container produced no AppImage at $built" + +# The runtime is a static-pie ELF and the payload starts where its section +# headers end; a truncated cat would still be "a file", so prove both halves +# survived before shipping. +head -c 4 "$built" | od -An -tx1 | tr -d ' \n' | grep -q '^7f454c46$' \ + || fail "built AppImage is not an ELF" +e_shoff=$(od -An -j40 -N8 -tu8 "$built" | tr -d ' ') +e_shentsize=$(od -An -j58 -N2 -tu2 "$built" | tr -d ' ') +e_shnum=$(od -An -j60 -N2 -tu2 "$built" | tr -d ' ') +sfs_offset=$((e_shoff + e_shentsize * e_shnum)) +[ "$(dd if="$built" bs=1 skip="$sfs_offset" count=4 2>/dev/null)" = "hsqs" ] \ + || fail "no squashfs payload at offset $sfs_offset (runtime/payload fusion failed)" + +out="$DIST/$(basename "$built")" +rm -f "$out" "$out.sha256" +mv "$built" "$out" +chmod +x "$out" +printf '%s %s\n' "$(sha256_file "$out")" "$(basename "$out")" > "$out.sha256" + +say "Linux arm64 build: $out ($(du -h "$out" | cut -f1))" +say "sha256: $(cut -d' ' -f1 "$out.sha256")" diff --git a/scripts/build_switch.sh b/scripts/build_switch.sh index 9bb2544f..f9c62e05 100755 --- a/scripts/build_switch.sh +++ b/scripts/build_switch.sh @@ -19,19 +19,22 @@ # (gen1recomp.nro + game.love side by side). Requires the pin # (run --fetch first, or combine --fetch --loose). # -# --fused Build a single gen1recomp--switch.nro via nacptool+elf2nro, -# then pack dist/switch/gen1recomp--switch.zip (SD-ready -# tree under switch/gen1recomp/). Uses native tools (PATH or -# $DEVKITPRO/tools/bin) first; else Docker from -# scripts/switch/dkp-docker.image (override GEN1_DKP_IMAGE). -# Requires the pin (run --fetch or combine). GitHub Releases -# publish the zip only; the versioned .nro stays local / PR CI. +# --fused Build fused gen1recomp--switch.nro (game in romfs), native +# OTA launcher NRO, and dual-NRO SD zip (gen1recomp.nro launcher + +# gen1recomp-game.nro under switch/gen1recomp/). The same +# gen1recomp--switch.zip is the OTA download asset. +# Requires DEVKITPRO + switch-dev; OTA launcher via native packages +# (install_devkitpro_deps.sh) or Docker. +# Uses native tools (PATH or $DEVKITPRO/tools/bin) first; else Docker +# from scripts/switch/dkp-docker.image (override GEN1_DKP_IMAGE). +# Requires the pin (run --fetch or combine). GitHub Releases publish +# the zip only; versioned .nro files stay local / PR CI. # # Combinable: --fetch alone, or --fetch with --loose / --fused. -# XOR: --loose and --fused cannot be used together. +# XOR: --loose and --fused cannot be combined with each other. # # Non-goals (never done by this script): -# MTP push, ROM install, dkp-pacman auto-install. +# MTP push, ROM install, dkp-pacman auto-install (use install_devkitpro_deps.sh). set -euo pipefail @@ -49,7 +52,7 @@ say() { printf '\033[1;32m==>\033[0m %s\n' "$*" >&2; } fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } usage() { - sed -n '2,34p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,38p' "$0" | sed 's/^# \{0,1\}//' } while [ $# -gt 0 ]; do @@ -129,11 +132,22 @@ if [ "$LOOSE" -eq 1 ]; then fi if [ "$FUSED" -eq 1 ]; then + # shellcheck source=scripts/switch/common.sh + . "$ROOT/scripts/switch/common.sh" + preflight_fused_build + GAME_LOVE="$(pack_game_love)" OUT_NRO="$DIST/gen1recomp-${VERSION}-switch.nro" OUT_ZIP="$DIST/gen1recomp-${VERSION}-switch.zip" "$ROOT/scripts/switch/build_fused.sh" "$GAME_LOVE" "$VERSION" "$OUT_NRO" - "$ROOT/scripts/switch/pack_sd_zip.sh" "$OUT_NRO" "$VERSION" "$OUT_ZIP" + + LAUNCHER_NRO="$DIST/gen1recomp-${VERSION}-launcher.nro" + say "building native OTA launcher" + "$ROOT/scripts/switch/build_ota_launcher.sh" "$LAUNCHER_NRO" "$VERSION" + say "packing dual-NRO SD zip (also the OTA download asset)" + "$ROOT/scripts/switch/pack_sd_zip.sh" "$OUT_NRO" "$VERSION" "$OUT_ZIP" "$LAUNCHER_NRO" + cp "$OUT_NRO" "$DIST/gen1recomp-${VERSION}-game.nro" + cp "$WORK/build-info.json" "$DIST/gen1recomp-${VERSION}-build-info.json" say "done. See $DIST/" exit 0 diff --git a/scripts/build_xbox_uwp.sh b/scripts/build_xbox_uwp.sh new file mode 100644 index 00000000..11c45f2f --- /dev/null +++ b/scripts/build_xbox_uwp.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Build the Xbox Dev Mode UWP package with the shared game.love payload. +# +# Usage: +# scripts/build_xbox_uwp.sh [--release|--relwithdebinfo] +# [--version X.Y.Z] [--game-love PATH] +# [--publisher SUBJECT] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=xbox-uwp/common.sh +. "$SCRIPT_DIR/xbox-uwp/common.sh" + +CONFIGURATION="Release" +PRESET="uwp-release" +GAME_LOVE="" +VERSION="0.0.0" +VERSION_EXPLICIT=0 +PUBLISHER="${GEN1RECOMP_UWP_PUBLISHER:-CN=Gen1Recomp}" + +while [ $# -gt 0 ]; do + case "$1" in + --release) + CONFIGURATION="Release" + PRESET="uwp-release" + shift + ;; + --relwithdebinfo) + CONFIGURATION="RelWithDebInfo" + PRESET="uwp-relwithdebinfo" + shift + ;; + --game-love) + [ $# -ge 2 ] || fail "--game-love requires a path" + GAME_LOVE="$2" + shift 2 + ;; + --version) + [ $# -ge 2 ] || fail "--version requires X.Y.Z" + VERSION="$2" + VERSION_EXPLICIT=1 + shift 2 + ;; + --publisher) + [ $# -ge 2 ] || fail "--publisher requires a certificate subject" + PUBLISHER="$2" + shift 2 + ;; + -h|--help) + sed -n '2,7p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) fail "unknown argument: $1" ;; + esac +done + +case "$(uname -s)" in + MINGW*|MSYS*|CYGWIN*) ;; + *) fail "Xbox UWP packages must be built from Git Bash on Windows" ;; +esac + +require_command cmake +require_command cygpath +require_command powershell.exe +require_command unzip +printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' \ + || fail "invalid version '$VERSION' (expected X.Y.Z)" + +if [ -z "$GAME_LOVE" ]; then + require_command zip + mkdir -p "$WORK" + GAME_LOVE="$WORK/game.love" + pack_args=( + --output "$GAME_LOVE" \ + --listing "$WORK/love-listing.txt" + ) + if [ "$VERSION_EXPLICIT" -eq 1 ]; then + pack_args+=(--version "$VERSION") + fi + "$ROOT/scripts/pack_love.sh" "${pack_args[@]}" +else + GAME_LOVE="$(cd "$(dirname "$GAME_LOVE")" && pwd)/$(basename "$GAME_LOVE")" + [ -f "$GAME_LOVE" ] || fail "missing game.love: $GAME_LOVE" + "$ROOT/scripts/switch/verify_payload.sh" "$GAME_LOVE" + if [ "$VERSION_EXPLICIT" -eq 1 ]; then + version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" + unzip -p "$GAME_LOVE" src/core/Version.lua \ + | grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \ + || fail "game.love does not report engine $VERSION" + fi +fi + +say "configuring Xbox UWP ($CONFIGURATION)" +cmake --preset "$PRESET" -S "$(windows_path "$UWP_ROOT")" \ + "-DGEN1RECOMP_LOVE:FILEPATH=$(windows_path "$GAME_LOVE")" \ + "-DGEN1RECOMP_VERSION:STRING=$VERSION" \ + "-DGEN1RECOMP_UWP_PUBLISHER:STRING=$PUBLISHER" + +say "building Xbox UWP ($CONFIGURATION)" +(cd "$UWP_ROOT" && cmake --build --preset "$PRESET") + +build_info="$WORK/xbox-uwp-build-info.json" +bash "$SCRIPT_DIR/xbox-uwp/write_build_info.sh" \ + "$build_info" "$VERSION" "$CONFIGURATION" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File \ + "$(windows_path "$SCRIPT_DIR/xbox-uwp/stage_release.ps1")" \ + -Version "$VERSION" \ + -Configuration "$CONFIGURATION" \ + -BuildInfo "$(windows_path "$build_info")" + +say "done. See dist/xbox-uwp/" diff --git a/scripts/linux-arm64/Dockerfile b/scripts/linux-arm64/Dockerfile new file mode 100644 index 00000000..ab12a3b7 --- /dev/null +++ b/scripts/linux-arm64/Dockerfile @@ -0,0 +1,45 @@ +# Build environment for the aarch64 Linux AppImage. +# +# Debian bullseye on purpose: it ships glibc 2.31, the oldest runtime we +# promise to support. Everything linked here therefore runs on bullseye and +# every later distro (glibc is backward compatible, not forward), which is +# what makes the resulting AppImage portable across Raspberry Pi OS, Armbian, +# Ubuntu 20.04+, and the aarch64 handheld distros. +# +# This image is arch-native: build it on an aarch64 host (Raspberry Pi 5, +# ubuntu-24.04-arm runner, Apple Silicon Docker) — no qemu emulation. +FROM debian:bullseye + +ENV DEBIAN_FRONTEND=noninteractive + +# build-essential/autoconf: LÖVE 11.5's linux-src tarball is autotools. +# squashfs-tools: packs the AppDir into the AppImage payload. +# +# Note what is deliberately ABSENT: libsdl2-dev, libtheora-dev and +# libopenal-dev. All three are built from source instead (see common.sh for +# why), and having Debian's copies installed would let pkg-config hand LÖVE's +# configure the system ones and silently undo it. +# +# The remaining lib*-dev set is LÖVE's optional-module surface. A missing one +# does not fail configure, it silently drops a module (love.sound decoders, +# love.font, love.video), so they are pinned here deliberately and asserted +# after the build. +# +# The X11/Wayland/audio -dev packages are here for SDL2's *build*, not for +# runtime linkage: SDL detects each backend at compile time and then dlopens +# it, so these headers decide which backends exist at all while adding no +# DT_NEEDED entry to the shipped library. +RUN apt-get update -qq \ + && apt-get install -y --no-install-recommends \ + build-essential pkg-config autoconf automake libtool cmake \ + ca-certificates curl file xz-utils bzip2 zip unzip squashfs-tools \ + libogg-dev libvorbis-dev \ + libmodplug-dev libmpg123-dev libfreetype6-dev libluajit-5.1-dev \ + zlib1g-dev libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev \ + libasound2-dev libpulse-dev libudev-dev libdbus-1-dev \ + libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev \ + libxinerama-dev libxss-dev libxkbcommon-dev \ + libwayland-dev wayland-protocols libdrm-dev libgbm-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /work diff --git a/scripts/linux-arm64/build_appimage.sh b/scripts/linux-arm64/build_appimage.sh new file mode 100755 index 00000000..f5bcdbcc --- /dev/null +++ b/scripts/linux-arm64/build_appimage.sh @@ -0,0 +1,428 @@ +#!/usr/bin/env bash +# Compiles LÖVE for aarch64 and fuses game.love into a self-contained +# AppImage. Runs INSIDE the Debian bullseye container from Dockerfile -- +# scripts/build_linux_arm64.sh is the entry point on the host. +# +# Mounts the host provides: +# /cache pinned downloads + the compiled LÖVE prefix (persists between runs) +# /in read-only inputs: game.love, icon.png +# /out the finished AppImage lands here +# +# Environment: +# LOVE_VERSION, APP_NAME, VERSION passed through from the host script +# JOBS make -j (defaults to nproc) + +set -euo pipefail + +LOVE_VERSION="${LOVE_VERSION:?}" +SDL2_VERSION="${SDL2_VERSION:?}" +SDL2_TARBALL="${SDL2_TARBALL:?}" +OPENAL_VERSION="${OPENAL_VERSION:?}" +OPENAL_TARBALL="${OPENAL_TARBALL:?}" +THEORA_VERSION="${THEORA_VERSION:?}" +THEORA_TARBALL="${THEORA_TARBALL:?}" +OGG_VERSION="${OGG_VERSION:?}" +OGG_TARBALL="${OGG_TARBALL:?}" +VORBIS_VERSION="${VORBIS_VERSION:?}" +VORBIS_TARBALL="${VORBIS_TARBALL:?}" +MPG123_VERSION="${MPG123_VERSION:?}" +MPG123_TARBALL="${MPG123_TARBALL:?}" +APP_NAME="${APP_NAME:?}" +VERSION="${VERSION:?}" +JOBS="${JOBS:-$(nproc)}" + +CACHE="/cache" +IN="/in" +OUT="/out" +WORK="/tmp/build" + +say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } + +mkdir -p "$WORK" + +# --------------------------------------------------------------- prefix +# Everything we compile lands in one prefix, cached because the compiling is +# the only slow part (~5 min cold on a Pi 5) and is identical for every game +# version. The key includes every source version, so bumping any of them +# invalidates the cache instead of silently reusing a stale mix. +PREFIX="$CACHE/prefix-love$LOVE_VERSION-sdl$SDL2_VERSION-al$OPENAL_VERSION-theora$THEORA_VERSION-ogg$OGG_VERSION-vorbis$VORBIS_VERSION-mpg$MPG123_VERSION" +export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig" +# Our own libraries must win over the system ones during LÖVE's configure and +# link, or the whole point of building them is lost. +export LD_LIBRARY_PATH="$PREFIX/lib" + +# ------------------------------------------------------ compile audio codecs +# Ordered by dependency: vorbis needs ogg, and theora needs ogg too. All three +# are small, plain autotools builds -- well under a minute each. +build_autotools() { # $1 = label $2 = version $3 = tarball $4 = probe lib $5.. = configure args + local label="$1" version="$2" tarball="$3" probe="$4"; shift 4 + if [ -f "$PREFIX/lib/$probe" ]; then + say "reusing cached $label $version" + return 0 + fi + say "compiling $label $version" + local src="$WORK/$label-src" + rm -rf "$src"; mkdir -p "$src" + case "$tarball" in + *.tar.bz2) tar -xjf "$CACHE/$tarball" -C "$src" --strip-components=1 ;; + *) tar -xzf "$CACHE/$tarball" -C "$src" --strip-components=1 ;; + esac + ( + cd "$src" + # Several of these tarballs predate aarch64's entry in config.guess; the + # distro's copies know about it, so refresh them or configure bails out + # with "cannot guess build type". + for helper in config.guess config.sub; do + [ -f "$helper" ] && cp "/usr/share/misc/$helper" . 2>/dev/null + done + ./configure --prefix="$PREFIX" --disable-static "$@" >/dev/null + make -j"$JOBS" >/dev/null + make install >/dev/null + ) +} + +build_autotools ogg "$OGG_VERSION" "$OGG_TARBALL" libogg.so.0 +build_autotools vorbis "$VORBIS_VERSION" "$VORBIS_TARBALL" libvorbis.so.0 +# mpg123's ports/ tree and the command-line player are irrelevant here; only +# libmpg123 gets linked, and --disable-modules keeps the output-backend +# plugins (and their dlopen of ALSA/pulse) out of the shipped library. +build_autotools mpg123 "$MPG123_VERSION" "$MPG123_TARBALL" libmpg123.so.0 \ + --disable-modules --with-audio=dummy --disable-lfs-alias + +# The symbol that was missing when this was bullseye's copy. Assert it, so a +# version bump that quietly regresses below the host's expectations fails the +# build instead of silently killing audio again. +objdump -T "$PREFIX/lib/libmpg123.so.0" | grep -q 'mpg123_info2' \ + || fail "bundled libmpg123 lacks mpg123_info2; the host's libsndfile will fail to relocate" + +# ------------------------------------------------------------ compile SDL2 +# --enable-*-shared (the defaults, made explicit so a future SDL release +# cannot flip them under us) is the entire reason this is built from source: +# each backend is dlopened at runtime rather than becoming a DT_NEEDED entry, +# so the AppImage starts on a host with only ALSA, or only Wayland, or only +# KMSDRM, instead of demanding all of them at once the way Debian's build does. +if [ -f "$PREFIX/lib/libSDL2-2.0.so.0" ]; then + say "reusing cached SDL2 $SDL2_VERSION" +else + say "compiling SDL2 $SDL2_VERSION (jobs: $JOBS)" + rm -rf "$WORK/sdl-src"; mkdir -p "$WORK/sdl-src" + tar -xzf "$CACHE/$SDL2_TARBALL" -C "$WORK/sdl-src" --strip-components=1 + ( + cd "$WORK/sdl-src" + ./configure --prefix="$PREFIX" --disable-static \ + --enable-alsa --enable-alsa-shared \ + --enable-pulseaudio --enable-pulseaudio-shared \ + --enable-video-x11 --enable-x11-shared \ + --enable-video-wayland --enable-wayland-shared \ + --enable-video-kmsdrm --enable-kmsdrm-shared \ + --enable-libudev --disable-sndio --disable-jack --disable-esd \ + --disable-arts --disable-nas --disable-oss >/dev/null + make -j"$JOBS" >/dev/null + make install >/dev/null + ) +fi + +# Prove the dlopen intent actually took. If SDL ever hard-links an audio or +# video backend again, the AppImage silently regains a startup dependency on +# the host having that exact stack -- which is the bug this replaced. +sdl_lib="$PREFIX/lib/libSDL2-2.0.so.0" +[ -f "$sdl_lib" ] || fail "SDL2 build produced no libSDL2-2.0.so.0" +for forbidden in libpulse libasound libX11 libwayland libdrm libgbm libsndio; do + if objdump -p "$sdl_lib" | grep -q "NEEDED.*$forbidden"; then + fail "SDL2 hard-links $forbidden; it must dlopen its backends (--enable-*-shared)" + fi +done + +# ---------------------------------------------------- compile openal-soft +# ALSOFT_DLOPEN keeps the ALSA and PulseAudio backends behind dlopen, and +# sndio is switched off outright -- Debian enables it, which is what chained +# libopenal -> libsndio -> libasound into a mandatory startup dependency. +if [ -f "$PREFIX/lib/libopenal.so.1" ]; then + say "reusing cached openal-soft $OPENAL_VERSION" +else + say "compiling openal-soft $OPENAL_VERSION (jobs: $JOBS)" + rm -rf "$WORK/openal-src"; mkdir -p "$WORK/openal-src" + tar -xzf "$CACHE/$OPENAL_TARBALL" -C "$WORK/openal-src" --strip-components=1 + ( + cd "$WORK/openal-src" + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DALSOFT_DLOPEN=ON \ + -DALSOFT_BACKEND_SNDIO=OFF \ + -DALSOFT_BACKEND_OSS=OFF \ + -DALSOFT_BACKEND_JACK=OFF \ + -DALSOFT_EXAMPLES=OFF \ + -DALSOFT_UTILS=OFF \ + -DALSOFT_TESTS=OFF \ + -DLIBTYPE=SHARED >/dev/null + cmake --build build -j"$JOBS" >/dev/null + cmake --install build >/dev/null + ) +fi + +openal_lib="$PREFIX/lib/libopenal.so.1" +[ -f "$openal_lib" ] || fail "openal-soft build produced no libopenal.so.1" +for forbidden in libsndio libasound libpulse libjack; do + if objdump -p "$openal_lib" | grep -q "NEEDED.*$forbidden"; then + fail "openal hard-links $forbidden; backends must stay behind dlopen" + fi +done + +# --------------------------------------------------- compile libtheora +# --disable-examples is what drops Debian's libcairo link (and with it libX11, +# libxcb, libfontconfig and libfreetype as startup dependencies). The encoder +# is dead weight for a player, but libtheoradec is what LÖVE actually links. +if [ -f "$PREFIX/lib/libtheoradec.so.1" ]; then + say "reusing cached libtheora $THEORA_VERSION" +else + say "compiling libtheora $THEORA_VERSION" + rm -rf "$WORK/theora-src"; mkdir -p "$WORK/theora-src" + tar -xjf "$CACHE/$THEORA_TARBALL" -C "$WORK/theora-src" --strip-components=1 + ( + cd "$WORK/theora-src" + # theora 1.1.1 predates the aarch64 config.guess, so refresh the autotools + # helper scripts or configure rejects the host outright. + for helper in config.guess config.sub; do + cp "/usr/share/misc/$helper" . 2>/dev/null || true + done + ./configure --prefix="$PREFIX" --disable-static \ + --disable-examples --disable-spec --disable-doc >/dev/null + make -j"$JOBS" >/dev/null + make install >/dev/null + ) +fi + +theora_lib="$PREFIX/lib/libtheoradec.so.1" +[ -f "$theora_lib" ] || fail "libtheora build produced no libtheoradec.so.1" +if objdump -p "$theora_lib" | grep -q "NEEDED.*libcairo"; then + fail "libtheoradec still links libcairo (--disable-examples stopped working)" +fi + +# ------------------------------------------------------------ compile LÖVE +if [ -x "$PREFIX/bin/love" ] && [ -f "$PREFIX/lib/liblove-$LOVE_VERSION.so" ]; then + say "reusing cached LÖVE $LOVE_VERSION aarch64 build" +else + say "compiling LÖVE $LOVE_VERSION for aarch64 (jobs: $JOBS)" + rm -rf "$WORK/love-src" + mkdir -p "$WORK/love-src" + tar -xzf "$CACHE/love-$LOVE_VERSION-linux-src.tar.gz" \ + -C "$WORK/love-src" --strip-components=1 + ( + cd "$WORK/love-src" + # No --disable-* flags on purpose: configure silently drops a love module + # when its -dev package is absent, so the Dockerfile pins the full set and + # the assertions below prove each one actually linked. CPPFLAGS/LDFLAGS + # point at our prefix so the SDL2 and theora just built above win over + # anything the base image might still provide. + ./configure --prefix="$PREFIX" --disable-static \ + CPPFLAGS="-I$PREFIX/include" LDFLAGS="-L$PREFIX/lib" >/dev/null + make -j"$JOBS" >/dev/null + make install >/dev/null + # Keep LÖVE's license inside the cached prefix: the unpacked source tree + # is thrown away, so a later cache-hit run would otherwise have nothing + # to ship and the AppImage would go out without its engine license. + cp license.txt "$PREFIX/license.txt" + ) +fi + +love_bin="$PREFIX/bin/love" +love_lib="$PREFIX/lib/liblove-$LOVE_VERSION.so" +[ -x "$love_bin" ] || fail "LÖVE build produced no bin/love" +[ -f "$love_lib" ] || fail "LÖVE build produced no lib/liblove-$LOVE_VERSION.so" +file "$love_bin" | grep -q 'ARM aarch64' \ + || fail "built love is not an aarch64 ELF (got: $(file -b "$love_bin"))" + +# A configure run that lost an optional dependency still exits 0 and still +# builds -- the loss only shows up as a missing love module at runtime, i.e. +# in a shipped artifact. Assert the decoder/font/video libs really linked. +for soname in libSDL2-2.0.so.0 libopenal.so.1 libfreetype.so.6 \ + libmodplug.so.1 libmpg123.so.0 libvorbisfile.so.3 \ + libtheoradec.so.1 libluajit-5.1.so.2; do + objdump -p "$love_lib" | grep -q "NEEDED.*$soname" \ + || fail "liblove is not linked against $soname (a -dev package went missing)" +done + +# --------------------------------------------------------------- AppDir +# Layout mirrors LÖVE's own x86_64 AppImage exactly (bin/ lib/ share/ at the +# AppDir root, not usr/-prefixed), so the AppRun contract below -- and the +# FUSE_PATH fusion scripts/build.sh performs on the x86_64 image -- stay the +# same idea on both architectures. +APPDIR="$WORK/AppDir" +rm -rf "$APPDIR" +mkdir -p "$APPDIR/bin" "$APPDIR/lib" "$APPDIR/share" + +cp "$love_bin" "$APPDIR/bin/love" +chmod +x "$APPDIR/bin/love" + +# ------------------------------------------------------ bundle dependencies +# Walk the DT_NEEDED graph from love + liblove, copying in everything that is +# not host-provided. Recursion stops at excluded libraries, so the driver and +# session subtrees behind SDL2 are never pulled in. +# +# Three reasons a library MUST come from the host, and every entry below is +# one of them: +# +# 1. Driver/session coupled. A bundled libGL would bypass Mesa's V3D driver +# on the Pi; a bundled libpulse/libdbus would fight the user's running +# session. GL/EGL/gbm/drm, X11/xcb/wayland/xkbcommon, dbus, pulse, alsa, +# systemd/udev. Note that after the source builds above, none of these are +# DT_NEEDED of anything we ship -- SDL2 and OpenAL dlopen them, so they are +# used when present and skipped when absent. +# +# 2. Loader coupled. glibc's pieces cannot be mixed with the host's ld.so at +# all, and libstdc++/libgcc_s must be at least as new as the compiler -- +# bullseye's gcc 10 is older than any supported host's, so the host copy +# always satisfies us. +# +# 3. The font/compression stack: freetype, fontconfig, libpng, brotli, zlib. +# These are shared with whatever the host's own graphics libraries have +# already loaded, and mixing vintages inside one process breaks the older +# copy. Bundling a bullseye freetype 2.10.4 is what made a host cairo fail +# to find FT_Get_Transform (added in 2.11) and killed the game at startup. +# Leaving the whole stack to the host keeps it self-consistent, and +# liblove -- compiled against 2.10.4 -- only ever asks for symbols every +# supported host already has. +EXCLUDE_RE='^(ld-linux-aarch64\.so\.1|libc\.so\.6|libm\.so\.6|libdl\.so\.2|libpthread\.so\.0|librt\.so\.1|libresolv\.so\.2|libutil\.so\.1|libanl\.so\.1|libnsl\.so\.[0-9]+|libstdc\+\+\.so\.6|libgcc_s\.so\.1|lib(GL|GLX|GLdispatch|OpenGL|EGL|GLESv[12]|glapi|gbm|drm)\..*|libX[a-z0-9]*\..*|libxcb.*|libwayland-.*|libxkbcommon.*|libdbus-1\..*|libpulse.*|libasound\..*|libsndfile\..*|libFLAC\..*|libopus\..*|libsystemd\..*|libudev\..*|libselinux\..*|libcap\..*|libgcrypt\..*|libgpg-error\..*|liblzma\..*|libzstd\..*|liblz4\..*|libffi\..*|libexpat\..*|libbsd\..*|libmd\..*|libuuid\..*|libg(lib|object|module|thread)-2\..*|libfontconfig\..*|libfreetype\..*|libpng[0-9]*\..*|libbrotli.*|libz\.so\..*|libwrap\..*|libasyncns\..*|libtirpc\..*|lib(gssapi_krb5|krb5|k5crypto|com_err|krb5support|keyutils)\..*|libpcre.*)$' + +# soname -> absolute path, harvested from the full ldd closure of both roots. +declare -A RESOLVED=() +while read -r soname _arrow path _addr; do + [ -n "${path:-}" ] || continue + [ -e "$path" ] || continue + RESOLVED["$soname"]="$path" +done < <(ldd "$love_bin" "$love_lib" | awk '/=>/ {print $1, $2, $3, $4}') + +declare -A BUNDLED=() +bundle_needed() { # $1 = ELF whose DT_NEEDED entries to walk + local soname target + while read -r soname; do + [ -n "$soname" ] || continue + if [[ "$soname" =~ $EXCLUDE_RE ]]; then continue; fi + if [ -n "${BUNDLED[$soname]:-}" ]; then continue; fi + target="${RESOLVED[$soname]:-}" + [ -n "$target" ] || fail "cannot resolve $soname (needed by $(basename "$1"))" + # Copy dereferenced and under the soname: the AppDir must not depend on + # the builder's libSDL2-2.0.so.0 -> libSDL2-2.0.so.0.14.0 symlink chain. + cp -L "$target" "$APPDIR/lib/$soname" + chmod 0644 "$APPDIR/lib/$soname" + BUNDLED["$soname"]=1 + bundle_needed "$APPDIR/lib/$soname" + done < <(objdump -p "$1" | awk '/NEEDED/ {print $2}') +} + +say "bundling shared libraries" +cp "$love_lib" "$APPDIR/lib/liblove-$LOVE_VERSION.so" +chmod 0644 "$APPDIR/lib/liblove-$LOVE_VERSION.so" +BUNDLED["liblove-$LOVE_VERSION.so"]=1 +bundle_needed "$APPDIR/bin/love" +bundle_needed "$APPDIR/lib/liblove-$LOVE_VERSION.so" +say "bundled $(ls "$APPDIR/lib" | wc -l) libraries: $(ls "$APPDIR/lib" | tr '\n' ' ')" + +# ------------------------------------------------- host dependency contract +# The portability promise, stated as an assertion instead of a paragraph in a +# README: these are the ONLY sonames the shipped objects may require from the +# host. Everything driver-, session- or audio-related has to be reached +# through dlopen, so the AppImage starts on a box with no PulseAudio, no X11 +# or no ALSA and simply uses whatever it does find. +# +# The original build failed exactly here and nobody noticed until CI ran on a +# headless runner: Debian's SDL2 hard-links libpulse/libasound/libX11/ +# libwayland, so the image only ever started on a full desktop. +HOST_ALLOWED_RE='^(ld-linux-aarch64\.so\.1|libc\.so\.6|libm\.so\.6|libdl\.so\.2|libpthread\.so\.0|librt\.so\.1|libstdc\+\+\.so\.6|libgcc_s\.so\.1|libatomic\.so\.1|libfreetype\.so\.6|libpng[0-9]*\.so\.[0-9]+|libz\.so\.1|libbrotli(dec|common)\.so\.1)$' + +unexpected="" +for object in "$APPDIR/bin/love" "$APPDIR"/lib/*.so*; do + while read -r soname; do + [ -n "$soname" ] || continue + # Satisfied from inside the AppDir, so not a host requirement at all. + if [ -n "${BUNDLED[$soname]:-}" ]; then continue; fi + if [[ "$soname" =~ $HOST_ALLOWED_RE ]]; then continue; fi + unexpected="$unexpected $(basename "$object") -> $soname"$'\n' + done < <(objdump -p "$object" | awk '/NEEDED/ {print $2}') +done +[ -z "$unexpected" ] || fail "$(printf '%s\n%s' \ + "these objects hard-require host libraries outside the allowed set (they must be dlopened, not linked):" \ + "$unexpected")" +say "host dependency contract holds (glibc, libstdc++ and the font stack only)" + +# LÖVE loads jit.* (jit.status, the profiler) through LUA_PATH; without these +# the modules are simply absent, so ship them the way upstream's image does. +jit_share="$(ls -d /usr/share/luajit-* 2>/dev/null | head -1)" +[ -n "$jit_share" ] || fail "luajit jit/*.lua modules not found under /usr/share" +LUAJIT_SHARE_DIR="$(basename "$jit_share")" +mkdir -p "$APPDIR/share/$LUAJIT_SHARE_DIR" "$APPDIR/share/lua/5.1" "$APPDIR/lib/lua/5.1" +cp -R "$jit_share/jit" "$APPDIR/share/$LUAJIT_SHARE_DIR/" + +# --------------------------------------------------------------- branding +cp "$IN/game.love" "$APPDIR/game.love" +# The .desktop's Icon= resolves against the AppDir root by basename, and +# .DirIcon is what appimaged and file-manager thumbnailers read. +cp "$IN/icon.png" "$APPDIR/$APP_NAME.png" +cp "$IN/icon.png" "$APPDIR/.DirIcon" + +cat > "$APPDIR/$APP_NAME.desktop" < "$APPDIR/AppRun" <. gzip at 128K blocks matches what +# LÖVE's official image uses and what every type-2 runtime can read; zstd would +# be smaller but is not universally supported by older runtimes users may have +# registered through appimaged. +say "packing squashfs" +sfs="$WORK/payload.squashfs" +rm -f "$sfs" +mksquashfs "$APPDIR" "$sfs" \ + -comp gzip -b 131072 -noappend -all-root -no-xattrs -quiet >/dev/null + +out="$OUT/$APP_NAME-$VERSION-linux-arm64.AppImage" +rm -f "$out" +cat "$CACHE/runtime-aarch64" "$sfs" > "$out" +chmod +x "$out" + +say "AppImage: $(basename "$out") ($(du -h "$out" | cut -f1))" diff --git a/scripts/linux-arm64/common.sh b/scripts/linux-arm64/common.sh new file mode 100755 index 00000000..292f1416 --- /dev/null +++ b/scripts/linux-arm64/common.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Shared helpers and pins for the aarch64 Linux AppImage build. +# Source from other scripts: . "$(dirname "$0")/common.sh" + +# shellcheck disable=SC2034 +if [ -z "${ROOT:-}" ]; then + ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +fi +export ROOT + +# ---------------------------------------------------------------- pins +# LÖVE ships no aarch64 binary of any kind -- the 11.5 release has win32/win64, +# macOS, Android, iOS and an x86_64 AppImage, and that is the whole list. So +# this port compiles the official linux-src tarball instead of unpacking a +# prebuilt image the way scripts/build.sh does for x86_64. +LOVE_VERSION="11.5" +LOVE_SRC_TARBALL="love-$LOVE_VERSION-linux-src.tar.gz" +LOVE_SRC_URL="https://github.com/love2d/love/releases/download/$LOVE_VERSION/$LOVE_SRC_TARBALL" +LOVE_SRC_SHA256="066e0843f71aa9fd28b8eaf27d41abb74bfaef7556153ac2e3cf08eafc874c39" + +# SDL2 is built from source rather than taken from bullseye, and this is a +# correctness requirement, not a version preference. Debian's libSDL2 lists +# libpulse, libasound, libX11 and libwayland-client as DT_NEEDED -- hard links +# resolved by the loader at startup -- so an AppImage bundling it refuses to +# launch unless the host has ALL FOUR installed. That is wrong for an artifact +# whose whole job is to run on arbitrary arm64 systems: an ALSA-only handheld +# or a minimal Wayland box would die before main(). Built from source, SDL +# defaults to dlopening every audio and video backend (--enable-*-shared), so +# it loads whichever the host actually has and degrades gracefully. +# The newer version is a bonus: 2.30 has a far better controller database and +# real KMSDRM support, both of which matter on Pi-class and handheld hardware. +SDL2_VERSION="2.30.12" +SDL2_TARBALL="SDL2-$SDL2_VERSION.tar.gz" +SDL2_URL="https://github.com/libsdl-org/SDL/releases/download/release-$SDL2_VERSION/$SDL2_TARBALL" +SDL2_SHA256="ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea" + +# libtheora likewise. Debian's libtheoradec.so.1 is linked against libcairo -- +# a packaging artifact, since a video decoder has no business drawing vector +# graphics -- and cairo drags in libX11, libxcb, libfontconfig and libfreetype +# as hard dependencies. LOVE needs theora for love.video, so that link would +# put the entire X11 and font stack on the critical path at startup, and it is +# what caused the FT_Get_Transform crash this build hit on a trixie host. +# Upstream's tarball with --disable-examples produces a libtheoradec that +# needs only libogg. +THEORA_VERSION="1.1.1" +THEORA_TARBALL="libtheora-$THEORA_VERSION.tar.bz2" +THEORA_URL="https://downloads.xiph.org/releases/theora/$THEORA_TARBALL" +THEORA_SHA256="b6ae1ee2fa3d42ac489287d3ec34c5885730b1296f0801ae577a35193d3affbc" + +# OpenAL for the same reason as SDL2, one level down. Debian's libopenal is +# openal-soft built with the sndio backend enabled, so it hard-links +# libsndio, which itself hard-links libasound -- reintroducing exactly the +# mandatory-ALSA dependency the SDL2 source build exists to remove. Upstream +# openal-soft dlopens its backends, so building it here leaves the shipped +# library with no audio-stack dependency at all. +OPENAL_VERSION="1.23.1" +OPENAL_TARBALL="openal-soft-$OPENAL_VERSION.tar.gz" +OPENAL_URL="https://github.com/kcat/openal-soft/archive/refs/tags/$OPENAL_VERSION.tar.gz" +OPENAL_SHA256="dfddf3a1f61059853c625b7bb03de8433b455f2f79f89548cbcbd5edca3d4a4a" + +# The audio codecs are built from source for a third, different reason: SONAME +# collision with the host's audio stack. +# +# OpenAL dlopens ALSA, ALSA's config loads its PulseAudio hook plugin, and that +# plugin pulls the HOST's libsndfile into our process. libsndfile links +# libogg, libvorbis and libmpg123 -- the same three we bundle. The loader +# resolves a SONAME once per process, so the host's libsndfile binds to OUR +# copies, and a bullseye libmpg123 has no mpg123_info2 (added in 1.32): +# +# openal -> libasound -> libasound_module_conf_pulse -> libsndfile (host) +# `-> mpg123_info2 -> libmpg123 (ours, bullseye) +# +# which failed to relocate and left the game with no audio device at all. +# Not bundling them instead would make libogg/libvorbis/libmpg123 mandatory +# host packages; building them current means our copies satisfy the host's +# libsndfile rather than starving it. libvorbisfile ships in the vorbis +# tarball. +OGG_VERSION="1.3.5" +OGG_TARBALL="libogg-$OGG_VERSION.tar.gz" +OGG_URL="https://downloads.xiph.org/releases/ogg/$OGG_TARBALL" +OGG_SHA256="0eb4b4b9420a0f51db142ba3f9c64b333f826532dc0f48c6410ae51f4799b664" + +VORBIS_VERSION="1.3.7" +VORBIS_TARBALL="libvorbis-$VORBIS_VERSION.tar.gz" +VORBIS_URL="https://downloads.xiph.org/releases/vorbis/$VORBIS_TARBALL" +VORBIS_SHA256="0e982409a9c3fc82ee06e08205b1355e5c6aa4c36bca58146ef399621b0ce5ab" + +MPG123_VERSION="1.32.10" +MPG123_TARBALL="mpg123-$MPG123_VERSION.tar.bz2" +MPG123_URL="https://www.mpg123.de/download/$MPG123_TARBALL" +MPG123_SHA256="87b2c17fe0c979d3ef38eeceff6362b35b28ac8589fbf1854b5be75c9ab6557c" + +# AppImage type-2 runtime: the ~900 KB static-pie ELF that gets prepended to +# the squashfs payload. Pinned to a dated tag, never "continuous", so a +# rebuild months from now produces the same bytes. +APPIMAGE_RUNTIME_TAG="20251108" +APPIMAGE_RUNTIME_NAME="runtime-aarch64" +APPIMAGE_RUNTIME_URL="https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_TAG/$APPIMAGE_RUNTIME_NAME" +APPIMAGE_RUNTIME_SHA256="00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444" + +# Debian bullseye (glibc 2.31) is the compile environment, NOT a statement +# about where the artifact runs. glibc is backward compatible but not forward +# compatible, so linking against the oldest glibc we support is what lets one +# AppImage cover Raspberry Pi OS bullseye/bookworm/trixie, Ubuntu 20.04+ and +# the aarch64 handheld distros. Building on a newer base would silently +# restrict the artifact to that base and newer. +BUILDER_BASE_IMAGE="debian:bullseye" +BUILDER_IMAGE="${GEN1_LINUX_ARM64_IMAGE:-gen1recomp-linux-arm64-builder}" + +APP_NAME="gen1recomp" + +say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; } +fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } + +# Print SHA-256 hex digest of PATH. Prefers sha256sum, falls back to shasum +# (same order-agnostic pair scripts/switch/common.sh uses). +sha256_file() { + local path="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{print $1}' + else + fail "need sha256sum or shasum (install coreutils)" + fi +} + +# download_pinned URL DEST EXPECTED_SHA256 +# +# A cache hit is only trusted if it still hashes to the pin: a download +# truncated by a network drop would otherwise be reused forever, which is the +# same trap scripts/build.sh guards for the win64 zip and the x86_64 AppImage. +download_pinned() { + local url="$1" dest="$2" want="$3" got="" + if [ -f "$dest" ]; then + got="$(sha256_file "$dest")" + if [ "$got" = "$want" ]; then + return 0 + fi + warn "cached $(basename "$dest") has the wrong digest, re-downloading" + rm -f "$dest" + fi + say "downloading $(basename "$dest")" + # --retry-all-errors because plain --retry skips TLS handshake failures, + # which is how xiph.org drops these tarballs; the pin below still gates it. + curl -fL --retry 5 --retry-delay 2 --retry-all-errors --progress-bar \ + "$url" -o "$dest.tmp" || fail "download failed: $url" + got="$(sha256_file "$dest.tmp")" + [ "$got" = "$want" ] || fail "$(printf '%s\n expected %s\n got %s' \ + "checksum mismatch for $(basename "$dest")" "$want" "$got")" + mv "$dest.tmp" "$dest" +} + +# Echo the container runtime to use: docker, else podman. +container_runtime() { + if [ -n "${GEN1_CONTAINER_RUNTIME:-}" ]; then + printf '%s' "$GEN1_CONTAINER_RUNTIME" + return 0 + fi + if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then + printf 'docker' + elif command -v podman >/dev/null 2>&1; then + printf 'podman' + else + return 1 + fi +} + +fail_need_container() { + fail "$(cat <<'EOF' +the aarch64 AppImage is compiled inside a Debian bullseye container and needs +docker or podman on an aarch64 host. + + Raspberry Pi OS / Debian / Ubuntu: sudo apt install docker.io && sudo usermod -aG docker "$USER" + Fedora / Asahi: sudo dnf install podman + macOS (Apple Silicon): brew install --cask docker + +Override the runtime with GEN1_CONTAINER_RUNTIME=podman. +See docs/linux-arm64-build.md. +EOF +)" +} diff --git a/scripts/linux-arm64/selftest_build_linux_arm64.sh b/scripts/linux-arm64/selftest_build_linux_arm64.sh new file mode 100755 index 00000000..81ac3008 --- /dev/null +++ b/scripts/linux-arm64/selftest_build_linux_arm64.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# Offline checks for the aarch64 Linux AppImage build. +# +# Runs anywhere -- no container, no network, no aarch64 host -- so PR CI can +# gate the parts of this build that do not need three minutes of compiling. +# The real build is exercised separately by the linux-arm64-build job. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=common.sh +. "$SCRIPT_DIR/common.sh" + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1" +} +require_command unzip +require_command zip + +say "checking shell entry points" +bash -n "$ROOT/scripts/build_linux_arm64.sh" "$SCRIPT_DIR"/*.sh +help="$(bash "$ROOT/scripts/build_linux_arm64.sh" --help)" +printf '%s' "$help" | grep -q -- '--version X.Y.Z' \ + || fail "build help does not document --version" +printf '%s' "$help" | grep -q 'linux-arm64\.AppImage' \ + || fail "build help does not name the artifact it produces" + +say "checking the host-architecture guard" +# The guard is what stops someone from kicking off a qemu-emulated build that +# takes hours and miscompiles LuaJIT. Prove it fires rather than trusting it. +# The guard is what stops someone from kicking off a qemu-emulated build that +# takes hours and has miscompiled LuaJIT before. Prove it fires by shadowing +# uname, rather than trusting the branch is reachable. +fake_bin="$(mktemp -d "${TMPDIR:-/tmp}/gen1recomp-fake-uname.XXXXXX")" +printf '#!/bin/sh\necho x86_64\n' > "$fake_bin/uname" +chmod +x "$fake_bin/uname" +guard_out="$(PATH="$fake_bin:$PATH" \ + bash "$ROOT/scripts/build_linux_arm64.sh" --version 0.0.0 2>&1 || true)" +rm -rf "$fake_bin" +printf '%s' "$guard_out" | grep -q 'aarch64 host' \ + || fail "build script does not refuse to run on a non-aarch64 host" + +say "checking pinned inputs" +# Pins must be real digests, and the AppImage runtime must come from a dated +# tag: "continuous" is a moving target and would make rebuilds unreproducible. +for pin_name in LOVE_SRC_SHA256 SDL2_SHA256 OPENAL_SHA256 THEORA_SHA256 \ + OGG_SHA256 VORBIS_SHA256 MPG123_SHA256 APPIMAGE_RUNTIME_SHA256; do + pin_value="${!pin_name}" + printf '%s' "$pin_value" | grep -Eq '^[0-9a-f]{64}$' \ + || fail "$pin_name is not a sha256 digest: $pin_value" +done +if printf '%s' "$APPIMAGE_RUNTIME_URL" | grep -q '/continuous/'; then + fail "the AppImage runtime is pinned to the moving 'continuous' tag" +fi +printf '%s' "$APPIMAGE_RUNTIME_URL" | grep -q "/$APPIMAGE_RUNTIME_TAG/$APPIMAGE_RUNTIME_NAME\$" \ + || fail "APPIMAGE_RUNTIME_URL does not match the pinned tag/asset" +printf '%s' "$LOVE_SRC_URL" | grep -q "/$LOVE_VERSION/$LOVE_SRC_TARBALL\$" \ + || fail "LOVE_SRC_URL does not match LOVE_VERSION/LOVE_SRC_TARBALL" + +say "checking the builder base image" +# Building on anything newer than bullseye silently raises the glibc floor and +# strands every user on an older distro, with no symptom until they run it. +grep -q '^FROM debian:bullseye$' "$SCRIPT_DIR/Dockerfile" \ + || fail "Dockerfile no longer builds on debian:bullseye (that raises the glibc floor)" +[ "$BUILDER_BASE_IMAGE" = "debian:bullseye" ] \ + || fail "BUILDER_BASE_IMAGE disagrees with the Dockerfile" + +say "checking the dependency exclude list" +# Extract the live regex from the build script and classify known sonames +# through it, so a future edit cannot quietly start bundling glibc or stop +# bundling the engine's own dependencies. +EXCLUDE_RE="$( + # shellcheck disable=SC1090 + grep -m1 "^EXCLUDE_RE=" "$SCRIPT_DIR/build_appimage.sh" | sed "s/^EXCLUDE_RE='//; s/'\$//" +)" +[ -n "$EXCLUDE_RE" ] || fail "could not read EXCLUDE_RE out of build_appimage.sh" + +must_exclude=(libc.so.6 ld-linux-aarch64.so.1 libstdc++.so.6 libgcc_s.so.1 + libGL.so.1 libEGL.so.1 libgbm.so.1 libdrm.so.2 libX11.so.6 + libwayland-client.so.0 libpulse.so.0 libasound.so.2 + libfreetype.so.6 libfontconfig.so.1 libpng16.so.16 libz.so.1) +must_bundle=(libSDL2-2.0.so.0 libopenal.so.1 libluajit-5.1.so.2 libmodplug.so.1 + libmpg123.so.0 libogg.so.0 libvorbis.so.0 libvorbisfile.so.3 + libtheoradec.so.1 liblove-11.5.so) + +for soname in "${must_exclude[@]}"; do + [[ "$soname" =~ $EXCLUDE_RE ]] \ + || fail "$soname must be host-provided but the exclude list would bundle it" +done +for soname in "${must_bundle[@]}"; do + if [[ "$soname" =~ $EXCLUDE_RE ]]; then + fail "$soname is an engine dependency but the exclude list drops it" + fi +done + +say "checking AppRun and the fusion contract" +# The AppImage must boot straight into the game. If AppRun ever loses --fused, +# users get vanilla LÖVE's "no game" screen instead, and nothing else catches +# that before someone downloads a release. +grep -qF -- '--fused "\$APPDIR/game.love"' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "AppRun no longer launches game.love with --fused" +grep -qF 'LD_LIBRARY_PATH="\$APPDIR/lib/' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "AppRun no longer puts the bundled lib directory on LD_LIBRARY_PATH" +grep -qF 'comp gzip -b 131072' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "squashfs payload is no longer gzip/128K (older type-2 runtimes cannot read it)" + +say "checking the linked-module assertions" +# configure exits 0 when an optional -dev package is missing and just drops the +# module, so these assertions are the only thing standing between a missing +# build dependency and a release that cannot play sound. +for soname in libSDL2-2.0.so.0 libopenal.so.1 libfreetype.so.6 libmodplug.so.1 \ + libmpg123.so.0 libvorbisfile.so.3 libtheoradec.so.1; do + grep -qF "$soname" "$SCRIPT_DIR/build_appimage.sh" \ + || fail "build_appimage.sh no longer asserts liblove links $soname" +done + +say "checking the dlopen guarantees" +# SDL2, OpenAL and libtheora are compiled from source for correctness, not for +# a newer version number: Debian's builds hard-link libpulse/libasound/libX11/ +# libwayland (SDL2), libsndio (OpenAL) and libcairo (libtheora), each of which +# turns an optional runtime capability into a mandatory startup dependency. +# If a future edit drops the source build and reaches for the -dev package +# again, the AppImage silently stops starting on lean systems. +for forbidden_pkg in libsdl2-dev libtheora-dev libopenal-dev; do + if grep -qE "^ +.*\b$forbidden_pkg\b" "$SCRIPT_DIR/Dockerfile"; then + fail "Dockerfile installs $forbidden_pkg; that library is built from source on purpose" + fi +done +grep -qF -- '--enable-alsa-shared' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "SDL2 is no longer configured to dlopen its audio backends" +grep -qF -- '--enable-x11-shared' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "SDL2 is no longer configured to dlopen its video backends" +grep -qF 'ALSOFT_DLOPEN=ON' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "openal-soft is no longer configured to dlopen its backends" +grep -qF -- '--disable-examples' "$SCRIPT_DIR/build_appimage.sh" \ + || fail "libtheora is no longer built with --disable-examples (it regains the libcairo link)" + +say "checking the host dependency contract" +# The shipped objects may require nothing from the host beyond glibc, +# libstdc++ and the font stack. Everything driver-, session- or audio-related +# has to be dlopened. This is the invariant a headless CI runner proved was +# broken the first time round. +HOST_ALLOWED_RE="$( + grep -m1 "^HOST_ALLOWED_RE=" "$SCRIPT_DIR/build_appimage.sh" \ + | sed "s/^HOST_ALLOWED_RE='//; s/'\$//" +)" +[ -n "$HOST_ALLOWED_RE" ] || fail "could not read HOST_ALLOWED_RE out of build_appimage.sh" +for soname in libpulse.so.0 libasound.so.2 libX11.so.6 libwayland-client.so.0 \ + libGL.so.1 libcairo.so.2 libsndio.so.7.0 libdbus-1.so.3; do + if [[ "$soname" =~ $HOST_ALLOWED_RE ]]; then + fail "$soname is allowed as a hard host dependency; it must be dlopened" + fi +done +for soname in libc.so.6 libstdc++.so.6 libfreetype.so.6 libz.so.1; do + [[ "$soname" =~ $HOST_ALLOWED_RE ]] \ + || fail "$soname must be allowed as a host dependency but the contract rejects it" +done + +say "checking the shared game.love payload" +temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/gen1recomp-linux-arm64-selftest.XXXXXX")" +trap 'rm -rf "$temp_dir"' EXIT +"$ROOT/scripts/pack_love.sh" \ + --output "$temp_dir/game.love" \ + --listing "$temp_dir/love-listing.txt" \ + --version 1.2.3 \ + --dry-run >/dev/null +unzip -p "$temp_dir/game.love" src/core/Version.lua \ + | grep -Eq 'engine[[:space:]]*=[[:space:]]*"1\.2\.3"' \ + || fail "shared payload version was not stamped" + +say "Linux arm64 self-test passed" diff --git a/scripts/pack_love.sh b/scripts/pack_love.sh index 05898d27..1a94a830 100755 --- a/scripts/pack_love.sh +++ b/scripts/pack_love.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# Shared game.love packer for desktop and Switch builds. +# Shared game.love packer for platform builds. # # Usage: # scripts/pack_love.sh [--output PATH] [--listing PATH] [--dry-run] -# [--build-info PATH] +# [--build-info PATH] [--version X.Y.Z] # # Packs the same include/exclude set as the desktop release. Materializes a # listing file once and greps it (avoids SIGPIPE from unzip|grep under pipefail). # # --build-info PATH copy JSON into the love archive root before verification +# --version X.Y.Z stamp the release version inside the archive # --dry-run pack + verify only (for CI gates; no platform artifacts) set -euo pipefail @@ -19,6 +20,7 @@ OUTPUT="$WORK/game.love" LISTING="$WORK/love-listing.txt" DRY_RUN=0 BUILD_INFO="" +VERSION="" say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } @@ -28,6 +30,7 @@ while [ $# -gt 0 ]; do --output) OUTPUT="$2"; shift 2 ;; --listing) LISTING="$2"; shift 2 ;; --build-info) BUILD_INFO="$2"; shift 2 ;; + --version) VERSION="$2"; shift 2 ;; --dry-run) DRY_RUN=1; shift ;; -h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' @@ -41,10 +44,10 @@ mkdir -p "$(dirname "$OUTPUT")" "$(dirname "$LISTING")" say "packing game.love" rm -f "$OUTPUT" -# libs/ carries the vendored FlexLove toolkit the launcher UI is built on -# (src/import/LauncherView.lua); a build without it dies on the first frame. +# The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); +# the vendored libs/flexlove tree it replaced is gone. (cd "$ROOT" && zip -q -9 -r "$OUTPUT" \ - main.lua conf.lua src libs data assets tools/save-editor \ + main.lua conf.lua src data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json \ -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') @@ -56,6 +59,23 @@ if [ -n "$BUILD_INFO" ]; then (cd "$(dirname "$BUILD_INFO")" && zip -q "$OUTPUT" build-info.json) fi +if [ -n "$VERSION" ]; then + printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' \ + || fail "invalid version '$VERSION' (expected X.Y.Z)" + stamp_dir="$(mktemp -d "$(dirname "$OUTPUT")/stamp-love.XXXXXX")" + output_abs="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")" + trap 'rm -rf "$stamp_dir"' EXIT + mkdir -p "$stamp_dir/src/core" + sed -E "s/(engine[[:space:]]*=[[:space:]]*\")[^\"]*(\")/\1$VERSION\2/" \ + "$ROOT/src/core/Version.lua" > "$stamp_dir/src/core/Version.lua" + (cd "$stamp_dir" && zip -q "$output_abs" src/core/Version.lua) + version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" + unzip -p "$output_abs" src/core/Version.lua \ + | grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \ + || fail "version stamp failed: game.love does not report engine $VERSION" + say "stamped engine version: $VERSION" +fi + # Materialize the listing once. Piping unzip→grep -q under `set -o pipefail` # SIGPIPEs unzip when grep exits early on a match and aborts the build. unzip -Z1 "$OUTPUT" > "$LISTING" @@ -68,7 +88,7 @@ for required in tools/save-editor/App.lua tools/save-editor/Kit.lua \ tools/save-editor/panels/Party.lua \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json \ - libs/flexlove/FlexLove.lua \ + src/ui/kit/Kit.lua \ src/import/LauncherView.lua; do grep -qxF "$required" "$LISTING" \ || fail "game.love is missing $required" diff --git a/scripts/run.sh b/scripts/run.sh index dc08f50d..f0110d21 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Run the LÖVE2D Pokémon Red port (macOS-friendly). # -# Assumes scripts/setup.sh has been run once (generated data present and -# LÖVE installed). Extra arguments are passed through to LÖVE. +# Assumes scripts/setup.sh has been run once for at least one game (generated +# data present and LÖVE installed). Extra arguments are passed through to LÖVE. # # Link play is peer-to-peer (lua-enet, bundled with LÖVE): one player # picks HOST A GAME in START > LINK and reads out the address shown; @@ -15,8 +15,11 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } -[ -f "$ROOT/data/generated/maps.lua" ] \ - || fail "generated data missing, run scripts/setup.sh first" +if [ ! -f "$ROOT/data/generated/maps.lua" ] \ + && [ ! -f "$ROOT/blue/data/generated/maps.lua" ] \ + && [ ! -f "$ROOT/yellow/data/generated/maps.lua" ]; then + fail "generated data missing, run scripts/setup.sh first" +fi find_love() { command -v love >/dev/null 2>&1 && { echo "love"; return; } diff --git a/scripts/switch/bake_ota_logo.py b/scripts/switch/bake_ota_logo.py new file mode 100644 index 00000000..bbfc989f --- /dev/null +++ b/scripts/switch/bake_ota_logo.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Bake assets/logo/logo.png into a pre-scaled RGBA blob for the Switch OTA UI. + +Output format (little-endian): + uint32 width, uint32 height, then width*height RGBA8888 pixels. + +Regenerate after editing the source PNG: + python3 scripts/switch/bake_ota_logo.py + +Uses Pillow when available (see scripts/setup.sh). On macOS without Pillow, +falls back to sips + BMP export. +""" + +from __future__ import annotations + +import math +import platform +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +DEFAULT_SRC = ROOT / "assets/logo/logo.png" +DEFAULT_OUT = ROOT / "ports/switch/assets/logo.rgba" +DEFAULT_MAX_W = 320 +DEFAULT_MAX_H = 90 + + +def fit_size(src_w: int, src_h: int, max_w: int, max_h: int) -> tuple[int, int]: + scale = min(max_w / src_w, max_h / src_h, 1.0) + w = max(1, int(math.floor(src_w * scale + 0.5))) + h = max(1, int(math.floor(src_h * scale + 0.5))) + return w, h + + +def read_png_size(path: Path) -> tuple[int, int]: + with path.open("rb") as fp: + sig = fp.read(8) + if sig != b"\x89PNG\r\n\x1a\n": + raise SystemExit(f"not a PNG: {path}") + while True: + raw = fp.read(8) + if len(raw) < 8: + raise SystemExit(f"truncated PNG: {path}") + length, ctype = struct.unpack(">I4s", raw) + data = fp.read(length) + fp.read(4) + if ctype == b"IHDR": + return struct.unpack(">II", data[:8]) + if ctype == b"IEND": + break + raise SystemExit(f"missing IHDR in PNG: {path}") + + +def read_bmp_rgba(path: Path) -> tuple[int, int, bytes]: + data = path.read_bytes() + if len(data) < 54 or data[:2] != b"BM": + raise SystemExit(f"not a BMP: {path}") + pixel_offset = struct.unpack_from(" tuple[int, int, bytes]: + if platform.system() != "Darwin": + raise SystemExit( + "Pillow is required to bake the OTA logo on this platform. " + "Run scripts/setup.sh or: pip install pillow" + ) + + with tempfile.TemporaryDirectory() as tmp: + tmp_dir = Path(tmp) + scaled_png = tmp_dir / "scaled.png" + scaled_bmp = tmp_dir / "scaled.bmp" + subprocess.run( + ["sips", "-z", str(out_h), str(out_w), str(src), "--out", str(scaled_png)], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + subprocess.run( + ["sips", "-s", "format", "bmp", str(scaled_png), "--out", str(scaled_bmp)], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + return read_bmp_rgba(scaled_bmp) + + +def bake_with_pillow(src: Path, out_w: int, out_h: int) -> tuple[int, int, bytes]: + from PIL import Image + + with Image.open(src) as image: + image = image.convert("RGBA") + if image.size != (out_w, out_h): + image = image.resize((out_w, out_h), Image.LANCZOS) + w, h = image.size + return w, h, image.tobytes() + + +def bake(src: Path, out: Path, max_w: int, max_h: int) -> tuple[int, int]: + if not src.is_file(): + raise SystemExit(f"missing source PNG: {src}") + + src_w, src_h = read_png_size(src) + out_w, out_h = fit_size(src_w, src_h, max_w, max_h) + + try: + from PIL import Image # noqa: F401 + + w, h, pixels = bake_with_pillow(src, out_w, out_h) + except ImportError: + w, h, pixels = bake_with_sips(src, out_w, out_h) + + if len(pixels) != w * h * 4: + raise SystemExit(f"unexpected pixel buffer size for {w}x{h}") + + out.parent.mkdir(parents=True, exist_ok=True) + with out.open("wb") as fp: + fp.write(struct.pack(" int: + src = Path(argv[1]) if len(argv) > 1 else DEFAULT_SRC + out = Path(argv[2]) if len(argv) > 2 else DEFAULT_OUT + max_w = int(argv[3]) if len(argv) > 3 else DEFAULT_MAX_W + max_h = int(argv[4]) if len(argv) > 4 else DEFAULT_MAX_H + + w, h = bake(src, out, max_w, max_h) + print(f"wrote {out} ({w}x{h}, {8 + w * h * 4} bytes)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/scripts/switch/build_fused.sh b/scripts/switch/build_fused.sh index 26024386..b6dbf5e3 100755 --- a/scripts/switch/build_fused.sh +++ b/scripts/switch/build_fused.sh @@ -15,7 +15,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" LOVE_NX_TAG="11.5-nx1" LOVE_NX_DIR="$ROOT/.bazinga/love-nx/$LOVE_NX_TAG" LOVE_ELF="$LOVE_NX_DIR/love.elf" -ICON="$ROOT/assets/switch/icon.jpg" +ICON="$ROOT/ports/switch/assets/icon.jpg" APP_NAME="gen1recomp" APP_AUTHOR="bryanthaboi, port by andrewqsantos" DKP_IMAGE_FILE="$ROOT/scripts/switch/dkp-docker.image" @@ -93,7 +93,7 @@ run_fused_docker() { set -euo pipefail nacptool --create '$APP_NAME' '$APP_AUTHOR' '$VERSION' /work/control.nacp elf2nro /src/.bazinga/love-nx/$LOVE_NX_TAG/love.elf /out/$out_base \ - --icon=/src/assets/switch/icon.jpg \ + --icon=/src/ports/switch/assets/icon.jpg \ --nacp=/work/control.nacp \ --romfsdir=/work/romfs " diff --git a/scripts/switch/build_ota_launcher.sh b/scripts/switch/build_ota_launcher.sh new file mode 100755 index 00000000..4de830fb --- /dev/null +++ b/scripts/switch/build_ota_launcher.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Build the native Switch OTA launcher NRO (DEVKITPRO or Docker). +# +# Usage: scripts/switch/build_ota_launcher.sh [OUT_NRO] [VERSION] +# +# Default OUT_NRO: dist/switch/gen1recomp-launcher.nro +# VERSION (optional X.Y.Z) is written into the NACP so hbmenu shows the +# same release as the fused game; defaults to 0.0.0 when omitted. +# Host-only protocol check: always runs `make host-test` first. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=common.sh +. "$SCRIPT_DIR/common.sh" + +LAUNCHER_DIR="$ROOT/ports/switch/ota-launcher" +OUT_NRO="${1:-$ROOT/dist/switch/gen1recomp-launcher.nro}" +APP_VERSION="${2:-${GEN1_LAUNCHER_VERSION:-0.0.0}}" +DKP_IMAGE_FILE="$ROOT/scripts/switch/dkp-docker.image" + +[ -d "$LAUNCHER_DIR" ] || fail "missing $LAUNCHER_DIR" +[ -f "$LAUNCHER_DIR/Makefile" ] || fail "missing Makefile" + +if ! devkitpro_ready; then + fail_missing_devkitpro +fi + +say "host-test ota_protocol (no DEVKITPRO required)" +make -C "$LAUNCHER_DIR" host-test + +mkdir -p "$(dirname "$OUT_NRO")" + +if ota_launcher_deps_ready; then + say "building launcher NRO with native DEVKITPRO (NACP $APP_VERSION)" + make -C "$LAUNCHER_DIR" clean || true + if ! make -C "$LAUNCHER_DIR" all APP_VERSION="$APP_VERSION"; then + fail_ota_launcher_toolchain + fi + cp "$LAUNCHER_DIR/gen1recomp.nro" "$OUT_NRO" + say "wrote $OUT_NRO" + exit 0 +fi + +resolve_dkp_image() { + if [ -n "${GEN1_DKP_IMAGE:-}" ]; then + printf '%s' "$GEN1_DKP_IMAGE" + return 0 + fi + [ -f "$DKP_IMAGE_FILE" ] || fail "missing Docker image pin: $DKP_IMAGE_FILE" + local line + line="$(grep -v '^[[:space:]]*#' "$DKP_IMAGE_FILE" | grep -v '^[[:space:]]*$' | head -1 || true)" + [ -n "$line" ] || fail "empty Docker image pin: $DKP_IMAGE_FILE" + printf '%s' "$line" +} + +if command -v docker >/dev/null 2>&1; then + image="$(resolve_dkp_image)" + say "building launcher NRO via Docker ($image, NACP $APP_VERSION)" + if ! docker run --rm \ + -v "$ROOT:/work" \ + -w /work/ports/switch/ota-launcher \ + -e "APP_VERSION=$APP_VERSION" \ + "$image" \ + bash -lc 'pacman -Sy --noconfirm switch-curl switch-mbedtls switch-zlib switch-zziplib >/dev/null 2>&1 || true; make clean; make all APP_VERSION="$APP_VERSION"'; then + fail_ota_launcher_toolchain + fi + cp "$LAUNCHER_DIR/gen1recomp.nro" "$OUT_NRO" + say "wrote $OUT_NRO" + exit 0 +fi + +fail_missing_ota_deps diff --git a/scripts/switch/common.sh b/scripts/switch/common.sh index 2ba59e1e..eb4cfb15 100644 --- a/scripts/switch/common.sh +++ b/scripts/switch/common.sh @@ -8,7 +8,8 @@ if [ -z "${ROOT:-}" ]; then fi export ROOT -say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } +# Progress on stderr so command-substitution (e.g. pack_game_love) stays a bare path. +say() { printf '\033[1;32m==>\033[0m %s\n' "$*" >&2; } fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } # Print SHA-256 hex digest of PATH. Prefers shasum, falls back to sha256sum. @@ -38,16 +39,137 @@ fail_need_fetch() { fail "${1:-missing pinned love-nx} — run: scripts/build_switch.sh --fetch" } +# OTA launcher native build packages (shared with install_devkitpro_deps.sh). +OTA_LAUNCHER_PKGS=(switch-curl switch-mbedtls switch-zlib switch-zziplib) + +ota_launcher_deps_ready() { + command -v dkp-pacman >/dev/null 2>&1 || return 1 + local pkg + for pkg in "${OTA_LAUNCHER_PKGS[@]}"; do + dkp-pacman -Q "$pkg" >/dev/null 2>&1 || return 1 + done +} + +# Space-separated list of missing OTA launcher packages (empty when all installed). +ota_launcher_missing_pkgs() { + command -v dkp-pacman >/dev/null 2>&1 || return 0 + local pkg missing="" + for pkg in "${OTA_LAUNCHER_PKGS[@]}"; do + if ! dkp-pacman -Q "$pkg" >/dev/null 2>&1; then + missing="${missing} ${pkg}" + fi + done + printf '%s' "$missing" +} + +devkitpro_ready() { + [ -n "${DEVKITPRO:-}" ] && [ -f "$DEVKITPRO/libnx/switch_rules" ] +} + +fail_missing_devkitpro() { + fail "$(cat <<'EOF' +--fused requires DEVKITPRO (release builds ship the OTA launcher for in-console updates). + +One-time setup on the Mac self-hosted runner (or your dev machine): + + 1. Install devkitPro pacman: https://devkitpro.org/wiki/devkitPro_pacman + 2. Install Switch tools: sudo dkp-pacman -S switch-dev + 3. Export DEVKITPRO (typical macOS): + export DEVKITPRO=/opt/devkitpro + export PATH="$DEVKITPRO/tools/bin:$PATH" + 4. OTA launcher toolchain (native or Docker — pick one): + bash scripts/switch/install_devkitpro_deps.sh + or install Docker (same pin as fused builds) + +Then re-run: scripts/build_switch.sh --fetch --fused + +See docs/switch-build.md (Runner provisioning). +EOF +)" +} + +fail_missing_ota_deps() { + local missing + missing="$(ota_launcher_missing_pkgs)" + fail "$(cat </dev/null 2>&1 && command -v elf2nro >/dev/null 2>&1; then + game_ok=1 + elif command -v docker >/dev/null 2>&1; then + game_ok=1 + fi + if [ "$game_ok" -eq 0 ]; then + fail_fused_toolchain + fi + + if ota_launcher_deps_ready; then + return 0 + fi + if command -v docker >/dev/null 2>&1; then + return 0 + fi + fail_missing_ota_deps +} + # Fused mode needs nacptool/elf2nro (native or Docker). Rich multi-OS hint. fail_fused_toolchain() { fail "$(cat <<'EOF' -fused packaging needs nacptool and elf2nro (devkitPro switch-dev). +fused packaging needs DEVKITPRO, nacptool, and elf2nro (devkitPro switch-dev). Install options: - macOS: https://devkitpro.org/wiki/devkitPro_pacman (installer / pacman) + macOS: https://devkitpro.org/wiki/devkitPro_pacman + sudo dkp-pacman -S switch-dev + OTA launcher (native or Docker): + bash scripts/switch/install_devkitpro_deps.sh + or install Docker Linux: https://devkitpro.org/wiki/devkitPro_pacman Windows: Git Bash / MSYS2 / WSL with devkitPro tools on PATH - Docker: install Docker; build_fused.sh falls back to the pinned image + Docker: install Docker; fused game and OTA launcher can build in-container + +Export DEVKITPRO (typical): export DEVKITPRO=/opt/devkitpro See docs/switch-build.md for details. EOF diff --git a/scripts/switch/install_devkitpro_deps.sh b/scripts/switch/install_devkitpro_deps.sh new file mode 100755 index 00000000..64bfc2d5 --- /dev/null +++ b/scripts/switch/install_devkitpro_deps.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Install DEVKITPRO Switch packages needed for the native OTA launcher. +# Run this in Terminal.app / iTerm (needs your macOS admin password): +# +# bash scripts/switch/install_devkitpro_deps.sh +# +# Optional: speeds up local native OTA launcher builds. CI/release can fall back +# to Docker when these packages are not installed. +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=common.sh +. "$SCRIPT_DIR/common.sh" + +if [ "$(id -u)" -ne 0 ]; then + echo "==> re-running with sudo…" + exec sudo -E bash "$0" "$@" +fi + +export DEVKITPRO="${DEVKITPRO:-/opt/devkitpro}" +export PATH="/usr/local/bin:$DEVKITPRO/pacman/bin:$DEVKITPRO/tools/bin:$PATH" + +if [ -f /etc/profile.d/devkit-env.sh ]; then + # shellcheck disable=SC1091 + . /etc/profile.d/devkit-env.sh +fi + +if ! command -v dkp-pacman >/dev/null 2>&1; then + echo "error: dkp-pacman not found. Install the pkg from:" >&2 + echo " https://github.com/devkitPro/pacman/releases/latest" >&2 + exit 1 +fi + +if ! dkp-pacman -Q switch-dev >/dev/null 2>&1; then + echo "error: switch-dev is not installed (needed for nacptool/elf2nro)." >&2 + echo " sudo dkp-pacman -S switch-dev" >&2 + echo "Then re-run: bash scripts/switch/install_devkitpro_deps.sh" >&2 + exit 1 +fi + +echo "==> DEVKITPRO=$DEVKITPRO" +# Named packages only (avoid interactive switch-dev group member prompt). +# ZIP reading uses switch-zziplib (dkp has no minizip port for Switch). +dkp-pacman -Sy --noconfirm --needed "${OTA_LAUNCHER_PKGS[@]}" + +echo "==> installed packages:" +dkp-pacman -Q | grep -E 'switch-(curl|mbedtls|zlib|zziplib|tools)|libnx|devkitA64' || true + +# Ensure zsh/bash sessions see DEVKITPRO +MARKER="# gen1recomp-devkitpro" +for rc in /Users/*/.zshrc /Users/*/.bash_profile; do + [ -f "$rc" ] || continue + if ! grep -q "$MARKER" "$rc" 2>/dev/null; then + { + echo "" + echo "$MARKER" + echo "export DEVKITPRO=/opt/devkitpro" + echo 'export PATH="$DEVKITPRO/tools/bin:$DEVKITPRO/devkitA64/bin:$PATH"' + } >> "$rc" + echo "==> appended DEVKITPRO exports to $rc" + fi +done + +echo "==> done. Open a new shell, then:" +echo " export DEVKITPRO=/opt/devkitpro" +echo " scripts/switch/build_ota_launcher.sh" diff --git a/scripts/switch/ota_launcher.manifest b/scripts/switch/ota_launcher.manifest new file mode 100644 index 00000000..79409299 --- /dev/null +++ b/scripts/switch/ota_launcher.manifest @@ -0,0 +1,16 @@ +# Switch OTA launcher packaging manifest +# +# Release / SD tree MUST include the native OTA launcher NRO as the hbmenu +# entrypoint. The LÖVE fused game NRO is loaded via envSetNextLoad after the +# update check. Self-update of the launcher itself is out of scope for v1. +# +# OTA downloads the install zip. +# +# Paths relative to microSD root: + +ENTRY_NRO=switch/gen1recomp/gen1recomp.nro +GAME_NRO=switch/gen1recomp/gen1recomp-game.nro +SAVE_DIR=switch/gen1recomp/pokemon-love2d +OTA_ASSET_GLOB=gen1recomp-*-switch.zip +REQUIRE_SHA256SUMS=1 +OTA_ENABLED=1 diff --git a/scripts/switch/pack_sd_zip.sh b/scripts/switch/pack_sd_zip.sh index 8f76cac0..6e7d7a7f 100755 --- a/scripts/switch/pack_sd_zip.sh +++ b/scripts/switch/pack_sd_zip.sh @@ -2,16 +2,18 @@ # Pack a Switch SD-ready zip: extract at microSD root (merge-safe update). # # Usage: -# scripts/switch/pack_sd_zip.sh NRO_PATH VERSION OUT_ZIP +# scripts/switch/pack_sd_zip.sh GAME_NRO VERSION OUT_ZIP [LAUNCHER_NRO] # # Layout inside the zip (SD root): -# switch/gen1recomp/gen1recomp.nro +# switch/gen1recomp/gen1recomp.nro (launcher if LAUNCHER_NRO set, else game) +# switch/gen1recomp/gen1recomp-game.nro (only when LAUNCHER_NRO set) +# switch/gen1recomp/version.txt # switch/gen1recomp/INSTALL.txt # switch/gen1recomp/pokemon-love2d/imports/.../README.txt # switch/gen1recomp/pokemon-love2d/exports/.../README.txt # # Does not ship ROMs, saves, or mods. Re-extracting merges over an existing -# install and only overwrites the NRO + these text placeholders — keep +# install and only overwrites the NRO(s) + these text placeholders — keep # pokemon-love2d/ to preserve progress. set -euo pipefail @@ -23,11 +25,15 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" NRO_PATH="${1:-}" VERSION="${2:-}" OUT_ZIP="${3:-}" +LAUNCHER_NRO="${4:-}" [ -n "$NRO_PATH" ] && [ -n "$VERSION" ] && [ -n "$OUT_ZIP" ] \ - || fail "usage: scripts/switch/pack_sd_zip.sh NRO_PATH VERSION OUT_ZIP" + || fail "usage: scripts/switch/pack_sd_zip.sh GAME_NRO VERSION OUT_ZIP [LAUNCHER_NRO]" -[ -f "$NRO_PATH" ] || fail "missing NRO at $NRO_PATH" +[ -f "$NRO_PATH" ] || fail "missing game NRO at $NRO_PATH" +if [ -n "$LAUNCHER_NRO" ]; then + [ -f "$LAUNCHER_NRO" ] || fail "missing launcher NRO at $LAUNCHER_NRO" +fi command -v zip >/dev/null 2>&1 || fail "need zip on PATH" @@ -45,7 +51,15 @@ trap cleanup EXIT APP_DIR="$STAGE/switch/gen1recomp" SAVE_ROOT="$APP_DIR/pokemon-love2d" mkdir -p "$APP_DIR" -cp "$NRO_PATH" "$APP_DIR/gen1recomp.nro" + +if [ -n "$LAUNCHER_NRO" ]; then + LAUNCHER_NRO="$(cd "$(dirname "$LAUNCHER_NRO")" && pwd)/$(basename "$LAUNCHER_NRO")" + cp "$LAUNCHER_NRO" "$APP_DIR/gen1recomp.nro" + cp "$NRO_PATH" "$APP_DIR/gen1recomp-game.nro" +else + cp "$NRO_PATH" "$APP_DIR/gen1recomp.nro" +fi +printf '%s\n' "$VERSION" > "$APP_DIR/version.txt" cat > "$APP_DIR/INSTALL.txt" </dev/null || unzip -l "$OUT_ZIP")" printf '%s\n' "$LISTING" | grep -q 'switch/gen1recomp/gen1recomp.nro' \ || fail "zip missing switch/gen1recomp/gen1recomp.nro" +printf '%s\n' "$LISTING" | grep -Fq 'switch/gen1recomp/version.txt' \ + || fail "zip missing switch/gen1recomp/version.txt" +if [ -n "$LAUNCHER_NRO" ]; then + printf '%s\n' "$LISTING" | grep -Fq 'switch/gen1recomp/gen1recomp-game.nro' \ + || fail "zip missing switch/gen1recomp/gen1recomp-game.nro (OTA dual-NRO layout)" +fi REQUIRED=( "switch/gen1recomp/INSTALL.txt" + "switch/gen1recomp/version.txt" "switch/gen1recomp/pokemon-love2d/imports/README.txt" "switch/gen1recomp/pokemon-love2d/imports/mods/README.txt" "switch/gen1recomp/pokemon-love2d/imports/saves/red/README.txt" diff --git a/scripts/switch/selftest_build_switch.sh b/scripts/switch/selftest_build_switch.sh index 7377c5d9..99928faf 100755 --- a/scripts/switch/selftest_build_switch.sh +++ b/scripts/switch/selftest_build_switch.sh @@ -51,10 +51,11 @@ MISSING="" printf '%s' "$HELP_LC" | grep -q 'fetch' || MISSING="${MISSING} fetch" printf '%s' "$HELP_LC" | grep -q 'loose' || MISSING="${MISSING} loose" printf '%s' "$HELP_LC" | grep -q 'fused' || MISSING="${MISSING} fused" +printf '%s' "$HELP_LC" | grep -Eq 'devkitpro' || MISSING="${MISSING} DEVKITPRO" printf '%s' "$HELP_LC" | grep -Eq 'auto-download|downloads' || MISSING="${MISSING} auto-download" printf '%s' "$HELP_LC" | grep -Eq 'non-goal|does not|never' || MISSING="${MISSING} non-goals" if [ -z "$MISSING" ]; then - ok "build_switch.sh --help mentions fetch, loose, fused (+ auto-download/non-goals)" + ok "build_switch.sh --help mentions fetch, loose, fused, DEVKITPRO (+ auto-download/non-goals)" else bad "build_switch.sh --help missing:$MISSING" fi @@ -180,7 +181,41 @@ else fi # --------------------------------------------------------------------------- -# 5c. fail_fused_toolchain mentions docs/switch-build.md +# 5c2. fail_missing_devkitpro cites install_devkitpro_deps.sh +# --------------------------------------------------------------------------- +MDK_ERR="$STAGING/missing-dkp.err" +MDK_RC=0 +( + . "$SCRIPT_DIR/common.sh" + fail_missing_devkitpro +) >"$STAGING/missing-dkp.out" 2>"$MDK_ERR" || MDK_RC=$? + +if [ "$MDK_RC" -ne 0 ] \ + && grep -q 'install_devkitpro_deps.sh' "$MDK_ERR" \ + && grep -q 'DEVKITPRO' "$MDK_ERR" \ + && grep -q 'switch-dev' "$MDK_ERR"; then + ok "fail_missing_devkitpro cites DEVKITPRO + switch-dev + install_devkitpro_deps.sh" +else + bad "fail_missing_devkitpro should cite setup steps (rc=$MDK_RC err=$(cat "$MDK_ERR"))" +fi + +OTA_ERR="$STAGING/missing-ota.err" +OTA_RC=0 +( + . "$SCRIPT_DIR/common.sh" + fail_missing_ota_deps +) >"$STAGING/missing-ota.out" 2>"$OTA_ERR" || OTA_RC=$? + +if [ "$OTA_RC" -ne 0 ] \ + && grep -q 'install_devkitpro_deps.sh' "$OTA_ERR" \ + && grep -qi 'docker' "$OTA_ERR"; then + ok "fail_missing_ota_deps cites native or Docker options" +else + bad "fail_missing_ota_deps should cite native + Docker (rc=$OTA_RC err=$(cat "$OTA_ERR"))" +fi + +# --------------------------------------------------------------------------- +# 5d. fail_fused_toolchain mentions docs/switch-build.md # --------------------------------------------------------------------------- FT_ERR="$STAGING/fused-toolchain.err" FT_RC=0 @@ -228,6 +263,7 @@ ZIP_LIST="$(unzip -Z1 "$FAKE_ZIP" 2>/dev/null || unzip -l "$FAKE_ZIP")" PACK_MISSING="" for rel in \ "switch/gen1recomp/gen1recomp.nro" \ + "switch/gen1recomp/version.txt" \ "switch/gen1recomp/INSTALL.txt" \ "switch/gen1recomp/pokemon-love2d/imports/README.txt" \ "switch/gen1recomp/pokemon-love2d/imports/mods/README.txt" \ @@ -311,6 +347,94 @@ else bad "pack_sd_zip.sh merge update lost user data or failed to replace NRO" fi +# --------------------------------------------------------------------------- +# 7. Dual-NRO OTA layout (launcher + game) when 4th arg set +# --------------------------------------------------------------------------- +FAKE_LAUNCHER="$STAGING/fake-launcher.nro" +FAKE_GAME="$STAGING/fake-game.nro" +printf 'fake-launcher\n' > "$FAKE_LAUNCHER" +printf 'fake-game\n' > "$FAKE_GAME" +OTA_ZIP="$STAGING/gen1recomp-0.0.2-ota-layout-switch.zip" +OTA_RC=0 +"$ROOT/scripts/switch/pack_sd_zip.sh" "$FAKE_GAME" "0.0.2" "$OTA_ZIP" "$FAKE_LAUNCHER" \ + >"$STAGING/ota-pack.out" 2>"$STAGING/ota-pack.err" || OTA_RC=$? +OTA_LIST="$(unzip -Z1 "$OTA_ZIP" 2>/dev/null || true)" +if [ "$OTA_RC" -eq 0 ] \ + && printf '%s\n' "$OTA_LIST" | grep -Fq 'switch/gen1recomp/gen1recomp.nro' \ + && printf '%s\n' "$OTA_LIST" | grep -Fq 'switch/gen1recomp/gen1recomp-game.nro' \ + && printf '%s\n' "$OTA_LIST" | grep -Fq 'switch/gen1recomp/version.txt' +then + EXTRACT_OTA="$STAGING/extract-ota" + rm -rf "$EXTRACT_OTA" + mkdir -p "$EXTRACT_OTA" + unzip -q "$OTA_ZIP" -d "$EXTRACT_OTA" + DUAL_OK=1 + cmp -s "$FAKE_LAUNCHER" "$EXTRACT_OTA/switch/gen1recomp/gen1recomp.nro" || DUAL_OK=0 + cmp -s "$FAKE_GAME" "$EXTRACT_OTA/switch/gen1recomp/gen1recomp-game.nro" || DUAL_OK=0 + [ "$(cat "$EXTRACT_OTA/switch/gen1recomp/version.txt")" = "0.0.2" ] || DUAL_OK=0 + if [ "$DUAL_OK" -eq 1 ]; then + ok "pack_sd_zip.sh dual-NRO OTA layout (launcher + game + version.txt)" + else + bad "pack_sd_zip.sh dual-NRO bytes/version mismatch" + fi +else + bad "pack_sd_zip.sh dual-NRO layout failed (rc=$OTA_RC err=$(cat "$STAGING/ota-pack.err"))" +fi + +MANIFEST="$ROOT/scripts/switch/ota_launcher.manifest" +if [ -f "$MANIFEST" ] && grep -q '^OTA_ENABLED=1$' "$MANIFEST" \ + && grep -q 'ENTRY_NRO=switch/gen1recomp/gen1recomp.nro' "$MANIFEST" \ + && grep -q 'GAME_NRO=switch/gen1recomp/gen1recomp-game.nro' "$MANIFEST" +then + ok "ota_launcher.manifest requires dual-NRO when OTA_ENABLED=1" +else + bad "ota_launcher.manifest missing OTA dual-NRO requirements" +fi + +if [ -f "$ROOT/scripts/switch/build_ota_launcher.sh" ] \ + && grep -q 'ota_launcher_deps_ready' "$ROOT/scripts/switch/build_ota_launcher.sh" \ + && grep -q 'fail_missing_devkitpro' "$ROOT/scripts/switch/build_ota_launcher.sh" \ + && grep -q 'preflight_fused_build' "$ROOT/scripts/build_switch.sh" \ + && [ -f "$ROOT/ports/switch/ota-launcher/Makefile" ] +then + ok "native OTA launcher sources + build_ota_launcher.sh present" +else + bad "missing native OTA launcher tree or build script" +fi + +# --------------------------------------------------------------------------- +# 8. Unified OTA asset = dual-NRO SD zip (no separate OTA-only archive) +# --------------------------------------------------------------------------- +LEGACY_OTA_PACKER="$ROOT/scripts/switch/pack_ota_zip.sh" +if [ ! -f "$LEGACY_OTA_PACKER" ] \ + && ! grep -Eq 'pack_ota_zip\.sh|switch-ota\.zip' "$ROOT/scripts/build_switch.sh" \ + && grep -q 'OTA_ASSET_GLOB=gen1recomp-\*-switch.zip' "$MANIFEST" +then + ok "OTA uses the same SD zip as install (legacy OTA-only packer gone)" +else + bad "legacy separate OTA-only packer / asset still present" +fi + +if grep -q 'ota_ui_prompt_update' "$ROOT/ports/switch/ota-launcher/src/main.c" \ + && grep -q 'ota_net_init' "$ROOT/ports/switch/ota-launcher/src/main.c" \ + && grep -q 'framebufferCreate\|COL_RAIL' "$ROOT/ports/switch/ota-launcher/src/ota_ui.c" \ + && grep -q '^ROMFS' "$ROOT/ports/switch/ota-launcher/Makefile" \ + && grep -q 'cacert.pem' "$ROOT/ports/switch/ota-launcher/Makefile" +then + ok "launcher UI uses framebuffer (no prompt when up to date)" +else + bad "launcher missing branded ota_ui / ROMFS" +fi + +if [ -f "$ROOT/ports/switch/ota-launcher/src/ota_unzip.c" ] \ + && grep -q 'zzip/zzip.h' "$ROOT/ports/switch/ota-launcher/src/ota_unzip.c" \ + && grep -q 'ota_unzip_extract_file' "$ROOT/ports/switch/ota-launcher/src/main.c" +then + ok "launcher wires zziplib ota_unzip_extract_file" +else + bad "launcher missing zziplib unzip wiring" +fi + # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- diff --git a/scripts/test.sh b/scripts/test.sh index 18bb960e..d88e6128 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -22,6 +22,7 @@ set -uo pipefail cd "$(dirname "$0")/.." LUA=${LUA:-luajit} +LUA54=${LUA54:-lua5.4} BLESS=0 QUICK=0 SHOTS=${WITH_SHOTS:-0} @@ -65,6 +66,7 @@ run_tier() { # ------- ROM-free tiers: these are what CI runs +run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua # NX Blue/Yellow asset overlay: ROM-free, must run on every checkout so a @@ -134,6 +136,16 @@ if [ -f data/generated/maps.lua ]; then run_tier "T3 save editor: wheel scrolling" "$LUA" tests/save_editor_wheel_bug595_test.lua run_tier "T3 save editor: pad / NX input" "$LUA" tests/save_editor_pad_input_test.lua run_tier "T5 link (loopback lockstep)" "$LUA" tests/run_link_tests.lua + # The oversize-save vendor oracle (tests/save_oversize_vendor_test.lua) + # cross-checks the launcher's footer-truncation import against the + # INDEPENDENT PKHeX-derived gen1lib codec, which cannot run under luajit + # (native 5.3+ operators). Needs a stock Lua 5.3/5.4; skip when absent. + if command -v "$LUA54" >/dev/null 2>&1; then + run_tier "T3 save oversize vendor oracle" "$LUA54" tests/save_oversize_vendor_test.lua + else + echo "" + echo "-- T3 save oversize vendor oracle: skipped (no '$LUA54' on PATH; set LUA54=...)" + fi fi else echo "" diff --git a/scripts/xbox-uwp/build_sdl2_angle.ps1 b/scripts/xbox-uwp/build_sdl2_angle.ps1 new file mode 100644 index 00000000..2656e556 --- /dev/null +++ b/scripts/xbox-uwp/build_sdl2_angle.ps1 @@ -0,0 +1,24 @@ +param( + [string]$Configuration = "Release", + [string]$WindowsSdkVersion = "10.0" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +$portRoot = Join-Path $repoRoot "ports\uwp" +$sourceRoot = Join-Path $portRoot "third_party\sdl2\source" +$buildRoot = Join-Path $portRoot "build\sdl2" +$installRoot = Join-Path $portRoot "build\sdl2-install" + +cmake --fresh -S $sourceRoot -B $buildRoot ` + -G "Visual Studio 17 2022" -A x64 ` + -DCMAKE_SYSTEM_NAME=WindowsStore ` + "-DCMAKE_SYSTEM_VERSION=$WindowsSdkVersion" ` + "-DCMAKE_INSTALL_PREFIX=$installRoot" ` + -DSDL_SHARED=ON -DSDL_STATIC=OFF ` + -DSDL_OPENGL=OFF -DSDL_OPENGLES=ON -DSDL_VULKAN=OFF +if ($LASTEXITCODE -ne 0) { throw "SDL2 configure failed with exit code $LASTEXITCODE." } + +cmake --build $buildRoot --config $Configuration --target INSTALL --parallel +if ($LASTEXITCODE -ne 0) { throw "SDL2 build failed with exit code $LASTEXITCODE." } diff --git a/scripts/xbox-uwp/common.sh b/scripts/xbox-uwp/common.sh new file mode 100644 index 00000000..6d2c1586 --- /dev/null +++ b/scripts/xbox-uwp/common.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +UWP_ROOT="$ROOT/ports/uwp" +WORK="$ROOT/.bazinga/work" + +say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1" +} + +sha256_file() { + local path="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{print $1}' + else + fail "sha256sum or shasum is required" + fi +} + +windows_path() { + cygpath -aw "$1" +} diff --git a/scripts/xbox-uwp/rebuild_dependencies.ps1 b/scripts/xbox-uwp/rebuild_dependencies.ps1 new file mode 100644 index 00000000..bde49fbb --- /dev/null +++ b/scripts/xbox-uwp/rebuild_dependencies.ps1 @@ -0,0 +1,436 @@ +[CmdletBinding()] +param( + [ValidateSet("Release", "RelWithDebInfo")] + [string]$Configuration = "Release", + [string]$WindowsSdkVersion = "10.0", + [switch]$SkipAngle, + [switch]$SkipPackage +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +$portRoot = Join-Path $repoRoot "ports\uwp" +$thirdPartyRoot = Join-Path $portRoot "third_party" +$manifestPath = Join-Path $thirdPartyRoot "manifest.json" +$metadata = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json + +$sdlRoot = Join-Path $thirdPartyRoot "sdl2" +$sdlSource = Join-Path $sdlRoot "source" +$sdlInstall = Join-Path $portRoot "build\sdl2-install" +$loveRoot = Join-Path $thirdPartyRoot "love" +$loveSource = Join-Path $loveRoot "source" +$loveBuild = Join-Path $portRoot "build\love" +$luajitSource = Join-Path $thirdPartyRoot "luajit\source" +$angleRoot = Join-Path $thirdPartyRoot "angle" +$angleSource = Join-Path $angleRoot "source" +$depotToolsSource = Join-Path $angleRoot "depot_tools" +$runtimeRoot = Join-Path $thirdPartyRoot "runtime" +$vcpkgSource = Join-Path $runtimeRoot "source" +$vcpkgInstalled = Join-Path $vcpkgSource "installed\x64-uwp" +$licensesRoot = Join-Path $thirdPartyRoot "licenses" + +function Invoke-Tool { + param([string]$FilePath) + + & $FilePath @args + if ($LASTEXITCODE -ne 0) { + throw "$FilePath failed with exit code $LASTEXITCODE." + } +} + +function Assert-Command { + param([Parameter(Mandatory)][string]$Name) + + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "Required tool not found on PATH: $Name" + } +} + +function Ensure-Directory { + param([Parameter(Mandatory)][string]$Path) + + if (-not (Test-Path -LiteralPath $Path -PathType Container)) { + New-Item -ItemType Directory -Path $Path -Force | Out-Null + } +} + +function Ensure-GitSource { + param( + [Parameter(Mandatory)][string]$Name, + [Parameter(Mandatory)][string]$Repository, + [Parameter(Mandatory)][string]$Revision, + [Parameter(Mandatory)][string]$Path, + [switch]$AllowTrackedChanges + ) + + if (-not (Test-Path -LiteralPath $Path -PathType Container)) { + Write-Host "Cloning $Name at $Revision..." + Ensure-Directory (Split-Path -Parent $Path) + Invoke-Tool git init $Path + Invoke-Tool git -C $Path remote add origin $Repository + Invoke-Tool git -C $Path fetch --depth 1 origin $Revision + Invoke-Tool git -C $Path checkout --detach FETCH_HEAD + } + + $gitDirectory = Join-Path $Path ".git" + if (-not (Test-Path -LiteralPath $gitDirectory -PathType Container)) { + throw "$Name source has no Git metadata. Remove '$Path' to recreate the pinned checkout." + } + + $actual = (& git -C $Path rev-parse HEAD).Trim() + if ($LASTEXITCODE -ne 0) { + throw "Unable to inspect the $Name source checkout." + } + if ($actual -ne $Revision) { + throw "$Name source is at $actual; expected $Revision. Remove '$Path' to recreate it." + } + if (-not $AllowTrackedChanges) { + Assert-GitSourceClean -Name $Name -Path $Path + } +} + +function Assert-GitSourceClean { + param( + [Parameter(Mandatory)][string]$Name, + [Parameter(Mandatory)][string]$Path + ) + + & git -C $Path diff --quiet --ignore-submodules -- + $worktreeDirty = $LASTEXITCODE -ne 0 + & git -C $Path diff --cached --quiet --ignore-submodules -- + $indexDirty = $LASTEXITCODE -ne 0 + if ($worktreeDirty -or $indexDirty) { + throw "$Name source has local changes. Remove '$Path' to recreate the pinned checkout." + } +} + +function Copy-RequiredFile { + param( + [Parameter(Mandatory)][string]$Source, + [Parameter(Mandatory)][string]$Destination + ) + + if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { + throw "Expected build output not found: $Source" + } + Ensure-Directory (Split-Path -Parent $Destination) + Copy-Item -LiteralPath $Source -Destination $Destination -Force +} + +function Reset-Directory { + param([Parameter(Mandatory)][string]$Path) + + if (Test-Path -LiteralPath $Path) { + Remove-Item -LiteralPath $Path -Recurse -Force + } + New-Item -ItemType Directory -Path $Path -Force | Out-Null +} + +function Ensure-DepotToolsGit { + $wrapper = Join-Path $depotToolsSource "git.bat" + if (Test-Path -LiteralPath $wrapper -PathType Leaf) { + return + } + + $gitExe = (Get-Command git).Source + $content = "@echo off`r`n`"$gitExe`" %*`r`n" + [System.IO.File]::WriteAllText($wrapper, $content, [System.Text.ASCIIEncoding]::new()) +} + +function Get-Sha256 { + param( + [Parameter(Mandatory)][string]$Path, + [switch]$NormalizeLineEndings + ) + + if ($NormalizeLineEndings) { + $bytes = [System.IO.File]::ReadAllBytes($Path) + $text = [System.Text.Encoding]::UTF8.GetString($bytes).Replace("`r`n", "`n") + $bytes = [System.Text.UTF8Encoding]::new($false).GetBytes($text) + $sha256 = [System.Security.Cryptography.SHA256]::Create() + try { + return [System.BitConverter]::ToString($sha256.ComputeHash($bytes)).Replace("-", "") + } finally { + $sha256.Dispose() + } + } + + $stream = [System.IO.File]::OpenRead($Path) + try { + $sha256 = [System.Security.Cryptography.SHA256]::Create() + try { + $hash = $sha256.ComputeHash($stream) + return [System.BitConverter]::ToString($hash).Replace("-", "") + } finally { + $sha256.Dispose() + } + } finally { + $stream.Dispose() + } +} + +function Update-Manifest { + $roots = @( + "angle\bin", + "love\bin", + "love\lib", + "runtime\bin", + "sdl2\bin", + "sdl2\include", + "sdl2\lib" + ) + $files = foreach ($relativeRoot in $roots) { + $path = Join-Path $thirdPartyRoot $relativeRoot + Get-ChildItem -LiteralPath $path -File -Recurse | ForEach-Object { + $relative = $_.FullName.Substring($thirdPartyRoot.Length + 1).Replace("\", "/") + $normalize = $relative.StartsWith("sdl2/include/", [System.StringComparison]::Ordinal) + [ordered]@{ + path = $relative + sha256 = Get-Sha256 -Path $_.FullName -NormalizeLineEndings:$normalize + } + } + } + + $metadata.configuration = $Configuration + $metadata.files = @($files | Sort-Object { $_['path'] }) + $json = $metadata | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText($manifestPath, "$json`r`n", [System.Text.UTF8Encoding]::new($false)) +} + +Assert-Command git +Assert-Command cmake + +Ensure-GitSource ` + -Name "SDL2" ` + -Repository $metadata.sources.sdl2.repository ` + -Revision $metadata.sources.sdl2.commit ` + -Path $sdlSource ` + -AllowTrackedChanges + +$sdlPatch = Join-Path $thirdPartyRoot $metadata.sources.sdl2.patch +Push-Location $sdlSource +try { + & git apply --unidiff-zero --reverse --check $sdlPatch 2>$null + if ($LASTEXITCODE -eq 0) { + Invoke-Tool git apply --unidiff-zero --reverse $sdlPatch + try { + Assert-GitSourceClean -Name "SDL2" -Path $sdlSource + } finally { + Invoke-Tool git apply --unidiff-zero $sdlPatch + } + } else { + Assert-GitSourceClean -Name "SDL2" -Path $sdlSource + Invoke-Tool git apply --unidiff-zero --check $sdlPatch + Invoke-Tool git apply --unidiff-zero $sdlPatch + } +} finally { + Pop-Location +} + +Write-Host "Building SDL2..." +& (Join-Path $PSScriptRoot "build_sdl2_angle.ps1") ` + -Configuration $Configuration ` + -WindowsSdkVersion $WindowsSdkVersion +if ($LASTEXITCODE -ne 0) { + throw "SDL2 build failed with exit code $LASTEXITCODE." +} + +Ensure-GitSource ` + -Name "vcpkg" ` + -Repository $metadata.sources.vcpkg.repository ` + -Revision $metadata.sources.vcpkg.commit ` + -Path $vcpkgSource + +$vcpkgExe = Join-Path $vcpkgSource "vcpkg.exe" +if (-not (Test-Path -LiteralPath $vcpkgExe -PathType Leaf)) { + Write-Host "Bootstrapping vcpkg..." + Invoke-Tool (Join-Path $vcpkgSource "bootstrap-vcpkg.bat") -disableMetrics +} + +Write-Host "Installing the LÖVE runtime dependencies..." +$vcpkgPackages = @($metadata.sources.vcpkg.packages | ForEach-Object { "${_}:x64-uwp" }) +Invoke-Tool $vcpkgExe install @vcpkgPackages --recurse --clean-after-build --vcpkg-root $vcpkgSource + +Ensure-GitSource ` + -Name "LÖVE" ` + -Repository $metadata.sources.love.repository ` + -Revision $metadata.sources.love.commit ` + -Path $loveSource + +Ensure-GitSource ` + -Name "LuaJIT" ` + -Repository $metadata.sources.luajit.repository ` + -Revision $metadata.sources.luajit.commit ` + -Path $luajitSource + +Write-Host "Building LÖVE..." +Invoke-Tool cmake --fresh -S $loveSource -B $loveBuild ` + -G "Visual Studio 17 2022" -A x64 ` + -DCMAKE_SYSTEM_NAME=WindowsStore ` + "-DCMAKE_SYSTEM_VERSION=$WindowsSdkVersion" ` + "-DCMAKE_TOOLCHAIN_FILE=$(Join-Path $vcpkgSource 'scripts\buildsystems\vcpkg.cmake')" ` + -DVCPKG_TARGET_TRIPLET=x64-uwp ` + "-DLOVE_UWP_SDL_ROOT=$sdlInstall" ` + -DLOVE_UWP_LUAJIT=ON ` + -DLOVE_UWP_ANGLE=ON ` + "-DFETCHCONTENT_SOURCE_DIR_LOVE_LUAJIT_SOURCE=$luajitSource" +Invoke-Tool cmake --build $loveBuild --config $Configuration --parallel + +if (-not $SkipAngle) { + Ensure-GitSource ` + -Name "depot_tools" ` + -Repository $metadata.sources.depotTools.repository ` + -Revision $metadata.sources.depotTools.commit ` + -Path $depotToolsSource + Ensure-DepotToolsGit + + Ensure-GitSource ` + -Name "ANGLE" ` + -Repository $metadata.sources.angle.repository ` + -Revision $metadata.sources.angle.commit ` + -Path $angleSource + + $gclientPath = Join-Path $angleSource ".gclient" + if (-not (Test-Path -LiteralPath $gclientPath -PathType Leaf)) { + $gclient = @" +solutions = [ + { + "name": ".", + "url": "$($metadata.sources.angle.repository)", + "deps_file": "DEPS", + "managed": False, + "custom_deps": {}, + "custom_vars": {}, + }, +] +target_os = ["winuwp"] +"@ + [System.IO.File]::WriteAllText($gclientPath, $gclient, [System.Text.UTF8Encoding]::new($false)) + } + + $gn = Join-Path $angleSource "buildtools\win\gn.exe" + $ninja = Join-Path $angleSource "third_party\ninja\ninja.exe" + if (-not (Test-Path -LiteralPath $gn -PathType Leaf) -or + -not (Test-Path -LiteralPath $ninja -PathType Leaf)) { + Write-Host "Synchronising ANGLE dependencies..." + $oldPath = $env:PATH + $oldDepotToolsUpdate = $env:DEPOT_TOOLS_UPDATE + $oldDepotToolsToolchain = $env:DEPOT_TOOLS_WIN_TOOLCHAIN + try { + $env:PATH = "$depotToolsSource;$oldPath" + $env:DEPOT_TOOLS_UPDATE = "0" + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = "0" + Push-Location $angleSource + try { + Invoke-Tool (Join-Path $depotToolsSource "gclient.bat") sync -D + } finally { + Pop-Location + } + } finally { + $env:PATH = $oldPath + $env:DEPOT_TOOLS_UPDATE = $oldDepotToolsUpdate + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = $oldDepotToolsToolchain + } + } else { + Write-Host "Using the existing ANGLE dependency checkout." + } + + Write-Host "Building ANGLE..." + $angleBuild = Join-Path $angleSource "out\uwp-release" + $gnArgs = ($metadata.sources.angle.gnArgs -join "`n") + $oldPath = $env:PATH + $oldDepotToolsToolchain = $env:DEPOT_TOOLS_WIN_TOOLCHAIN + try { + $env:PATH = "$depotToolsSource;$oldPath" + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = "0" + Push-Location $angleSource + try { + Invoke-Tool $gn gen $angleBuild "--args=$gnArgs" + } finally { + Pop-Location + } + Invoke-Tool $ninja -C $angleBuild libEGL libGLESv2 + } finally { + $env:PATH = $oldPath + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = $oldDepotToolsToolchain + } + + Reset-Directory (Join-Path $angleRoot "bin") + foreach ($name in @("libEGL.dll", "libGLESv2.dll", "d3dcompiler_47.dll")) { + Copy-RequiredFile (Join-Path $angleBuild $name) (Join-Path $angleRoot "bin\$name") + } + Copy-RequiredFile (Join-Path $angleSource "AUTHORS") (Join-Path $angleRoot "AUTHORS") + Copy-RequiredFile (Join-Path $angleSource "LICENSE") (Join-Path $angleRoot "LICENSE") +} else { + Write-Host "Keeping the existing ANGLE runtime (-SkipAngle)." +} + +Write-Host "Staging SDL2..." +Reset-Directory (Join-Path $sdlRoot "bin") +Reset-Directory (Join-Path $sdlRoot "lib") +Reset-Directory (Join-Path $sdlRoot "include") +Copy-RequiredFile (Join-Path $sdlInstall "bin\SDL2.dll") (Join-Path $sdlRoot "bin\SDL2.dll") +Copy-RequiredFile (Join-Path $sdlInstall "lib\SDL2.lib") (Join-Path $sdlRoot "lib\SDL2.lib") +Copy-Item -LiteralPath (Join-Path $sdlInstall "include\SDL2") -Destination (Join-Path $sdlRoot "include\SDL2") -Recurse + +Write-Host "Staging LÖVE and LuaJIT..." +$loveOutput = Join-Path $loveBuild $Configuration +Reset-Directory (Join-Path $loveRoot "bin") +Reset-Directory (Join-Path $loveRoot "lib") +foreach ($name in @("love.dll", "lua51.dll")) { + Copy-RequiredFile (Join-Path $loveOutput $name) (Join-Path $loveRoot "bin\$name") +} +foreach ($name in @("liblove.lib", "lovestatic.lib", "lua51.lib")) { + Copy-RequiredFile (Join-Path $loveOutput $name) (Join-Path $loveRoot "lib\$name") +} + +Write-Host "Staging the vcpkg runtime..." +Reset-Directory (Join-Path $runtimeRoot "bin") +foreach ($name in $metadata.sources.vcpkg.runtimeFiles) { + Copy-RequiredFile (Join-Path $vcpkgInstalled "bin\$name") (Join-Path $runtimeRoot "bin\$name") +} + +Write-Host "Updating dependency licences..." +Ensure-Directory $licensesRoot +Copy-RequiredFile (Join-Path $loveSource "license.txt") (Join-Path $licensesRoot "love.txt") +Copy-RequiredFile (Join-Path $luajitSource "COPYRIGHT") (Join-Path $licensesRoot "luajit.txt") +Copy-RequiredFile (Join-Path $sdlSource "LICENSE.txt") (Join-Path $licensesRoot "sdl2.txt") +Copy-RequiredFile (Join-Path $angleRoot "LICENSE") (Join-Path $licensesRoot "angle.txt") +foreach ($license in $metadata.sources.vcpkg.licenses.PSObject.Properties) { + Copy-RequiredFile ` + (Join-Path $vcpkgInstalled "share\$($license.Name)\copyright") ` + (Join-Path $licensesRoot $license.Value) +} + +Write-Host "Updating dependency hashes..." +Update-Manifest +& (Join-Path $PSScriptRoot "verify_dependencies.ps1") -Manifest $manifestPath +if ($LASTEXITCODE -ne 0) { + throw "Dependency verification failed with exit code $LASTEXITCODE." +} + +if (-not $SkipPackage) { + Write-Host "Building the Release package..." + $gitRoot = Split-Path -Parent (Split-Path -Parent (Get-Command git).Source) + $bash = Join-Path $gitRoot "bin\bash.exe" + if (-not (Test-Path -LiteralPath $bash -PathType Leaf)) { + throw "Git Bash was not found at $bash." + } + + $buildScript = [System.IO.Path]::GetFullPath( + (Join-Path $portRoot "..\..\scripts\build_xbox_uwp.sh") + ).Replace("\", "/") + if ($buildScript -match "^([A-Za-z]):/(.*)$") { + $buildScript = "/$($Matches[1].ToLowerInvariant())/$($Matches[2])" + } + $packageConfiguration = if ($Configuration -eq "RelWithDebInfo") { + "--relwithdebinfo" + } else { + "--release" + } + Invoke-Tool $bash $buildScript $packageConfiguration +} + +Write-Host "UWP dependencies rebuilt and staged successfully." diff --git a/scripts/xbox-uwp/selftest_build_xbox_uwp.sh b/scripts/xbox-uwp/selftest_build_xbox_uwp.sh new file mode 100644 index 00000000..500da659 --- /dev/null +++ b/scripts/xbox-uwp/selftest_build_xbox_uwp.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Offline checks for the Xbox UWP build entry points. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=common.sh +. "$SCRIPT_DIR/common.sh" + +require_command zip +require_command unzip + +say "checking shell entry points" +bash -n "$ROOT/scripts/build_xbox_uwp.sh" "$SCRIPT_DIR"/*.sh +help="$(bash "$ROOT/scripts/build_xbox_uwp.sh" --help)" +printf '%s' "$help" | grep -q -- '--version X.Y.Z' \ + || fail "build help does not document --version" + +say "checking the shared game.love payload" +temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/gen1recomp-uwp-selftest.XXXXXX")" +trap 'rm -rf "$temp_dir"' EXIT +love_file="$temp_dir/game.love" +listing="$temp_dir/love-listing.txt" +"$ROOT/scripts/pack_love.sh" \ + --output "$love_file" \ + --listing "$listing" \ + --version 1.2.3 \ + --dry-run >/dev/null +unzip -p "$love_file" src/core/Version.lua \ + | grep -Eq 'engine[[:space:]]*=[[:space:]]*"1\.2\.3"' \ + || fail "shared payload version was not stamped" + +say "checking the manifest template" +grep -q '@UWP_PACKAGE_VERSION@' "$UWP_ROOT/Package.appxmanifest.in" \ + || fail "manifest template is missing the package version placeholder" +grep -q '@UWP_PUBLISHER_XML@' "$UWP_ROOT/Package.appxmanifest.in" \ + || fail "manifest template is missing the publisher placeholder" + +bash "$SCRIPT_DIR/write_build_info.sh" \ + "$temp_dir/build-info.json" 1.2.3 Release >/dev/null +grep -q '"version": "1.2.3"' "$temp_dir/build-info.json" \ + || fail "build metadata does not contain the requested version" + +say "Xbox UWP self-test passed" diff --git a/scripts/xbox-uwp/stage_release.ps1 b/scripts/xbox-uwp/stage_release.ps1 new file mode 100644 index 00000000..20a7d733 --- /dev/null +++ b/scripts/xbox-uwp/stage_release.ps1 @@ -0,0 +1,150 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)][ValidatePattern('^\d+\.\d+\.\d+$')] + [string]$Version, + [ValidateSet('Release', 'RelWithDebInfo')] + [string]$Configuration = 'Release', + [string]$PackageRoot, + [string]$OutputRoot, + [string]$BuildInfo, + [string]$CertificatePath, + [string]$CertificatePassword +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..')) +$configurationDirectory = $Configuration.ToLowerInvariant() +if (-not $PackageRoot) { + $PackageRoot = Join-Path $repositoryRoot "ports\uwp\build\$configurationDirectory\AppPackages\Gen1RecompUWP" +} +if (-not $OutputRoot) { + $OutputRoot = Join-Path $repositoryRoot 'dist\xbox-uwp' +} + +$packageVersion = "$Version.0" +$packageDirectory = Join-Path $PackageRoot "Gen1RecompUWP_${packageVersion}_x64_Test" +$package = Join-Path $packageDirectory "Gen1RecompUWP_${packageVersion}_x64.msix" +if (-not (Test-Path -LiteralPath $package -PathType Leaf)) { + throw "Expected MSIX was not found: $package" +} + +function Get-SignTool { + $kitsRoot = ${env:ProgramFiles(x86)} + if (-not $kitsRoot) { + throw 'ProgramFiles(x86) is not defined.' + } + $binRoot = Join-Path $kitsRoot 'Windows Kits\10\bin' + $tool = Get-ChildItem -LiteralPath $binRoot -Filter signtool.exe -File -Recurse | + Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } | + Sort-Object FullName -Descending | + Select-Object -First 1 + if (-not $tool) { + throw 'SignTool was not found in the Windows SDK.' + } + return $tool.FullName +} + +$certificate = $null +if ($CertificatePath) { + if (-not (Test-Path -LiteralPath $CertificatePath -PathType Leaf)) { + throw "Signing certificate was not found: $CertificatePath" + } + $flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet + $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( + $CertificatePath, + $CertificatePassword, + $flags + ) + + Add-Type -AssemblyName System.IO.Compression + Add-Type -AssemblyName System.IO.Compression.FileSystem + $archive = [System.IO.Compression.ZipFile]::OpenRead($package) + try { + $entry = $archive.GetEntry('AppxManifest.xml') + if (-not $entry) { throw 'AppxManifest.xml is missing from the MSIX.' } + $reader = [System.IO.StreamReader]::new($entry.Open()) + try { [xml]$manifest = $reader.ReadToEnd() } finally { $reader.Dispose() } + } finally { + $archive.Dispose() + } + $publisher = $manifest.Package.Identity.Publisher + if ($publisher -ne $certificate.Subject) { + throw "Manifest publisher '$publisher' does not match certificate subject '$($certificate.Subject)'." + } + + $signTool = Get-SignTool + $signArguments = @('sign', '/fd', 'SHA256', '/f', $CertificatePath) + if ($CertificatePassword) { + $signArguments += @('/p', $CertificatePassword) + } + $signArguments += $package + & $signTool @signArguments + if ($LASTEXITCODE -ne 0) { throw "SignTool failed with exit code $LASTEXITCODE." } + & $signTool verify /pa $package + if ($LASTEXITCODE -ne 0) { throw "Signature verification failed with exit code $LASTEXITCODE." } +} + +New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null +$stage = Join-Path $OutputRoot 'stage' +if (Test-Path -LiteralPath $stage) { + Remove-Item -LiteralPath $stage -Recurse -Force +} +New-Item -ItemType Directory -Path $stage | Out-Null + +$releasePackage = "gen1recomp-$Version-xbox-uwp.msix" +Copy-Item -LiteralPath $package -Destination (Join-Path $stage $releasePackage) +$dependencies = Join-Path $packageDirectory 'Dependencies\x64' +if (Test-Path -LiteralPath $dependencies -PathType Container) { + $dependencyStage = Join-Path $stage 'Dependencies\x64' + New-Item -ItemType Directory -Path $dependencyStage -Force | Out-Null + Copy-Item -Path (Join-Path $dependencies '*') -Destination $dependencyStage -Recurse +} +if ($BuildInfo -and (Test-Path -LiteralPath $BuildInfo -PathType Leaf)) { + Copy-Item -LiteralPath $BuildInfo -Destination (Join-Path $stage 'build-info.json') +} +if ($certificate) { + [System.IO.File]::WriteAllBytes( + (Join-Path $stage 'Gen1RecompUWP.cer'), + $certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) + ) +} + +$install = @" +Gen1Recomp $Version for Xbox Dev Mode + +Install the MSIX and any packages under Dependencies through Xbox Device Portal. +The certificate is public and is included only when the package was release-signed. +ROMs, saves and mods are not included. +"@ +[System.IO.File]::WriteAllText( + (Join-Path $stage 'INSTALL.txt'), + $install, + [System.Text.UTF8Encoding]::new($false) +) + +$archivePath = Join-Path $OutputRoot "gen1recomp-$Version-xbox-uwp.zip" +if (Test-Path -LiteralPath $archivePath) { + Remove-Item -LiteralPath $archivePath -Force +} +Compress-Archive -Path (Join-Path $stage '*') -DestinationPath $archivePath -CompressionLevel Optimal +$stream = [System.IO.File]::OpenRead($archivePath) +try { + $sha256 = [System.Security.Cryptography.SHA256]::Create() + try { + $hash = ([System.BitConverter]::ToString($sha256.ComputeHash($stream))).Replace('-', '').ToLowerInvariant() + } finally { + $sha256.Dispose() + } +} finally { + $stream.Dispose() +} +[System.IO.File]::WriteAllText( + "$archivePath.sha256", + "$hash $([System.IO.Path]::GetFileName($archivePath))`n", + [System.Text.UTF8Encoding]::new($false) +) + +Remove-Item -LiteralPath $stage -Recurse -Force +Write-Host "Staged $archivePath" diff --git a/scripts/xbox-uwp/verify_dependencies.ps1 b/scripts/xbox-uwp/verify_dependencies.ps1 new file mode 100644 index 00000000..f7bc48b9 --- /dev/null +++ b/scripts/xbox-uwp/verify_dependencies.ps1 @@ -0,0 +1,55 @@ +param( + [string]$Manifest = (Join-Path $PSScriptRoot "..\..\ports\uwp\third_party\manifest.json") +) + +$ErrorActionPreference = "Stop" + +$manifestPath = (Resolve-Path $Manifest).Path +$thirdPartyRoot = Split-Path -Parent $manifestPath +$metadata = Get-Content -Raw $manifestPath | ConvertFrom-Json + +function Get-Sha256 { + param( + [Parameter(Mandatory)][string]$Path, + [switch]$NormalizeLineEndings + ) + + if ($NormalizeLineEndings) { + $bytes = [System.IO.File]::ReadAllBytes($Path) + $text = [System.Text.Encoding]::UTF8.GetString($bytes).Replace("`r`n", "`n") + $bytes = [System.Text.UTF8Encoding]::new($false).GetBytes($text) + $sha256 = [System.Security.Cryptography.SHA256]::Create() + try { + return [System.BitConverter]::ToString($sha256.ComputeHash($bytes)).Replace("-", "") + } finally { + $sha256.Dispose() + } + } + + $stream = [System.IO.File]::OpenRead($Path) + try { + $sha256 = [System.Security.Cryptography.SHA256]::Create() + try { + return [System.BitConverter]::ToString($sha256.ComputeHash($stream)).Replace("-", "") + } finally { + $sha256.Dispose() + } + } finally { + $stream.Dispose() + } +} + +foreach ($file in $metadata.files) { + $path = Join-Path $thirdPartyRoot $file.path + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { + throw "Missing UWP dependency: $($file.path)" + } + + $normalize = $file.path.StartsWith("sdl2/include/", [System.StringComparison]::Ordinal) + $actual = Get-Sha256 -Path $path -NormalizeLineEndings:$normalize + if ($actual -ne $file.sha256) { + throw "UWP dependency hash mismatch: $($file.path)" + } +} + +Write-Host "Verified $($metadata.files.Count) UWP dependency files." diff --git a/scripts/xbox-uwp/write_build_info.sh b/scripts/xbox-uwp/write_build_info.sh new file mode 100644 index 00000000..57718737 --- /dev/null +++ b/scripts/xbox-uwp/write_build_info.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Write provenance for an Xbox UWP package. +# +# Usage: scripts/xbox-uwp/write_build_info.sh OUTPUT VERSION CONFIGURATION + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=common.sh +. "$SCRIPT_DIR/common.sh" + +OUTPUT="${1:-}" +VERSION="${2:-}" +CONFIGURATION="${3:-}" +MANIFEST="$UWP_ROOT/third_party/manifest.json" + +[ -n "$OUTPUT" ] && [ -n "$VERSION" ] && [ -n "$CONFIGURATION" ] \ + || fail "usage: $0 OUTPUT VERSION CONFIGURATION" +[ -f "$MANIFEST" ] || fail "missing dependency manifest: $MANIFEST" + +mkdir -p "$(dirname "$OUTPUT")" +GIT_COMMIT="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || echo unknown)" +BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +MANIFEST_SHA256="$(sha256_file "$MANIFEST")" +LOVE_COMMIT="$(awk ' + /"love"[[:space:]]*:/ { in_love=1 } + in_love && /"commit"[[:space:]]*:/ { + value=$0 + sub(/^.*"commit"[[:space:]]*:[[:space:]]*"/, "", value) + sub(/".*$/, "", value) + print value + exit + } +' "$MANIFEST")" +[ -n "$LOVE_COMMIT" ] || fail "LÖVE revision is missing from the dependency manifest" + +cat > "$OUTPUT" </ text macros (home/text.asm -- PlaceMoveUsersName): battle texts naming the enemy mon print -- "Enemy " before the nickname; player-side mons never get it. +-- Translatable as one "Enemy %s" template (#779) so languages that +-- qualify after the name, or decline, can (e.g. "%s ennemi"). local function displayName(b) - return b.isPlayer and b.name or ("Enemy " .. b.name) + return b.isPlayer and b.name or Strings("Enemy %s", b.name) end --- Apply the "Enemy " prefix to a pre-built message from a module that --- only knows the raw nickname (Status.beforeMove/residual, --- TrainerAI.useItem): splice it in before the first name occurrence. +-- Apply the enemy qualifier to a pre-built message from a module that +-- only knows the raw nickname (Status.beforeMove/residual): replace the +-- first name occurrence with the qualified form. local function prefixEnemy(msg, battler) if battler.isPlayer then return msg end local s = msg:find(battler.name, 1, true) if not s then return msg end - return msg:sub(1, s - 1) .. "Enemy " .. msg:sub(s) + return msg:sub(1, s - 1) .. Strings("Enemy %s", battler.name) + .. msg:sub(s + #battler.name) end -- Level-up stats window (PrintStatsBox .LevelUpStatsBox: box (9,2) @@ -415,10 +452,13 @@ function StatBox:draw() Font.drawBox(9, 2, 11, 10) love.graphics.setColor(0, 0, 0, 1) local s = self.mon.stats - local rows = { { "ATTACK", s.attack }, { "DEFENSE", s.defense }, - { "SPEED", s.speed }, { "SPECIAL", s.special } } + -- labels through Strings so a mod catalog translates them (#811) + local rows = { { Strings("ATTACK"), s.attack }, + { Strings("DEFENSE"), s.defense }, + { Strings("SPEED"), s.speed }, + { Strings("SPECIAL"), s.special } } for i, r in ipairs(rows) do - Font.draw(r[1], 88, 24 + (i - 1) * 16) + Font.draw(Strings(r[1]), 88, 24 + (i - 1) * 16) Font.draw(("%3d"):format(r[2]), 128, 32 + (i - 1) * 16) end love.graphics.setColor(1, 1, 1, 1) @@ -525,6 +565,10 @@ BattleState.StatBox = StatBox -- the level-up stat window (PrintStatsBox) local function newBattle(game) local self = setmetatable({}, BattleState) self.game = game + -- InitBattleVariables (engine/battle/init_battle_variables.asm) zeroes + -- wPartyAndBillsPCSavedMenuItem, so entering a battle drops the party + -- cursor the field menu has been carrying (src/ui/PartyMenu.lua). #768 + game.partyMenuSavedIndex = nil self.data = game.data -- ruleset from the merged registry (the requires above are the same -- records on a mod-free boot); an unknown save value falls back to the @@ -641,6 +685,9 @@ function BattleState.newTrainer(game, oppClass, partyIndex) local self = newBattle(game) self.kind = "trainer" self.oppClass = oppClass + -- the object_event trainer arg (roster index). computeMusicKind keys + -- data/scripts/victories.lua on class#party, so keep it on the battle (#782). + self.partyIndex = partyIndex or 1 self.trainer = game.data.trainers[oppClass] assert(self.trainer, "unknown trainer class " .. tostring(oppClass)) -- pret GetTrainerName_: RIVAL1/2/3 copy wRivalName into wTrainerName @@ -783,7 +830,7 @@ end -- tutorial passes failThrow -- it stands in for that event (#636). function BattleState:makeOldManDemo(name, failThrow) self.demo = true - self.demoName = name or "OLD MAN" + self.demoName = name or Strings("OLD MAN") self.demoFails = failThrow and true or false -- LoadPlayerBackPic and DisplayBattleMenu split on the same wBattleType: -- BATTLE_TYPE_OLD_MAN gets .oldManName + OldManPicBack, BATTLE_TYPE_PIKACHU @@ -821,6 +868,16 @@ function BattleState:say(text) table.insert(self.queue, { text = text }) end +-- A message whose ROM tail is `text_end` / `done` rather than `prompt`: +-- NextTextCommand returns straight out of PrintText on TX_END +-- (home/text.asm:328-334) and only TX_PROMPT_BUTTON blinks the arrow and +-- runs ManualTextScroll (home/text.asm:434-446), so these pages never wait +-- on the player. autoDelay is the frame hold before the queue moves on +-- (0 = the next row starts immediately, as PrintText returning does) (#765). +function BattleState:sayAuto(text, delay) + table.insert(self.queue, { text = text, auto = true, autoDelay = delay or 0 }) +end + -- Message that opens YES/NO once typed out, keeping the text visible -- underneath (pokered `done` + TWO_OPTION_MENU / TextBox opts.choice). function BattleState:sayChoice(text, onChoose) @@ -861,6 +918,13 @@ function BattleState:sayNext(text) table.insert(self.queue, self.nextInsert, { text = text }) end +-- sayNext for a page that ends in `text_end` (see sayAuto) (#765) +function BattleState:sayNextAuto(text, delay) + self.nextInsert = (self.nextInsert or 0) + 1 + table.insert(self.queue, self.nextInsert, + { text = text, auto = true, autoDelay = delay or 0 }) +end + -- insert a UI push right after the current queue item (dex page, the -- level-up stat box -- anything that must keep queue order) function BattleState:uiNext(factory) @@ -1011,6 +1075,8 @@ function BattleState:startMessage(item) self.charIndex = 0 self.msgWaiting = nil self.msgPrompt = nil + self.msgAutoWait = nil + self.msgHold = nil self.scrollPx = nil self:beginMsgLine() end @@ -1265,7 +1331,25 @@ function BattleState:updateQueue() end)) return true end - if not (item and item.choice) then + if item and item.auto then + -- No prompt: this page's ROM tail is `text_end`, so PrintText is + -- already back with the box still on screen -- pokered's used-move + -- line (engine/battle/used_move_text.asm EndUsedMove1Text.. + -- EndUsedMove5Text) and the item-use line (ItemUseText00, + -- engine/items/item_effects.asm) are both of that kind. Only + -- TX_PROMPT_BUTTON waits on A/B (home/text.asm:434-446) (#765). + self.msgAutoWait = self.msgAutoWait or item.autoDelay or 0 + if self.msgAutoWait > 0 then + self.msgAutoWait = self.msgAutoWait - 1 + else + self.msgAutoWait = nil + -- the typed page stays drawn behind whatever runs next (the move + -- animation, the ball toss): PrintText leaves the textbox tilemap + -- alone and animations only touch sprites (#296) + self.msgHold = true + self.current = nil + end + elseif not (item and item.choice) then -- The page is typed out and waiting on the player: PromptText -- (home/text.asm:209-217) writes '▼' at (18,16) and ManualTextScroll -- blinks it until A/B, so the arrow belongs on a finished page and not @@ -1312,19 +1396,39 @@ function BattleState:sendOutText(name) return self:romText("_EnemysWeakText", "The enemy's weak!\nGet'm! %s!", name) end +-- The cry a mon makes as it takes the field. Yellow does not run its +-- starter Pikachu through PlayCry at all: SendOutMon branches to +-- .starterPikachu (engine/battle/core.asm:1807-1817) and voices PCM +-- PikachuCry11, the short "Pika!", or PikachuCry37 when the Pikachu is +-- asleep (IsPlayerPikachuAsleepInParty); PrintBeginningBattleText does the +-- same for the BATTLE_TYPE_PIKACHU intro (engine/battle/common_text.asm: +-- 12-19). Without a clip the bare playCry reached for clip 1, the long +-- title-screen "Pikachuuu" (#837). Every PIKACHU gets it here, the same +-- starter approximation the rest of the port makes +-- (PikachuFollower.starterInParty). +function BattleState:playEntranceCry(battler) + local mon = battler and battler.mon + if not mon then return end + require("src.core.Sound").playCry(self.data, mon.species, + mon.status == "SLP" and 37 or 11) +end + -- audio/play_battle_music.asm: gym leaders (wGymLeaderNo) get the -- gym-leader theme, Lance does too, and the Champion (OPP_RIVAL3) -- gets the final-battle theme function BattleState:computeMusicKind() local isBoss = false - if self.kind == "trainer" and self.trainer then + if self.kind == "trainer" and self.oppClass then + -- wGymLeaderNo is written only by the eight gym scripts + -- (scripts/PewterGym.asm .. ViridianGym.asm), so the badge rosters in + -- victories.lua are exactly the fights that set it. The lookup must + -- include the party index: a class-wide prefix match also caught + -- Giovanni's Rocket Hideout (#1) and Silph Co (#2) battles, which never + -- touch wGymLeaderNo and take MUSIC_TRAINER_BATTLE like any other + -- trainer (#782). local victories = require("data.scripts.victories") - for key, reward in pairs(victories) do - if reward.badge and key:find(self.trainer.id .. "#", 1, true) == 1 then - isBoss = true - break - end - end + local reward = victories[self.oppClass .. "#" .. tostring(self.partyIndex or 1)] + isBoss = reward ~= nil and reward.badge ~= nil end -- init_battle.asm: challenging a gym leader (wGymLeaderNo, the badge -- fights only -- not Lance or the Champion) bumps the companion's @@ -1341,6 +1445,26 @@ function BattleState:computeMusicKind() return "wild" end +-- a mod-set per-trainer battle theme (trainers.battleTheme, an audio.songs +-- id); nil for vanilla trainers, so the kind default is untouched (#782) +function BattleState:battleTheme() + local trainer = self.trainer + if trainer and trainer.battleTheme then return trainer.battleTheme end + return nil +end + +-- the battle-theme cue for this battle: the mod-set trainer battleTheme +-- when the class has one, else the kind default. The single choke point +-- both the transition-wipe start (OverworldController:pushBattle) and +-- enter() route through, so a per-trainer override can't drift between +-- them. self.musicKind is set by enter(); pushBattle runs before that, +-- so compute it here when absent. +function BattleState:playBattleTheme() + require("src.core.Music").playBattle(self.data, + self.musicKind or self:computeMusicKind(), + self.trainer and self.trainer.id, self:battleTheme()) +end + -- side tables mirror the singles battlers; called before every -- battler-switch notification so sides[i].battlers[1] stays honest function BattleState:syncSides() @@ -1392,7 +1516,6 @@ function BattleState:enter() .. Strings("%s blacked\nout!", name), blackedOut)) return end - local Music = require("src.core.Music") self.musicKind = self:computeMusicKind() if self.isGymLeader then require("src.world.PikachuFollower") @@ -1402,7 +1525,7 @@ function BattleState:enter() -- (audio/play_battle_music.asm runs before the transition, and -- Music.play no-ops on the same song); this covers battles pushed -- without a transition (link battles, scripted pushes) - Music.playBattle(self.data, self.musicKind) + self:playBattleTheme() -- intro presentation (SlidePlayerAndEnemySilhouettesOnScreen): both -- sides slide in; the trainer pics stay up until the send-outs -- BATTLE BG "world" drops this battle's opacity so StateStack keeps drawing @@ -1442,7 +1565,7 @@ function BattleState:enter() -- a different point in each battle kind, so queue it per branch local function queueEnemyCry() self:act(function() - require("src.core.Sound").playCry(self.data, self.enemy.mon.species) + self:playEntranceCry(self.enemy) end) end -- PrintBeginningBattleText (engine/battle/common_text.asm:10-19): a wild @@ -1516,7 +1639,7 @@ function BattleState:enter() -- out of the ball after "X sent out Y!" (not the wild "already there" -- intro that LinkBattle previously inherited from newWild). self.enemySendingOut = true - self:say(Strings("%s sent\nout %s!", self.opponentName or "FOE", + self:say(Strings("%s sent\nout %s!", self.opponentName or Strings("FOE"), self.enemy.name)) self:act(function() self.enemySendingOut = false @@ -1553,7 +1676,7 @@ function BattleState:enter() -- SendOutMon (core.asm:1757-1762): after the poof the mon grows -- out of the ball (AnimateSendingOutMon at hlcoord 4,11) self:startGrowIn(self.player) - require("src.core.Sound").playCry(self.data, self.player.mon.species) + self:playEntranceCry(self.player) end) self:markParticipant() end @@ -1572,6 +1695,9 @@ end -- (end_of_battle.asm clears wLowHealthAlarm when a battle ends) function BattleState:exit() require("src.core.Sound").stopLoop("Low_Health_Alarm") + -- end_of_battle.asm clears wPartyAndBillsPCSavedMenuItem as well, so the + -- field party menu comes back on slot 1 after a battle. #768 + self.game.partyMenuSavedIndex = nil -- Free this battle's own GPU objects now rather than waiting on a GC -- finalizer: the two full-screen wavy-effect canvases (colorMode) and -- the AnimPlayer's per-instance tilesheet images/quads. The shared @@ -1598,6 +1724,19 @@ local function clearTrapping(battler) battler.trapDamage = nil end +-- SendOutMon (core.asm:1733-1735) clears both battle cursors, though the +-- disassembly only names one of them: `ld hl, wBattleAndStartSavedMenuItem / +-- ld [hli], a / ld [hl], a` writes zero to that byte AND to the byte behind +-- it, which is wPlayerMoveListIndex (wram.asm:242-244). So every player +-- send-out puts the main menu back on FIGHT and the move list back on the +-- first slot; the cursors are only remembered across sub-menus of the mon +-- that is already out (#737). Enemy send-outs run EnemySendOutFirstMon, +-- not SendOutMon, and leave both alone. +local function sendOutMonCursors(self) + self.menuIndex = 1 + self.moveIndex = 1 +end + -- core.asm:297-300: both sides' FLINCHED bits are cleared as a turn's move -- selection opens, but the clear is skipped for a mon that must recharge or -- is locked into Rage (core.asm:293-295 -- the Hyper Beam flinch-recharge @@ -1779,6 +1918,7 @@ function BattleState:update(dt) end self.menuIndex = row * 2 + col + 1 if input:wasPressed("a") then + require("src.core.Sound").play(self.data, "Press_AB") self:safariAction(({ "ball", "bait", "rock", "run" })[self.menuIndex]) end return @@ -1815,6 +1955,7 @@ function BattleState:update(dt) end self.menuIndex = row * 2 + col + 1 if input:wasPressed("a") then + require("src.core.Sound").play(self.data, "Press_AB") local choice = ({ "fight", "pkmn", "item", "run" })[self.menuIndex] if choice == "fight" and self.ghost then self:say(Strings("%s is too\nscared to move!", self.player.name)) @@ -1823,6 +1964,9 @@ function BattleState:update(dt) self:act(function() self:executeAction(self.enemy, self.player, self:enemyAction()) end) + -- the scared turn still ticks the player's residual (PrintGhostText + -- -> ExecutePlayerMoveDone, core.asm:3056, 3275-3279) + self:queueResidual(self.player, self.enemy) self:act(function() self:endOfTurn() end) elseif choice == "fight" then -- After the menu: own trapping/Bide or foe Wrap skips the move @@ -1857,7 +2001,7 @@ function BattleState:update(dt) -- The widescreen layout lays the four slots out as a 2x2 grid, so all -- four directions navigate it; nil means no direction was pressed and -- A / B / SELECT below behave the same in either layout. - local grid = self:wideLayout() + local grid = self:moveGridNavigation() and WideBattle.navigate(self.moveIndex, #moves, input) if grid then self.moveIndex = grid @@ -1873,9 +2017,11 @@ function BattleState:update(dt) self.moveSwapIndex = self.moveIndex end elseif input:wasPressed("b") then + require("src.core.Sound").play(self.data, "Press_AB") self.moveSwapIndex = nil self.phase = "menu" elseif input:wasPressed("a") then + require("src.core.Sound").play(self.data, "Press_AB") if self.moveSwapIndex then self:swapMoves(self.moveSwapIndex, self.moveIndex) self.moveSwapIndex = nil @@ -1905,7 +2051,7 @@ function BattleState:update(dt) local moves = self.mimicMoves -- the copy menu shares the widescreen move grid, so it navigates the -- same way there (the classic layout keeps the vertical list) - local grid = self:wideLayout() + local grid = self:moveGridNavigation() and WideBattle.navigate(self.mimicIndex, #moves, input) if grid then self.mimicIndex = grid @@ -1914,6 +2060,7 @@ function BattleState:update(dt) elseif input:wasPressed("down") then self.mimicIndex = self.mimicIndex < #moves and self.mimicIndex + 1 or 1 elseif input:wasPressed("a") then + require("src.core.Sound").play(self.data, "Press_AB") local pick = moves[self.mimicIndex] local ctx = self.mimicCtx self.mimicMoves, self.mimicCtx = nil, nil @@ -2081,7 +2228,7 @@ function BattleState:oldManThrow() self.phase = "messages" self.afterQueue = "finish" self.result = "run" -- nothing is kept; wBattleResult only ends the demo - self:say(Strings("%s used\nPOKé BALL!", self.demoName or "OLD MAN")) + self:sayAuto(Strings("%s used\nPOKé BALL!", self.demoName or Strings("OLD MAN"))) self:act(function() require("src.core.Sound").play(self.data, "Ball_Toss") -- ItemUseBall's beat before the toss chain (like throwBall) @@ -2275,6 +2422,7 @@ function BattleState:resolveSwitch(newMon) previous = previous, }) self:markParticipant() + sendOutMonCursors(self) self.sendingOut = true self:sayNext(self:sendOutText(self.player.name)) self:animNext("POOF_ANIM", false) @@ -2282,7 +2430,7 @@ function BattleState:resolveSwitch(newMon) self.sendingOut = false -- SendOutMon (core.asm:1757-1762): poof, then the grow-in self:startGrowIn(self.player) - require("src.core.Sound").playCry(self.data, self.player.mon.species) + self:playEntranceCry(self.player) end) end) self:act(function() @@ -2291,6 +2439,48 @@ function BattleState:resolveSwitch(newMon) self:act(function() self:endOfTurn() end) end +-- Gen 1 calls HandlePoisonBurnLeechSeed right after the acting side's +-- move (core.asm:426-464), so the drain lands before the slower mon acts; +-- the modern ruleset sweeps residuals at end of round instead (Gen 3+). +local function residualAfterMove(battle) + local ruleset = battle.ruleset + return not ruleset or ruleset.residualAfterMove ~= false +end + +-- HandlePoisonBurnLeechSeed for one side, run right after its action. +-- Skipped when the action settled the battle (a Teleport escape rets +-- before the call), when an AI switch swapped the side out mid-action, or +-- when the move already knocked the opponent out (core.asm:423-425, +-- 452-454) -- the same bypass the end-of-round sweep applies. +function BattleState:residualFor(b, opp) + if self.result then return end + if self.player ~= b and self.enemy ~= b then return end + if b.mon.hp <= 0 or opp.mon.hp <= 0 then return end + local msgs = Status.residual(b, opp, self) + for _, m in ipairs(msgs) do self:sayNext(prefixEnemy(m, b)) end + if b.leechSeeded and b.mon.hp > 0 then + -- the drain plays the ABSORB animation from the healing side + -- (core.asm:506-517 flips hWhoseTurn before PlayMoveAnimation) + self:animNext("ABSORB", opp.isPlayer) + end + if #msgs > 0 then self:drainNext() end -- poison/burn/seed HP moved + self.sideToxic = self.sideToxic or {} + if b.toxicCounter then + self.sideToxic[b.isPlayer and "player" or "enemy"] = b.toxicCounter + end + if b.mon.hp <= 0 then + self:onFaint(b) + end +end + +-- append one side's residual to the queue under Gen 1 timing; a no-op +-- under the modern ruleset, whose sweep runs in endOfTurn instead +function BattleState:queueResidual(b, opp) + if residualAfterMove(self) then + self:act(function() self:residualFor(b, opp) end) + end +end + function BattleState:endOfTurn() -- the same ret: a decided battle never reaches HandlePoisonBurnLeechSeed -- or CheckNumAttacksLeft (core.asm:417-421, 456-460), so the residual @@ -2310,6 +2500,9 @@ function BattleState:endOfTurn() -- that sets the flag also zeroes the counter, so a stale value is -- unobservable (a switch or cure downgrades Toxic to plain poison). self.sideToxic = self.sideToxic or {} + -- Gen 1 timing already ran each side's residual right after its move + -- (see executeAction); the end-of-round sweep is the modern ruleset's + local sweep = not residualAfterMove(self) -- a battler whose opponent was already knocked out by a move this turn -- skips its own residual (HandlePoisonBurnLeechSeed is bypassed when the -- move faints the target); snapshot before residual so one side's @@ -2319,7 +2512,7 @@ function BattleState:endOfTurn() for _, pair in ipairs({ { self.player, self.enemy, "player", enemyAlive }, { self.enemy, self.player, "enemy", playerAlive } }) do local b, opp, side, oppAlive = pair[1], pair[2], pair[3], pair[4] - if b.mon.hp > 0 and oppAlive then + if sweep and b.mon.hp > 0 and oppAlive then local msgs = Status.residual(b, opp, self) for _, m in ipairs(msgs) do self:sayNext(prefixEnemy(m, b)) end if #msgs > 0 then self:drainNext() end -- poison/burn/seed HP moved @@ -2330,6 +2523,9 @@ function BattleState:endOfTurn() self:onFaint(b) end end + -- the Haze move-forfeit only covers the turn Haze was used; if the + -- cured mon had already moved, drop the flag before next turn + b.skipMove = nil -- CheckNumAttacksLeft (core.asm:683-697): a trapping counter that -- hit 0 this turn releases its bit only now, at the end of the turn if b.trappingTurns and b.trappingTurns <= 0 then @@ -2702,7 +2898,16 @@ function BattleState:applyHitFx(hit) local t = hit.animType if not t and hit.blink then t = hit.blink.isPlayer and 1 or 4 end if hit.sfx then - require("src.core.Sound").play(self.data, hit.sfx) + local Sound = require("src.core.Sound") + -- EffectRegistry hands the row the PlayApplyingAttackSound sound WITH its + -- wFrequencyModifier byte, so it goes through the same pitch/tempo path + -- move sounds use (#826). A bare string -- an older row, or a mod that + -- built its own hit fx -- still plays unmodified. + if type(hit.sfx) == "table" then + Sound.playMove(self.data, hit.sfx) + else + Sound.play(self.data, hit.sfx) + end end if not t or not self:animationsOn() then return end if t == 1 then @@ -3041,11 +3246,24 @@ function BattleState:executeAction(user, target, action) user.boundTurns = target.trappingTurns and math.max(1, target.trappingTurns) or nil + -- wPlayerSelectedMove / wEnemySelectedMove as the status gauntlet + -- reads it: the locked specials keep continuing the move they + -- started, so they carry a move id too. Resolved once here and + -- handed to every statusInterrupt below, which is where + -- .TriedToUseDisabledMoveCheck lives (#860). + local selectedId = action.id + or (action.special == "trapping" and user.trapMove) + or (action.special == "bide" and "BIDE") + or nil + -- trainer class AI actions (engine/battle/trainer_ai.asm) if action.special == "aiItem" then self.aiUses = (self.aiUses or 1) - 1 + -- useItem's messages arrive final: its item line prints the raw + -- nickname on purpose (no "Enemy " in AIPrintItemUseText), so the + -- prefix splice must not touch them. for _, m in ipairs(TrainerAI.useItem(self, action.item)) do - self:sayNext(prefixEnemy(m, self.enemy)) + self:sayNext(m) end self:drainNext() require("src.core.Sound").play(self.data, "Heal_Ailment") @@ -3095,22 +3313,28 @@ function BattleState:executeAction(user, target, action) return end if action.special == "trapping" then - if self:statusInterrupt(user, target) then return end + if self:statusInterrupt(user, target, selectedId) then return end self:continueTrapping(user, target) return end if action.special == "bide" then - if self:statusInterrupt(user, target) then return end + if self:statusInterrupt(user, target, selectedId) then return end self:continueBide(user, target) return end - if self:statusInterrupt(user, target) then return end + if self:statusInterrupt(user, target, selectedId) then return end self:performMove(user, target, action, false) end run() -- after announce/anim/effect text (pokered DrawHUDsAndHPBars) self:actNext(function() self:syncShownStatus() end) + -- MainInBattleLoop calls HandlePoisonBurnLeechSeed right after each + -- Execute*Move (core.asm:426-464): the acting side's poison/burn/leech + -- seed ticks before the slower mon acts, not at end of round + if residualAfterMove(self) then + self:actNext(function() self:residualFor(user, target) end) + end end -- Sleep / confusion onomatopoeia from Check*StatusConditions @@ -3193,8 +3417,8 @@ end -- Runs Status.beforeMove plus the shared interruption bookkeeping; -- returns true when the user's action is interrupted. -function BattleState:statusInterrupt(user, target) - local canMove, msgs, selfHit = Status.beforeMove(user, self.rng, self) +function BattleState:statusInterrupt(user, target, selectedId) + local canMove, msgs, selfHit = Status.beforeMove(user, self.rng, self, selectedId) for _, m in ipairs(msgs) do self:sayStatusMsg(user, m) end if selfHit then -- confusion self-hit (core.asm:3428-3434): clears everything in @@ -3246,10 +3470,16 @@ end -- damaging pipeline (EffectRegistry.runDamaging). -- Gen 1 status/stat primary effects call PlayCurrentMoveAnimation only --- after they land; these failure texts print with no animation. +-- after they land; these failure texts print with no animation. Failures +-- whose text is an ordinary sentence rather than one of the shared fail +-- lines set msgs.failed instead of relying on this sniffer -- Substitute's +-- two failure lines name the move, not the failure (#644). local function primaryEffectFailed(msgs) if not msgs or #msgs == 0 then return true end - local m = msgs[1] + if msgs.failed then return true end + -- the extracted lines keep the ROM's own trailing blank ("But, it + -- failed! "), so match with it trimmed or a refused status animates + local m = msgs[1]:gsub("%s+$", "") if m == "But, it failed!" or m == "Nothing happened!" then return true end if m:find("didn't affect", 1, true) then return true end if m:find("is unaffected", 1, true) then return true end @@ -3294,7 +3524,7 @@ function BattleState:performMove(user, target, moveInst, isCalled) self.moveAnimRow = nil if not (user.thrashTurns and moveInst == user.thrashMove and user.thrashAnnounced) then - self:sayNext(self:romText("_ItemUseText001", "%s\nused %s!", displayName(user), move.name)) + self:sayNextAuto(self:romText("_ItemUseText001", "%s\nused %s!", displayName(user), move.name)) -- the move's animation plays right after the announcement; the -- damage path attaches the target's hit blink to this row so the -- blink follows the animation (pokered's order). Mimic is the @@ -3758,7 +3988,7 @@ function BattleState:enemyMonFainted() self.enemySendingOut = false self:startGrowIn(self.enemy) self:actNext(function() - require("src.core.Sound").playCry(self.data, self.enemy.mon.species) + self:playEntranceCry(self.enemy) end) end) end) @@ -3790,13 +4020,14 @@ function BattleState:enemyMonFainted() self.participants = {} self:markParticipant() self.nextInsert = 0 + sendOutMonCursors(self) self.sendingOut = true self:sayNext(self:sendOutText(self.player.name)) self:animNext("POOF_ANIM", false) self:actNext(function() self.sendingOut = false self:startGrowIn(self.player) - require("src.core.Sound").playCry(self.data, self.player.mon.species) + self:playEntranceCry(self.player) end) end) return @@ -3982,6 +4213,7 @@ function BattleState:openReplacementMenu() }) self:markParticipant() self.nextInsert = 0 + sendOutMonCursors(self) self.sendingOut = true self:sayNext(self:sendOutText(self.player.name)) self:animNext("POOF_ANIM", false) @@ -3989,7 +4221,7 @@ function BattleState:openReplacementMenu() self.sendingOut = false -- SendOutMon (core.asm:1757-1762): poof, then the grow-in self:startGrowIn(self.player) - require("src.core.Sound").playCry(self.data, self.player.mon.species) + self:playEntranceCry(self.player) end) end, }) @@ -4020,7 +4252,7 @@ function BattleState:safariAction(choice) if choice == "ball" then st.balls = st.balls - 1 - self:say(Strings("%s used\nSAFARI BALL!", playerName)) + self:sayAuto(Strings("%s used\nSAFARI BALL!", playerName)) self:act(function() require("src.core.Sound").play(self.data, "Ball_Toss") self.lastBall = "SAFARI_BALL" @@ -4167,6 +4399,9 @@ function BattleState:tryRun() self:act(function() self:executeAction(self.enemy, self.player, self:enemyAction()) end) + -- a failed escape loses the turn (core.asm:1572): the player's + -- residual still ticks, same as an item turn + self:queueResidual(self.player, self.enemy) self:act(function() self:endOfTurn() end) end end @@ -4189,6 +4424,11 @@ function BattleState:itemUsed(messages) self:act(function() self:executeAction(self.enemy, self.player, self:enemyAction()) end) + -- the item spends the player's move, but its residual still ticks: + -- ExecutePlayerMove rets early on wActionResultOrTookBattleTurn and + -- MainInBattleLoop calls HandlePoisonBurnLeechSeed anyway + -- (core.asm:3086-3088, 3275-3279) + self:queueResidual(self.player, self.enemy) self:act(function() self:endOfTurn() end) end @@ -4342,7 +4582,7 @@ function BattleState:throwBall(ball) -- " used !" line (#291). Safari and the old man demo are -- still wIsInBattle == 1, and this port models both as kind == "wild". if self.kind == "wild" then - self:say(self:romText("_ItemUseText001", "%s used\n%s!", self.game.save.player.name, + self:sayAuto(self:romText("_ItemUseText001", "%s used\n%s!", self.game.save.player.name, self.data.items[ball].name)) end self:act(function() @@ -4370,6 +4610,9 @@ function BattleState:throwBall(ball) self:act(function() self:executeAction(self.enemy, self.player, self:enemyAction()) end) + -- a thrown ball spends the turn like an item: the player's residual + -- still ticks (core.asm:3275-3279) + self:queueResidual(self.player, self.enemy) self:act(function() self:endOfTurn() end) return end @@ -4388,6 +4631,9 @@ function BattleState:throwBall(ball) self:act(function() self:executeAction(self.enemy, self.player, self:enemyAction()) end) + -- a thrown ball spends the turn like an item: the player's residual + -- still ticks (core.asm:3275-3279) + self:queueResidual(self.player, self.enemy) self:act(function() self:endOfTurn() end) return end @@ -4414,6 +4660,9 @@ function BattleState:throwBall(ball) self:act(function() self:executeAction(self.enemy, self.player, self:enemyAction()) end) + -- a thrown ball spends the turn like an item: the player's residual + -- still ticks (core.asm:3275-3279) + self:queueResidual(self.player, self.enemy) self:act(function() self:endOfTurn() end) end end) @@ -4961,11 +5210,32 @@ function BattleState:drawZonePass(src, sx, sy) local shader = PaletteFX.shader() local pals = self:sgbBattlePals() local bgp = self:activeBgp() + -- #822: OG / OG INV / CLASSIC are forced-mono modes, so sgbPalettes() being + -- nil here makes PaletteFX.ensureZones invent a whole-screen zone and the + -- WHOLE finished frame is re-thresholded through the shade shader at blit + -- time -- which is why picImage already hands those modes raw DMG grays. + -- This pass has to leave DMG shades behind for the same reason: sendColors + -- runs the mode substitution HERE too, and the frame-level pass then + -- substitutes a second time. OG INV inverts twice and comes out upright; + -- CLASSIC's color 0 (155,188,15) has red 0.61, which falls in the shader's + -- c1 bucket, so the paper darkens one shade. Either way the battle stops + -- matching the YES/NO box an overlay state draws over it, since that box + -- only ever sees the frame-level pass. OG is the identity, which is why + -- only the other two showed it. Keep this mode set in sync with picImage / + -- PaletteFX.ensureZones / WideBattle.monoMode. + local mono = PaletteFX.mode == "og" or PaletteFX.mode == "og_inv" + or PaletteFX.mode == "classic" love.graphics.setColor(1, 1, 1, 1) love.graphics.setShader(shader) local shaking = sx ~= 0 or sy ~= 0 for _, z in ipairs(BATTLE_ZONES) do - PaletteFX.sendColors(shader, PaletteFX.permute(pals[z.pal], bgp)) + if mono then + -- the BGP fade still runs, just in gray: the frame-level pass colors + -- whatever DMG shade this leaves behind + PaletteFX.sendShades(shader, PaletteFX.permute(PaletteFX.GRAYS, bgp)) + else + PaletteFX.sendColors(shader, PaletteFX.permute(pals[z.pal], bgp)) + end local zx, zy = z[1] * 8, z[2] * 8 local zw, zh = (z[3] - z[1] + 1) * 8, (z[4] - z[2] + 1) * 8 love.graphics.setScissor(zx, zy, zw, zh) @@ -5107,7 +5377,7 @@ function BattleState:drawPicsLayer(slide, sx, sy, onlySide, skipMenuClip) -- .mimicmenu) wipes rows 7+. The port draws pics above the menu -- layer in the colorized pipeline, so clip them to the visible rows. local g = love.graphics - local clipY = not skipMenuClip + local clipY = not skipMenuClip and self:bottomUIVisible() and (self.phase == "mimicSelect" and 56 or self.phase == "moveSelect" and 64) or nil @@ -5219,6 +5489,7 @@ function BattleState:drawHUDs(slide) -- per-pixel tint (grayFill) -- otherwise GREENBAR's red-channel-0 fill -- double-applies and the zone shade shader maps the whole bar to black (#229). local grayFill = self:colorMode() + local showStatus = self:statusHUDVisible() local barData = self.data local fx = self.fx local hudShake = (fx and fx.hudShakeX) or 0 @@ -5228,7 +5499,8 @@ function BattleState:drawHUDs(slide) -- DrawEnemyHUDAndHPBar is called from _InitBattleCommon (core.asm:6763) -- AFTER PrintBeginningBattleText returns, so "Wild X appeared!" shows the -- player's ball row with no enemy HUD beside it (#317) - if self.enemy and not self.showEnemyTrainer and not self.enemySendingOut + if showStatus and self.enemy and not self.showEnemyTrainer + and not self.enemySendingOut and not self:growInScale(self.enemy) and slide == 0 and not self.introBalls and not self.enemy.fainted then -- enemy HUD (DrawEnemyHUDAndHPBar): name row 0, +level (4,1), @@ -5315,7 +5587,7 @@ function BattleState:drawHUDs(slide) self:drawBallRow(self.playerParty or self.game.save.party, 88, 80, 8) end local hidePlayer = self.safari or self.demo - if self.player and not hidePlayer and not self.showPlayerBack + if showStatus and self.player and not hidePlayer and not self.showPlayerBack and slide == 0 then -- player HUD (DrawPlayerHUDAndHPBar): name (10,7), +level -- (14,8), HP bar (10,9), HP numbers row 10, underline row 11 with @@ -5340,9 +5612,11 @@ function BattleState:drawHUDs(slide) end function BattleState:drawTextArea() + if not self:bottomUIVisible() then return end Font.drawBox(0, 12, 20, 6) love.graphics.setColor(0, 0, 0, 1) - if self.phase == "messages" and (self.current or self.animPlaying) then + if self.phase == "messages" + and (self.current or self.animPlaying or self.msgHold) then -- during the move animation self.current is nil but shown still holds -- the "used X!" lines; keep drawing them like pokered, whose move -- animations only touch sprites and never the textbox tilemap (#296) @@ -5374,9 +5648,9 @@ function BattleState:drawTextArea() -- -- next to FIGHT (9,14) for the first 80 frames, then ITEM (9,16) Font.drawBox(8, 12, 12, 6) love.graphics.setColor(0, 0, 0, 1) - Font.draw(Strings("FIGHT"), 80, 112) + Font.draw(Strings("FIGHT", "battle"), 80, 112) Font.drawCode(0xE1, 128, 112); Font.drawCode(0xE2, 136, 112) - Font.draw(Strings("ITEM"), 80, 128); Font.draw(Strings("RUN"), 128, 128) + Font.draw(Strings("ITEM", "battle"), 80, 128); Font.draw(Strings("RUN", "battle"), 128, 128) Font.drawCode(0xED, 72, (self.demoTimer or 0) <= 80 and 112 or 128) elseif self.phase == "menu" then local col = (self.menuIndex - 1) % 2 @@ -5386,7 +5660,7 @@ function BattleState:drawTextArea() -- THROW ROCK RUN" from (2,14) Font.drawBox(0, 12, 20, 6) Font.draw(Strings("BALLx"), 16, 112); Font.draw(Strings("BAIT"), 112, 112) - Font.draw(Strings("THROW ROCK"), 16, 128); Font.draw(Strings("RUN"), 112, 128) + Font.draw(Strings("THROW ROCK"), 16, 128); Font.draw(Strings("RUN", "battle"), 112, 128) -- DisplayBattleMenu .safariLeftColumn / .safariRightColumn print -- wNumSafariBalls at hlcoord 7,14 with `lb bc, 1, 2` -- one byte, two -- digits, space padded -- right after the "BALLx" label at columns @@ -5397,9 +5671,9 @@ function BattleState:drawTextArea() -- BATTLE_MENU_TEMPLATE: box (8,12)-(19,17), "FIGHT / -- ITEM RUN" from (10,14); cursor columns 9 / 15 Font.drawBox(8, 12, 12, 6) - Font.draw(Strings("FIGHT"), 80, 112) + Font.draw(Strings("FIGHT", "battle"), 80, 112) Font.drawCode(0xE1, 128, 112); Font.drawCode(0xE2, 136, 112) - Font.draw(Strings("ITEM"), 80, 128); Font.draw(Strings("RUN"), 128, 128) + Font.draw(Strings("ITEM", "battle"), 80, 128); Font.draw(Strings("RUN", "battle"), 128, 128) Font.drawCode(0xED, (col == 0 and 72 or 120), 112 + row * 16) end elseif self.phase == "moveSelect" then @@ -5430,8 +5704,13 @@ function BattleState:drawTextArea() local def = self.data.moves[mv.id] Font.draw(def and def.name or tostring(mv.id), 48, 96 + i * 8) end - Font.drawCode((self.moveSwapIndex == self.moveIndex) and 0xEC or 0xED, - 40, 96 + self.moveIndex * 8) + -- Swap cursor: SelectMenuItem parks the hollow arrow on the marked row + -- (core.asm:2600-2607), then HandleMenuInput's PlaceMenuCursor writes the + -- filled arrow into the tilemap over it whenever the cursor sits there + -- (home/window.asm:184-185), so the current row is always filled. Only + -- one glyph may land per cell -- drawCode blits black-on-transparent, so + -- stacking 0xED over 0xEC would merge the two arrows (#814). + Font.drawCode(0xED, 40, 96 + self.moveIndex * 8) if self.moveSwapIndex and self.moveSwapIndex ~= self.moveIndex then Font.drawCode(0xEC, 40, 96 + self.moveSwapIndex * 8) end diff --git a/src/battle/EffectRegistry.lua b/src/battle/EffectRegistry.lua index a6789ade..05c219d9 100644 --- a/src/battle/EffectRegistry.lua +++ b/src/battle/EffectRegistry.lua @@ -29,7 +29,7 @@ end -- pokered's / text macros print "Enemy " before the enemy -- mon's nickname (home/text.asm PlaceMoveUsersName) local function displayName(b) - return b.isPlayer and b.name or ("Enemy " .. b.name) + return b.isPlayer and b.name or Strings("Enemy %s", b.name) -- #779 end EffectRegistry.displayName = displayName @@ -209,8 +209,28 @@ function EffectRegistry.runDamaging(battle, ctx, record) -- announcement-time moveAnimRow, later hits queue fresh anim rows. -- Thrash/rage continuations have no announcement anim -- a bare -- hitRow carries the blink instead. - local hitSfx = info.typeMult > 10 and "Super_Effective" - or info.typeMult < 10 and "Not_Very_Effective" or "Damage" + -- PlayApplyingAttackSound (engine/battle/animations.asm, the routine after + -- PlayApplyingAttackAnimation) picks the sound off wDamageMultipliers -- 10 + -- is neutral, above it super effective, below it not very -- and sets + -- wFrequencyModifier/wTempoModifier alongside it: $20/$30 damage, $e0/$ff + -- super effective, $50/$01 not very. All three programs live on the noise + -- channel (audio/sfx/{damage,super_effective,not_very_effective}.asm, + -- `channel 8`), where the frequency modifier is added to the polynomial + -- counter and so IS the pitch of the hit, while the tempo modifier is + -- skipped outright (audio/engine_2.asm Audio2_note_length: `cp CHAN8 / + -- jr z, .skip` keeps the noise channel at the default $100). Playing them + -- bare made the super effective hit a dull thud and the not very effective + -- one a bright crack, which is why they sounded swapped (#826); the tempo + -- byte is deliberately not carried, since applying it would stretch notes + -- the hardware never stretches. + local hitSfx + if info.typeMult > 10 then + hitSfx = { sound = "Super_Effective", pitch = 0xe0 } + elseif info.typeMult < 10 then + hitSfx = { sound = "Not_Very_Effective", pitch = 0x50 } + else + hitSfx = { sound = "Damage", pitch = 0x20 } + end -- GetPlayerAnimationType / GetEnemyAnimationType (engine/battle/core.asm -- :3159 / :5555): wAnimationType is 4 (blink the enemy pic) or 1 (shake -- the screen vertically) for a damaging move with no added effect, and diff --git a/src/battle/MoveEffects.lua b/src/battle/MoveEffects.lua index acdd2118..a96127fd 100644 --- a/src/battle/MoveEffects.lua +++ b/src/battle/MoveEffects.lua @@ -23,12 +23,17 @@ local MoveEffects = {} -- pokered's / text macros print "Enemy " before the -- enemy mon's nickname (home/text.asm PlaceMoveUsersName) local function displayName(b) - return b.isPlayer and b.name or ("Enemy " .. b.name) + return b.isPlayer and b.name or Strings("Enemy %s", b.name) -- #779 end +-- Stat names as printed (data/battle/stat_mod_names.asm +-- StatModTextStrings). Strings.source, not Strings: this table is built +-- at require time, before Strings.load has a catalog, so changeStage +-- looks each label up at use time (#811). local STAT_LABEL = { - attack = "ATTACK", defense = "DEFENSE", speed = "SPEED", - special = "SPECIAL", accuracy = "ACCURACY", evasion = "EVADE", + attack = Strings.source("ATTACK"), defense = Strings.source("DEFENSE"), + speed = Strings.source("SPEED"), special = Strings.source("SPECIAL"), + accuracy = Strings.source("ACCURACY"), evasion = Strings.source("EVADE"), } -- --------------------------------------------------------------------- @@ -54,14 +59,15 @@ local function changeStage(battle, who, stat, delta, fromEnemy) who.hazeStatReset = nil -- _MonsStatsRoseText/_MonsStatsFellText: "X's / STAT rose!"; the -- two-stage variants scroll "greatly" onto a third line + local label = Strings(STAT_LABEL[stat]) -- looked up here, not at require (#811) if delta >= 2 then - return { Strings("%s's\n%s\ngreatly rose!", displayName(who), STAT_LABEL[stat]) } + return { Strings("%s's\n%s\ngreatly rose!", displayName(who), label) } elseif delta == 1 then - return { Strings("%s's\n%s rose!", displayName(who), STAT_LABEL[stat]) } + return { Strings("%s's\n%s rose!", displayName(who), label) } elseif delta == -1 then - return { Strings("%s's\n%s fell!", displayName(who), STAT_LABEL[stat]) } + return { Strings("%s's\n%s fell!", displayName(who), label) } end - return { Strings("%s's\n%s\ngreatly fell!", displayName(who), STAT_LABEL[stat]) } + return { Strings("%s's\n%s\ngreatly fell!", displayName(who), label) } end MoveEffects.changeStage = changeStage @@ -259,15 +265,29 @@ MoveEffects.primary = { return { romText(battle.data, "_StatusChangesEliminatedText", "All STATUS changes\nare eliminated!") } end, + -- substitute.asm reaches its PlayCurrentMoveAnimation / AnimationSubstitute + -- Bankswitch only inside the success branch, after `set HAS_SUBSTITUTE_UP`; + -- .alreadyHasSubstitute and .notEnoughHP fall straight through to PrintText, + -- so both failures print with no animation at all. That is load bearing + -- here: the SUBSTITUTE animation opens with SE_SLIDE_MON_OFF, which leaves + -- the user's pic hidden (BattleState.lua slideOff end state) until the doll + -- is drawn in its place -- and with no substituteHP raised there is no doll, + -- so a failed Substitute used to erase the user's sprite for the rest of the + -- battle (#644). The failed flag rides the message list so performMove can + -- peel the announcement-time anim row without matching on printed text. SUBSTITUTE_EFFECT = function(battle, user) - if user.substituteHP then return { romText(battle.data, "_HasSubstituteText", "%s\nhas a SUBSTITUTE!", displayName(user)) } end + if user.substituteHP then + return { romText(battle.data, "_HasSubstituteText", "%s\nhas a SUBSTITUTE!", displayName(user)), + failed = true } + end local cost = math.floor(user.mon.stats.hp / 4) - -- substitute.asm only fails on subtraction underflow (current HP - -- strictly below maxHP/4); at equality the substitute is built and - -- the user is left standing on exactly 0 HP (it faints only when - -- the engine next checks HP, not here) - if user.mon.hp < cost then - return { romText(battle.data, "_TooWeakSubstituteText", "Too weak to make\na SUBSTITUTE!") } + -- A Substitute costs one quarter of max HP, rounded down. Do not let + -- the cost consume the user's last HP: the move must fail at the exact + -- boundary as well as below it, or the next turn's HP guard can leave a + -- trainer battle unable to progress. + if user.mon.hp <= cost then + return { romText(battle.data, "_TooWeakSubstituteText", "Too weak to make\na SUBSTITUTE!"), + failed = true } end user.mon.hp = user.mon.hp - cost user.substituteHP = cost + 1 @@ -480,7 +500,9 @@ MoveEffects.full = { chooseDamage = function(ctx) -- no immunity check: SetDamageEffects skips AdjustDamageForMoveType (#616) local dmg = fixedDamageFor(ctx) - if not dmg then return nil, "But, it failed!" end + if not dmg then + return nil, romText(ctx.battle.data, "_ButItFailedText", "But, it failed!") + end return dmg, plainInfo() end, }, @@ -496,7 +518,7 @@ MoveEffects.full = { local blocked = immuneMsg(ctx) if blocked then return false, blocked end if TurnOrder.effectiveSpeed(ctx.user) < TurnOrder.effectiveSpeed(ctx.target) then - return false, "But, it failed!" + return false, romText(ctx.battle.data, "_ButItFailedText", "But, it failed!") end return true end, @@ -521,7 +543,9 @@ MoveEffects.full = { DREAM_EATER_EFFECT = { -- only works on sleeping targets (checked before damage) gate = function(ctx) - if ctx.target.mon.status ~= "SLP" then return false, "But, it failed!" end + if ctx.target.mon.status ~= "SLP" then + return false, romText(ctx.battle.data, "_ButItFailedText", "But, it failed!") + end return true end, afterDamage = drainHalf("_DreamWasEatenText", Strings.source("%s's\ndream was eaten!")), diff --git a/src/battle/Status.lua b/src/battle/Status.lua index 8fad8716..90dd8d74 100644 --- a/src/battle/Status.lua +++ b/src/battle/Status.lua @@ -6,6 +6,7 @@ -- back to the vanilla records, which is bit-identical behavior. local Strings = require("src.core.Strings") +local romText = require("src.core.RomText") local Status = {} @@ -34,8 +35,8 @@ end -- sentence rather than the noun: "hurt by poison" and "hurt by the burn" -- decline differently once translated, so a shared fragment cannot be the -- translatable unit. -local function damageOverTime(template) - return function(battler) +local function damageOverTime(label, template) + return function(battler, _, battle) local mon = battler.mon local base = math.max(1, math.floor(mon.stats.hp / 16)) local dmg = base @@ -44,7 +45,7 @@ local function damageOverTime(template) battler.toxicCounter = battler.toxicCounter + 1 end mon.hp = math.max(0, mon.hp - dmg) - return { Strings(template, name(battler)) } + return { romText(battle and battle.data, label, template, name(battler)) } end end @@ -63,53 +64,63 @@ Status.RECORDS = { id = "SLP", label = "SLP", hudLabel = "SLP", catchBonus = 25, shakeBonus = 10, beforeMovePriority = 40, - beforeMove = function(battler) + beforeMove = function(battler, _, battle) battler.sleepTurns = (battler.sleepTurns or 1) - 1 if battler.sleepTurns <= 0 then battler.mon.status = nil - return false, { Strings("%s\nwoke up!", name(battler)) } -- wakes, loses the turn + -- wakes, loses the turn + return false, { romText(battle and battle.data, "_WokeUpText", + "%s\nwoke up!", name(battler)) } end - return false, { Strings("%s\nis fast asleep!", name(battler)) } + return false, { romText(battle and battle.data, "_FastAsleepText", + "%s\nis fast asleep!", name(battler)) } end, onInflict = function(battle, target, opts, display) target.sleepTurns = battle.rng(1, 7) - return { Strings("%s\nfell asleep!", display) } + return { romText(battle.data, "_FellAsleepText", + "%s\nfell asleep!", display) } end, }, FRZ = { id = "FRZ", label = "FRZ", hudLabel = "FRZ", catchBonus = 25, shakeBonus = 10, beforeMovePriority = 30, - beforeMove = function(battler) - return false, { Strings("%s\nis frozen solid!", name(battler)) } + beforeMove = function(battler, _, battle) + return false, { romText(battle and battle.data, "_IsFrozenText", + "%s\nis frozen solid!", name(battler)) } end, canInflict = function(target) return not hasType(target, "ICE") end, - onInflict = function(_, _, _, display) - return { Strings("%s\nwas frozen solid!", display) } + onInflict = function(battle, _, _, display) + return { romText(battle and battle.data, "_FrozenText", + "%s\nwas frozen solid!", display) } end, }, PSN = { id = "PSN", label = "PSN", hudLabel = "PSN", catchBonus = 12, shakeBonus = 5, - residual = damageOverTime(Strings.source("%s's\nhurt by poison!")), + residual = damageOverTime("_HurtByPoisonText", + Strings.source("%s's\nhurt by poison!")), canInflict = function(target) return not hasType(target, "POISON") end, - onInflict = function(_, target, opts, display) + onInflict = function(battle, target, opts, display) if opts.toxic then target.toxicCounter = 1 - -- _BadlyPoisonedText - return { Strings("%s's\nbadly poisoned!", display) } + return { romText(battle and battle.data, "_BadlyPoisonedText", + "%s's\nbadly poisoned!", display) } end - return { Strings("%s\nwas poisoned!", display) } + return { romText(battle and battle.data, "_PoisonedText", + "%s\nwas poisoned!", display) } end, }, BRN = { id = "BRN", label = "BRN", hudLabel = "BRN", catchBonus = 12, shakeBonus = 5, statPenalty = { stat = "attack", div = 2 }, - residual = damageOverTime(Strings.source("%s's\nhurt by the burn!")), + residual = damageOverTime("_HurtByBurnText", + Strings.source("%s's\nhurt by the burn!")), canInflict = function(target) return not hasType(target, "FIRE") end, - onInflict = function(_, _, _, display) - return { Strings("%s\nwas burned!", display) } + onInflict = function(battle, _, _, display) + return { romText(battle and battle.data, "_BurnedText", + "%s\nwas burned!", display) } end, }, PAR = { @@ -117,10 +128,11 @@ Status.RECORDS = { catchBonus = 12, shakeBonus = 5, statPenalty = { stat = "speed", div = 4 }, beforeMovePriority = 10, - beforeMove = function(battler, rng) + beforeMove = function(battler, rng, battle) -- cp 25 percent / jr nc: fully paralyzed on rand < 63 (63/256) if rng(0, 255) < 63 then - return false, { Strings("%s's\nfully paralyzed!", name(battler)) } + return false, { romText(battle and battle.data, "_FullyParalyzedText", + "%s's\nfully paralyzed!", name(battler)) } end return true, {} end, @@ -128,9 +140,10 @@ Status.RECORDS = { -- ParalyzeEffect_: Electric-type moves can't paralyze Ground-types return not (opts.moveType == "ELECTRIC" and hasType(target, "GROUND")) end, - onInflict = function(_, _, _, display) - -- _ParalyzedMayNotAttackText (primary and secondary paralysis) - return { Strings("%s's\nparalyzed! It may\nnot attack!", display) } + onInflict = function(battle, _, _, display) + -- primary and secondary paralysis share this line + return { romText(battle and battle.data, "_ParalyzedMayNotAttackText", + "%s's\nparalyzed! It may\nnot attack!", display) } end, }, } @@ -155,7 +168,7 @@ end -- The active status record's beforeMove runs at its priority slot: above -- VOLATILE_PRIORITY before the held/disable/confusion block (sleep, -- freeze), at or below after it (paralysis) -- the original's order. -function Status.beforeMove(battler, rng, battle) +function Status.beforeMove(battler, rng, battle, selectedMoveId) local mon = battler.mon -- Haze curing this mon's sleep/freeze forfeits its pending move for -- the turn, silently (haze.asm writes $ff/CANNOT_MOVE to the selected @@ -166,7 +179,8 @@ function Status.beforeMove(battler, rng, battle) end if battler.flinched then battler.flinched = false - return false, { Strings("%s\nflinched!", name(battler)) } + return false, { romText(battle and battle.data, "_FlinchedText", + "%s\nflinched!", name(battler)) } end local record = Status.recordFor(battleStatuses(battle), mon.status) local handler = record and record.beforeMove @@ -184,29 +198,55 @@ function Status.beforeMove(battler, rng, battle) end if battler.boundTurns and battler.boundTurns > 0 then battler.boundTurns = battler.boundTurns - 1 - msgs[#msgs + 1] = Strings("%s\ncan't move!", name(battler)) + msgs[#msgs + 1] = romText(battle and battle.data, "_CantMoveText", + "%s\ncan't move!", name(battler)) return false, msgs end if battler.disabledTurns then battler.disabledTurns = battler.disabledTurns - 1 if battler.disabledTurns <= 0 then battler.disabledTurns, battler.disabledSlot = nil, nil - table.insert(msgs, Strings("%s's\ndisabled no more!", name(battler))) + table.insert(msgs, romText(battle and battle.data, "_DisabledNoMoreText", + "%s's\ndisabled no more!", name(battler))) end end if battler.confusedTurns then battler.confusedTurns = battler.confusedTurns - 1 if battler.confusedTurns <= 0 then battler.confusedTurns = nil - table.insert(msgs, Strings("%s\nsnapped out of\nconfusion!", name(battler))) + table.insert(msgs, romText(battle and battle.data, "_ConfusedNoMoreText", + "%s\nsnapped out of\nconfusion!", name(battler))) else - table.insert(msgs, Strings("%s\nis confused!", name(battler))) + table.insert(msgs, romText(battle and battle.data, "_IsConfusedText", + "%s\nis confused!", name(battler))) -- cp 50 percent + 1 / jr c: hurt itself on rand >= 128 (128/256) if rng(0, 255) < 128 then return false, msgs, true -- hurt itself end end end + -- .TriedToUseDisabledMoveCheck (engine/battle/core.asm, and the enemy + -- copy .checkIfTriedToUseDisabledMove): the disabled-move test runs at + -- EXECUTION time, comparing wPlayerDisabledMoveNumber against the + -- already SELECTED move, so a Disable that lands earlier in the same + -- turn still blocks the slower mon's move (#860). It sits after the + -- confusion block and before the paralysis roll, so a confusion self-hit + -- still pre-empts it and the paralysis roll is never spent on a turn the + -- disable eats. PrintMoveIsDisabledText clears CHARGING_UP before + -- printing, so a disabled charge move drops its stored turn instead of + -- releasing later. + if selectedMoveId and battler.disabledSlot then + local disabled = (battler.curMoves or {})[battler.disabledSlot] + if disabled and disabled.id == selectedMoveId then + battler.charging, battler.chargeReady = nil, nil + local moves = battle and battle.data and battle.data.moves + local shown = moves and moves[selectedMoveId] and moves[selectedMoveId].name + or tostring(selectedMoveId) + table.insert(msgs, romText(battle and battle.data, "_MoveIsDisabledText", + "%s's\n%s is\ndisabled!", name(battler), shown)) + return false, msgs + end + end if handler then local canMove, selfHit = runStatus() if not canMove or selfHit then return canMove, msgs, selfHit end @@ -241,7 +281,8 @@ function Status.residual(battler, opponent, battle) dmg = math.min(dmg, mon.hp) mon.hp = mon.hp - dmg opponent.mon.hp = math.min(opponent.mon.stats.hp, opponent.mon.hp + dmg) - table.insert(msgs, Strings("LEECH SEED saps\n%s!", name(battler))) + table.insert(msgs, romText(battle and battle.data, "_HurtByLeechSeedText", + "LEECH SEED saps\n%s!", name(battler))) end return msgs end diff --git a/src/battle/StatusRegistry.lua b/src/battle/StatusRegistry.lua index 55de40c3..ba3afa7e 100644 --- a/src/battle/StatusRegistry.lua +++ b/src/battle/StatusRegistry.lua @@ -12,7 +12,7 @@ local StatusRegistry = {} -- pokered's / text macros (home/text.asm -- PlaceMoveUsersName): enemy-mon texts print "Enemy " before the name local function displayName(b) - return b.isPlayer and b.name or ("Enemy " .. b.name) + return b.isPlayer and b.name or Strings("Enemy %s", b.name) -- #779 end -- opts: toxic (start the Toxic counter), moveType (for the type gates), diff --git a/src/battle/TrainerAI.lua b/src/battle/TrainerAI.lua index 08b9aca0..9654029a 100644 --- a/src/battle/TrainerAI.lua +++ b/src/battle/TrainerAI.lua @@ -20,9 +20,16 @@ local TypeChart = require("src.battle.TypeChart") local Strings = require("src.core.Strings") +local romText = require("src.core.RomText") local TrainerAI = {} +-- pokered's / text macros print "Enemy " before the +-- enemy mon's nickname (home/text.asm PlaceMoveUsersName) +local function displayName(b) + return b.isPlayer and b.name or Strings("Enemy %s", b.name) -- #779 +end + local HEAL_AMOUNT = { POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200 } local X_STAT = { X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed" } @@ -95,12 +102,16 @@ function TrainerAI.switchAction(battle) return { special = "aiSwitch", index = alive[1] } end --- Apply an aiItem action to the enemy battler; returns messages. +-- Apply an aiItem action to the enemy battler; returns messages, already +-- final: the item line prints the raw nickname (AIPrintItemUseText has no +-- "Enemy " prefix in pokered), the stat lines carry it via displayName, so +-- the caller must not run these through prefixEnemy. function TrainerAI.useItem(battle, item) local enemy = battle.enemy local trainerName = battle.trainer.name local itemName = battle.data.items[item] and battle.data.items[item].name or item - local msgs = { Strings("%s\nused %s!", trainerName, itemName) } + local msgs = { romText(battle.data, "_AIBattleUseItemText", + "%s\nused %s!", trainerName, itemName, enemy.name) } if item == "FULL_HEAL" then enemy.mon.status = nil enemy.toxicCounter = nil @@ -113,10 +124,10 @@ function TrainerAI.useItem(battle, item) elseif X_STAT[item] then local stat = X_STAT[item] enemy.stages[stat] = math.min(6, (enemy.stages[stat] or 0) + 1) - table.insert(msgs, Strings("%s's\n%s rose!", enemy.name, stat:upper())) + table.insert(msgs, Strings("%s's\n%s rose!", displayName(enemy), stat:upper())) elseif item == "GUARD_SPEC" then enemy.mist = true - table.insert(msgs, Strings("%s's\nprotected against\nstat changes!", enemy.name)) + table.insert(msgs, Strings("%s's\nprotected against\nstat changes!", displayName(enemy))) end return msgs end diff --git a/src/battle/WideBattle.lua b/src/battle/WideBattle.lua index ad87b326..87e43bd2 100644 --- a/src/battle/WideBattle.lua +++ b/src/battle/WideBattle.lua @@ -128,7 +128,8 @@ local function drawIntroBalls(battle) end local function drawHUDs(battle, slide) - if battle.enemy and not battle.showEnemyTrainer + local showStatus = battle:statusHUDVisible() + if showStatus and battle.enemy and not battle.showEnemyTrainer and not battle.enemySendingOut and not battle:growInScale(battle.enemy) and slide == 0 and not battle.introBalls and not battle.enemy.fainted then drawStatusPanel(battle, battle.enemy, 0, 0, false) @@ -139,7 +140,7 @@ local function drawHUDs(battle, slide) -- item, not a HUD element (DisplayBattleMenu prints wNumSafariBalls inside -- the battle menu box, engine/battle/core.asm:2074-2079), so it rides in -- drawCommandMenu below like the classic layout's (#540). - if not battle.safari and battle.player and not battle.demo + if showStatus and not battle.safari and battle.player and not battle.demo and not battle.showPlayerBack and slide == 0 then drawStatusPanel(battle, battle.player, 184, 56, true) end @@ -244,7 +245,10 @@ end local function drawMoveMenu(battle) drawMoveGrid(battle, battle.player.curMoves, battle.moveIndex) - if battle.moveSwapIndex then + -- The filled cursor replaces the hollow swap marker when they share a row + -- (PlaceMenuCursor's tilemap write, home/window.asm:184-185); drawCode blits + -- black-on-transparent, so skip the 0xEC instead of stacking glyphs (#814). + if battle.moveSwapIndex and battle.moveSwapIndex ~= battle.moveIndex then local col = (battle.moveSwapIndex - 1) % 2 local row = math.floor((battle.moveSwapIndex - 1) / 2) Font.drawCode(0xEC, col == 0 and 8 or 112, 112 + row * 16) @@ -252,6 +256,7 @@ local function drawMoveMenu(battle) end local function drawTextArea(battle) + if not battle:bottomUIVisible() then return end if battle.phase == "messages" and (battle.current or battle.animPlaying) then drawMessageBox(battle) elseif battle.phase == "menu" then diff --git a/src/battle/rulesets/gen1_faithful.lua b/src/battle/rulesets/gen1_faithful.lua index 7b338511..68f171ab 100644 --- a/src/battle/rulesets/gen1_faithful.lua +++ b/src/battle/rulesets/gen1_faithful.lua @@ -19,4 +19,8 @@ return { -- Gen 1 Hyper Beam: no recharge when the target faints (or its -- substitute breaks). Set false to always recharge like Gen 2+. hyperBeamSkipRechargeOnKO = true, + -- Gen 1 runs HandlePoisonBurnLeechSeed right after each side's move + -- (core.asm:426-464): poison/burn/leech seed tick before the slower + -- mon acts, not in an end-of-round sweep like Gen 3+. + residualAfterMove = true, } diff --git a/src/battle/rulesets/modern_clean.lua b/src/battle/rulesets/modern_clean.lua index d251d301..42e8678a 100644 --- a/src/battle/rulesets/modern_clean.lua +++ b/src/battle/rulesets/modern_clean.lua @@ -13,4 +13,7 @@ return { enemyUnlimitedPP = false, -- Gen 2+: Hyper Beam always forces a recharge turn, even on a KO. hyperBeamSkipRechargeOnKO = false, + -- Gen 3+ style: poison/burn/leech seed tick in an end-of-round sweep + -- after both sides have moved. + residualAfterMove = false, } diff --git a/src/core/BattleCheckpoint.lua b/src/core/BattleCheckpoint.lua new file mode 100644 index 00000000..912ea1b2 --- /dev/null +++ b/src/core/BattleCheckpoint.lua @@ -0,0 +1,432 @@ +-- Semantic, data-only capture for settled single-player battle checkpoints. +-- Reconstruction lives here too; public mods only see the opaque checkpoint +-- facade in Loader. + +local BattleCheckpoint = {} +local BattleState = require("src.battle.BattleState") +local BUILTIN_RULESETS = { + gen1_faithful = require("src.battle.rulesets.gen1_faithful"), + modern_clean = require("src.battle.rulesets.modern_clean"), +} + +local function rulesets(game) + return game.data.rulesets or BUILTIN_RULESETS +end + +local function rulesetId(game, record) + for id, candidate in pairs(rulesets(game)) do + if candidate == record then return id end + end +end + +local BATTLER_FIELDS = { + "shownHP", "shownStatus", "stages", "curStats", "curTypes", "curMoves", + "sleepTurns", "confusedTurns", "disabledSlot", "disabledTurns", + "toxicCounter", "substituteHP", "bideDamage", "bideTurns", "boundTurns", + "chargeReady", "invulnerable", "mustRecharge", + "thrashTurns", "thrashAnnounced", "focusEnergy", "leechSeeded", + "lightScreen", "reflect", "mist", "xAccuracy", "lastMove", "flinched", + "skipMove", "hazeStatReset", "drainFloor", "drainHold", "trappingTurns", + "trapMove", "trapDamage", "fainted", + "aiLayer2", +} + +local MOVE_REFERENCE_FIELDS = { + charging = "chargingSlot", + thrashMove = "thrashMoveSlot", + rageMove = "rageMoveSlot", +} + +local BATTLE_FIELDS = { + "oppClass", "partyIndex", "enemyIndex", "turnCount", "menuIndex", + "moveIndex", "moveSwapIndex", "aiUses", "runAttempts", "payDay", + "sideToxic", "isGymLeader", "musicKind", "lastBall", "lockedBall", + "lowHealthAlarmDisabled", "lowHealthAlarmOn", "victoryMusicPlayed", + "endBattleText", +} + +local function partyIndex(party, mon) + for index, candidate in ipairs(party or {}) do + if candidate == mon then return index end + end +end + +local function indexSet(set, party) + local out = {} + for mon, present in pairs(set or {}) do + if present then + local index = partyIndex(party, mon) + if index then out[#out + 1] = index end + end + end + table.sort(out) + return out +end + +local function captureBattler(battler, index, copy) + local out = { + index = index, + curStatsFromMon = battler.curStats == battler.mon.stats, + curTypesFromDefinition = battler.curTypes == battler.def.types, + curMovesFromMon = battler.curMoves == battler.mon.moves, + } + for _, field in ipairs(BATTLER_FIELDS) do + if battler[field] ~= nil then out[field] = battler[field] end + end + for field, slotField in pairs(MOVE_REFERENCE_FIELDS) do + local reference = battler[field] + if reference ~= nil then + for slot, move in ipairs(battler.curMoves or {}) do + if move == reference then out[slotField] = slot break end + end + if out[slotField] == nil then return nil end + end + end + return copy(out) +end + +local function integer(value, min, max) + return type(value) == "number" and value % 1 == 0 + and value >= (min or -math.huge) and value <= (max or math.huge) +end + +local function validateMoveList(data, moves) + if type(moves) ~= "table" then return false end + for _, move in ipairs(moves) do + if type(move) ~= "table" or type(move.id) ~= "string" + or type(data.moves[move.id]) ~= "table" or type(move.pp) ~= "number" then + return false + end + end + return true +end + +local function validateMon(data, mon) + return type(mon) == "table" and type(mon.species) == "string" + and type(data.pokemon[mon.species]) == "table" and integer(mon.level, 1, 100) + and type(mon.hp) == "number" and type(mon.stats) == "table" + and validateMoveList(data, mon.moves) +end + +local function validateBattler(data, battler, maxIndex) + if type(battler) ~= "table" or not integer(battler.index, 1, maxIndex) then + return false + end + if type(battler.curMoves) ~= "table" then return false end + if battler.stages ~= nil then + if type(battler.stages) ~= "table" then return false end + for _, stage in pairs(battler.stages) do + if not integer(stage, -6, 6) then return false end + end + end + for _, slotField in pairs(MOVE_REFERENCE_FIELDS) do + if battler[slotField] ~= nil + and not integer(battler[slotField], 1, #battler.curMoves) then + return false + end + end + return validateMoveList(data, battler.curMoves) + and type(battler.curStats) == "table" and type(battler.curTypes) == "table" +end + +local function clone(value, copy) + if type(value) ~= "table" then return value end + return assert(copy(value)) +end + +local function captureMimicRestores(battle) + local out = {} + for _, restore in ipairs(battle.mimicRestores or {}) do + local side = restore.battler == battle.player and "player" + or restore.battler == battle.enemy and "enemy" or nil + local slot + for index, move in ipairs(restore.battler and restore.battler.curMoves or {}) do + if move == restore.entry then slot = index break end + end + if not side or not slot or type(restore.id) ~= "string" then return nil end + out[#out + 1] = { side = side, slot = slot, id = restore.id } + end + return out +end + +function BattleCheckpoint.validate(game, checkpoint) + local model = checkpoint.runtime and checkpoint.runtime.battle + local rngState = checkpoint.rng and checkpoint.rng.love + if type(model) ~= "table" or type(model.origin) ~= "table" + or type(rngState) ~= "string" or rngState == "" then + return nil, "invalid_checkpoint", "Battle checkpoint data or RNG is missing." + end + local expectedOrigin = model.kind == "wild" and "wild_encounter" + or model.kind == "trainer" and "trainer_encounter" or nil + if not expectedOrigin or model.origin.kind ~= expectedOrigin + or model.origin.map ~= checkpoint.runtime.overworld.map then + return nil, "battle_origin_unsupported", + "Battle continuation data is unsupported or inconsistent." + end + if type(model.rulesetId) ~= "string" + or type(rulesets(game)[model.rulesetId]) ~= "table" then + return nil, "invalid_content", "Battle ruleset is unavailable." + end + if model.kind == "trainer" and (type(model.origin.npcId) ~= "string" + or model.origin.trainerClass ~= model.oppClass + or model.origin.partyIndex ~= (model.partyIndex or 1)) then + return nil, "battle_origin_unsupported", + "Trainer continuation data is incomplete or inconsistent." + end + local party = checkpoint.save.party + if type(party) ~= "table" or not validateBattler(game.data, model.player, #party) then + return nil, "invalid_content", "Player battle state is invalid." + end + if model.kind == "wild" then + if not validateMon(game.data, model.enemyMon) + or not validateBattler(game.data, model.enemy, 1) then + return nil, "invalid_content", "Wild opponent state is invalid." + end + else + local trainer = game.data.trainers and game.data.trainers[model.oppClass] + if type(trainer) ~= "table" or not integer(model.partyIndex, 1) + or type(model.enemyParty) ~= "table" or #model.enemyParty == 0 + or not integer(model.enemyIndex, 1, #model.enemyParty) + or not validateBattler(game.data, model.enemy, #model.enemyParty) then + return nil, "invalid_content", "Trainer battle identity or roster is invalid." + end + for _, mon in ipairs(model.enemyParty) do + if not validateMon(game.data, mon) then + return nil, "invalid_content", "Trainer opponent state is invalid." + end + end + end + for _, indices in ipairs({ model.participants, model.leveledUp }) do + if type(indices) ~= "table" then + return nil, "invalid_checkpoint", "Battle party reference set is missing." + end + for _, index in ipairs(indices) do + if not integer(index, 1, #party) then + return nil, "invalid_checkpoint", "Battle party reference is invalid." + end + end + end + if type(model.mimicRestores) ~= "table" then + return nil, "invalid_checkpoint", "Mimic restore state is missing." + end + for _, restore in ipairs(model.mimicRestores) do + local battler = restore.side == "player" and model.player + or restore.side == "enemy" and model.enemy or nil + if not battler or not integer(restore.slot, 1, #battler.curMoves) + or type(restore.id) ~= "string" + or type(game.data.moves[restore.id]) ~= "table" then + return nil, "invalid_content", "Mimic restore state is invalid." + end + end + return true +end + +local function applyBattler(target, captured, copy) + for _, field in ipairs(BATTLER_FIELDS) do + if field ~= "curStats" and field ~= "curTypes" and field ~= "curMoves" then + if captured[field] ~= nil then + target[field] = clone(captured[field], copy) + else + target[field] = nil + end + end + end + target.curStats = captured.curStatsFromMon and target.mon.stats + or assert(copy(captured.curStats)) + target.curTypes = captured.curTypesFromDefinition and target.def.types + or assert(copy(captured.curTypes)) + target.curMoves = captured.curMovesFromMon and target.mon.moves + or assert(copy(captured.curMoves)) + for field, slotField in pairs(MOVE_REFERENCE_FIELDS) do + target[field] = captured[slotField] and target.curMoves[captured[slotField]] or nil + end + return target +end + +local function restoreIndexSet(indices, party) + local out = {} + for _, index in ipairs(indices or {}) do out[party[index]] = true end + return next(out) and out or nil +end + +function BattleCheckpoint.restore(game, checkpoint, copy) + local model = checkpoint.runtime.battle + local battle + if model.kind == "trainer" then + battle = BattleState.newTrainer(game, model.oppClass, model.partyIndex) + battle.enemyParty = assert(copy(model.enemyParty)) + battle.enemyIndex = model.enemyIndex + else + battle = BattleState.newWild(game, model.enemyMon.species, model.enemyMon.level) + end + + battle.player = BattleState.makeBattler(game.data, + game.save.party[model.player.index], true, game.save) + applyBattler(battle.player, model.player, copy) + local enemyMon + if model.kind == "trainer" then + enemyMon = battle.enemyParty[model.enemy.index] + else + enemyMon = assert(copy(model.enemyMon)) + end + battle.enemy = BattleState.makeBattler(game.data, enemyMon, false) + applyBattler(battle.enemy, model.enemy, copy) + + battle.mimicRestores = {} + for _, restore in ipairs(model.mimicRestores or {}) do + local battler = restore.side == "player" and battle.player or battle.enemy + battle.mimicRestores[#battle.mimicRestores + 1] = { + battler = battler, + entry = battler.curMoves[restore.slot], + id = restore.id, + } + end + if #battle.mimicRestores == 0 then battle.mimicRestores = nil end + + for _, field in ipairs(BATTLE_FIELDS) do + if model[field] ~= nil then + battle[field] = clone(model[field], copy) + else + battle[field] = nil + end + end + battle.kind = model.kind + battle.ruleset = rulesets(game)[model.rulesetId] + battle.checkpointOrigin = assert(copy(model.origin)) + battle.participants = restoreIndexSet(model.participants, game.save.party) + battle.leveledUp = restoreIndexSet(model.leveledUp, game.save.party) + battle.sides = assert(copy(model.sides)) + battle.sides[1].battlers = { battle.player } + battle.sides[2].battlers = { battle.enemy } + battle.field = assert(copy(model.field)) + battle.field.sides = battle.sides + battle.phase, battle.queue = "menu", {} + battle.frame = 0 + battle.current, battle.afterQueue, battle.nextInsert = nil, nil, nil + battle.pendingHit, battle.waitingUI, battle.waitingSound = nil, nil, nil + battle.waitFrames, battle.draining, battle.animPlaying = nil, nil, nil + battle.introText, battle.introBalls, battle.introSlide = nil, nil, nil + battle.showPlayerBack, battle.showEnemyTrainer, battle.showEnemyBalls = nil, nil, nil + battle.player.shownHP, battle.player.shownStatus = + battle.player.mon.hp, battle.player.mon.status + battle.enemy.shownHP, battle.enemy.shownStatus = + battle.enemy.mon.hp, battle.enemy.mon.status + + local ow = game.overworld + if not ow or type(ow.restoreBattleContinuation) ~= "function" + or ow:restoreBattleContinuation(battle, battle.checkpointOrigin) ~= true then + error("battle continuation reconstruction is unavailable", 0) + end + if type(game.restoreCheckpointBattle) ~= "function" then + error("game has no battle checkpoint reconstruction path", 0) + end + game:restoreCheckpointBattle(battle) + local setState = love and love.math and love.math.setRandomState + if type(setState) ~= "function" then error("battle RNG restore is unavailable", 0) end + setState(checkpoint.rng.love) + return battle +end + +local function captureExtensions(battle, copy) + local sides = {} + for i = 1, 2 do + local side = battle.sides and battle.sides[i] or {} + local encoded, err = copy({ + index = i, + screens = side.screens or {}, + hazards = side.hazards or {}, + tokens = side.tokens or {}, + }) + if not encoded then return nil, err end + sides[i] = encoded + end + local field, err = copy({ + weather = battle.field and battle.field.weather or nil, + tokens = battle.field and battle.field.tokens or {}, + }) + if not field then return nil, err end + return sides, field +end + +function BattleCheckpoint.capture(game, battle, progress, copy) + local getState = love and love.math and love.math.getRandomState + local setState = love and love.math and love.math.setRandomState + if type(getState) ~= "function" or type(setState) ~= "function" then + return nil, "rng_state_unavailable", + "This runtime cannot preserve deterministic battle randomness." + end + local ok, rngState = pcall(getState) + if not ok or type(rngState) ~= "string" or rngState == "" then + return nil, "rng_state_unavailable", + "The gameplay random-number state could not be captured." + end + + local origin, originErr = copy(battle.checkpointOrigin) + if not origin then + return nil, "battle_origin_unsupported", + "The battle completion path is not data-only: " .. tostring(originErr) + end + local sides, fieldOrErr = captureExtensions(battle, copy) + if not sides then + return nil, "battle_extension_unsafe", + "Battle extension state is not data-only: " .. tostring(fieldOrErr) + end + local field = fieldOrErr + + local liveParty = game.save.party + local playerIndex = partyIndex(liveParty, battle.player.mon) + if not playerIndex then + return nil, "battle_state_invalid", + "The active player battler is not in the current party." + end + + local model = { + kind = battle.kind, + rulesetId = rulesetId(game, battle.ruleset), + origin = origin, + player = captureBattler(battle.player, playerIndex, copy), + participants = indexSet(battle.participants, liveParty), + leveledUp = indexSet(battle.leveledUp, liveParty), + sides = sides, + field = field, + mimicRestores = captureMimicRestores(battle), + } + if not model.rulesetId then + return nil, "battle_state_invalid", "Battle ruleset identity is unavailable." + end + if not model.player then + return nil, "battle_state_invalid", "Player move references are inconsistent." + end + if not model.mimicRestores then + return nil, "battle_state_invalid", "Mimic restore state is inconsistent." + end + if battle.kind == "trainer" then + model.enemyParty = copy(battle.enemyParty) + model.enemy = captureBattler(battle.enemy, battle.enemyIndex, copy) + else + model.enemyMon = copy(battle.enemy.mon) + model.enemy = captureBattler(battle.enemy, 1, copy) + end + if not model.enemy then + return nil, "battle_state_invalid", "Enemy move references are inconsistent." + end + for _, fieldName in ipairs(BATTLE_FIELDS) do + if battle[fieldName] ~= nil then model[fieldName] = battle[fieldName] end + end + + model = copy(model) + if not model then + return nil, "battle_state_invalid", + "Battle state contains non-serializable runtime data." + end + local player = progress.player + return { + overworld = { + map = player.map, x = player.x, y = player.y, + facing = player.facing, surfing = player.surfing and true or false, + }, + battle = model, + }, { love = rngState } +end + +return BattleCheckpoint diff --git a/src/core/Checkpoint.lua b/src/core/Checkpoint.lua new file mode 100644 index 00000000..e6a3b036 --- /dev/null +++ b/src/core/Checkpoint.lua @@ -0,0 +1,424 @@ +-- Public runtime checkpoint implementation. Loader exposes bound forwarding +-- methods; mods never receive controller or state-stack internals from here. + +local SaveSerializer = require("src.core.SaveSerializer") +local SaveData = require("src.core.SaveData") +local Version = require("src.core.Version") +local BattleState = require("src.battle.BattleState") +local BattleCheckpoint = require("src.core.BattleCheckpoint") +local ModRuntime = require("src.mods.Runtime") + +local Checkpoint = {} + +Checkpoint.FORMAT = 1 + +local function refusal(kind, reason, message) + return { + canCapture = false, + canRestore = false, + kind = kind or "unknown", + reason = reason, + message = message, + } +end + +local function running(runner) + return runner and runner.isRunning and runner:isRunning() +end + +local function nonempty(value) + return type(value) == "table" and next(value) ~= nil +end + +local function scriptsBusy(ow) + return running(ow.runner) or nonempty(ow.parallelRunners) + or nonempty(ow.pendingScripts) or nonempty(ow.parallelQueue) + or nonempty(ow.scriptMoves) +end + +local BATTLE_BUSY_FIELDS = { + "current", "afterQueue", "nextInsert", "pendingHit", "waitingUI", + "waitingSound", "waitFrames", "draining", "animPlaying", "growIn", + "introSlide", "ghostReveal", "mimicCtx", "mimicMoves", "result", +} + +local function inspectBattle(ow, battle) + if battle.kind == "link" then + return refusal("battle", "link_battle_unsupported", + "Network battles cannot be checkpointed.") + end + if battle.safari or battle.ghost or battle.scopeReveal or battle.demo + or battle.noCatch then + return refusal("battle", "battle_variant_unsupported", + "This battle variant does not have a checkpoint contract.") + end + if battle.kind ~= "wild" and battle.kind ~= "trainer" then + return refusal("battle", "battle_variant_unsupported", + "This battle kind does not have a checkpoint contract.") + end + local origin = battle.checkpointOrigin + local expectedOrigin = battle.kind == "wild" and "wild_encounter" + or "trainer_encounter" + if type(origin) ~= "table" or origin.kind ~= expectedOrigin then + return refusal("battle", "battle_origin_unsupported", + "The battle completion path cannot be reconstructed safely.") + end + if scriptsBusy(ow) then + return refusal("battle", "script_busy", + "A suspended or queued script cannot be checkpointed.") + end + if battle.phase ~= "menu" or nonempty(battle.queue) then + return refusal("battle", "battle_phase_busy", + "Wait for the player command menu before creating a checkpoint.") + end + for _, field in ipairs(BATTLE_BUSY_FIELDS) do + if battle[field] ~= nil and battle[field] ~= false then + return refusal("battle", "battle_phase_busy", + "Wait for the current battle action to finish.") + end + end + if not battle.player or not battle.enemy or battle.player.mon.hp <= 0 + or (battle.menuLockedAction and battle:menuLockedAction(battle.player)) then + return refusal("battle", "battle_phase_busy", + "Wait for an ordinary player decision before creating a checkpoint.") + end + for _, battler in ipairs({ battle.player, battle.enemy }) do + if battler.shownHP ~= battler.mon.hp + or battler.shownStatus ~= battler.mon.status + or battler.drainFloor ~= nil or battler.drainHold ~= nil + or battler.faintQueued then + return refusal("battle", "battle_phase_busy", + "Wait for battle status and HP presentation to settle.") + end + end + return { canCapture = true, canRestore = true, kind = "battle" } +end + +function Checkpoint.inspect(game) + local save = game and game.save + if type(save) ~= "table" or type(save.version) ~= "string" then + return refusal("unknown", "not_in_playthrough", + "A checkpoint requires an identified active playthrough.") + end + + local ow = game.overworld + if type(ow) ~= "table" or type(ow.map) ~= "table" + or type(ow.map.id) ~= "string" or type(ow.player) ~= "table" then + return refusal("unknown", "not_overworld", + "Only a settled overworld can be checkpointed.") + end + local top = game.stack and game.stack.top and game.stack:top() + if getmetatable(top) == BattleState then + return inspectBattle(ow, top) + end + if top ~= ow then + return refusal("overworld", "screen_busy", + "Close the active menu or screen before creating a checkpoint.") + end + local identity = save.meta and save.meta.playthroughId + if type(identity) ~= "string" or identity == "" then + identity = SaveData.ensurePlaythroughId(save) + end + if type(identity) ~= "string" or identity == "" then + return refusal("overworld", "not_in_playthrough", + "The active playthrough could not be identified.") + end + if ow.transitioning then + return refusal("overworld", "transition_busy", + "Wait for the map transition to finish.") + end + if scriptsBusy(ow) then + return refusal("overworld", "script_busy", + "Wait for the active or queued script to finish.") + end + + local animationFields = { + "engaging", "emote", "teleportOut", "dustAnim", "cutAnim", "fishPose", + "pikaHop", "healAnim", "flyAnim", "flyArrive", + } + for _, field in ipairs(animationFields) do + if ow[field] then + return refusal("overworld", "animation_busy", + "Wait for the overworld animation to finish.") + end + end + if ow.player.moving or ow.player.targetX ~= nil or ow.player.targetY ~= nil then + return refusal("overworld", "movement_busy", + "Wait for movement to settle on a tile.") + end + return { canCapture = true, canRestore = true, kind = "overworld" } +end + +local function dataCopy(value) + local ok, encoded = pcall(SaveSerializer.encode, value) + if not ok then return nil, tostring(encoded) end + local decoded, err = SaveSerializer.decode(encoded) + if not decoded then return nil, err end + return decoded +end + +local function captureRng() + local getState = love and love.math and love.math.getRandomState + local setState = love and love.math and love.math.setRandomState + if type(getState) ~= "function" or type(setState) ~= "function" then return nil end + local ok, state = pcall(getState) + if ok and type(state) == "string" and state ~= "" then + return { love = state } + end + return nil +end + +local function restoreRng(rng) + if rng == nil then return end -- legacy format-1 overworld checkpoint + local setState = love and love.math and love.math.setRandomState + if type(rng) ~= "table" or type(rng.love) ~= "string" + or type(setState) ~= "function" then + error("checkpoint RNG restore is unavailable", 0) + end + setState(rng.love) +end + +function Checkpoint.capture(game) + local capability = Checkpoint.inspect(game) + if not capability.canCapture then + return nil, capability.reason, capability.message + end + + local progress = {} + for key, value in pairs(game.save) do + if key ~= "options" then progress[key] = value end + end + progress = dataCopy(progress) + if not progress then + return nil, "capture_failed", "Progress contains non-serializable runtime data." + end + + local ok, err = pcall(game.overworld.captureSave, game.overworld, progress) + if not ok then + return nil, "capture_failed", "Could not synchronize overworld progress: " + .. tostring(err) + end + progress, err = dataCopy(progress) + if not progress then + return nil, "capture_failed", "Synchronized progress is not data-only: " + .. tostring(err) + end + + if capability.kind == "battle" then + local battle = game.stack:top() + local runtime, rngOrCode, battleMessage = + BattleCheckpoint.capture(game, battle, progress, dataCopy) + if not runtime then return nil, rngOrCode, battleMessage end + return { + format = Checkpoint.FORMAT, + kind = "battle", + identity = { + engineVersion = Version.engine, + gameVersion = game.save.version, + playthroughId = game.save.meta.playthroughId, + }, + save = progress, + runtime = runtime, + rng = rngOrCode, + } + end + + local player = game.overworld.player + return { + format = Checkpoint.FORMAT, + kind = "overworld", + identity = { + engineVersion = Version.engine, + gameVersion = game.save.version, + playthroughId = game.save.meta.playthroughId, + }, + save = progress, + runtime = { overworld = { + map = game.overworld.map.id, + x = player.cellX, + y = player.cellY, + facing = player.facing, + surfing = player.surfing and true or false, + } }, + rng = captureRng(), + } +end + +local FACINGS = { up = true, down = true, left = true, right = true } + +local function validate(game, checkpoint) + if type(checkpoint) ~= "table" then + return nil, "invalid_checkpoint", "Checkpoint root must be a table." + end + if checkpoint.format ~= Checkpoint.FORMAT then + return nil, "unsupported_format", "This checkpoint format is not supported." + end + if checkpoint.kind ~= "overworld" and checkpoint.kind ~= "battle" then + return nil, "unsupported_runtime_kind", "This checkpoint runtime kind is not supported." + end + + local copy, copyErr = dataCopy(checkpoint) + if not copy then + return nil, "invalid_checkpoint", "Checkpoint is not data-only: " + .. tostring(copyErr) + end + local identity = copy.identity + local current = game and game.save + local currentId = current and current.meta and current.meta.playthroughId + if type(identity) ~= "table" or type(identity.engineVersion) ~= "string" + or type(identity.gameVersion) ~= "string" + or type(identity.playthroughId) ~= "string" then + return nil, "invalid_checkpoint", "Checkpoint identity is missing or corrupt." + end + if identity.gameVersion ~= current.version then + return nil, "wrong_game", "Checkpoint belongs to another game version." + end + if identity.playthroughId ~= currentId then + return nil, "wrong_playthrough", "Checkpoint belongs to another playthrough." + end + + local save = copy.save + local runtime = copy.runtime and copy.runtime.overworld + if type(save) ~= "table" or type(save.player) ~= "table" + or type(runtime) ~= "table" then + return nil, "invalid_checkpoint", "Checkpoint progress or runtime data is missing." + end + if save.version ~= identity.gameVersion + or not save.meta or save.meta.playthroughId ~= identity.playthroughId then + return nil, "invalid_checkpoint", "Checkpoint progress identity is inconsistent." + end + if copy.rng ~= nil and (type(copy.rng) ~= "table" + or type(copy.rng.love) ~= "string" or copy.rng.love == "") then + return nil, "invalid_checkpoint", "Checkpoint RNG state is corrupt." + end + if type(runtime.map) ~= "string" or type(runtime.x) ~= "number" + or type(runtime.y) ~= "number" or runtime.x % 1 ~= 0 or runtime.y % 1 ~= 0 + or not FACINGS[runtime.facing] or type(runtime.surfing) ~= "boolean" then + return nil, "invalid_checkpoint", "Overworld position is missing or corrupt." + end + if save.player.map ~= runtime.map or save.player.x ~= runtime.x + or save.player.y ~= runtime.y or save.player.facing ~= runtime.facing + or (save.player.surfing and true or false) ~= runtime.surfing then + return nil, "invalid_checkpoint", "Progress and runtime position disagree." + end + + local map = game.data and game.data.maps and game.data.maps[runtime.map] + if type(map) ~= "table" then + return nil, "invalid_map", "Checkpoint references a map that is unavailable." + end + local width, height = tonumber(map.width), tonumber(map.height) + if not width or not height or runtime.x < 0 or runtime.y < 0 + or runtime.x >= width * 2 or runtime.y >= height * 2 then + return nil, "invalid_position", "Checkpoint position is outside the map." + end + + -- A checkpoint is a strict restoration record, not an ordinary CONTINUE + -- migration. Reuse the canonical save validator on the detached copy, but + -- reject any quarantine, remap, reclaim, clamp, or content repair it would + -- perform instead of silently changing the state the caller selected. + local beforeContent = SaveSerializer.encode(copy.save) + local validOk, report = pcall(SaveData.validate, copy.save, game.data) + local afterOk, afterContent = pcall(SaveSerializer.encode, copy.save) + if not validOk or not afterOk or not SaveData.emptyReport(report) + or afterContent ~= beforeContent then + return nil, "invalid_content", + "Checkpoint references unavailable or invalid game content." + end + if copy.kind == "battle" then + local battleOk, battleCode, battleMessage = BattleCheckpoint.validate(game, copy) + if not battleOk then return nil, battleCode, battleMessage end + elseif copy.runtime.battle ~= nil then + return nil, "invalid_checkpoint", + "Overworld checkpoint contains unexpected battle state." + end + return copy +end + +local function apply(game, checkpoint, options) + local save, err = dataCopy(checkpoint.save) + if not save then error("checkpoint progress decode failed: " .. tostring(err), 0) end + local runtime = checkpoint.runtime.overworld + save.options = options + save.player.map = runtime.map + save.player.x = runtime.x + save.player.y = runtime.y + save.player.facing = runtime.facing + save.player.surfing = runtime.surfing + if type(game.restoreCheckpointSave) ~= "function" then + error("game has no checkpoint reconstruction path", 0) + end + game:restoreCheckpointSave(save) + if checkpoint.kind == "battle" then + BattleCheckpoint.restore(game, checkpoint, dataCopy) + else + restoreRng(checkpoint.rng) + end +end + +local function equalData(a, b) + local okA, encodedA = pcall(SaveSerializer.encode, a) + local okB, encodedB = pcall(SaveSerializer.encode, b) + return okA and okB and encodedA == encodedB +end + +local function firstDifference(a, b, path) + path = path or "$" + if type(a) ~= type(b) then return path .. " (type)" end + if type(a) ~= "table" then + if a ~= b then return path end + return nil + end + for key, value in pairs(a) do + if b[key] == nil and value ~= nil then + return path .. "." .. tostring(key) .. " (missing)" + end + local found = firstDifference(value, b[key], path .. "." .. tostring(key)) + if found then return found end + end + for key, value in pairs(b) do + if a[key] == nil and value ~= nil then + return path .. "." .. tostring(key) .. " (unexpected)" + end + end + return nil +end + +function Checkpoint.restore(game, checkpoint) + local capability = Checkpoint.inspect(game) + if not capability.canRestore then + return false, capability.reason, capability.message + end + local validated, code, message = validate(game, checkpoint) + if not validated then return false, code, message end + + local rollback, captureCode, captureMessage = Checkpoint.capture(game) + if not rollback then return false, captureCode, captureMessage end + local options = game.save.options + + local ok, err = pcall(apply, game, validated, options) + if ok then + local restored, verifyCode = Checkpoint.capture(game) + if restored and validated.rng == nil then restored.rng = nil end + if restored and equalData(restored, validated) then + if ModRuntime.wants("checkpoint.restored") then + ModRuntime.emit("checkpoint.restored", { + game = game, + kind = validated.kind, + }) + end + return true + end + err = restored and ("restored state differed at " + .. tostring(firstDifference(validated, restored) or "canonical encoding")) + or ("restored state could not be captured: " .. tostring(verifyCode)) + end + + local rolledBack, rollbackErr = pcall(apply, game, rollback, options) + if not rolledBack then + return false, "rollback_failed", + "Checkpoint restore and rollback both failed: " .. tostring(rollbackErr) + end + return false, "restore_failed", "Checkpoint restoration failed: " .. tostring(err) +end + +return Checkpoint diff --git a/src/core/ChipSynth.lua b/src/core/ChipSynth.lua index dfbc5cc1..01f4f376 100644 --- a/src/core/ChipSynth.lua +++ b/src/core/ChipSynth.lua @@ -111,6 +111,11 @@ local NOISE_DIVISORS = { [4] = 64, [5] = 80, [6] = 96, [7] = 112, } + +local HPF_CHARGE = 0.999958 ^ (GB_CLOCK / SAMPLE_RATE) +local LPF_ALPHA = 0.8 +local MIX_SCALE = 0.5 + local function snapTicks(ticks) return math.floor((ticks * 1470 + 256) / 512) end @@ -202,6 +207,30 @@ local function headerChannels(banks, header) return channels end +-- Which software channels (CHAN5-8) an sfx occupies: its header carries one +-- 3-byte descriptor per channel. Audio2_PlaySound walks exactly this list to +-- decide whether a new sfx may start at all (audio/engine_2.asm +-- .sfxChannelLoop), so Sound.playMove needs the set to reproduce that gate. +-- nil = not knowable here (a file def, or the banks are not readable yet), +-- which callers read as "no conflict". +function ChipSynth.effectChannels(data, def) + if type(def) ~= "table" then return nil end + local chip = def.chip + local specs = chip and chip.channels + if not specs then + if not def.address then return nil end + local ok, banks = pcall(engineBanks, data, chip) + if not ok then return nil end + local read + ok, read = pcall(headerChannels, banks, def) + if not ok then return nil end + specs = read + end + local channels = {} + for _, spec in ipairs(specs) do channels[#channels + 1] = spec.number end + return channels +end + local function fadeValue(nibble) if bit.band(nibble, 8) ~= 0 then return -bit.band(nibble, 7) end return nibble @@ -245,6 +274,7 @@ function Channel.new(engine, spec, options) phase = 0, noiseLfsr = 0x7FFF, noiseClock = 0, + drumTail = nil, timeTicks = 0, }, Channel) end @@ -406,7 +436,15 @@ function Channel:nextEvent() elseif command == 0xEC then self.duty = bit.band(self:byte(), 3) elseif command == 0xED then - self.engine.tempo = self:byte() * 0x100 + self:byte() + local high = self:byte() + local low = self:byte() + -- a header carrying its own tempo is one of audio/alternate_tempo.asm's + -- Music_*AlternateTempo entry points, which re-point channel 1 at a + -- stub that sets the tempo and jumps into the normal body -- the body's + -- own tempo command never runs there, so ignore it here (#847) + if not self.engine.tempoLocked then + self.engine.tempo = high * 0x100 + low + end elseif command == 0xEE then self.engine.pan = self:byte() elseif command == 0xEF or command == 0xF0 then @@ -458,7 +496,15 @@ function Channel:nextEvent() local volume = bit.rshift(packed, 4) local fade = fadeValue(bit.band(packed, 0x0F)) if self.noise then - local parameter = self:byte() + -- Audio2_ApplyWavePatternAndFrequency adds wFrequencyModifier to the + -- frequency low byte for every channel at or past CHAN5, the noise + -- channel included (audio/engine_2.asm Audio2_ApplyFrequencyModifier). + -- On CHAN8 that byte is the polynomial counter, so the modifier moves + -- the noise pitch; it wraps at 8 bits, the carry landing in the high + -- byte that noise does not use for frequency. Dropping it left the + -- battle hit sounds at their unmodified pitches, where super effective + -- reads as the duller of the two (#826). + local parameter = bit.band(self:byte() + self.frequencyOffset, 0xFF) return self:noiseEvent( self:durationTicks(length), volume, fade, parameter) end @@ -487,6 +533,25 @@ local function envelopeVolume(volume, fade, elapsed) return math.min(15, volume + steps) end + +local function envelopeRingSamples(volume, fade) + if not fade or fade <= 0 or not volume or volume <= 0 then return 0 end + return math.floor(volume * (fade / 64) * SAMPLE_RATE + 0.5) +end + +local function extendDrumEnvelope(segments) + local last = segments and segments[#segments] + if not last then return segments end + local ringEnd = last.startSample + envelopeRingSamples(last.volume, last.fade) + if ringEnd > last.endSample then last.endSample = ringEnd end + return segments +end + +local function drumAudioEnd(drum) + local last = drum and drum[#drum] + return last and last.endSample or 0 +end + function Channel:resetNoise() self.noiseLfsr = 0x7FFF self.noiseClock = 0 @@ -526,8 +591,7 @@ function Channel:sampleNoise(parameter) end end end - -- LuaGB: instantaneous inverted LFSR LSB (high when bit0 == 0) - return bit.band(self.noiseLfsr, 1) == 0 and 1 or -1 + return bit.band(self.noiseLfsr, 1) == 0 and 1 or 0 end local function sweepCalculation(register, sweep) @@ -571,21 +635,51 @@ end function Channel:sample() while not self.ended and (not self.event or self.event.sample >= self.event.samples) do + local prev = self.event self.event = self:nextEvent() self.phase = 0 - self:resetNoise() + if self.event and self.event.drum then + self.drumTail = nil + self:resetNoise() + elseif prev and prev.drum and prev.sample < drumAudioEnd(prev.drum) then + -- ..(audio/engine_1.asm ln 197) + self.drumTail = prev + elseif not (self.event and self.event.silence and self.drumTail) then + self.drumTail = nil + self:resetNoise() + end end local event = self.event - if not event then return 0 end + local gain = channelVolume[self.hardware] or 1 + if not event then + local tail = self.drumTail + if not tail then return 0 end + local sampleIndex = tail.sample + tail.sample = sampleIndex + 1 + if sampleIndex >= drumAudioEnd(tail.drum) then + self.drumTail = nil + return 0 + end + return self:sampleDrum(tail, sampleIndex) * gain + end local sampleIndex = event.sample event.elapsed = sampleIndex / SAMPLE_RATE event.sample = sampleIndex + 1 - if event.silence then return 0 end - - local gain = channelVolume[self.hardware] or 1 + if event.silence then + local tail = self.drumTail + if not tail then return 0 end + local tailIndex = tail.sample + tail.sample = tailIndex + 1 + if tailIndex >= drumAudioEnd(tail.drum) then + self.drumTail = nil + return 0 + end + return self:sampleDrum(tail, tailIndex) * gain + end if event.drum then return self:sampleDrum(event, sampleIndex) * gain end + self.drumTail = nil local volume = envelopeVolume( event.volume or 0, event.fade or 0, event.elapsed) if event.noise then @@ -625,7 +719,8 @@ function Channel:sample() -- a def-local program may omit its wave table entirely if not wave then return 0 end local index = math.min(32, math.floor(phase * 32) + 1) - return wave[index] * event.waveLevel * gain + local nibble = math.max(0, math.min(15, wave[index] * 8 + 8)) + return (nibble / 15) * event.waveLevel * gain end local duty = event.duty if type(duty) == "table" then @@ -634,7 +729,7 @@ function Channel:sample() local pattern = WAVE_PATTERN_TABLES[duty or 2] or WAVE_PATTERN_TABLES[2] local step = math.floor(phase * 8) % 8 if pattern[step + 1] == 0 then - return -volume / 15 * gain + return 0 end return volume / 15 * gain end @@ -645,7 +740,7 @@ Engine.__index = Engine function Engine:noiseInstrument(number) -- a def-local drum wins over the ROM engine's table for that id local custom = self.customDrums and self.customDrums[number] - if custom then return custom end + if custom then return extendDrumEnvelope(custom) end local cached = self.noiseInstruments[number] if cached then return cached end @@ -678,6 +773,7 @@ function Engine:noiseInstrument(number) end end + extendDrumEnvelope(segments) self.noiseInstruments[number] = segments return segments end @@ -752,7 +848,15 @@ function Engine.new(data, header, options) customDrums = chip and chip.drums or nil, noiseInstruments = {}, channels = {}, + hpfCap = 0, hpfCapLeft = 0, hpfCapRight = 0, + lpf = 0, lpfLeft = 0, lpfRight = 0, }, Engine) + -- header.tempo: the Music_*AlternateTempo override Music.play stamps onto + -- a copy of the song def (audio/alternate_tempo.asm) (#847) + if header.tempo then + engine.tempo = header.tempo + engine.tempoLocked = true + end for _, spec in ipairs(chip and chip.channels or headerChannels(banks, header)) do local frameTicks = options.frameTicks @@ -780,10 +884,20 @@ function Engine:finished() return true end +local function analogOut(engine, input, hpfField, lpfField) + local cap = engine[hpfField] + local hp = input - cap + engine[hpfField] = input - hp * HPF_CHARGE + local prev = engine[lpfField] + local lp = prev + LPF_ALPHA * (hp - prev) + engine[lpfField] = lp + return math.max(-1, math.min(1, lp * MIX_SCALE)) +end + function Engine:sample() local value = 0 for _, channel in ipairs(self.channels) do value = value + channel:sample() end - return math.max(-1, math.min(1, value / 4)) + return analogOut(self, value, "hpfCap", "lpf") end function Engine:sampleStereo() @@ -794,8 +908,8 @@ function Engine:sampleStereo() if not event or event.panLeft ~= false then left = left + value end if not event or event.panRight ~= false then right = right + value end end - return math.max(-1, math.min(1, left / 4)), - math.max(-1, math.min(1, right / 4)) + return analogOut(self, left, "hpfCapLeft", "lpfLeft"), + analogOut(self, right, "hpfCapRight", "lpfRight") end function Engine:sampleChannel(number) @@ -804,7 +918,7 @@ function Engine:sampleChannel(number) local value = channel:sample() if channel.number == number then selected = value end end - return math.max(-1, math.min(1, selected / 4)) + return analogOut(self, selected, "hpfCap", "lpf") end -- render `samples` frames into a fresh SoundData (mono or stereo). love.sound @@ -824,22 +938,7 @@ local function soundData(engine, samples, channels) return result end --- Render a one-shot effect (SFX/cry) to a two-channel SoundData, or nil when --- it is too short to be audible. The caller wraps it in a static --- love.audio.Source (a playback concern, hence not done here). --- --- The synthesis is mono (one summed value per frame, unlike the music path's --- sampleStereo), but the buffer is written stereo on purpose: OpenAL only --- spatializes 1-channel Sources, and a Source left at the default (0,0,0) --- position, exactly where the listener sits, is rendered as an ambient sound --- spread over EVERY output channel the device exposes at gains that differ --- from the front pair. On an interface with more than two outputs that put --- the SFX on outputs 5+6 as well, while the 2-channel music source --- (ChipAudio.playMusic) stayed on 1+2 (#626). Multi-channel buffers skip --- spatialization entirely and map onto the front pair, so duplicating the --- sample costs one buffer's memory and makes effects route exactly like --- music. Deliberately not sampleStereo: that honors the NR51 panning byte --- and would newly hard-pan any effect whose header issues command 0xEE. + local function renderEffectData(data, header, options) if not header then return nil end options = options or {} diff --git a/src/core/Data.lua b/src/core/Data.lua index a6ec72e8..405fe107 100644 --- a/src/core/Data.lua +++ b/src/core/Data.lua @@ -84,6 +84,15 @@ function Data:applyVersionedFieldData() -- Yellow caches carry the wrong demo species too. The fixed import -- manifest below stamps RATTATA for fresh imports. self.field.oldManBattle = { species = "RATTATA", level = 5 } + -- The Oak-speech show-off mon is the player's Pikachu in Yellow + -- (engine/battle/core.asm BATTLE_TYPE_PIKACHU / the ProfOak demo) + -- but caches imported before the manifest carried demoSpecies fell + -- back to Red's NIDORINO (#915). The fixed import manifest below + -- stamps PIKACHU for fresh imports; fill it here for stale caches. + local oakSpeech = self.field.oakSpeech + if type(oakSpeech) == "table" and not oakSpeech.demoSpecies then + oakSpeech.demoSpecies = "PIKACHU" + end end end diff --git a/src/core/Game.lua b/src/core/Game.lua index 97662298..9cb05fda 100644 --- a/src/core/Game.lua +++ b/src/core/Game.lua @@ -16,6 +16,10 @@ local Screens = require("src.ui.Screens") local Game = {} +local function renderVisible(stack, state) + return state and (not stack.renderVisible or stack:renderVisible(state)) +end + -- dev-mode gate for the F5/backtick hotkeys; false keeps every src/dev -- module unloaded, so a player boot never touches a byte of dev code local devMode = os.getenv("POKEPORT_DEV") == "1" or _G.POKEPORT_DEV_MODE == true @@ -41,6 +45,13 @@ function Game:load() -- render pipelines dispatch off the merged dataset; point them at the -- one the mods just merged into before anything can draw a frame require("src.render.Pipelines").install(Data) + -- Same reason, same moment: TypeChart caches the merged type records in an + -- upvalue, and until now only BattleState loaded it, on entering a battle. + -- Every non-battle reader of a type -- the summary screen's TYPE1/TYPE2 + -- rows, the move-select TYPE/ box -- ran against an unloaded module and got + -- the raw id back instead of the display name, so a translation could not + -- reach them. Loading here means a type reads the same whoever asks first. + require("src.battle.TypeChart").load(Data) self.input = Input Input:init() @@ -210,24 +221,44 @@ function Game:step(dt) -- it on its own real-time 60Hz accumulator instead. end +-- The per-category (RFC 0007) save.options multiplier for whichever of +-- "battle"/"overworld"/"menu" Game.speedCategoryInStack says is active +-- right now. This is the "vanilla" the core.logic_speed hook wraps below +-- -- Game:logicSpeed calls it AFTER the link and speedOverride checks, so +-- neither a mod nor the category resolution ever has a seam to defeat them. +function Game:_resolveLogicSpeed() + local GameSpeed = require("src.core.GameSpeed") + local category = Game.speedCategoryInStack(self.stack) + local key = GameSpeed.optionKey(category) + local opts = self.save and self.save.options + return GameSpeed.clamp(opts and opts[key] or GameSpeed.DEFAULT) +end + -- The logic multiplier for this frame. Read live rather than cached so the --- Options row takes effect immediately; speedOverride is the --speed / +-- Options rows take effect immediately; speedOverride is the --speed / -- POKEPORT_SPEED run argument, which wins over the saved option so a bot -- or screenshot run does not depend on whatever the player last chose. function Game:logicSpeed() local GameSpeed = require("src.core.GameSpeed") -- Link play is always 1X on both machines, and this wins over every other - -- source including POKEPORT_SPEED. Fast-forward multiplies the logic - -- clock, so a peer at 10X burned a tournament shot clock ten times faster - -- than the opponent it is racing, and drove its own animation/message - -- queue at a different rate than the peer it is locked to. Nothing about - -- a match should depend on what either player set this to. + -- source including POKEPORT_SPEED and every per-category option. + -- Fast-forward multiplies the logic clock, so a peer at 10X burned a + -- tournament shot clock ten times faster than the opponent it is racing, + -- and drove its own animation/message queue at a different rate than the + -- peer it is locked to. Nothing about a match should depend on what + -- either player set this to -- checked here, before the core.logic_speed + -- hook ever runs, so a mod cannot defeat it either. if self.linkSession or (self.linkNet and not self.linkNet.closed) then return 1 end if self.speedOverride then return GameSpeed.clamp(self.speedOverride) end - local opts = self.save and self.save.options - return GameSpeed.clamp(opts and opts.speed or GameSpeed.DEFAULT) + -- Clamp here too, not just in _resolveLogicSpeed's vanilla path: a mod's + -- core.logic_speed hook can return anything (0, negative, nil, NaN) and + -- Hooks:call only guards against a hook that throws, not one that + -- returns a bad value, so an unclamped result would flow straight into + -- the FixedStep accumulator math below and freeze or destabilize logic. + return GameSpeed.clamp(ModRuntime.call("core.logic_speed", + function(g) return g:_resolveLogicSpeed() end, self)) end function Game:update(dt) @@ -279,6 +310,21 @@ function Game.worldBgBattleDim(stack) return nil end +-- Is a BATTLE BG "world" battle composing itself over the live map right now? +-- Same whole-stack walk as worldBgBattleDim, asked for a different reason: the +-- dark-cave shade shift (wMapPalOffset) must not reach a frame a battle is +-- drawing in. InitBattleCommon (engine/battle/core.asm) pushes wMapPalOffset, +-- InitBattleVariables (engine/battle/init_battle_variables.asm) writes 0 over +-- it and core.asm pops it back when the battle ends, so a battle in an +-- un-flashed Rock Tunnel is lit on hardware. Every other BATTLE BG gets that +-- for free -- no map draws beneath an opaque battle, so nothing re-arms the +-- per-frame shade map -- but "world" keeps the overworld drawing underneath, +-- and its arming then darkened the battle's own pics, HUD and text at colorize +-- time (#773). +function Game.worldBgBattleInStack(stack) + return Game.worldBgBattleDim(stack) ~= nil +end + -- Does anything on the stack want the surface scaled to FILL the window -- (aspect preserved, bars on the long axis) rather than sit at the fixed -- integer scale? @@ -311,6 +357,28 @@ function Game.wideBattleInStack(stack) return nil end +-- Which of "battle"/"overworld"/"menu" per-category GAME SPEED (RFC 0007) +-- applies right now. Whole-stack, the same idiom as fillScaleInStack/ +-- wideBattleInStack above: an overlay with neither marker (PartyMenu, +-- ChoiceBox, a NamingScreen, a text box) is transparent to the walk and +-- inherits whatever is under it, making the category a property of the +-- STACK POSITION the overlay sits over, not of the overlay itself. A +-- scripted sequence (script.started/ended) never pushes a state of its +-- own either -- it runs through the owning overworld/battle state's own +-- script runner or message queue -- so it inherits the same way. Nothing +-- identifying as either (the title screen, credits, an intro cutscene +-- with nothing under it) falls to "menu", the bucket every non-gameplay +-- screen gets; see the RFC's Decisions section for the full reasoning. +function Game.speedCategoryInStack(stack) + local states = stack and stack.states + for i = #(states or {}), 1, -1 do + local state = states[i] + if state and state.isBattle then return "battle" end + if state and state.isOverworld then return "overworld" end + end + return "menu" +end + -- Whether a state on the stack composes its own screen and so wants the -- edge anchors held off (BattleState.holdsUIAnchors). Whole-stack, like -- everything else here: the text box and YES/NO a battle puts up are states @@ -438,7 +506,7 @@ function Game:draw() local state = self.stack.states[i] local wideState = state and state.isWideBattleLayout and state:isWideBattleLayout() - if state and state.draw then + if renderVisible(self.stack, state) and state.draw then if classicOffset ~= 0 and not wideState then love.graphics.push() love.graphics.translate(classicOffset, 0) @@ -462,7 +530,7 @@ function Game:draw() local zones, worldZones, zoneOwner for i = #self.stack.states, 1, -1 do local s = self.stack.states[i] - if s.sgbPalettes then + if renderVisible(self.stack, s) and s.sgbPalettes then zones = s:sgbPalettes(self) zoneOwner = s break @@ -530,8 +598,15 @@ function Game:_cycleSpeed(dir) or ow.engaging or ow.emote)) end if busy then return end + -- Cycles whichever category Game.speedCategoryInStack says is active + -- right now (RFC 0007) -- pressing the hotkey during a battle speeds up + -- just the battle, on the overworld just the walk, in a menu just the + -- menu. A single physical control that means "speed up whatever I'm + -- looking at right now" needs no new UI and matches what a player + -- pressing it mid-battle almost certainly wants. local GameSpeed = require("src.core.GameSpeed") - self.save.options.speed = GameSpeed.cycle(self.save.options.speed, dir) + local key = GameSpeed.optionKey(Game.speedCategoryInStack(self.stack)) + self.save.options[key] = GameSpeed.cycle(self.save.options[key], dir) self:writeOptions() end @@ -771,12 +846,15 @@ function Game:joystickhat(joystick, hat, direction) end -- Window focus/visibility flips: a release due while unfocused/hidden can --- be swallowed by the OS. Reset on both edges -- gaining focus with a --- physically held key won't re-fire keypressed, so trusting leftover --- state is worse than asking the player to re-press. +-- be swallowed by the OS. Reset on both edges; on the regain, reconcile +-- re-arms only what is still physically held -- a held key won't re-fire +-- keypressed by itself, and without the rebuild a spurious lifecycle event +-- parked the player until every direction was re-pressed (#799). function Game:focus(f) Input:reset() + if f then Input:reconcile() end TouchControls:reset() + self:cancelPointers() end function Game:visible(v) @@ -785,12 +863,15 @@ function Game:visible(v) else Input:reset() TouchControls:reset() + self:cancelPointers() end end function Game:onResume() Input:reset() + Input:reconcile() TouchControls:reset() + self:cancelPointers() -- Chip music may survive NX suspend as a duplicate stream; stop it and let -- the active screen re-cue on the next frame (hardware audio check: T19). -- Desktop/mobile window-visible flips must not kill overworld music. @@ -805,7 +886,16 @@ end function Game:recoverInput(event, joystick) Input:reset() + -- A hotplug can arrive with no hotplug (macOS Bluetooth re-enumeration), + -- and the blanket reset above also drops unrelated keyboard holds; put + -- back whatever is still physically down (#799). + Input:reconcile() TouchControls:reset() + -- reset just dropped every source, mod holds included: retire the mods' + -- outstanding press tokens so nothing stale can be released later, and + -- tell subscribers their live pointers died (#807) + if self.mods and self.mods.releaseModInput then self.mods:releaseModInput() end + self:cancelPointers() local SwitchDiagnostics = require("src.debug.SwitchDiagnostics") if SwitchDiagnostics.isEnabled() then if joystick then @@ -828,16 +918,118 @@ function Game:joystickremoved(joystick) TouchControls:joystickremoved() end -function Game:touchpressed(id, x, y) - TouchControls:touchpressed(id, x, y) +-- Gameplay pointer seam (#807). TouchControls keeps first refusal: a +-- pointer that begins on a virtual control belongs to the pad for its +-- whole lifecycle and never reaches mods, while one that begins outside +-- stays mod-visible even if it later wanders across a control +-- (TouchControls only tracks ids it captured at press). Everything a +-- subscriber costs -- the per-pointer records in self.modPointers, the +-- payload tables -- sits behind wantsHook, so a mod-free boot allocates +-- nothing here. + +-- vanilla for input.pointer: nobody consumed the event +local function pointerUnclaimed() return false end + +-- coordinates are LOVE window units, the same space render.hud's viewport +-- and the touch overlay lay out in +function Game:pointerEvent(phase, source, id, x, y, dx, dy, pressure, button) + return ModRuntime.call("input.pointer", pointerUnclaimed, self, { + phase = phase, source = source, id = id, x = x, y = y, + dx = dx or 0, dy = dy or 0, pressure = pressure, button = button, + }) end -function Game:touchmoved(id, x, y) +function Game:touchpressed(id, x, y, dx, dy, pressure) + if TouchControls:touchpressed(id, x, y) then return end + if not ModRuntime.wantsHook("input.pointer") then return end + -- POKEPORT_TOUCH routes the mouse through here as a stand-in finger + -- under the id "mouse" (see main.lua); mods still see its true source + local source = id == "mouse" and "mouse" or "touch" + self.modPointers = self.modPointers or {} + self.modPointers[id] = { source = source, x = x, y = y, + pressure = pressure } + self:pointerEvent("pressed", source, id, x, y, dx, dy, pressure) +end + +function Game:touchmoved(id, x, y, dx, dy, pressure) TouchControls:touchmoved(id, x, y) + local p = self.modPointers and self.modPointers[id] + if not p then return end + -- the POKEPORT_TOUCH mouse path carries no deltas; derive them from the + -- pointer's last seen position so drags read the same either way + if dx == nil then dx, dy = x - p.x, y - p.y end + p.x, p.y = x, y + if pressure ~= nil then p.pressure = pressure end + if ModRuntime.wantsHook("input.pointer") then + self:pointerEvent("moved", p.source, id, x, y, dx, dy, pressure) + end end -function Game:touchreleased(id, x, y) +function Game:touchreleased(id, x, y, dx, dy, pressure) TouchControls:touchreleased(id, x, y) + local p = self.modPointers and self.modPointers[id] + if not p then return end + self.modPointers[id] = nil + if ModRuntime.wantsHook("input.pointer") then + self:pointerEvent("released", p.source, id, x, y, dx, dy, pressure) + end +end + +-- A real mouse without POKEPORT_TOUCH (#807). Gameplay itself has no +-- mouse verbs, so the pointer hook is the only consumer and everything is +-- behind the wantsHook gate. A synthesized istouch twin is dropped +-- unconditionally: the same contact already arrived through +-- Game:touchpressed, and forwarding both would fire a mobile touch twice. +function Game:mousepressed(x, y, button, istouch) + if istouch then return end + if not ModRuntime.wantsHook("input.pointer") then return end + self.modPointers = self.modPointers or {} + local p = self.modPointers.mouse + if p then + p.held, p.x, p.y = (p.held or 1) + 1, x, y + else + self.modPointers.mouse = { source = "mouse", x = x, y = y, + held = 1, button = button } + end + self:pointerEvent("pressed", "mouse", "mouse", x, y, 0, 0, nil, button) +end + +-- hover moves are delivered too (button = nil); only pressed pointers are +-- tracked, because only they owe a released/cancelled later +function Game:mousemoved(x, y, dx, dy, istouch) + if istouch then return end + local p = self.modPointers and self.modPointers.mouse + if p then p.x, p.y = x, y end + if not ModRuntime.wantsHook("input.pointer") then return end + self:pointerEvent("moved", "mouse", "mouse", x, y, dx, dy, nil, nil) +end + +function Game:mousereleased(x, y, button, istouch) + if istouch then return end + local p = self.modPointers and self.modPointers.mouse + if not p then return end + p.held = (p.held or 1) - 1 + if p.held <= 0 then self.modPointers.mouse = nil end + if ModRuntime.wantsHook("input.pointer") then + self:pointerEvent("released", "mouse", "mouse", x, y, 0, 0, nil, button) + end +end + +-- Focus/visibility loss and input recovery swallow pointer releases the +-- same way they swallow key-ups (the hazard Input:reset exists for): +-- every mod-visible pointer gets a "cancelled" instead of leaving +-- subscribers waiting on a "released" that can never arrive (#807). +-- Cleared even when the subscriber is already gone, so no stale record +-- outlives its mod. +function Game:cancelPointers() + local pointers = self.modPointers + if not pointers then return end + self.modPointers = nil + if not ModRuntime.wantsHook("input.pointer") then return end + for id, p in pairs(pointers) do + self:pointerEvent("cancelled", p.source, id, p.x, p.y, 0, 0, + p.pressure, p.button) + end end -- Point the loader's mod.save backing at this save's modData so per-mod @@ -987,4 +1179,28 @@ function Game:restoreSave(loaded, recovered) end end +-- Reconstruct a previously validated runtime checkpoint without replaying the +-- ordinary CONTINUE lifecycle. In particular, map onEnter scripts and +-- save.loading/save.loaded events must not run a second time. Validation, +-- identity checks and transactional rollback live in Checkpoint.lua. +function Game:restoreCheckpointSave(loaded) + self.save = loaded + self:adoptSave(loaded) + while self.stack:top() do self.stack:pop() end + self.stack:push(self.overworld, loaded.player.map, + loaded.player.x, loaded.player.y, loaded.player.facing, + { via = "checkpoint", checkpoint = true }) +end + +-- Install a reconstructed battle without calling BattleState:enter(), whose +-- transition, intro queues and battle-start side effects already happened in +-- the checkpointed timeline. +function Game:restoreCheckpointBattle(battle) + if self.stack:top() ~= self.overworld then + error("battle checkpoint requires a reconstructed overworld base", 0) + end + self.stack.states[#self.stack.states + 1] = battle + if battle.resumeCheckpoint then battle:resumeCheckpoint() end +end + return Game diff --git a/src/core/GameSpeed.lua b/src/core/GameSpeed.lua index 0a5ddde3..96f08815 100644 --- a/src/core/GameSpeed.lua +++ b/src/core/GameSpeed.lua @@ -51,4 +51,19 @@ function GameSpeed.cycle(v, dir) return levels[nextIdx] end +-- Per-category speed (RFC 0007): overworld walking, battle turns and menu +-- navigation each cycle their own multiplier instead of one global "speed" +-- value. This list is the single source of truth for which categories +-- exist and the order the Options rows/save.options keys follow; +-- Game.lua's stack-walk (Game.speedCategoryInStack) decides WHICH category +-- is active on a given frame, this module only knows the category names. +GameSpeed.CATEGORIES = { "overworld", "battle", "menu" } + +-- the save.options field name a category's multiplier lives under, e.g. +-- "overworld" -> "speedOverworld". Centralized so Game.lua, OptionsMenu.lua, +-- LauncherSettings.lua and the SaveData migration never hand-spell the key. +function GameSpeed.optionKey(category) + return "speed" .. category:sub(1, 1):upper() .. category:sub(2) +end + return GameSpeed diff --git a/src/core/GameVersion.lua b/src/core/GameVersion.lua index 855b632f..774eef6e 100644 --- a/src/core/GameVersion.lua +++ b/src/core/GameVersion.lua @@ -4,9 +4,10 @@ -- extracted cache lives, and the save-file suffix -- so the importer, -- cache mount, SaveData, title screen and palette all agree. -- --- Red keeps every un-suffixed path it always used (save.lua, the root cache), --- so existing installs are untouched; Blue is namespaced under blue/ and --- _blue, Yellow under yellow/ and _yellow, so all three can be imported and +-- Red keeps the un-suffixed save paths it always used (save.lua) so existing +-- saves are untouched, but its extracted cache lives under red/ like Blue and +-- Yellow (issue #899); a legacy root cache is moved into red/ once by +-- CacheFs.migrateLegacyRedCache. All three versions can be imported and -- played side by side. -- -- Zero requires, so it loads during love.conf and under plain Lua for tools @@ -23,7 +24,7 @@ GameVersion.VERSIONS = { launcherName = "Red", -- game-panel header in the launcher sha1 = "ea9bcae617fdf159b045185467ae58b2e4a48b9a", manifest = "tools/rom_manifest.json", - cachePrefix = "", -- Red owns the cache root (backwards compatible) + cachePrefix = "red/", -- red/data/generated, red/assets/generated (#899) saveSuffix = "", -- save.lua / save.lua.bak / save.lua.tmp }, blue = { @@ -40,7 +41,7 @@ GameVersion.VERSIONS = { id = "yellow", label = "Yellow", displayName = "Pokemon Yellow", - launcherName = "Yellow (alpha)", + launcherName = "Yellow", sha1 = "cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1", manifest = "tools/rom_manifest_yellow.json", cachePrefix = "yellow/", -- yellow/data/generated, yellow/assets/generated diff --git a/src/core/GamepadMap.lua b/src/core/GamepadMap.lua index c76a1504..9d8b921f 100644 --- a/src/core/GamepadMap.lua +++ b/src/core/GamepadMap.lua @@ -1,5 +1,5 @@ -- Shared gamepad + raw joystick button tables for launcher and gameplay. --- Hardware-measured NX overrides live in NX_* tables (see docs/switch-development.md). +-- Hardware-measured NX overrides live in NX_* tables below. local GamepadMap = {} diff --git a/src/core/HostShell.lua b/src/core/HostShell.lua index 96dbd065..0c39e3c3 100644 --- a/src/core/HostShell.lua +++ b/src/core/HostShell.lua @@ -62,13 +62,86 @@ function HostShell.hideHostConsole() return consoleHidden end +-- #254 was fixed inside the launcher and nowhere else: a native dialog opened +-- while a mouse button is still down blocks the whole loop in io.popen, so SDL +-- never processes the button-up and never drops the pointer capture it took +-- for the press (on X11 an XGrabPointer with owner_events). The grab outlives +-- the click, every pointer event over the child dialog is still routed to our +-- window, and the dialog draws and keyboard-navigates but ignores the mouse. +-- src/import/RomImporter.lua owns the launcher's copy; hoisting it here means +-- every host spawn inherits it, including one a mod reaches through HostShell. +-- Pump until nothing is held so SDL sees the release first; bounded, so a +-- stuck button costs a moment and never the game. pump() drains OS events +-- into LOVE's queue and dispatches nothing, so there is no reentry. Worker +-- threads load neither love.mouse nor love.event, so the guard below makes +-- this a no-op off the main thread. +function HostShell.releasePointerGrab() + if not (love and love.mouse and love.mouse.isDown and love.event + and love.event.pump and love.timer) then + return + end + local deadline = love.timer.getTime() + 1 + while love.mouse.isDown(1, 2, 3) do + love.event.pump() + if love.timer.getTime() > deadline then break end + love.timer.sleep(0.005) + end +end + +-- POPEN IS NOT THREAD SAFE, and this app calls it from four threads (the main +-- one, the update checker, and a pool of three fetch workers). +-- +-- On Darwin, popen() flushes every open stream first: _fwalk walks libc's +-- global FILE list and locks each entry as it goes. pclose() frees a FILE and +-- takes it off that list. Run the two concurrently and the walker can end up +-- waiting on the lock of a FILE another thread has already freed -- a wait +-- that nothing will ever satisfy. That is the launcher freezing on close +-- after a visit to the mod tabs: sampling a hung process shows a fetch worker +-- parked in popen -> _fwalk -> flockfile with NO curl running anywhere on the +-- machine, and the main thread blocked in Thread:wait() for that worker, which +-- is why LOVE never reaches the process exit. +-- +-- The fix is a process-wide mutex around the two list-mutating calls, and only +-- those: a LOVE Channel's performAtomic runs its callback holding the +-- channel's own mutex, which is the one lock primitive shared across love +-- threads. Reading a pipe stays outside it, so the fetch pool still runs its +-- transfers in parallel -- a spawn is microseconds, a transfer is seconds. +local POPEN_LOCK = "hostshell_popen_lock" + +local function popenLock() + if not (love and love.thread and love.thread.getChannel) then return nil end + local ok, ch = pcall(love.thread.getChannel, POPEN_LOCK) + return ok and ch or nil +end + +-- Run `fn` with the spawn lock held, or plain when there is no love.thread to +-- take one from (the headless test stub, a plain luajit run). +local function withPopenLock(fn) + local ch = popenLock() + if not ch then return fn() end + local okAtomic = pcall(function() ch:performAtomic(fn) end) + if not okAtomic then fn() end +end + -- Wraps io.popen with the AppImage env fix applied and lua errors swallowed function HostShell.popen(command, mode) - local ok, pipe = pcall(io.popen, HostShell.envPrefix() .. command, mode or "r") - if not ok or not pipe then return nil end + HostShell.releasePointerGrab() + local pipe + withPopenLock(function() + local ok, p = pcall(io.popen, HostShell.envPrefix() .. command, mode or "r") + pipe = (ok and p) or nil + end) return pipe end +-- Close a pipe HostShell.popen opened. Callers MUST use this rather than +-- pipe:close(): pclose is the other half of the race above, and a close that +-- skips the lock can free a FILE out from under another thread's spawn. +function HostShell.pclose(pipe) + if not pipe then return end + withPopenLock(function() pcall(function() pipe:close() end) end) +end + -- Restart the whole app. The obvious love.event.quit("restart") re-runs LÖVE's -- boot in-process, which calls love.filesystem.init a second time -- and inside -- an AppImage physfs is already initialized, so that second init throws @@ -130,6 +203,65 @@ end -- block the calling thread and deal in whole files, so callers keep exactly -- the contract they had with curl. +-- DIAGNOSING A FAILED FETCH. curl's own stderr ("curl: (56) The requested +-- URL returned error: 403") went straight to the terminal, naming neither the +-- URL nor which of the launcher's many fetches produced it, while the caller +-- got back a generic "empty response". Both curl branches below now merge +-- stderr into the pipe and ask curl for the HTTP status with --write-out, so +-- the message that reaches the UI and the log says which URL failed and how. +-- +-- The status rides a marker rather than a bare "%{http_code}": a GET streams +-- its body through the same pipe, so the code has to be findable at the end +-- of arbitrary text. Matched from the END, and only the last occurrence is +-- cut, so a body that happens to contain the marker keeps its content. +-- Two spellings on purpose. HTTP_MARK is what comes back down the pipe; the +-- FMT one is what goes to curl, where the newline MUST be the two characters +-- backslash-n (curl expands the escape itself). A literal newline inside the +-- argument would be quoted fine by a POSIX shell and be a syntax error in +-- cmd.exe, which has no multi-line quoted string. +local HTTP_MARK = "\n__gen1recomp_http__" +local HTTP_MARK_FMT = "\\n__gen1recomp_http__%{http_code}" + +-- Split a curl pipe's output into (body, status, noise). `status` is nil +-- when curl never got far enough to have one (DNS failure, no route, a +-- timeout), in which case `noise` carries curl's own complaint. +local function splitCurlOutput(out) + out = tostring(out or "") + local at = nil + local from = 1 + while true do + local s = out:find(HTTP_MARK, from, true) + if not s then break end + at, from = s, s + 1 + end + if not at then return out, nil, out end + local body = out:sub(1, at - 1) + local code = tonumber(out:sub(at + #HTTP_MARK):match("^(%d+)")) + -- curl writes http_code 0 when it never got a response at all (DNS, no + -- route, connect timeout). That is not a status, and reporting it as + -- "HTTP 0" buries the real reason, which is in curl's own message. + if code == 0 then code = nil end + return body, code, body +end + +-- The error string a caller (and the launcher's notice line) sees. It always +-- names the URL, because "403" on its own is unactionable when the launcher +-- has an index feed, a releases API and a page of thumbnails in flight. +local function fetchError(url, status, noise) + if status then + local extra = (noise or ""):gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") + if #extra > 160 then extra = extra:sub(1, 157) .. "..." end + if extra ~= "" then + return ("HTTP %d from %s (%s)"):format(status, url, extra) + end + return ("HTTP %d from %s"):format(status, url) + end + local why = (noise or ""):gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") + if why == "" then why = "no response" end + if #why > 160 then why = why:sub(1, 157) .. "..." end + return ("fetch failed for %s: %s"):format(url, why) +end + -- Shell quoting for one curl argument; cmd.exe has no single-quote form. function HostShell.quote(s) s = tostring(s) @@ -140,12 +272,21 @@ function HostShell.quote(s) return "'" .. s:gsub("'", "'\\''") .. "'" end +-- MEMOISED per Lua state (so once per thread). This used to spawn a whole +-- `curl --version` process on every single fetch -- twice for a GET through +-- the Android-bridge fallback -- which doubled the number of spawns the lock +-- above has to serialise, for an answer that cannot change while the app is +-- running. +local curlAvailable = nil + function HostShell.haveCurl() + if curlAvailable ~= nil then return curlAvailable end local pipe = HostShell.popen("curl --version") - if not pipe then return false end + if not pipe then curlAvailable = false return false end local readOk, out = pcall(function() return pipe:read("*a") end) - pcall(function() pipe:close() end) - return readOk and out ~= nil and out:find("curl", 1, true) ~= nil + HostShell.pclose(pipe) + curlAvailable = readOk and out ~= nil and out:find("curl", 1, true) ~= nil + return curlAvailable end -- An older mobile build reports nil here and falls back to the "no transport" @@ -154,8 +295,15 @@ local function haveBridge() if not (love and love.system and type(love.system.httpDownload) == "function") then return false end + -- The OS allowlist is deliberate: the bridge is a per-port native addition, + -- not part of LOVE, so a build that exports the name on a platform we never + -- wired one for is a name collision, not a transport. UWP is listed because + -- Xbox has no curl and no way to spawn one (Platform.canSpawnProcess is + -- false there), so the bridge is its only possible transport (#876). Its + -- LOVE backend does not export it today and this still returns false, but + -- the gate is no longer the thing in the way. local osName = love.system.getOS and love.system.getOS() - return osName == "Android" or osName == "iOS" + return osName == "Android" or osName == "iOS" or osName == "UWP" end -- Is any transport available at all? Callers gate on this, never on curl. @@ -166,21 +314,41 @@ end -- Download url to an absolute host path. Returns true, or nil plus an error. -- The curl branch deliberately ignores curl's exit code, as the download paths -- always did: callers judge the result by the file they got. -function HostShell.httpDownload(url, absPath, userAgent, accept) +-- `maxTime` bounds curl's total transfer seconds. It matters at QUIT, not +-- during the transfer: LOVE waits for every live love.thread before the +-- process exits (#339), and a worker sitting inside a blocking curl cannot +-- notice a quit command until curl returns. With the launcher's default 300s +-- ceiling, closing the window during a mod download hung the process for +-- minutes. Callers on the interactive fetch pool pass something short. +function HostShell.httpDownload(url, absPath, userAgent, accept, maxTime) if type(url) ~= "string" or url == "" then return nil, "missing url" end if type(absPath) ~= "string" or absPath == "" then return nil, "missing path" end userAgent = userAgent or "gen1recomp" if HostShell.haveCurl() then - local cmd = "curl -fsSL --connect-timeout 15 --max-time 300 " + local cmd = ("curl -fsSL --connect-timeout 15 --max-time %d ") + :format(tonumber(maxTime) or 300) .. "-H " .. HostShell.quote("User-Agent: " .. userAgent) .. " " if accept then cmd = cmd .. "-H " .. HostShell.quote("Accept: " .. accept) .. " " end - cmd = cmd .. "-o " .. HostShell.quote(absPath) .. " " .. HostShell.quote(url) + cmd = cmd .. "-o " .. HostShell.quote(absPath) .. " " + .. "-w " .. HostShell.quote(HTTP_MARK_FMT) .. " " + .. HostShell.quote(url) .. " 2>&1" local pipe = HostShell.popen(cmd) if not pipe then return nil, "could not start download" end - pcall(function() pipe:read("*a") end) - pcall(function() pipe:close() end) + local readOk, out = pcall(function() return pipe:read("*a") end) + HostShell.pclose(pipe) + -- The file is still what the caller judges success by (-f writes nothing + -- on an HTTP error, and the callers all check the file anyway). The + -- status is here purely so the failure can NAME itself: "download failed" + -- with no URL and no code is the report this whole change exists to fix. + local body, status, noise = splitCurlOutput(readOk and out or "") + if status and (status < 200 or status >= 300) then + return nil, fetchError(url, status, body) + end + if not status and (noise or ""):match("%S") then + return nil, fetchError(url, nil, noise) + end return true end if not haveBridge() then @@ -188,29 +356,42 @@ function HostShell.httpDownload(url, absPath, userAgent, accept) end local ok, done = pcall(love.system.httpDownload, url, absPath, userAgent, accept) if ok and done then return true end - return nil, "download failed" + return nil, "download failed for " .. url end -- GET returning the body. curl streams it through a pipe; the Android bridge -- can only write a file, so there we fetch into the save directory (the only -- writable root on Android) and read it back. -function HostShell.httpGet(url, userAgent, accept) +function HostShell.httpGet(url, userAgent, accept, maxTime) if type(url) ~= "string" or url == "" then return nil, "missing url" end userAgent = userAgent or "gen1recomp" if HostShell.haveCurl() then - local cmd = "curl -fsSL --connect-timeout 10 --max-time 40 " + -- No -f here (the download branch keeps it). -f suppresses the error + -- BODY, and on the two services this talks to that body is the whole + -- diagnosis: GitHub's 403 says "API rate limit exceeded for ", which + -- tells a user to wait rather than to go hunting for a broken index. + local cmd = ("curl -sSL --connect-timeout 10 --max-time %d ") + :format(tonumber(maxTime) or 40) .. "-H " .. HostShell.quote("User-Agent: " .. userAgent) .. " " if accept then cmd = cmd .. "-H " .. HostShell.quote("Accept: " .. accept) .. " " end - cmd = cmd .. HostShell.quote(url) + cmd = cmd .. "-w " .. HostShell.quote(HTTP_MARK_FMT) .. " " + .. HostShell.quote(url) .. " 2>&1" local pipe = HostShell.popen(cmd) if not pipe then return nil, "could not run curl" end local readOk, out = pcall(function() return pipe:read("*a") end) - pcall(function() pipe:close() end) - if not readOk then return nil, "fetch failed: " .. tostring(out) end - if not out or out == "" then return nil, "empty response from " .. url end - return out + HostShell.pclose(pipe) + if not readOk then + return nil, fetchError(url, nil, tostring(out)) + end + local body, status, noise = splitCurlOutput(out) + if not status then return nil, fetchError(url, nil, noise) end + if status < 200 or status >= 300 then + return nil, fetchError(url, status, body) + end + if body == "" then return nil, "empty response from " .. url end + return body end if not haveBridge() then return nil, "no network transport on this platform" diff --git a/src/core/Input.lua b/src/core/Input.lua index b450994d..33543c97 100644 --- a/src/core/Input.lua +++ b/src/core/Input.lua @@ -186,6 +186,21 @@ function Input:overlayReleased(btn) release(self, btn, "touch:" .. btn) end +-- Programmatic mod input (#807). mod.input taps and holds land here under +-- loader-issued "mod::" source names, riding the same per-source +-- bookkeeping as every physical path above, so releasing one can never +-- clear a hold a key, stick, hat, the overlay, or another mod still owns. +-- A tap is a sourcePress immediately followed by its sourceRelease: the +-- queued edge survives into the next step, and the emptied source map +-- keeps the hold from being revived (see Input:step's sources == {} rule). +function Input:sourcePress(btn, source) + press(self, btn, source) +end + +function Input:sourceRelease(btn, source) + release(self, btn, source) +end + function Input:gamepadpressed(joystick, button) local btn = self.padBindings[button] if btn then @@ -286,6 +301,71 @@ function Input:joystickhat(joystick, hat, direction) self.hatDirs[hat] = dirs end +-- Lifecycle resets (focus/visibility flips, joystick add/remove, resume) +-- wipe held state because a release can be swallowed while the OS owns the +-- event stream. A direction the player is STILL holding never re-fires +-- keypressed/gamepadpressed after the wipe either, so a spurious reset -- +-- macOS re-enumerating a Bluetooth pad fires joystickadded with no hotplug, +-- and the blanket reset took unrelated keyboard holds down with it -- +-- parked the player in place until every direction was released and +-- pressed again (#799). Rebuild holds from the devices' ground truth +-- instead: only what is physically down right now comes back, so the +-- swallowed-release hazards the resets guard against stay cleared. +-- Deliberately separate from reset(): the soft-reset chord path in +-- Game:step needs the clean slate (re-arming A there would read it as a +-- title-menu choice). +function Input:reconcile() + local kb = love and love.keyboard + if kb and kb.isDown then + for key, btn in pairs(self.keyBindings) do + local ok, down = pcall(kb.isDown, key) + if ok and down then press(self, btn, "key:" .. key) end + end + end + local js = love and love.joystick + if not (js and js.getJoysticks) then return end + local ok, joysticks = pcall(js.getJoysticks) + if not ok or type(joysticks) ~= "table" then return end + for _, j in ipairs(joysticks) do + if GamepadMap.ignoreRawForJoystick(j) then + -- SDL-recognized pad: buttons + left stick, the gamepad surfaces + if j.isGamepadDown then + for button, btn in pairs(self.padBindings) do + local ok2, down = pcall(j.isGamepadDown, j, button) + if ok2 and down then press(self, btn, "pad:" .. button) end + end + end + if j.getGamepadAxis then + for _, axis in ipairs({ "leftx", "lefty" }) do + local ok2, v = pcall(j.getGamepadAxis, j, axis) + if ok2 and type(v) == "number" then self:gamepadaxis(j, axis, v) end + end + end + else + -- raw stick (#620/#632): the surfaces the joystick* events feed + if j.isDown then + for index, btn in pairs(self.joyBindings) do + local ok2, down = pcall(j.isDown, j, index) + if ok2 and down then press(self, btn, "joy:" .. index) end + end + end + if j.getAxis then + for _, axis in ipairs({ 1, 2 }) do + local ok2, v = pcall(j.getAxis, j, axis) + if ok2 and type(v) == "number" then self:joystickaxis(j, axis, v) end + end + end + if j.getHatCount and j.getHat then + local ok2, count = pcall(j.getHatCount, j) + for hat = 1, (ok2 and count) or 0 do + local ok3, dir = pcall(j.getHat, j, hat) + if ok3 and dir then self:joystickhat(j, hat, dir) end + end + end + end + end +end + function Input:isDown(btn) return self.state[btn] or false end diff --git a/src/core/LaunchOptions.lua b/src/core/LaunchOptions.lua new file mode 100644 index 00000000..216e26df --- /dev/null +++ b/src/core/LaunchOptions.lua @@ -0,0 +1,119 @@ +-- Launch options: boot straight into a game, skipping the launcher. +-- +-- love . --game=red -- boot Red +-- love . --game=yellow --slot=2 -- boot Yellow on save slot 2 +-- love . --game=red --launcher -- open the launcher anyway (a shortcut +-- the player wants to edit) +-- POKEPORT_GAME=blue love . -- same, for launchers that only pass env +-- +-- The "--flag value" spelling parses here (argValue reads argv[i + 1]), but it +-- does not survive LOVE: boot.lua takes the first bare argument as a path to a +-- game to run, so `--game red` dies with "Cannot load game at path .../red" +-- before love.load is ever called, fused or not. Only the "=" spelling is +-- reachable, so that is the one the docs quote. +-- +-- This exists for the click-once cases: a desktop shortcut per game, a Steam +-- entry, an EmulationStation/Playnite entry, a handheld frontend. Those all +-- want "start the thing" and treat any menu in between as a defect. +-- +-- Everything here is pure resolution and validation -- no love.* beyond the +-- filesystem read that slot selection needs -- so the engine test tier can +-- cover the parsing without a window. + +local GameVersion = require("src.core.GameVersion") + +local LaunchOptions = {} + +-- Set by main.lua when a requested game turns out not to be importable yet: +-- the launcher opens on that tab instead of booting. +LaunchOptions.pendingTab = nil + +local function normalizeVersion(v) + if type(v) ~= "string" then return nil end + v = v:lower():gsub("^%s+", ""):gsub("%s+$", "") + if v == "" then return nil end + -- Accept the aliases people actually type. + local alias = { + r = "red", red = "red", + b = "blue", blue = "blue", + y = "yellow", yellow = "yellow", + } + v = alias[v] or v + if GameVersion.VERSIONS and not GameVersion.VERSIONS[v] then return nil end + return v +end + +-- Pull "--flag value" (and "--flag=value") out of LOVE's arg table. +local function argValue(argv, name) + if type(argv) ~= "table" then return nil end + for i = 1, #argv do + local a = argv[i] + if a == "--" .. name then + return argv[i + 1] + end + local inline = type(a) == "string" and a:match("^%-%-" .. name .. "=(.*)$") + if inline then return inline end + end + return nil +end + +local function argFlag(argv, name) + if type(argv) ~= "table" then return false end + for i = 1, #argv do + if argv[i] == "--" .. name then return true end + end + return false +end + +-- Returns version, slotId (either may be nil). Command line wins over env, +-- so a shortcut can override a machine-wide default. +function LaunchOptions.resolve(argv) + local game = normalizeVersion(argValue(argv, "game")) + or normalizeVersion(os.getenv("POKEPORT_GAME")) + or normalizeVersion(os.getenv("POKEPORT_LAUNCH")) + local slot = argValue(argv, "slot") or os.getenv("POKEPORT_SLOT") + if type(slot) == "string" then + slot = slot:gsub("^%s+", ""):gsub("%s+$", "") + if slot == "" then slot = nil end + end + return game, slot +end + +function LaunchOptions.forceLauncher(argv) + return argFlag(argv, "launcher") or os.getenv("POKEPORT_FORCE_LAUNCHER") == "1" +end + +-- Point a version at a save slot before it boots. Accepts either a slot id +-- ("slot2") or a 1-based index ("2"), because a shortcut author should not +-- have to know the internal id scheme. A slot that does not exist is +-- ignored: booting the game on its previous slot beats refusing to start. +-- Returns the id actually selected, or nil. +function LaunchOptions.selectSlot(version, slot) + local ok, SaveData = pcall(require, "src.core.SaveData") + if not ok then return nil end + local listed = SaveData.listSlots and SaveData.listSlots(version) or nil + if type(listed) ~= "table" or #listed == 0 then return nil end + + local target + local index = tonumber(slot) + if index and listed[index] then + target = listed[index].id + else + for _, s in ipairs(listed) do + if s.id == slot then target = s.id break end + end + end + if not target then return nil end + pcall(SaveData.setActiveSlot, version, target) + return target +end + +-- The shortcut command a player would use for this game, for the launcher to +-- show and for docs to quote. +function LaunchOptions.commandFor(version, slot) + local cmd = "--game " .. tostring(version) + if slot then cmd = cmd .. " --slot " .. tostring(slot) end + return cmd +end + +return LaunchOptions diff --git a/src/core/Music.lua b/src/core/Music.lua index b5d54908..7ac621bc 100644 --- a/src/core/Music.lua +++ b/src/core/Music.lua @@ -1,12 +1,3 @@ --- Music playback supports compact ROM channel programs synthesized live by --- ChipAudio, def-local chip programs (ChipAsm), and file definitions. The --- branch is chosen per song definition, never by a global import flag, so a --- file-backed song and a chip song coexist in one dataset. Songs with split --- files chain def.file into def.loopFile in Music.update(). --- Map themes switch on map change; battles override with the battle --- theme and restore afterwards; riding the bike overrides outdoor map --- themes with the bike song until dismount. - local Logger = require("src.core.Logger") local Runtime = require("src.mods.Runtime") @@ -14,20 +5,10 @@ local Music = {} local VOLUME = 0.7 --- port additions driven by OptionsMenu / save.options: musicVol scales --- VOLUME (0-7 level like the GB's NR50 master volume) and musicFilter --- low-passes the song. Each filter step keeps 40% of the previous --- step's treble (highgain 0.4^level), so 2X/3X are the 1X filter --- applied twice/three times over. local volumeScale = 1 local FILTER_HIGHGAIN = { 0.4, 0.16, 0.064 } local filterLevel = 0 --- Forward-declared here so applyVolume (below) closes over the real playback --- state rather than a nil global: the table literal is assigned further down, --- but a `local state = {}` there would leave every reference above it bound --- to the global `state`. Before this, registering the `music.volume` mod --- hook crashed applyVolume on `state.current` (a nil index). local state local function applyVolume(src) @@ -82,6 +63,7 @@ state = { fanfare = nil, -- fanfare SFX source; the song pauses while it plays fanfareResume = false, -- start/resume state.source when the fanfare ends fade = nil, -- active volume-ramp fade-out (see Music.fadeOut) + tempo = nil, -- alternate-tempo override in force for `current` failed = {}, -- labels whose def could not be started; logged once } @@ -114,11 +96,6 @@ function Music.duckForFanfare(src) end end --- Overworld themes where the bike can be ridden (outdoor maps plus the --- caves/dungeons where gen-1 allows cycling). Indoor themes such as --- Pokecenter/Gym/SilphCo never get replaced by the bike theme. --- data.audio.outdoorSongs supersedes this; the copy stays as the fallback --- for caches built before the importer wrote the table. local OUTDOOR = { Music_PalletTown = true, Music_Cities1 = true, @@ -217,6 +194,7 @@ end -- the single choke point every song choice passes through, so one hook -- covers map themes, battle themes, jingles and scene music local function selectSong(song, ctx) + if ctx and ctx.selected then return song end if not Runtime.wantsHook("music.select") then return song end return Runtime.call("music.select", function(chosen) return chosen end, song, { reason = ctx and ctx.reason or "direct", @@ -233,12 +211,35 @@ end function Music.play(data, song, loop, ctx) if not song then return end if not love.audio then return end -- headless test stub + ctx = ctx or {} song = selectSong(song, ctx) + + local tempo = ctx and ctx.tempo or nil -- a hook may silence the cue outright, or swap in a label the dedupe -- below has to compare against - if not song or song == state.current then return end + if not song or (song == state.current and tempo == state.tempo) then return end local def = songDef(data, song) if not def or state.failed[song] then return end + + if ctx.fade and state.source then + local queued = {} + for key, value in pairs(ctx) do queued[key] = value end + queued.fade, queued.selected = nil, true + local pending = { data = data, song = song, loop = loop, ctx = queued } + if state.fade then + state.fade.pending = pending + else + Music.fadeOut(ctx.fade, pending) + end + return + end + if tempo then + -- shallow copy: the registry def is shared, only this playback is slowed + local slowed = {} + for key, value in pairs(def) do slowed[key] = value end + slowed.tempo = tempo + def = slowed + end local wantLoop = loop ~= false local src, loopSrc, isChip, err = startSong(data, def, wantLoop) if not src then @@ -274,6 +275,7 @@ function Music.play(data, song, loop, ctx) local previous = state.current state.source, state.loopSource, state.chip = src, loopSrc, isChip state.current = song + state.tempo = tempo if Runtime.wants("music.started") then Runtime.emit("music.started", { song = song, previous = previous, chip = isChip, @@ -288,6 +290,7 @@ function Music.stop() stopSource(state.loopSource) require("src.core.ChipAudio").stopMusic() state.current, state.source, state.loopSource, state.fade = nil, nil, nil, nil + state.tempo = nil state.chip = false state.pendingRestore = nil if previous and Runtime.wants("music.stopped") then @@ -302,24 +305,26 @@ function Music.reload() Music.stop() end --- Ramp the current song's volume to silence, then stop it, mirroring the --- Game Boy's audio fade-out (home/fade_audio.asm FadeOutAudio + --- home/audio.asm's .fadeOut): rAUDVOL's master volume steps 7 -> 0 in --- integer levels, one level every `control` frames, and the music stops --- when it reaches 0. `control` is the wAudioFadeOutControl value the ROM --- writes (oak_speech.asm sets 10 at the shrink beat -> 7*10 = 70 frames --- to silence). Ticked once per frame from Music.update(). -function Music.fadeOut(control) - if not state.source then Music.stop() return end +function Music.fadeOut(control, pending) + if not state.source then + Music.stop() + if pending then + Music.play(pending.data, pending.song, pending.loop, pending.ctx) + end + return + end control = math.max(1, control or 10) state.fade = { control = control, counter = control, -- frames until the next volume step level = 7, -- current master-volume level (rAUDVOL nibble) from = VOLUME * volumeScale, -- level-7 (full) source volume + pending = pending, } end +Music.MAP_FADE = 10 + -- the song a map should currently play, honoring the bike/surf overrides local function effectiveMapSong(data, song) if not song or not outdoorSongs(data)[song] then return song end @@ -336,14 +341,17 @@ end -- overworld map theme; onBike/surfing override outdoor themes with the -- bike/surf songs and restore the map theme when they end -function Music.playMap(data, mapId, onBike, surfing) +function Music.playMap(data, mapId, onBike, surfing, fade) local song = data and data.audio and data.audio.mapSongs and mapId and data.audio.mapSongs[mapId] or nil state.mapSong = song state.onBike = not not onBike state.surfing = not not surfing local play = effectiveMapSong(data, song) - if play then Music.play(data, play, nil, { reason = "map", mapId = mapId }) end + if play then + Music.play(data, play, nil, + { reason = "map", mapId = mapId, fade = fade }) + end end -- toggle the surf override mid-map (starting/ending a surf) @@ -353,11 +361,12 @@ function Music.setSurfing(data, surfing) if play then Music.play(data, play, nil, { reason = "map" }) end end --- battle themes; kind = "wild"|"trainer"|"gym"|"final" -function Music.playBattle(data, kind, trainerId) +-- battle themes; kind = "wild"|"trainer"|"gym"|"final". `song`, when +-- given, overrides the kind's default -- a mod-set trainer battleTheme. +function Music.playBattle(data, kind, trainerId, song) local b = data.audio and data.audio.battle if b then - Music.play(data, b[kind] or b.wild, nil, + Music.play(data, song or b[kind] or b.wild, nil, { reason = "battle", kind = kind, trainerId = trainerId }) end end @@ -459,8 +468,13 @@ function Music.update(data) f.counter = f.control f.level = f.level - 1 if f.level <= 0 then + -- ..(home/fade_audio.asm ln 36) state.fade = nil + local pending = f.pending Music.stop() + if pending then + Music.play(pending.data, pending.song, pending.loop, pending.ctx) + end return end local vol = f.from * f.level / 7 diff --git a/src/core/Platform.lua b/src/core/Platform.lua index ebac81e5..2d1adc4a 100644 --- a/src/core/Platform.lua +++ b/src/core/Platform.lua @@ -1,4 +1,4 @@ --- Platform capability detection for NX / mobile / desktop. +-- Platform capability detection for console, mobile and desktop builds. local Platform = {} @@ -8,20 +8,35 @@ local function compute() local osName = (love and love.system and love.system.getOS and love.system.getOS()) or "Unknown" local nx = osName == "NX" + local uwp = osName == "UWP" local mobile = osName == "Android" or osName == "iOS" local nativePicker = love and love.system and type(love.system.pickFile) == "function" + local nativeHttp = love and love.system + and type(love.system.httpDownload) == "function" return { os = osName, nx = nx, + uwp = uwp, mobile = mobile, - console = nx, + console = nx or uwp, hasNativePicker = nativePicker, canSpawnProcess = osName == "OS X" or osName == "Windows" or osName == "Linux", romImportMode = nx and "save-directory" or (nativePicker and "native-picker") or "desktop", - networkValidated = not nx, + networkValidated = not nx and not uwp, + -- networkValidated is the self-updater's gate and stays a per-platform + -- policy call: a console package cannot replace itself on disk, so that + -- answer never depends on whether a transport exists. Fetching a mod + -- index or a mod zip is the narrower question, and #876 showed the two + -- had been conflated, so Xbox lost the mod catalog for the updater's + -- reason. Desktop answers it with curl through HostShell; the mobile and + -- console ports answer it with the native love.system.httpDownload bridge + -- (#597). The UWP LOVE backend does not export that bridge yet, so this + -- still resolves false on Xbox and the launcher still says so, but the + -- day the backend grows one, nothing here or in RomImporter has to change. + canFetchRemote = (not nx and not uwp) or nativeHttp, } end @@ -34,6 +49,10 @@ function Platform.isNX() return Platform.detect().nx end +function Platform.isUWP() + return Platform.detect().uwp +end + function Platform.romImportMode() return Platform.detect().romImportMode end @@ -46,6 +65,10 @@ function Platform.networkValidated() return Platform.detect().networkValidated end +function Platform.canFetchRemote() + return Platform.detect().canFetchRemote +end + -- Tests may swap love.system between cases. function Platform._resetForTests() cached = nil diff --git a/src/core/PlatformHooks.lua b/src/core/PlatformHooks.lua new file mode 100644 index 00000000..77bf83f5 --- /dev/null +++ b/src/core/PlatformHooks.lua @@ -0,0 +1,18 @@ +-- Generic process-lifecycle mod hooks so a platform-specific launcher +-- integration (a native shell embedding this engine, e.g. wrapping the +-- window in a platform UI) can live entirely in a mod instead of +-- hand-patching main.lua, which every other engine change also touches. +-- See docs/modding.md's "Process-lifecycle hooks" section. +local ModRuntime = require("src.mods.Runtime") + +local PlatformHooks = {} + +function PlatformHooks.update(game, dt) + return ModRuntime.call("core.update", function(g, d) g:update(d) end, game, dt) +end + +function PlatformHooks.quitToLauncher(vanilla) + return ModRuntime.call("core.quit_to_launcher", vanilla) +end + +return PlatformHooks diff --git a/src/core/SafeArea.lua b/src/core/SafeArea.lua index 86c404fb..37d600a5 100644 --- a/src/core/SafeArea.lua +++ b/src/core/SafeArea.lua @@ -28,6 +28,24 @@ function SafeArea.rect() return 0, 0, ww, wh end + -- A safe rect that cannot fit the window's unit space is a backend + -- reporting framebuffer PIXELS -- the iOS build (LOVE 12 + SDL3) did this + -- in portrait on iOS 16, and clamping it as-is kept a DPI-inflated top + -- inset that pushed the whole launcher a band down the screen (#810). + -- Convert back to units with per-axis ratios; the axes can disagree on + -- forced-rotation devices (see displayMetrics in src/render/Renderer.lua, + -- #208). + if (w > ww + 0.5 or h > wh + 0.5) + and love.graphics.getPixelDimensions then + local pw, ph = love.graphics.getPixelDimensions() + local dx = (pw and pw > 0) and (pw / ww) or 1 + local dy = (ph and ph > 0) and (ph / wh) or 1 + if dx > 1.01 or dy > 1.01 then + x, w = x / dx, w / dx + y, h = y / dy, h / dy + end + end + -- Clamp to the drawable window so a bad / mid-rotation backend cannot -- push layout outside the surface. x = math.max(0, math.min(x, ww)) diff --git a/src/core/SaveData.lua b/src/core/SaveData.lua index 6ce20c58..4b8511bf 100644 --- a/src/core/SaveData.lua +++ b/src/core/SaveData.lua @@ -30,6 +30,15 @@ local SaveData = {} -- deliberately shared across versions (it holds global preferences and the -- mod enable-state, not per-playthrough data). local OPTIONS_FILENAME = "options.lua" +-- #828: options.lua is rewritten whole on every write (see saveOptions), and +-- unlike the progress files it had no staged copy, so a write interrupted +-- between the truncate and the flush -- the process replaced by +-- HostShell.restart on the way back to the launcher, an Android +-- external-storage volume that never flushed -- left a truncated or empty +-- file that loadOptions could only answer with defaults: every setting +-- "reset" at once. Same .bak/.tmp witness names the save files use. +local OPTIONS_BACKUP_FILENAME = OPTIONS_FILENAME .. ".bak" +local OPTIONS_TMP_FILENAME = OPTIONS_FILENAME .. ".tmp" -- Main / backup / staged-witness names for a version (defaults to the active -- one). The backup is a rolling copy and .tmp is the staged-write witness; @@ -207,6 +216,13 @@ local function persistFs(fs) return SaveData.portableFs() or fs or (love and love.filesystem) end +-- Engine-owned persistence routing for subsystems that must follow the same +-- standard/portable root as saves without exposing raw filesystem access to a +-- mod. An explicitly injected headless filesystem still wins for tests. +function SaveData.persistenceFs(fs) + return persistFs(fs) +end + -- Port + original Options menu defaults. Missing keys on load are filled -- from this table so old options.lua files stay compatible. function SaveData.defaultOptions() @@ -245,8 +261,12 @@ function SaveData.defaultOptions() -- (the PIKACHU VOL row appears only on Yellow; see Sound.lua) pikaVol = 7, musicFilter = 0, - -- logic fast-forward multiplier; audio is unaffected (GameSpeed.lua) - speed = 1, + -- Per-category logic fast-forward multiplier (RFC 0007); audio is + -- unaffected (GameSpeed.lua). Superseded from a single "speed" field -- + -- mergeOptions migrates an old save's value into all three below. + speedOverworld = 1, + speedBattle = 1, + speedMenu = 1, -- port display options (OptionsMenu / hotkeys 2/3/4/5) colors = "gbc", tilt = 0, @@ -305,6 +325,13 @@ function SaveData.defaultOptions() -- layout (#633). Pre-#633 files stored one top-level positions table; -- TouchControls.normalizeConfig folds it into both orientations on load. touchControls = { enabled = true }, + -- Haptic feedback level for on-screen pad presses (#806): + -- off | light | medium | heavy, mapped to a love.system.vibrate + -- duration in src/core/TouchControls.lua. LIGHT by default, like the + -- overlay itself defaulting on, so an options.lua predating this key + -- gets the tick without going looking for the row. Inert wherever the + -- overlay never appears (desktop) or LOVE has no vibrator. + haptics = "light", } end @@ -316,6 +343,19 @@ function SaveData.mergeOptions(loaded) for k, v in pairs(loaded) do opts[k] = v end + -- RFC 0007 migration: a save from before per-category GAME SPEED still + -- has a single "speed" and none of the three new fields, so seed all + -- three from it -- an existing player's fast-forward preference + -- carries over instead of two of the three categories silently + -- resetting to 1X. "speed" is dropped on the way out (not kept as a + -- stale alias), so a re-save never re-triggers this migration. + if loaded.speed ~= nil and loaded.speedOverworld == nil + and loaded.speedBattle == nil and loaded.speedMenu == nil then + opts.speedOverworld = loaded.speed + opts.speedBattle = loaded.speed + opts.speedMenu = loaded.speed + end + opts.speed = nil end return opts end @@ -335,6 +375,22 @@ local function readTable(fs, name) return SaveSerializer.decode(body) end +-- Deep-copy a value folded in from the on-disk decode so the returned +-- options table never aliases the file's nested tables (SaveData must not +-- depend on src/mods/Merge.lua for this). Options data is plain tables of +-- strings/numbers/booleans/tables, so a cycle guard is belt-and-braces. +local function deepCopy(v, seen) + if type(v) ~= "table" then return v end + seen = seen or {} + if seen[v] then return seen[v] end + local copy = {} + seen[v] = copy + for k, val in pairs(v) do + copy[deepCopy(k, seen)] = deepCopy(val, seen) + end + return copy +end + -- the stub filesystem some headless harnesses inject has no remove; a -- lingering tmp/bak there is harmless local function remove(fs, name) @@ -348,13 +404,48 @@ end -- options round-trip headless (no love global). function SaveData.saveOptions(opts, fs) fs = persistFs(fs) + -- #932: options.lua is a WHOLE-FILE rewrite, so a caller that hands over a + -- PARTIAL table (just the keys it changed) would silently drop every key it + -- does not mention -- launcher-only keys like lastVersion, and keys the + -- launcher set (battleBg, tilt...) all fall back to defaults. Read the + -- on-disk file FIRST and fold caller-absent values underneath, so a delta + -- write changes only what it names. + -- + -- A table holding EVERY defaultOptions key is a full snapshot + -- (loadOptions() results, game.save.options, the RESET REBINDS / + -- activeProfile-drop paths) and stays authoritative: its absent keys are + -- deliberate deletions, so nothing folds for it. Partial tables get every + -- on-disk key they do not provide folded in (deep-copied so the caller's + -- table is never aliased). This is the reconciling rule: bindings and + -- activeProfile -- not defaultOptions members -- can be deleted by their + -- sites precisely because those sites always write full tables. + local onDisk = readTable(fs, OPTIONS_FILENAME) + local isFull = type(opts) == "table" + if isFull then + for k in pairs(SaveData.defaultOptions()) do + if opts[k] == nil then isFull = false break end + end + end + if not isFull then + local merged = {} + if type(opts) == "table" then + for k, v in pairs(opts) do merged[k] = v end + end + if type(onDisk) == "table" then + for k, v in pairs(onDisk) do + if k ~= "modOptions" and merged[k] == nil then + merged[k] = deepCopy(v) + end + end + end + opts = merged + end opts = SaveData.mergeOptions(opts) -- modOptions is per-mod nested state: fold the on-disk sub-tree -- underneath (newest value winning per key) so one caller's partial -- write cannot clobber another mod's persisted keys. Every other -- option stays on the shallow path. - local onDisk = readTable(fs, OPTIONS_FILENAME) - if onDisk and type(onDisk.modOptions) == "table" then + if type(onDisk) == "table" and type(onDisk.modOptions) == "table" then local merged = {} for modId, bucket in pairs(onDisk.modOptions) do merged[modId] = bucket @@ -368,11 +459,54 @@ function SaveData.saveOptions(opts, fs) end opts.modOptions = merged end - local ok, err = fs.write(OPTIONS_FILENAME, SaveSerializer.encode(opts)) + local encoded = SaveSerializer.encode(opts) + -- Stage the new bytes and roll the last good file aside BEFORE the main + -- write truncates it, the same tmp/bak dance SaveData.save uses for + -- progress: whatever ends the process mid-write, one of the three copies + -- is complete and loadOptions promotes it instead of falling back to + -- defaults (#828). + local ok, err = fs.write(OPTIONS_TMP_FILENAME, encoded) if not ok then Logger.error("options save failed: %s", tostring(err)) + return nil end - return ok and opts or nil + local prev = fs.getInfo(OPTIONS_FILENAME) and fs.read(OPTIONS_FILENAME) + if type(prev) == "string" and prev ~= "" and prev ~= encoded then + fs.write(OPTIONS_BACKUP_FILENAME, prev) + end + ok, err = fs.write(OPTIONS_FILENAME, encoded) + if not ok then + Logger.error("options save failed: %s", tostring(err)) + return nil + end + -- #828: settings "reset" on Android and Steam Deck with nothing in the log. + -- Every options write is a WHOLE-FILE rewrite, so a write that reports + -- success without the bytes landing (an external-storage volume that went + -- away mid-session, a read-only or full save dir) is indistinguishable from + -- "the launcher never saved". Read the file back and fail loudly instead: + -- callers already treat nil as a failed write, and the log line is what the + -- next report from those platforms needs to carry. + local wrote = fs.getInfo(OPTIONS_FILENAME) and fs.read(OPTIONS_FILENAME) + if wrote ~= encoded then + Logger.error("options save did not land (%d bytes written, %s on disk)", + #encoded, type(wrote) == "string" and tostring(#wrote) or "nothing") + return nil + end + -- #828: roll the backup FORWARD to the bytes just verified. The + -- pre-write roll above only preserves the previous file for a death + -- during this rewrite; at rest the backup must hold the newest verified + -- state, because the hard teardown out of a game session (HostShell's + -- restartApp kill on Android, execv on a SteamOS AppImage) can eat the + -- main file outright and loadOptions then promotes this copy. The + -- encoder is key-sorted, so the follow-up rewrites a play session makes + -- (play()'s lastVersion stamp, the in-game save flush) are byte-identical + -- and skip the conditional roll -- without this line the backup still + -- held the file from BEFORE the launcher's change, and recovery reverted + -- the just-changed setting (BATTLE LAYOUT back to OG). + fs.write(OPTIONS_BACKUP_FILENAME, encoded) + -- the staged witness has served its purpose; the main file is verified + remove(fs, OPTIONS_TMP_FILENAME) + return opts end function SaveData.loadOptions(fs) @@ -382,6 +516,26 @@ function SaveData.loadOptions(fs) if fs.getInfo(OPTIONS_FILENAME) then Logger.error("options load failed: %s", tostring(err)) end + -- #828: answering defaults here is what "closing the game reset all my + -- settings" looked like -- one interrupted whole-file rewrite and every + -- preference, the mod enable-state and the slot registry were gone. + -- Promote the staged copy, then the rolled-aside backup, exactly as + -- SaveData.load does for progress, and heal the main file from whichever + -- one parsed. + local recovered = readTable(fs, OPTIONS_TMP_FILENAME) + local from = "tmp" + if not recovered then + recovered = readTable(fs, OPTIONS_BACKUP_FILENAME) + from = "bak" + end + if recovered then + Logger.warn("options.lua %s; recovered from %s copy", + fs.getInfo(OPTIONS_FILENAME) and "corrupt" or "missing", from) + if fs.write then + fs.write(OPTIONS_FILENAME, SaveSerializer.encode(recovered)) + end + return SaveData.mergeOptions(recovered) + end return SaveData.defaultOptions() end return SaveData.mergeOptions(data) @@ -400,6 +554,10 @@ end -- working unchanged. local activeSlotCache = {} -- version -> slotId in use, or false when none local slotsChecked = {} -- version -> true once resolved this process +-- At most one New Game can be the live candidate for a first public tool +-- request. A single strong reference models that runtime fact without adding +-- marker data to the save or retaining abandoned playthrough tables. +local freshPlaythrough local function slotDir(version) return "saves/" .. version end @@ -736,6 +894,77 @@ end function SaveData.resetSlotState() for k in pairs(activeSlotCache) do activeSlotCache[k] = nil end for k in pairs(slotsChecked) do slotsChecked[k] = nil end + freshPlaythrough = nil +end + +-- ------- opaque playthrough identity + +-- An id must never perturb the engine's gameplay RNG: savestate tools need +-- repeatable random outcomes, and allocating persistence scope is not gameplay. +-- Combine wall/process time, a process-local sequence and a fresh table address +-- into four hex words. This is an opaque collision-resistant identifier, not a +-- secret or a player-visible value. +local playthroughSeq = 0 + +local function word(n) + return math.floor(tonumber(n) or 0) % 4294967296 +end + +function SaveData.newPlaythroughId() + playthroughSeq = playthroughSeq + 1 + local address = tostring({}):match("0x(%x+)") or "0" + local addressLo = tonumber(address:sub(-8), 16) or 0 + local clock = math.floor((os.clock() or 0) * 1000000) + return ("%08x%08x%08x%08x"):format( + word(os.time()), word(clock), word(addressLo), word(playthroughSeq)) +end + +local function playthroughScope(version, injectedFs) + version = version or GameVersion.get() + local fs = persistFs(injectedFs) + ensureVersionSlots(version, fs) + return activeSlotCache[version] or "legacy" +end + +local function rememberPlaythroughId(save, opts, injectedFs) + local meta = type(save) == "table" and save.meta + local id = type(meta) == "table" and meta.playthroughId + if type(id) ~= "string" or id == "" then return opts, false end + local version = save.version or GameVersion.get() + local scope = playthroughScope(version, injectedFs) + opts = opts or SaveData.loadOptions(injectedFs) + opts.playthroughIds = opts.playthroughIds or {} + opts.playthroughIds[version] = opts.playthroughIds[version] or {} + local changed = opts.playthroughIds[version][scope] ~= id + opts.playthroughIds[version][scope] = id + return opts, changed +end + +-- Return an existing save identity or give a pre-identity save a stable one. +-- Legacy backfill lives in options.lua until the next normal SAVE stamps the id +-- into progress, so installing a tool mod never rewrites the player's checkpoint. +function SaveData.ensurePlaythroughId(save, injectedFs) + if type(save) ~= "table" then return nil end + save.meta = type(save.meta) == "table" and save.meta or {} + local id = save.meta.playthroughId + if type(id) == "string" and id ~= "" then return id end + + local version = save.version or GameVersion.get() + local scope = playthroughScope(version, injectedFs) + local opts = SaveData.loadOptions(injectedFs) + local isFresh = save == freshPlaythrough + if isFresh then freshPlaythrough = nil end + local byVersion = opts.playthroughIds and opts.playthroughIds[version] + id = not isFresh and byVersion and byVersion[scope] or nil + if type(id) ~= "string" or id == "" then + id = SaveData.newPlaythroughId() + opts.playthroughIds = opts.playthroughIds or {} + opts.playthroughIds[version] = opts.playthroughIds[version] or {} + opts.playthroughIds[version][scope] = id + SaveData.saveOptions(opts, injectedFs) + end + save.meta.playthroughId = id + return id end -- ------- meta @@ -759,6 +988,7 @@ function SaveData.buildMeta(mods, previous) format = Version.saveFormat, engine = Version.engine, savedAt = os.time(), + playthroughId = type(previous) == "table" and previous.playthroughId or nil, mods = list, } end @@ -969,7 +1199,15 @@ function SaveData.save(data, mods) -- one, so Blue/Yellow playthroughs land in save_blue.lua / save_yellow.lua local FILENAME, BACKUP_FILENAME, TMP_FILENAME = saveNames(data.version) if data.options then - SaveData.saveOptions(data.options) + local opts = data.options + if data.meta and data.meta.playthroughId then + opts = rememberPlaythroughId(data, data.options) + end + data.options = opts + SaveData.saveOptions(opts) + elseif data.meta and data.meta.playthroughId then + local opts, changed = rememberPlaythroughId(data) + if changed then SaveData.saveOptions(opts) end end if mods ~= nil or data.meta == nil then data.meta = SaveData.buildMeta(mods, data.meta) @@ -1391,8 +1629,12 @@ function SaveData.newGame(boot) options = SaveData.loadOptions(), } -- a total conversion reshapes the skeleton (spawn, party, money) - -- before anything reads it; unhooked this returns save unchanged - return Runtime.call("save.new_game", function(s) return s end, save) + -- before anything reads it; unhooked this returns save unchanged. Keep the + -- "fresh playthrough" marker outside the serialized table so a later tool + -- request can distinguish two unsaved New Games sharing one vanilla slot. + save = Runtime.call("save.new_game", function(s) return s end, save) + freshPlaythrough = save + return save end return SaveData diff --git a/src/core/SaveSerializer.lua b/src/core/SaveSerializer.lua index 77ba482d..9f821f67 100644 --- a/src/core/SaveSerializer.lua +++ b/src/core/SaveSerializer.lua @@ -41,6 +41,13 @@ local function serialize(v, indent) error("cannot serialize " .. t) end +-- LuaJIT 2.1 can lose a just-added nested table entry when a GC step lands +-- inside a compiled recursive serialization trace. The symptom is valid input +-- becoming `{" followed by only the trailing comma, which then cannot be read +-- back. Save encoding is infrequent and I/O-bound, so keep this correctness- +-- critical recursion in the interpreter while leaving the game JIT enabled. +if jit and jit.off then jit.off(serialize, true) end + function SaveSerializer.encode(data) return "return " .. serialize(data) .. "\n" end diff --git a/src/core/Sound.lua b/src/core/Sound.lua index e0427380..f3fd35b0 100644 --- a/src/core/Sound.lua +++ b/src/core/Sound.lua @@ -208,29 +208,91 @@ end -- sfx table; older audio.lua builds without the variants fall back to -- the unmodified sound. -- anim: a moves.lua anim table { sound, pitch, tempo }. +-- +-- Whether a row sound is heard at all is Audio2_PlaySound's channel gate +-- (audio/engine_2.asm .playSfx/.sfxChannelLoop): for every channel the new +-- sfx wants, a channel still busy with a LOWER sound id aborts the whole +-- request (`cp [hl] / jr z,.playChannel / jr c,.playChannel / ret`), while +-- an equal or lower id takes those channels over. A sound id is +-- (header address - SFX_Headers_1) / 3 (constants/music_constants.asm +-- music_const), so a def's header address orders ids inside one engine +-- bank. Blizzard's animation is two rows, BLIZZARD then HYDRO_PUMP +-- (data/moves/animations.asm BlizzardAnim), and SFX_BATTLE_29 (CHAN5+8) is +-- still sounding when the second row starts, so the original never plays +-- SFX_BATTLE_2A (CHAN5+6+8) at all -- unguarded, its tail is heard running +-- past the end of the animation (#844). +local lastMoveSfx -- { src, rank, engine, channels } of the last row sound + +local function channelsOverlap(a, b) + if not (a and b) then return false end + for _, x in ipairs(a) do + for _, y in ipairs(b) do + if x == y then return true end + end + end + return false +end + +-- would PlaySound start this def now? Taking a channel over also stops the +-- sound that held it, the way .playChannel resets the channel. +local function sfxChannelGate(data, def) + local cur = lastMoveSfx + if not cur then return true end + local ok, playing = pcall(cur.src.isPlaying, cur.src) + if not (ok and playing) then + lastMoveSfx = nil + return true + end + -- an unrankable def (file asset, or another engine's bank) has no + -- comparable sound id: leave it to the mixer, as before + if type(def) ~= "table" or not def.address or def.engine ~= cur.engine then + return true + end + local channels = require("src.core.ChipSynth").effectChannels(data, def) + if not channelsOverlap(channels, cur.channels) then return true end + if def.address > cur.rank then return false end + pcall(cur.src.stop, cur.src) + lastMoveSfx = nil + return true +end + +local function noteMoveSfx(data, def, src) + if not src or type(def) ~= "table" or not def.address then + lastMoveSfx = nil + return + end + lastMoveSfx = { + src = src, rank = def.address, engine = def.engine, + channels = require("src.core.ChipSynth").effectChannels(data, def), + } +end + function Sound.playMove(data, anim) if not anim or not anim.sound then return end local sfx = data.audio and data.audio.sfx if not sfx then return end local name = anim.sound local pitch, tempo = anim.pitch or 0, anim.tempo or 0x80 + local def = sfx[name] + if not sfxChannelGate(data, def) then return end + local src -- a chip program synthesizes the modified variant on demand; a file def -- can only reach for a pre-rendered one - if isChipDef(sfx[name]) then - if playPath(data, ("%s@%02x%02x"):format(name, pitch, tempo), - sfx[name], pitch, tempo) then - played("move", name) - end - return - end - if pitch ~= 0 or tempo ~= 0x80 then + if isChipDef(def) then + src = playPath(data, ("%s@%02x%02x"):format(name, pitch, tempo), + def, pitch, tempo) + else local key = ("%s@%02x%02x"):format(name, pitch, tempo) - if sfx[key] then - if playPath(data, key, sfx[key]) then played("move", name) end - return + if (pitch ~= 0 or tempo ~= 0x80) and sfx[key] then + src = playPath(data, key, sfx[key]) + else + src = playPath(data, name, def) end end - if playPath(data, name, sfx[name]) then played("move", name) end + if src then + played("move", name) + noteMoveSfx(data, def, src) + end end -- A derived cry ({ base = "RHYDON", pitch, length }) borrows another @@ -304,12 +366,18 @@ end -- returns the source (nil headless) so callers that block on the cry -- like the original's PlayCry -> WaitForSoundToFinish can poll it -function Sound.playCry(data, species) +function Sound.playCry(data, species, pikaClip) if not love.audio then return nil end -- Yellow voices every Pikachu cry with the PCM clips (the chip cry is - -- never used for the species there); clip 1 is the everyday "Pika!" + -- never used for the species there). Which clip is a property of the + -- call site in the original -- every caller of PlayPikachuSoundClip sets + -- its own `ldpikacry e, PikachuCryN` -- so pikaClip carries that choice + -- in; it is ignored for every other species. Clip 1 is the LONG + -- title-screen "Pikachuuu" (engine/movie/title.asm:146), kept as the + -- default only for the sites that have not been given their own clip + -- yet; battle entrances pass 11/37 (#837). if species == "PIKACHU" then - local src = Sound.playPikaCry(data, 1) + local src = Sound.playPikaCry(data, pikaClip or 1) if src then return src end end local cries = data.audio and data.audio.cries @@ -451,6 +519,7 @@ end -- hot reload / jukebox A-B: drop one key's sources (its pitch-tempo -- variants included) or all of them, so the next play re-resolves the def function Sound.invalidate(name) + lastMoveSfx = nil -- its source is about to be dropped or stopped local function evict(store, key) local src = store[key] if src then pcall(src.stop, src) end diff --git a/src/core/StateStack.lua b/src/core/StateStack.lua index 898fd3dc..3a1995a7 100644 --- a/src/core/StateStack.lua +++ b/src/core/StateStack.lua @@ -39,17 +39,29 @@ function StateStack:update(dt) if top and top.update then top:update(dt) end end +local function visibleByDefault() return true end + +-- A mod may mirror a state elsewhere and hide only its main-screen render. +-- The state stays on the stack, so update and input ownership do not move. +function StateStack:renderVisible(state) + if not state then return false end + if not Runtime.wantsHook("screen.render_visible") then return true end + return Runtime.call("screen.render_visible", visibleByDefault, state) ~= false +end + -- index of the lowest state drawn this frame (highest opaque, else 1) function StateStack:visibleBase() for i = #self.states, 1, -1 do - if self.states[i].isOpaque then return i end + local state = self.states[i] + if self:renderVisible(state) and state.isOpaque then return i end end return 1 end function StateStack:draw() for i = self:visibleBase(), #self.states do - if self.states[i].draw then self.states[i]:draw() end + local state = self.states[i] + if self:renderVisible(state) and state.draw then state:draw() end end end diff --git a/src/core/TouchControls.lua b/src/core/TouchControls.lua index 4a6dae2c..a2e3b22b 100644 --- a/src/core/TouchControls.lua +++ b/src/core/TouchControls.lua @@ -85,6 +85,55 @@ local function clampScale(v) return v end +-- Haptic feedback (#806): a short vibration the instant a control takes a GB +-- button, the way every mobile emulator front-end does it -- the pad has no +-- edges under a thumb, so the buzz is the only confirmation a press landed. +-- Persisted as options.haptics (src/core/SaveData.lua defaultOptions), NOT +-- under options.touchControls: TouchControls:config() is the launcher +-- editor's save snapshot and only emits enabled + layouts, so a nested key +-- would be dropped on every editor save. +-- love.system.vibrate takes a duration and nothing else, so "intensity" is a +-- duration preset: Android runs the platform vibrator for exactly that long, +-- while iOS ignores the duration and fires the fixed system vibration, so +-- there the three levels all read as simply on. +TouchControls.HAPTICS = { "off", "light", "medium", "heavy" } +TouchControls.HAPTIC_DEFAULT = "light" + +local HAPTIC_SECONDS = { off = 0, light = 0.012, medium = 0.025, heavy = 0.045 } +local HAPTIC_LABELS = { + off = "OFF", light = "LIGHT", medium = "MEDIUM", heavy = "HEAVY", +} + +function TouchControls.normalizeHaptics(level) + if HAPTIC_SECONDS[level] then return level end + return TouchControls.HAPTIC_DEFAULT +end + +function TouchControls.hapticLabel(level) + return HAPTIC_LABELS[TouchControls.normalizeHaptics(level)] +end + +function TouchControls.cycleHaptics(level, dir) + local cur, idx = TouchControls.normalizeHaptics(level), 1 + for i, m in ipairs(TouchControls.HAPTICS) do + if m == cur then idx = i break end + end + local n = #TouchControls.HAPTICS + return TouchControls.HAPTICS[(idx - 1 + (dir or 1)) % n + 1] +end + +-- One pulse at the given level. Feature-guarded rather than platform-gated: +-- love.system.vibrate is a no-op on desktop and absent from the headless love +-- stubs, so the press path below stays identical everywhere and the tests +-- never reach a vibrator. +function TouchControls.buzz(level) + local secs = HAPTIC_SECONDS[TouchControls.normalizeHaptics(level)] + if not secs or secs <= 0 then return false end + if not (love and love.system and love.system.vibrate) then return false end + pcall(love.system.vibrate, secs) + return true +end + -- Copy a persisted positions table, dropping unknown / non-numeric entries. -- Always a fresh table: two orientations seeded from the same pre-#633 -- layout must not alias, or dragging one would still move the other. @@ -174,6 +223,10 @@ end function TouchControls:init() self.active = wantsOverlay() self.enabled = true + -- vibration level for presses (#806); applyOptions overwrites it from + -- options.haptics, this is the value a harness that never applies options + -- runs with + self.haptics = TouchControls.HAPTIC_DEFAULT -- per-orientation buckets (#633); self.positions / self.scale mirror the -- one currently on screen so layout(), the editor and the tests keep a -- single lookup @@ -210,6 +263,9 @@ end function TouchControls:applyOptions(opts) local cfg = TouchControls.normalizeConfig(opts and opts.touchControls) self.enabled = cfg.enabled + -- haptics is a plain top-level option, not part of the layout config the + -- launcher editor round-trips through config() (#806) + self.haptics = TouchControls.normalizeHaptics(opts and opts.haptics) self.layouts = cfg.layouts self.layoutW, self.layoutH = nil, nil self.layoutOx, self.layoutOy = nil, nil @@ -401,7 +457,14 @@ end local function pressBtn(self, btn) local n = (self.held[btn] or 0) + 1 self.held[btn] = n - if n == 1 then Input:overlayPressed(btn) end + -- Buzz only on the 0 -> 1 edge, the same edge that presses the GB button: + -- a second finger landing on a button that is already held, and a d-pad + -- finger resting inside one direction, must not retrigger it. Sliding the + -- d-pad to a new direction does, which is the point (#806). + if n == 1 then + Input:overlayPressed(btn) + TouchControls.buzz(self.haptics) + end end local function releaseBtn(self, btn) @@ -423,11 +486,17 @@ local function setDpad(self, touch, dir) if dir then pressBtn(self, dir) end end +-- Returns true when this touch was captured by a virtual control -- the +-- pad's first refusal on the gameplay pointer seam (#807). Capture is +-- decided here, at press, and rides self.touches[id] for the touch's +-- whole lifecycle; an uncaptured touch is never tracked, so wandering +-- across a control later neither presses it nor hides the touch from mods. function TouchControls:touchpressed(id, x, y) -- preview mode is layout-edit only: never press GB buttons if self.preview then return end if not (self.active and self.enabled ~= false and self.img) then return end -- a controller hid the overlay; the first touch only brings it back + -- (uncaptured: it began on no control, so mods may still see it) if self.controllerHidden then self.controllerHidden = false return @@ -437,7 +506,7 @@ function TouchControls:touchpressed(id, x, y) if inCircle(L[btn], x, y, SLOP[btn]) then self.touches[id] = { control = btn } pressBtn(self, btn) - return + return true end end -- square hit zone a bit past the cross art; one owning finger at a time @@ -449,6 +518,7 @@ function TouchControls:touchpressed(id, x, y) local touch = { control = "dpad", dir = nil } self.touches[id] = touch setDpad(self, touch, dpadDir(dz, x, y)) + return true end end diff --git a/src/dev/HotReload.lua b/src/dev/HotReload.lua index 0a36309f..22264be0 100644 --- a/src/dev/HotReload.lua +++ b/src/dev/HotReload.lua @@ -31,6 +31,12 @@ function HotReload.run(game, opts) local Logger = require("src.core.Logger") local data = game.data if data and data.reloadGenerated then data:reloadGenerated() end + -- outstanding mod.input holds and mod-visible pointers belong to the + -- loader being torn down; retire them while its subscribers still + -- exist, or the fresh loader inherits phantom "mod:*" sources and + -- pointers nobody left alive can release (#807) + if game.mods and game.mods.releaseModInput then game.mods:releaseModInput() end + if game.cancelPointers then game:cancelPointers() end local loader = Loader.new(opts and { fs = opts.fs, dev = opts.dev } or nil) loader.game = game -- mod.save keeps pointing at the live slot across the reload diff --git a/src/import/CacheFs.lua b/src/import/CacheFs.lua index 1f19dc66..4c99b048 100644 --- a/src/import/CacheFs.lua +++ b/src/import/CacheFs.lua @@ -28,15 +28,16 @@ -- ordinary love.filesystem/save-directory behaviour. local CacheFs = {} +local Platform = require("src.core.Platform") local SEP = package.config:sub(1, 1) -- Cache-relative paths are prefixed with this before every read/write, so a --- Blue/Yellow import lands under its GameVersion.cachePrefix (blue/, yellow/) --- while a Red import keeps the historical root. The launcher sets it per --- import / per readiness check; it stays "" for Red. Runtime *reads* --- (require / newImage) do NOT go through here -- CacheFs.mountVersion overlays --- the active version's subtree onto the un-prefixed paths instead. +-- version's import lands under its GameVersion.cachePrefix (red/, blue/, +-- yellow/). The launcher sets it per import / per readiness check; it stays +-- "" outside those flows. Runtime *reads* (require / newImage) do NOT go +-- through here -- CacheFs.mountVersion overlays the active version's subtree +-- onto the un-prefixed paths instead. CacheFs.prefix = "" local function withPrefix(rel) @@ -52,6 +53,7 @@ local mkdirFn = nil local function resolveMkdir() if mkdirFn ~= nil then return mkdirFn end mkdirFn = false + if Platform.isUWP() then return mkdirFn end local ok, ffi = pcall(require, "ffi") if not ok then return mkdirFn end if ffi.os == "Windows" then @@ -83,6 +85,7 @@ local rmdirFn = nil local function resolveRmdir() if rmdirFn ~= nil then return rmdirFn end rmdirFn = false + if Platform.isUWP() then return rmdirFn end local ok, ffi = pcall(require, "ffi") if not ok then return rmdirFn end if ffi.os == "Windows" then @@ -291,6 +294,9 @@ function CacheFs.read(rel) f:close() return data end + -- headless (plain luajit, e.g. the modkit validate/pack driver): there is + -- no save directory to read from, so a cache miss is nil, not a crash + if not (love and love.filesystem) then return nil end return love.filesystem.read(rel) end @@ -377,17 +383,120 @@ function CacheFs.removeTree(rel) walk(rel) end +-- One-time move of Red's pre-#899 cache (data/generated, assets/generated +-- and the rom-cache.complete marker at the cache root) into red/, the +-- layout Blue and Yellow always used. Idempotent: an existing red/ cache +-- wins and a missing root marker means nothing to do. +-- +-- The two cache homes are handled separately: the save directory goes +-- through love.filesystem so every host (NX included) and the headless test +-- stub take the same path, and the portable game folder goes through +-- os.rename on real paths -- skipped for a source run, where the game +-- folder IS the checkout and its data/generated is Red's source data, not +-- a cache. Called from RomImporter.new (before the readiness loop) and +-- from mountVersion, so no boot path can probe red/ before the move ran. +function CacheFs.migrateLegacyRedCache() + if not (love and love.filesystem and love.filesystem.getInfo) then return end + local fs = love.filesystem + + local function hasFile(p) return fs.getInfo(p, "file") ~= nil end + local function hasDir(p) return fs.getInfo(p, "directory") ~= nil end + + local function moveFile(src, dst) + local data = fs.read(src) + if data then + local parent = dst:match("^(.*)/[^/]+$") + if parent and fs.createDirectory then fs.createDirectory(parent) end + fs.write(dst, data) + end + fs.remove(src) + end + + local function moveTree(src, dst) + for _, child in ipairs(fs.getDirectoryItems(src) or {}) do + local sp, dp = src .. "/" .. child, dst .. "/" .. child + if hasDir(sp) then moveTree(sp, dp) else moveFile(sp, dp) end + end + -- remove only takes an empty directory; a non-empty one simply stays + fs.remove(src) + end + + -- --- save directory + if hasDir("red/data/generated") or hasFile("red/rom-cache.complete") then + -- already on the new layout + elseif hasFile("rom-cache.complete") then + -- The marker must be a save-dir file before anything moves: a developer + -- checkout also resolves data/generated at the root, but from the physfs + -- SOURCE, and moving that tree would gut the repository. + local real = fs.getRealDirectory and fs.getRealDirectory("rom-cache.complete") + if not real or (fs.getSaveDirectory and real == fs.getSaveDirectory()) then + -- cheap path first: renames inside the same directory; the copy below + -- covers whatever rename could not take (or hosts where the save dir + -- is not a plain os path, like the headless stub) + local saveDir = fs.getSaveDirectory and fs.getSaveDirectory() + if saveDir and fs.createDirectory then + fs.createDirectory("red/data") + fs.createDirectory("red/assets") + os.rename(saveDir .. SEP .. "data" .. SEP .. "generated", + saveDir .. SEP .. "red" .. SEP .. "data" .. SEP .. "generated") + os.rename(saveDir .. SEP .. "assets" .. SEP .. "generated", + saveDir .. SEP .. "red" .. SEP .. "assets" .. SEP .. "generated") + os.rename(saveDir .. SEP .. "rom-cache.complete", + saveDir .. SEP .. "red" .. SEP .. "rom-cache.complete") + end + if hasDir("data/generated") then + moveTree("data/generated", "red/data/generated") + end + if hasDir("assets/generated") then + moveTree("assets/generated", "red/assets/generated") + end + if hasFile("rom-cache.complete") then + moveFile("rom-cache.complete", "red/rom-cache.complete") + end + -- drop the emptied roots; a non-empty one (e.g. mods/ beside them is + -- untouched -- only data and assets are cache subtrees) simply stays + fs.remove("data") + fs.remove("assets") + end + end + + -- --- portable game folder (desktop only): rename on real paths + local root = CacheFs.root() + if root and not (fs.getSource and root == fs.getSource()) then + local function rootHas(rel) + local f = io.open(realPath(root, rel), "rb") + if f then f:close() return true end + return false + end + if rootHas("rom-cache.complete") and not rootHas("red/rom-cache.complete") then + local mkdir = resolveMkdir() + if mkdir then + mkdir(realPath(root, "red")) + mkdir(realPath(root, "red/data")) + mkdir(realPath(root, "red/assets")) + os.rename(realPath(root, "data/generated"), + realPath(root, "red/data/generated")) + os.rename(realPath(root, "assets/generated"), + realPath(root, "red/assets/generated")) + os.rename(realPath(root, "rom-cache.complete"), + realPath(root, "red/rom-cache.complete")) + end + end + end +end + -- Overlay the active version's extracted cache onto the un-prefixed read -- paths, so require("data.generated.*") and love.graphics.newImage( -- "assets/generated/*") resolve to that version's files. -- --- Non-Red versions live under blue/ / yellow/ in the save directory. On --- desktop fused+portable we PHYSFS_mount that folder by absolute path. On --- NX (and any host without a working FFI mount) love.filesystem.mount of --- the save-dir-relative name must succeed, or Play boots with Red's paths --- and Data:load dies. Always also prepend-mount the version's --- data/generated + assets/generated onto the un-prefixed paths so PhysFS --- directory non-merge (archive data/ vs save generated) cannot hide them. +-- Each version lives under its cachePrefix folder in the save directory. +-- On desktop fused+portable we PHYSFS_mount that folder by absolute path. +-- On NX (and any host without a working FFI mount) love.filesystem.mount +-- of the save-dir-relative name must succeed, or Play boots with another +-- version's paths and Data:load dies. Always also prepend-mount the +-- version's data/generated + assets/generated onto the un-prefixed paths +-- so PhysFS directory non-merge (archive data/ vs save generated) cannot +-- hide them. local function mountGeneratedTrees(prefix) prefix = prefix or "" if not (love and love.filesystem and love.filesystem.mount) then @@ -410,10 +519,13 @@ local function mountGeneratedTrees(prefix) end function CacheFs.mountVersion(version) + -- A legacy root Red cache has to move into red/ before anything probes + -- red/ paths (idempotent and near-free once migrated, issue #899). + if version == "red" then CacheFs.migrateLegacyRedCache() end local prefix = require("src.core.GameVersion").cachePrefix(version) local sub = prefix:gsub("/+$", "") - -- Save-dir relative mount first (NX / no-FFI). Prepend so blue|yellow win. + -- Save-dir relative mount first (NX / no-FFI). Prepend so the version wins. if sub ~= "" and love.filesystem.mount and love.filesystem.getInfo(sub, "directory") then love.filesystem.mount(sub, "", false) @@ -430,21 +542,21 @@ function CacheFs.mountVersion(version) end end - -- Version-scoped generated trees → un-prefixed paths (Red prefix is ""). + -- Version-scoped generated trees → un-prefixed paths. mountGeneratedTrees(prefix) return true end -- Undo mountVersion. A process normally mounts exactly one version and then --- boots it, but the launcher can open the save editor on a Blue/Yellow save, --- close it, and press Play on Red: with that version's subtree still --- prepended, Red's require("data.generated.*") and its generated art would --- silently resolve to the other game's files. Callers must also drop the --- generated modules from package.loaded (src.core.Data:unloadGenerated) -- --- unmounting alone only fixes the read path, not what require already cached. +-- boots it, but the launcher can open the save editor on one game's save, +-- close it, and press Play on another: with the first version's subtree +-- still prepended, the other's require("data.generated.*") and generated +-- art would silently resolve to the first game's files. Callers must also +-- drop the generated modules from package.loaded +-- (src.core.Data:unloadGenerated) -- unmounting alone only fixes the read +-- path, not what require already cached. -- --- Returns true when nothing was mounted or the unmount took. Red is a no-op --- because its cache lives at the root and was never overlaid. +-- Returns true when nothing was mounted or the unmount took. function CacheFs.unmountVersion(version) local prefix = require("src.core.GameVersion").cachePrefix(version) if prefix == "" then return true end diff --git a/src/import/LauncherSettings.lua b/src/import/LauncherSettings.lua index 19e74312..03f1b8cc 100644 --- a/src/import/LauncherSettings.lua +++ b/src/import/LauncherSettings.lua @@ -62,7 +62,7 @@ local FILTERS = { "OFF", "1X", "2X", "3X" } -- The core rows. Helper modules are required lazily under pcall: they are -- pure label/cycle tables, but the launcher must never die because a render -- module grew a dependency on live game data. -local function coreRows(opts) +local function coreRows(opts, hooks) local rows = {} local function add(label, value, step) rows[#rows + 1] = { label = label, value = value, step = step } @@ -214,10 +214,24 @@ local function coreRows(opts) local okSpd, GameSpeed = pcall(require, "src.core.GameSpeed") if okSpd then - add(Strings("GAME SPEED"), - function() return GameSpeed.levelLabel(opts.speed) end, + -- Per-category (RFC 0007): overworld/battle/menu each cycle their own + -- multiplier, mirroring OptionsMenu.lua's three rows. + add(Strings("OVERWORLD SPEED"), + function() return GameSpeed.levelLabel(opts.speedOverworld) end, function(dir) - opts.speed = GameSpeed.cycle(opts.speed, dir) + opts.speedOverworld = GameSpeed.cycle(opts.speedOverworld, dir) + return true + end) + add(Strings("BATTLE SPEED"), + function() return GameSpeed.levelLabel(opts.speedBattle) end, + function(dir) + opts.speedBattle = GameSpeed.cycle(opts.speedBattle, dir) + return true + end) + add(Strings("MENU SPEED"), + function() return GameSpeed.levelLabel(opts.speedMenu) end, + function(dir) + opts.speedMenu = GameSpeed.cycle(opts.speedMenu, dir) return true end) end @@ -242,9 +256,61 @@ local function coreRows(opts) opts.touchControls = tc return true end) + -- VIBRATION sits with it (#806): same gate, same subsystem. Stepping + -- the row buzzes once at the level being selected. + local okTC, TC = pcall(require, "src.core.TouchControls") + if okTC then + add(Strings("VIBRATION"), + function() return Strings(TC.hapticLabel(opts.haptics)) end, + function(dir) + opts.haptics = TC.cycleHaptics(opts.haptics, dir) + TC.buzz(opts.haptics) + return true + end) + end end end + -- TOUCH CONTROLS, the on-screen pad's layout editor. It used to be a + -- button on the game panel, once per game -- but the overlay layout is + -- global (options.touchControls.layouts), so three tabs offered three + -- buttons that edited the same thing while crowding the column that has to + -- hold Play. It belongs with the other control rows, behind the gear. + -- The host owns the editor screen, so the row only fires when a hook was + -- supplied (the standalone save editor opens this model with none). + if hooks and hooks.editTouchControls then + rows[#rows + 1] = { + label = Strings("TOUCH CONTROLS"), + actionLabel = Strings("Edit"), + action = function() + hooks.editTouchControls() + -- The editor replaces the whole screen: nothing left to persist here + -- beyond what the caller already saved on the way out. + return false + end, + } + end + + -- RESET REBINDS, directly under the touch-pad row. Rebinds are additive + -- (src/core/Input.lua:applyBindings layers options.bindings over the + -- defaults rather than replacing them), so a player who has bound + -- themselves into a corner has no in-game way back -- there is no "unbind" + -- gesture. Clearing the table restores the stock keyboard and pad layout + -- on the next Input:applyBindings, which the game does on its next start. + -- The dragged touch-overlay layout goes with it: it is the same class of + -- customisation and the same class of getting stuck. + rows[#rows + 1] = { + label = Strings("RESET REBINDS"), + actionLabel = Strings("Reset"), + danger = true, + action = function() + opts.bindings = nil + local tc = opts.touchControls + if type(tc) == "table" then tc.layouts = nil end + return true + end, + } + return rows end @@ -385,10 +451,12 @@ end -- sections of rows, and a save() that persists it. The caller keeps the -- model for as long as the panel is open; nothing else in the launcher -- writes options while a modal covers it, so the cached table stays true. -function LauncherSettings.open() +-- `hooks` carries the host actions a row cannot perform itself: +-- editTouchControls() -- hand the screen to the touch-overlay editor +function LauncherSettings.open(hooks) local opts = SaveData.loadOptions() local sections = { - { title = Strings("OPTIONS"), rows = coreRows(opts) }, + { title = Strings("OPTIONS"), rows = coreRows(opts, hooks) }, } for _, mod in ipairs(discoverModSchemas(opts)) do local rows = modRows(opts, mod) diff --git a/src/import/LauncherView.lua b/src/import/LauncherView.lua index 98ecd6ec..54518c40 100644 --- a/src/import/LauncherView.lua +++ b/src/import/LauncherView.lua @@ -1,188 +1,63 @@ --- The launcher's FlexLove view. RomImporter owns every piece of state and --- all import/platform logic; this module rebuilds the immediate-mode element --- tree from that state once per frame, so the UI can never drift from the --- importer and every window size lays out fresh (no cached geometry, no hand --- hit-testing). Flat design: solid fills and hairline borders only, no --- gradients or glows. +-- The launcher's view, drawn with the shared immediate-mode kit +-- (src/ui/kit/). RomImporter owns every piece of state and all +-- import/platform logic; this module paints that state once per frame, so the +-- UI can never drift from the importer and every window size lays out fresh. -- --- Interaction contract with RomImporter: --- * every click handler only QUEUES work (imp._uiActions); update() drains --- the queue after FlexLove.update, so a handler that tears the view down --- (Play, Edit save) never destroys the tree that is dispatching it. --- * clicks are deduped per control key (a touch tap can surface as both a --- touch release and a synthesized mouse click; one action must not fire --- twice -- the shape of #553's double import). --- * hover state lives in imp._hot, written by events this frame and read --- by styles next frame (immediate mode recreates elements every frame). --- * the gamepad virtual cursor clicks through clickAt(), which dispatches --- a synthetic event to the element under the pad pointer. +-- WHAT CHANGED, AND WHY. This used to build a retained FlexLove element tree +-- every frame. That cost ~9ms of build+draw on a real profile before a +-- single row of content existed (measure it yourself: POKEPORT_LAUNCHER_PROF= +-- 200 love .), because the engine hashed props per element, snapshotted every +-- public scalar for its immediate-mode persistence, and re-ran an O(n^2) +-- auto-size pass. Painting the same screen directly is a small fraction of +-- that, and it removes a whole class of layout bug along with it: percentage +-- widths resolving against the wrong box, auto-sized buttons measuring zero +-- height, and flex-shrink compressing text until it overlapped. +-- +-- THE RULES THIS FILE FOLLOWS: +-- * NO SCROLLING. Every list paginates (Kit.pager). Rows per page come +-- from the real viewport height, so a tall window shows more and a phone +-- shows fewer -- but a page's row count is bounded either way, which is +-- what makes a 500-mod index cost the same as a 10-mod one. +-- * Every click handler only QUEUES work (imp._uiActions); update() drains +-- the queue, so an action that tears the view down (Play, Edit save) +-- never runs inside the frame that dispatched it. +-- * Clicks are deduped per control key: a touch tap can surface as both a +-- touch release and a synthesized mouse click, and one action must not +-- fire twice (the shape of #553's double import). +-- * Anything that waits raises a non-dismissable loader (Loader.overlay), +-- driven by imp._busy / imp.workState. +-- * Layout is explicit pixels off Layout.metrics. No percentages. -local FlexLove = require("libs.flexlove.FlexLove") -local Color = FlexLove.Color -local SafeArea = require("src.core.SafeArea") +local Kit = require("src.ui.kit.Kit") +local Theme = require("src.ui.kit.Theme") +local Layout = require("src.ui.kit.Layout") +local Loader = require("src.ui.kit.Loader") local GameVersion = require("src.core.GameVersion") +local Version = require("src.core.Version") local Strings = require("src.core.Strings") +local PAL = Theme.PAL local LauncherView = {} --- ------- palette (flat; alpha per use site) -local function rgba(r, g, b, a) - return Color.new(r / 255, g / 255, b / 255, a or 1) -end -local PAL = { - bg = { 10, 15, 34 }, - card = { 16, 23, 48 }, - rowBg = { 9, 14, 34 }, - border = { 120, 150, 220 }, - red = { 255, 60, 72 }, - blue = { 70, 150, 255 }, - gold = { 255, 203, 5 }, - green = { 62, 224, 138 }, - greenDark = { 22, 163, 90 }, - greenInk = { 6, 32, 18 }, - white = { 255, 255, 255 }, - detail = { 198, 208, 230 }, - warn = { 159, 176, 208 }, - gray = { 143, 163, 200 }, - disabled = { 120, 132, 158 }, - link = { 127, 208, 255 }, - danger = { 255, 83, 97 }, - chipModTop = { 61, 74, 109 }, -} -local function C(name, a) - local c = PAL[name] - return rgba(c[1], c[2], c[3], a) -end - --- Non-scroll elements refuse to flex-shrink: the engine otherwise compresses --- auto-height children inside height-constrained columns until their text --- overlaps (the portrait single-column layout was the visible case). Scroll --- regions are the opposite — they MUST shrink to the viewport, or their --- height grows with content, maxScrollY stays 0, and drag/wheel do nothing. --- horizontal padding of a props table, for content-width bookkeeping -local function propsPadH(p) - local pad = p.padding - if type(pad) == "number" then return pad * 2 end - if type(pad) == "table" then - return (pad.left or pad.horizontal or 0) + (pad.right or pad.horizontal or 0) - end - return 0 -end - -local function isScrollOverflow(props) - local o = props.overflowY or props.overflowX or props.overflow - return o == "scroll" or o == "auto" -end - -local function mk(props) - if props.flexShrink == nil then - props.flexShrink = isScrollOverflow(props) and 1 or 0 - end - if isScrollOverflow(props) then - if props.minHeight == nil then props.minHeight = 0 end - -- Every launcher list scrolls like a native one: interpolated wheel - -- steps instead of hard 20px jumps, and a bigger per-notch distance so - -- long save/mod lists don't take dozens of notches to traverse. - if props.smoothScrollEnabled == nil then props.smoothScrollEnabled = true end - if props.scrollSpeed == nil then props.scrollSpeed = 60 end - end - -- Resolve "100%" here, against the parent's CONTENT width: the engine - -- resolves a percentage against the parent's border box and ignores its - -- padding, so every percent child of a padded container overflowed to the - -- right by exactly that padding (the clipped LOADED/Delete chips). - -- Parents are always created before their children in this view, so the - -- tracked inner width is available by the time a child asks. - if props.width == "100%" and props.parent and props.parent._innerW then - props.width = props.parent._innerW - end - local el = FlexLove.new(props) - if el then - if type(props.width) == "number" then - el._innerW = props.width - propsPadH(props) - elseif props.parent and props.parent._innerW then - el._innerW = props.parent._innerW - propsPadH(props) - end - end - return el -end - local COMMUNITY_URL = "https://bois.icu" +-- One dedup window covers a touch release plus the mouse click SDL +-- synthesizes for the same tap. +local ACT_DEDUP = 0.35 +-- Finger travel past this (px) is a drag, not a tap. +local TAP_SLOP2 = 16 * 16 + local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end --- FlexLove's addChild auto-sizing only propagates one ancestor while this --- immediate-mode tree is assembled. Reconcile a completed nested container --- with the same height box model used by Element:resize. -local function refreshAutoHeight(el) - if type(el) ~= "table" or type(el.autosizing) ~= "table" - or not el.autosizing.height - or type(el.calculateAutoHeight) ~= "function" then - return false - end - local ok, contentHeight = pcall(el.calculateAutoHeight, el) - if not ok or type(contentHeight) ~= "number" - or contentHeight ~= contentHeight - or contentHeight == math.huge or contentHeight == -math.huge then - return false - end - local padding = el.padding or {} - local top, bottom = padding.top or 0, padding.bottom or 0 - local borderBoxHeight = clamp(contentHeight + top + bottom, - el.minHeight or -math.huge, el.maxHeight or math.huge) - el._borderBoxHeight = borderBoxHeight - el.height = clamp(math.max(0, borderBoxHeight - top - bottom), - el.minHeight or -math.huge, el.maxHeight or math.huge) - if type(el.invalidateLayout) == "function" then - el:invalidateLayout() - end - return borderBoxHeight -end +-- ------------------------------------------------------------- lifecycle -LauncherView._refreshAutoHeight = refreshAutoHeight - - --- ------- lifecycle - --- NX-only: FlexLove's init maps `performanceMonitoring = false` to true --- (`false or true`), which leaves layout/render timers + memory sampling on --- every immediate-mode frame and makes the pad cursor feel lagged. Force --- them off after init. Desktop keeps the library default. Exported so the --- engine tier can assert the Switch guards without drawing the full tree. -function LauncherView.applyNxPerfGuards(imp) - if not (imp and imp.isNX and FlexLove.isReady() and FlexLove._Performance) then - return false - end - FlexLove._Performance.enabled = false - local mp = FlexLove._Performance._memoryProfiler - if mp then mp.enabled = false end - -- Immediate-mode rebuilds allocate a full tree every frame; the default - -- auto GC steps hitch the pad cursor on Switch. Less frequent steps, higher - -- threshold — desktop keeps FlexLove defaults. - if FlexLove._gcConfig then - FlexLove._gcConfig.strategy = "periodic" - FlexLove._gcConfig.interval = 90 - FlexLove._gcConfig.stepSize = 40 - FlexLove._gcConfig.memoryThreshold = 180 - end - return true -end - -local function ensureFlex(imp) - if not FlexLove.isReady() then - FlexLove.init({ - immediateMode = true, - performanceMonitoring = false, - keyboardNavigation = false, - }) - end - -- Re-apply on every ensure: FlexLove may already be ready from a prior - -- init (hot reload / editor round-trip). No-op when not NX. - LauncherView.applyNxPerfGuards(imp) +local function ensureState(imp) if not imp._flex then imp._flex = true imp._hot = imp._hot or {} imp._actAt = imp._actAt or {} imp._uiActions = imp._uiActions or {} + imp._pages = imp._pages or {} -- Held backspace/arrows must repeat in the text fields; restored on -- detach because the game's Input does its own per-step edge detection -- and never expects repeated keypressed events. @@ -192,11 +67,17 @@ local function ensureFlex(imp) end end --- Tear the tree down before handing the screen to the game / editor: the --- engine draws with raw love.graphics and must not share canvases or input --- polling with a live UI toolkit. +-- Kept as a no-op hook: the engine tier asserts this exists, and the guards +-- it used to apply were FlexLove's (performance monitoring, GC tuning). The +-- kit has neither a profiler nor a GC strategy to tune -- it does not +-- allocate per frame -- so there is nothing left to guard. +function LauncherView.applyNxPerfGuards(imp) + return imp ~= nil +end + +-- Tear down before handing the screen to the game / editor. function LauncherView.detach(imp) - -- Restore the NX mouse shim even if _flex was never set (bridge can + -- Restore the NX mouse shim even if _flex was never set (the bridge can -- install on the first update before the first draw). if imp and imp.parkNxPointerForHost then pcall(imp.parkNxPointerForHost, imp) @@ -208,348 +89,232 @@ function LauncherView.detach(imp) if love.keyboard and love.keyboard.setKeyRepeat then pcall(love.keyboard.setKeyRepeat, false) end - pcall(FlexLove.destroy) + Kit.clearCaches() end +-- ---------------------------------------------------------------- input +-- The kit is polled, not evented: update() samples the mouse and turns a +-- rising edge into a click point that the next draw consumes. Host-forwarded +-- mousepressed stays unused, exactly as before, so Android's synthesized +-- mouse path cannot double-fire a tap (#553) -- the dedup window below is the +-- other half of that guarantee. function LauncherView.update(imp, dt) if not imp._flex then return end - FlexLove.update(dt) - -- Drain the action queue OUTSIDE FlexLove's dispatch, so an action is free - -- to destroy the view (Play/Edit) or block in a native picker. + + local down = false + if love.mouse and love.mouse.isDown then + down = love.mouse.isDown(1) and true or false + end + if down and not imp._prevMouseDown and not imp._padCursorActive then + -- On touch platforms SDL synthesizes a mouse button from the finger, so + -- this rising edge fires at finger-DOWN while touchreleased dispatches + -- the same tap again at finger-UP: every control acted twice per tap + -- (the pager visibly jumped two pages). While a touch is alive, or + -- inside the dedup window one just closed, the polled mouse IS that + -- finger and must not mint a second click. A real desktop mouse has no + -- touches, so its press-down click is unchanged. + -- _suppressMouseUntil, NOT _suppressClickUntil: the latter is consulted + -- by queueAction and would swallow the tap's own action along with the + -- synthesized echo. + local now = love.timer.getTime() + local touching = imp._touchAt ~= nil and next(imp._touchAt) ~= nil + if not touching and now >= (imp._suppressMouseUntil or 0) + and now >= (imp._suppressClickUntil or 0) then + local mx, my = love.mouse.getPosition() + imp._clickPt = { x = mx, y = my } + end + end + imp._prevMouseDown = down + + -- Drain the action queue OUTSIDE the draw, so an action is free to destroy + -- the view (Play/Edit) or block in a native picker. The batch is resolved + -- by RomImporter:runActions so the drop/disarm rules stay testable without + -- a live view (#780). local queue = imp._uiActions if queue and #queue > 0 then imp._uiActions = {} - for _, fn in ipairs(queue) do - local ok, err = pcall(fn) - if not ok then print("launcher action error: " .. tostring(err)) end - end + imp:runActions(queue) end end --- One dedup window covers a touch release plus the mouse click SDL --- synthesizes for the same tap. -local ACT_DEDUP = 0.35 --- Finger travel past this (px) is a scroll drag, not a tap — so dragging a --- list row does not also fire that row's button. -local TAP_SLOP2 = 16 * 16 - function LauncherView.wheelmoved(imp, dx, dy) if not imp._flex then return end - pcall(FlexLove.wheelmoved, dx, dy) + imp._wheelY = (imp._wheelY or 0) + (dy or 0) end --- Touch drag scroll: FlexLove's ScrollManager only moves when these are --- hooked. Clicks still come from EventHandler's love.touch / mouse polling; --- the view's action dedupe covers a tap that also synthesizes a mouse click, --- and a drag past TAP_SLOP suppresses the click that would otherwise fire --- on the row under the finger. -function LauncherView.touchpressed(imp, id, x, y, dx, dy, pressure) +function LauncherView.touchpressed(imp, id, x, y) if not imp._flex then return end imp._touchAt = imp._touchAt or {} imp._touchAt[tostring(id)] = { x = x, y = y } - pcall(FlexLove.touchpressed, id, x, y, dx, dy, pressure) end -function LauncherView.touchmoved(imp, id, x, y, dx, dy, pressure) +function LauncherView.touchmoved(imp, id, x, y) if not imp._flex then return end local start = imp._touchAt and imp._touchAt[tostring(id)] if start then local ddx, ddy = x - start.x, y - start.y if ddx * ddx + ddy * ddy > TAP_SLOP2 then - imp._suppressClickUntil = love.timer.getTime() + ACT_DEDUP + start.dragged = true end + -- Short-window mode: a vertical drag scrolls the page (draw() clamps). + if start.dragged and (imp._pageScrollMax or 0) > 0 then + local last = start.lastY or start.y + imp._pageScroll = (imp._pageScroll or 0) - (y - last) + end + start.lastY = y end - pcall(FlexLove.touchmoved, id, x, y, dx, dy, pressure) end -function LauncherView.touchreleased(imp, id, x, y, dx, dy, pressure) +-- A tap dispatches on RELEASE (not press) so a drag can disqualify it. +function LauncherView.touchreleased(imp, id, x, y) if not imp._flex then return end + local start = imp._touchAt and imp._touchAt[tostring(id)] if imp._touchAt then imp._touchAt[tostring(id)] = nil end - pcall(FlexLove.touchreleased, id, x, y, dx, dy, pressure) + if start and start.dragged then + -- Suppress the mouse click SDL will synthesize for this same gesture. + imp._suppressClickUntil = love.timer.getTime() + ACT_DEDUP + return + end + -- The tap dispatches HERE, once: suppress update()'s rising-edge path for + -- the mouse press SDL synthesizes from this same gesture. Mouse-only + -- suppression -- _suppressClickUntil would also make queueAction drop the + -- tap's own action. + imp._suppressMouseUntil = love.timer.getTime() + ACT_DEDUP + imp._clickPt = { x = x, y = y } end --- Synthetic click for the gamepad virtual cursor: find the element under the --- pad pointer and run its handler with a click-shaped event. +-- Synthetic click for the gamepad virtual cursor. function LauncherView.clickAt(imp, x, y) if not imp._flex then return end - local ok, el = pcall(FlexLove.getElementAtPosition, x, y) - if not ok then return end - while el and not el.onEvent do el = el.parent end - if el and el.onEvent then - pcall(el.onEvent, el, { type = "click", button = 1, x = x, y = y, - modifiers = {}, clickCount = 1 }) - end + imp._clickPt = { x = x, y = y } end --- ------- shared widget helpers +-- Keyboard focus ring. Returns true when the key was consumed. Arrows arm +-- the ring; Enter only activates a focused control once the user has actually +-- used the arrows this session, so the long-standing "Enter plays the visible +-- game" shortcut keeps working for anyone who never touches the ring. +function LauncherView.keypressed(imp, key) + if not imp._flex then return false end + if key == "up" or key == "down" or key == "left" or key == "right" then + imp._ringArmed = true + Kit.navigate(key) + return true + end + if imp._ringArmed and (key == "return" or key == "kpenter" or key == "space") then + Kit.activateFocused() + return true + end + return false +end + +-- ------------------------------------------------------------- actions local function queueAction(imp, key, fn, keepArm) local now = love.timer.getTime() local last = imp._actAt[key] if last and now - last < ACT_DEDUP then return end + local untilT = imp._suppressClickUntil + if untilT and now < untilT then return end imp._actAt[key] = now -- Any press that is not a Delete's own second click disarms the pending - -- delete confirm (#433's rule, preserved from the hit-rect launcher). - if not keepArm then imp._confirmDelete = nil end - imp._uiActions[#imp._uiActions + 1] = fn + -- delete confirm (#433's rule). The disarm is applied by runActions when + -- the batch drains, not here: one touch lands on a row AND on the chip + -- inside it, and clearing the arm as the row queued left Delete stuck on + -- its first press (#780). + imp._uiActions[#imp._uiActions + 1] = { key = key, fn = fn, keepArm = keepArm } end -local function handler(imp, key, action, keepArm) - return function(_, ev) - if ev.type == "hover" then - imp._hot[key] = true - elseif ev.type == "unhover" then - imp._hot[key] = nil - elseif action and (ev.type == "click" or ev.type == "touchrelease") then - if ev.type == "touchrelease" then - local dx, dy = ev.dx or 0, ev.dy or 0 - if dx * dx + dy * dy > TAP_SLOP2 then - imp._suppressClickUntil = love.timer.getTime() + ACT_DEDUP - return - end - end - local untilT = imp._suppressClickUntil - if untilT and love.timer.getTime() < untilT then return end - queueAction(imp, key, action, keepArm) +-- Every interactive control in this file goes through one of these two, so +-- the queueing and dedup rules cannot be forgotten at a call site. +local function btn(imp, x, y, w, h, key, label, opts) + opts = opts or {} + opts.id = key + if Kit.button(x, y, w, h, label, opts) and opts.action then + queueAction(imp, key, opts.action, opts.keepArm) + end +end + +local function rowHit(imp, x, y, w, h, selected, key, action) + local clicked, ink = Kit.row(x, y, w, h, selected, key) + if clicked and action then queueAction(imp, key, action) end + return ink +end + +-- ------------------------------------------------------- shared widgets + +-- Read-only text field. The importer owns the string (its textinput / +-- keypressed routing writes it); this only renders it, keeps the TAIL +-- visible while typing, and blinks a caret on the importer's pulse clock. +local function textField(imp, x, y, w, h, key, rawText, placeholder, focused, action) + Kit._audit("control", x, y, w, h, key) + Kit.focusable(key, x, y, w, h) + Theme.fill(x, y, w, h, PAL.bg, 1) + Theme.stroke(x, y, w, h, PAL.line, + focused and Theme.A.focus or + (Kit.hover(x, y, w, h) and Theme.A.hover or Theme.A.hairline), + focused and 2 or 1) + local pad = math.floor(10 * Kit.scale) + local ty = y + (h - Kit.textHeight("button")) / 2 + local text = rawText or "" + if text == "" and not focused then + Kit.text("button", Kit.ellipsize("button", placeholder or "", w - 2 * pad), + x + pad, ty, PAL.faint) + else + local shown = Kit.ellipsizeLeft("button", text, w - 2 * pad) + local tw = Kit.text("button", shown, x + pad, ty, PAL.heading) + if focused and (imp.pulse * 2 % 1) < 0.5 then + Theme.fill(x + pad + tw + 2, ty, math.max(1, Kit.scale), + Kit.textHeight("button"), PAL.ink, 1) end end -end - --- Shared measuring fonts, cached by integer size: control widths and heights --- come from the same faces the elements render with. Estimating them from --- character counts broke at every scale except the one it was tuned on --- (clipped Delete chips, button rows spilling out of their cards). -local measureFonts = {} -local function mfont(size) - size = math.max(8, math.floor(size + 0.5)) - local f = measureFonts[size] - if not f then - f = love.graphics.newFont(size) - measureFonts[size] = f + if action and (Kit.press(x, y, w, h) or Kit._activateId == key) then + queueAction(imp, key, action) end - return f -end -local function textWidth(size, text) return mfont(size):getWidth(text) end -local function textHeight(size) return mfont(size):getHeight() end - --- wrapped text height at a width, from the same font the element renders -local function wrapHeight(size, text, width) - if not text or text == "" or (width or 0) <= 0 then return 0 end - local f = mfont(size) - local _, lines = f:getWrap(text, width) - return math.max(1, #lines) * f:getHeight() end --- Every text size in this file is already scaled by m.s, so FlexLove's own --- viewport text scaling must stay off: with it on, the layout boxes shrink --- away from the rendered glyphs on non-reference window sizes and lines --- overlap. -local function label(parent, text, size, color, props) - -- integer sizes only: the measuring fonts are integer-sized, and a - -- fractional rendered size drifting a few percent wider than its measure - -- is exactly how button rows crept out of their cards on some displays - size = math.floor(size + 0.5) - local p = { - parent = parent, text = text, textSize = size, textColor = color, - textWrap = "word", autoScaleText = false, - -- id keyed by the text: the engine's Persistable behavior snapshots - -- every scalar prop (text included) per id and stomps it back onto the - -- recreated element, freezing any label whose text changes between - -- frames (typed search text stuck on its first letter). A new text is - -- a new id, so it always renders fresh. - id = "lbl:" .. tostring(text), - } - for k, v in pairs(props or {}) do p[k] = v end - return mk(p) -end - --- kinds: primary (solid green), accent (green outline), danger (red outline), --- dangerArmed (solid red), neutral (translucent white), disabled (inert) -local function button(imp, parent, key, text, opts) - opts = opts or {} - local hot = imp._hot[key] - local kind = opts.kind or "neutral" - local bgc, fgc, brc - if kind == "primary" then - bgc = hot and C("green") or C("greenDark") - fgc, brc = C("greenInk"), C("green", 0.9) - elseif kind == "accent" then - bgc = C("green", hot and 0.30 or 0.12) - fgc, brc = hot and C("white") or C("green"), C("green", 0.7) - elseif kind == "danger" then - bgc = C("danger", hot and 0.30 or 0.12) - fgc, brc = hot and C("white") or C("danger"), C("danger", 0.7) - elseif kind == "dangerArmed" then - bgc, fgc, brc = C("danger"), C("white"), C("danger") - elseif kind == "disabled" then - bgc = C("disabled", 0.22) - fgc, brc = C("disabled"), C("disabled", 0.35) - else - bgc = C("white", hot and 0.22 or 0.10) - fgc, brc = C("white"), C("white", hot and 0.4 or 0.2) +-- A square icon control: the header's gear and quit, and the game panel's +-- manage button. Inverts to a solid white fill when hot, the same signal +-- every other control here uses, and rounds to the shared control radius. +-- `image` draws a texture; `drawFn(x, y, size, hot)` draws a hand-rolled +-- glyph (the quit X, which ships no asset). +local function iconButton(imp, key, x, y, size, image, action, drawFn) + Kit._audit("control", x, y, size, size, key) + local focused = Kit.focusable(key, x, y, size, size) + local hot = focused or Kit.hover(x, y, size, size) + Theme.fillRounded(x, y, size, size, hot and PAL.ink or PAL.surface, 1) + Theme.strokeRounded(x, y, size, size, PAL.line, + hot and Theme.A.focus or Theme.A.hairline, 1) + if image then + local iw, ih = image:getDimensions() + local pad = math.floor(size * 0.24) + local s = math.min((size - 2 * pad) / iw, (size - 2 * pad) / ih) + if hot then love.graphics.setColor(0, 0, 0, 1) + else love.graphics.setColor(1, 1, 1, 0.85) end + love.graphics.draw(image, Theme.snap(x + (size - iw * s) / 2), + Theme.snap(y + (size - ih * s) / 2), 0, s, s) + love.graphics.setColor(1, 1, 1, 1) + elseif drawFn then + drawFn(x, y, size, hot) end - -- Explicit measured size always: the layout engine measures an auto-sized - -- button as zero-height while its parent card is auto-sizing, which let - -- bottom action rows spill past their card's edge. - local size = math.floor((opts.size or 14) + 0.5) - local pad = opts.pad or { horizontal = 12, vertical = 6 } - local padX = pad.horizontal or 12 - local padY = pad.vertical or 6 - local w = opts.w - if not w and not opts.flex then - w = math.ceil(textWidth(size, text)) + 2 * padX + 2 + if action and (Kit.press(x, y, size, size) or Kit._activateId == key) then + queueAction(imp, key, action) end - local h = opts.h or (math.ceil(textHeight(size)) + 2 * padY + 2) - local p = { - parent = parent, - -- same Persistable-stomp guard as label(): a button whose caption - -- changes (Delete -> Sure?, Update ladders) must not keep frame one's - id = "btn:" .. key .. ":" .. tostring(text), - width = w, height = h, - flex = opts.flex, - backgroundColor = bgc, - border = 1, borderColor = brc, - cornerRadius = opts.r or 8, - text = text, textColor = fgc, textSize = size, - textAlign = "center-center", autoScaleText = false, - } - if kind ~= "disabled" and opts.action then - p.onEvent = handler(imp, key, opts.action, opts.keepArm) - elseif kind ~= "disabled" then - p.onEvent = handler(imp, key, nil) - end - return mk(p) end -local function card(parent, props) - local p = { - parent = parent, - width = "100%", - backgroundColor = C("card", 0.75), - border = 1, borderColor = C("border", 0.28), - cornerRadius = 14, - positioning = "flex", flexDirection = "vertical", - } - for k, v in pairs(props or {}) do p[k] = v end - return mk(p) +-- The cartridge colour for a game, matching its tab in the header. Play +-- wears it, so "which game is this button going to boot" is answered before +-- the label is read. Unknown versions fall back to the commit green. +local CART_COLOR = { + red = PAL.railRed, blue = PAL.railBlue, yellow = PAL.railGold, +} +local function cartColor(version) + return CART_COLOR[version] or PAL.green end -local function pill(parent, text, colName, size) - size = math.floor((size or 12) + 0.5) - local h = math.ceil(textHeight(size)) + 8 - return mk({ - parent = parent, text = text, textColor = C(colName), - id = "pill:" .. tostring(text), - textSize = size, textAlign = "center-center", autoScaleText = false, - width = math.ceil(textWidth(size, text)) + 20, height = h, - backgroundColor = C(colName, 0.12), - border = 1, borderColor = C(colName, 0.55), - cornerRadius = h / 2, - }) -end - -local function progressBar(parent, frac, colName, h) - h = h or 10 - frac = clamp(frac or 0, 0, 1) - local track = mk({ - parent = parent, width = "100%", height = h, - backgroundColor = C("bg", 0.9), cornerRadius = h / 2, - }) - mk({ - parent = track, width = (frac * 100) .. "%", - -- id keyed by the fraction, or Persistable pins the bar at frame one - id = "prog:" .. math.floor(frac * 1000), - height = "100%", backgroundColor = C(colName), cornerRadius = h / 2, - }) - return track -end - --- Flat toggle switch (read-only visual; the pressable area is the caller's). --- The knob is flex-aligned rather than absolutely positioned: absolute --- children resolve in screen space here, not against the parent. -local function toggleSwitch(parent, on, w, h, idKey) - w, h = w or 46, h or 24 - local track = mk({ - parent = parent, width = w, height = h, - -- state-keyed id: justifyContent is a persisted scalar, and a stale - -- snapshot would hold the knob on its frame-one side after a toggle - id = idKey and (idKey .. (on and ":on" or ":off")) or nil, - backgroundColor = on and C("greenDark") or C("disabled", 0.4), - border = 1, borderColor = on and C("green", 0.8) or C("disabled", 0.6), - cornerRadius = h / 2, - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", - justifyContent = on and "flex-end" or "flex-start", - padding = 3, - }) - mk({ - parent = track, - width = h - 6, height = h - 6, - backgroundColor = C("white"), cornerRadius = (h - 6) / 2, - }) - return track -end - --- a darkened copy of a palette color, for the embossed chips' bottom ledge -local function darken(name, f, a) - local c = PAL[name] - return rgba(c[1] * f, c[2] * f, c[3] * f, a or 1) -end - --- Hand-rolled text field: the importer owns the string (textinput / --- keypressed routing), this draws it. The field clips its content, keeps --- the TAIL of the text visible while typing (the interesting end), and --- shows a font-height caret on the importer's pulse clock. -local function dropFirstChar(t) - local i = 2 - while i <= #t do - local b = t:byte(i) - if b < 0x80 or b >= 0xC0 then break end - i = i + 1 - end - return t:sub(i) -end - -local function textField(imp, parent, key, rawText, placeholder, focused, action) - local size = 14 - local h = math.max(36, math.ceil(textHeight(size)) + 18) - local field = mk({ - parent = parent, width = "100%", height = h, - backgroundColor = C("bg", focused and 1 or 0.85), - border = focused and 2 or 1, - borderColor = focused and C("green", 0.85) - or C("border", imp._hot[key] and 0.7 or 0.4), - cornerRadius = 8, overflow = "hidden", - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 2, - padding = { horizontal = 12 }, - onEvent = action and handler(imp, key, action) or nil, - }) - local avail = (field._innerW or 200) - 6 - local shown = rawText or "" - local f = mfont(size) - while #shown > 1 and f:getWidth(shown) > avail do - shown = dropFirstChar(shown) - end - if shown ~= "" then - label(field, shown, size, C("white"), { textWrap = false }) - elseif placeholder and not focused then - label(field, placeholder, size, C("disabled"), { textWrap = false }) - end - if focused and (imp.pulse * 2 % 1) < 0.5 then - mk({ parent = field, width = 2, - height = math.ceil(textHeight(size)) + 2, - backgroundColor = C("green"), cornerRadius = 1 }) - end - return field -end - --- ------- status derivations shared with the old panels - -local function modStatusChip(status) - if status == "ok" then return Strings("Ready"), "green" end - if status == "conflict" then return Strings("Conflict"), "danger" end - return Strings("Incompatible"), "gold" +local function modStatusColor(status) + if status == "ok" then return Strings("Ready"), PAL.green end + if status == "conflict" then return Strings("Conflict"), PAL.red end + return Strings("Incompatible"), PAL.yellow end local function findActionFor(entry, installedVersion) @@ -567,7 +332,7 @@ local function findActionFor(entry, installedVersion) return Strings("Reinstall"), "Installed v" .. tostring(installedVersion) end -local DELETE_LABEL = function(armed) +local function DELETE_LABEL(armed) return armed and Strings("Sure?") or Strings("Delete") end @@ -576,380 +341,603 @@ local function deleteArmed(imp, kind, id, version) return a ~= nil and a.kind == kind and a.id == id and a.version == version end --- ------- header: strip, logo row (with the settings gear), tab bar +-- Page state lives on the importer keyed by list, so switching tabs and +-- coming back keeps your place -- the one thing scrolling did better. +local function page(imp, key) + return imp._pages[key] or 1 +end -local function buildHeader(imp, root, m) - -- tricolor strip - local strip = mk({ - parent = root, width = "100%", height = math.max(4, 5 * m.s), - positioning = "flex", flexDirection = "horizontal", - }) - for _, name in ipairs({ "red", "blue", "gold" }) do - mk({ parent = strip, flex = 1, height = "100%", - backgroundColor = C(name) }) +local function setPage(imp, key, v) + imp._pages[key] = v +end + +-- A hand-drawn X, for the same reason drawCheck exists below: the UI font has +-- no guaranteed glyph, and the launcher ships no icon asset for it. +local function drawCross(x, y, size, color) + love.graphics.push("all") + love.graphics.setColor(color) + love.graphics.setLineWidth(math.max(2, size * 0.16)) + love.graphics.setLineJoin("bevel") + love.graphics.line(x, y, x + size, y + size) + love.graphics.line(x + size, y, x, y + size) + love.graphics.pop() +end + +-- ------------------------------------------------------------- header +-- Rail, logo row (settings and quit on the right), tab bar. +-- Returns the y at which content may start. Its vertical arithmetic is +-- mirrored by headerHeight() at the bottom of this file (the short-window +-- scroll decision needs the height before anything draws) -- keep in sync. +local function buildHeader(imp, m) + local y = m.top + Theme.versionRail(m.x, y, m.w, m.railH) + y = y + m.railH + + -- logo row + local rowH = m.logoH + math.floor(12 * m.s) + local gear = m.chip + + -- The wordmark is centred in the row MINUS the right cluster, mirrored on + -- the left so it still reads as centred in the window. Centring it in the + -- FULL row (what this used to do) let a phone-width wordmark run straight + -- under the gear and the quit X -- "the settings is covering the logo". + -- Reserving the space on both sides costs a little width and cannot + -- overlap at any window size. + local clusterW = 2 * gear + math.floor(6 * m.s) + m.pad + local boxX = m.x + clusterW + local boxW = math.max(0, m.w - 2 * clusterW) + if imp.logo and boxW > 0 then + local lw, lh = imp.logo:getDimensions() + local maxW = math.min(320 * m.s, boxW) + local scale = math.min(maxW / lw, m.logoH / lh) + local dw, dh = lw * scale, lh * scale + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(imp.logo, Theme.snap(boxX + (boxW - dw) / 2), + Theme.snap(y + (rowH - dh) / 2), 0, scale, scale) end - -- logo row: spacer / centered logo / settings gear, so the logo stays - -- centered while the gear holds the app's top-right corner - local gearSize = math.max(34, 40 * m.s) - local row = mk({ - parent = root, width = "100%", height = m.logoH + 12 * m.s, - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 8 * m.s, - padding = { horizontal = m.pad }, - }) - mk({ parent = row, width = gearSize, height = 1 }) - local mid = mk({ parent = row, flex = 1, - positioning = "flex", justifyContent = "center", alignItems = "center" }) - mk({ - parent = mid, image = imp.logo, objectFit = "contain", - width = math.min(320 * m.s, m.w * 0.6), height = m.logoH, - }) + local rx = m.x + m.w - m.pad + local by = y + (rowH - gear) / 2 + + -- Switch-only: show the running app version opposite the settings gear so + -- players can confirm which build is on the microSD (OTA / zip updates). + if imp.isNX then + local label = "v" .. tostring(Version.engine or "?") + local tw = Kit.textWidth("small", label) + local padX = math.floor(12 * m.s) + local chipW = math.max(tw + 2 * padX, gear) + local lx = m.x + m.pad + Theme.fill(lx, by, chipW, gear, PAL.bg, 1) + Theme.stroke(lx, by, chipW, gear, PAL.yellow, Theme.A.hover, 1) + local th = Kit.textHeight("small") + Kit.text("small", label, lx + math.floor((chipW - tw) / 2), + by + math.floor((gear - th) / 2), PAL.yellow) + end + + -- The right cluster is laid out right to left -- Quit outermost, the gear + -- inboard of it -- but the two are REGISTERED gear first, because the first + -- focusable of the first frame adopts the keyboard ring and that must not be + -- the button that exits the app. + local quitX = rx - gear + rx = quitX - math.floor(6 * m.s) + + -- Settings gear. It now also owns the CONTROL settings (touch overlay + -- editor, reset rebinds), which used to be buttons stacked in the game + -- panel -- see LauncherSettings.coreRows. imp._gearIcon = imp._gearIcon or love.graphics.newImage("assets/launcher/gear.png") - mk({ - parent = row, - width = gearSize, height = gearSize, - backgroundColor = C("white", imp._hot.gear and 0.20 or 0.07), - border = 1, borderColor = C("border", imp._hot.gear and 0.85 or 0.4), - cornerRadius = 10, - image = imp._gearIcon, objectFit = "contain", - imageTint = imp._hot.gear and C("white") or C("detail"), - padding = math.floor(gearSize * 0.18), - onEvent = handler(imp, "gear", function() - imp:_openSettings() - end), - }) + rx = rx - gear + iconButton(imp, "gear", rx, by, gear, imp._gearIcon, + function() imp:_openSettings() end) + + -- Quit, top-right corner. + iconButton(imp, "quit", quitX, by, gear, nil, + function() imp:_quitApp() end, + function(x, y, size, hot) + local pad = math.floor(size * 0.32) + drawCross(x + pad, y + pad, size - 2 * pad, + hot and { 0, 0, 0, 1 } or { 1, 1, 1, 0.85 }) + end) + + -- The self-update control lives in the FOOTER next to the BCG mark (small, + -- out of the wordmark's way -- it used to overlap the logo on a phone). It + -- still GLOWS through Kit.button when there is something to act on. + y = y + rowH -- tab bar - local bar = mk({ - parent = root, width = "100%", - positioning = "flex", flexDirection = "horizontal", flexWrap = "wrap", - alignItems = "center", gap = 8 * m.s, - padding = { horizontal = m.pad, vertical = 8 * m.s }, - }) imp._modsIcon = imp._modsIcon or love.graphics.newImage("assets/launcher/mods.png") imp._findIcon = imp._findIcon or love.graphics.newImage("assets/launcher/find.png") + -- The three game tabs keep their cartridge colours -- that is the one piece + -- of brand identity in the launcher, and "the red one" is how people + -- actually refer to these. The colour rides the outline and the glyph at + -- rest and becomes the fill when active, the same rule the buttons follow. local tabs = { - { id = "red", letter = "R", col = "red", ink = "white", labelText = Strings("RED") }, - { id = "blue", letter = "B", col = "blue", ink = "white", labelText = Strings("BLUE") }, - { id = "yellow", letter = "Y", col = "gold", ink = "bg", labelText = Strings("YELLOW") }, - { id = "mods", icon = imp._modsIcon, col = "chipModTop", ink = "white", labelText = Strings("MODS") }, - { id = "find", icon = imp._findIcon, col = "chipModTop", ink = "white", labelText = Strings("FIND MODS") }, + { id = "red", letter = "R", label = Strings("RED"), color = PAL.railRed }, + { id = "blue", letter = "B", label = Strings("BLUE"), color = PAL.railBlue }, + { id = "yellow", letter = "Y", label = Strings("YELLOW"), color = PAL.railGold }, + { id = "mods", icon = imp._modsIcon, label = Strings("MODS") }, + { id = "find", icon = imp._findIcon, label = Strings("FIND MODS") }, } + local tabH = m.chip + local tx = m.x + m.pad + local ty = y + math.floor(6 * m.s) for _, t in ipairs(tabs) do - if t.id == "mods" then - mk({ parent = bar, width = 1, height = m.chip * 0.8, - backgroundColor = C("border", 0.3) }) - end local active = imp.tab == t.id local key = "tab-" .. t.id - local labelSize = 13 * m.s + 4 - local hot = imp._hot[key] - -- embossed chip: a darker base ledge under the face gives the tab a - -- raised look; hover lifts the face brightness and rims it white - local ledge = math.max(2, math.floor(3 * m.s)) - local baseEl = mk({ - parent = bar, width = m.chip, height = m.chip + ledge, - backgroundColor = darken(t.col, 0.35, active and 1 or 0.8), - cornerRadius = 10, - onEvent = handler(imp, key, function() - imp:_switchTab(t.id) - end), - }) - local face = { - parent = baseEl, width = "100%", height = m.chip, - backgroundColor = C(t.col, active and 1 or (hot and 0.75 or 0.42)), - border = (active or hot) and 1 or false, - borderColor = C("white", active and 0.6 or 0.35), - cornerRadius = 10, - } + local labelW = Kit.textWidth("tab", t.label) + -- The active tab spells its name out; inactive tabs are the glyph alone, + -- so five tabs fit a phone width without wrapping. + local w = active and (tabH + math.floor(8 * m.s) + labelW + math.floor(12 * m.s)) + or tabH + Kit._audit("control", tx, ty, w, tabH, key) + local focused = Kit.focusable(key, tx, ty, w, tabH) + local hot = focused or Kit.hover(tx, ty, w, tabH) + local invert = active or hot + local tint = t.color or PAL.ink + Theme.fillRounded(tx, ty, w, tabH, invert and tint or PAL.surface, 1) + if not invert then + Theme.strokeRounded(tx, ty, w, tabH, tint, + t.color and Theme.A.hover or Theme.A.hairline, 1) + end + -- Ink on a filled tab must contrast with THAT fill: black on the light + -- red/blue/gold cartridge colours, which are all high-luminance. + local ink = invert and PAL.inverse or (t.color or PAL.text) if t.icon then - face.image = t.icon - face.objectFit = "contain" - face.padding = math.floor(m.chip * 0.2) - face.imageTint = C("white", active and 1 or 0.85) + local iw, ih = t.icon:getDimensions() + local pad = math.floor(tabH * 0.24) + local s = math.min((tabH - 2 * pad) / iw, (tabH - 2 * pad) / ih) + if invert then love.graphics.setColor(0, 0, 0, 1) + else love.graphics.setColor(1, 1, 1, 0.9) end + love.graphics.draw(t.icon, Theme.snap(tx + (tabH - iw * s) / 2), + Theme.snap(ty + (tabH - ih * s) / 2), 0, s, s) + love.graphics.setColor(1, 1, 1, 1) else - face.text = t.letter - -- the gold chip's dark ink only reads on the full-strength active - -- fill; dimmed inactive chips all take light ink - face.textColor = (active and t.ink == "bg") and C("bg") or C("white") - face.textSize = math.floor(m.chip * 0.45) - face.textAlign = "center-center" - face.autoScaleText = false + Kit.textCenter("tab", t.letter, tx, + ty + (tabH - Kit.textHeight("tab")) / 2, tabH, ink) end - mk(face) if active then - -- explicit width: a percentage inside this auto-sized wrap would not - -- resolve (LayoutEngine LAY_004), so the underline takes the label's - -- measured pixel width - local lw = math.ceil(textWidth(labelSize, t.labelText)) + 2 - local wrap = mk({ parent = bar, width = lw, - positioning = "flex", flexDirection = "vertical", gap = 3 * m.s }) - label(wrap, t.labelText, labelSize, C("white"), { textWrap = false }) - mk({ parent = wrap, width = lw, height = 3, - backgroundColor = C(t.col) }) + Kit.text("tab", t.label, tx + tabH + math.floor(4 * m.s), + ty + (tabH - Kit.textHeight("tab")) / 2, ink) end + if Kit.press(tx, ty, w, tabH) or Kit._activateId == key then + queueAction(imp, key, function() imp:_switchTab(t.id) end) + end + tx = tx + w + math.floor(6 * m.s) end - -- "N of 3 ready" right-aligned filler - local ready = 0 - for _, v in ipairs(GameVersion.ORDER) do - if imp.ready[v] then ready = ready + 1 end - end - mk({ parent = bar, flex = 1 }) - label(bar, Strings("%d of 3 ready", ready), 12 * m.s + 2, C("gray"), - { textWrap = false }) - mk({ parent = root, width = "100%", height = 1, - backgroundColor = C("border", 0.22) }) + + y = ty + tabH + math.floor(8 * m.s) + Theme.fill(m.x, y, m.w, 1, PAL.line, Theme.A.hairline) + return y + math.floor(10 * m.s) end --- ------- game panel - -local function buildRomCard(imp, parent, m, version, info, ready, locked) - local dropHint = imp.isNX and Strings("Copy the .gb/.gbc via MTP into imports/.") - or (imp.android and Strings("Copy the .gb/.gbc via USB.") - or Strings("Or drop the .gb/.gbc file here.")) - local importLabel = imp.isNX and Strings("Scan again") or Strings("Import ROM") - local romState, romDetail, romBtnLabel, romBtnEnabled, romProgress - if locked then - romState, romDetail = Strings("Not supported yet"), - Strings("Support for this game is on the way.") - romBtnLabel, romBtnEnabled = Strings("Import unavailable"), false - else - local importing = imp.importing == version - local erroring = imp.workState == "error" and imp.errorVersion == version - local notice = imp.notice and imp.notice.version == version and imp.notice - if importing and (imp.workState == "working" or imp.workState == "complete") then - romState = imp.status or Strings("Importing") - romDetail = imp.detail or "" - romProgress = imp.progress or 0 - elseif ready then - romState = imp.romName[version] or Strings("ROM imported") - romDetail = Strings("Verified.") - romBtnLabel, romBtnEnabled = Strings("Re-import ROM"), true - elseif erroring then - romState = Strings("Import failed") - romDetail = imp.detail or Strings("That ROM could not be imported.") - romBtnLabel, romBtnEnabled = importLabel, true - elseif notice then - romState = Strings("No ROM imported") - romDetail = ((notice.status or "") .. " " .. (notice.detail or "")) - :gsub("^%s+", ""):gsub("%s+$", "") - romBtnLabel, romBtnEnabled = importLabel, true - elseif imp.returning[version] then - romState = Strings("Update required") - romDetail = Strings("This build needs a few more things from your ") - .. info.label .. Strings(" ROM. Re-import to continue.") - romBtnLabel, romBtnEnabled = Strings("Re-import ROM"), true - else - romState = Strings("No ROM imported") - romDetail = Strings("The ROM is verified before any files are created. ") - .. dropHint - romBtnLabel, romBtnEnabled = importLabel, true - end +-- The state of the self-updater, as a top-right control. +-- Returns status, label, action, glow. +function LauncherView._updateControl(imp) + if not imp.Check then return nil end + local ok, st = pcall(imp.Check.state) + st = (ok and type(st) == "table") and st or nil + local status = st and st.status or "idle" + if status == "checking" then + return status, Strings("Checking..."), nil, false + elseif status == "downloading" then + local pct = st.progress and math.floor(st.progress * 100) or 0 + return status, Strings("Updating %d%%", pct), nil, false + elseif status == "available" then + return status, st.latest and (Strings("Update v") .. st.latest) + or Strings("Update"), function() pcall(imp.Check.download) end, true + elseif status == "ready" then + return status, Strings("Restart to update"), + function() require("src.core.HostShell").restart() end, true + elseif status == "needs_full" then + return status, Strings("Open releases"), + function() love.system.openURL(imp.Check.releaseUrl()) end, true end + -- idle / uptodate / error: offer a manual check, with no glow. + return status, Strings("Check for updates"), + function() pcall(imp.Check.start) end, false +end - local accent = version == "yellow" and "gold" or version - local c = card(parent, { padding = m.cardPad, gap = 8 * m.s }) - label(c, "ROM", 12 * m.s + 1, C("gray")) - label(c, romState, 15 * m.s + 2, C("white")) - label(c, romDetail, 12 * m.s + 2, C("detail")) - if romProgress ~= nil then - progressBar(c, romProgress, accent, math.max(8, 10 * m.s)) - else - button(imp, c, "rom-" .. version, romBtnLabel, { - w = "100%", h = m.btnH, size = 14 * m.s, - kind = romBtnEnabled and "neutral" or "disabled", - action = romBtnEnabled and function() - if imp.ready[version] then imp:reimport(version) - else imp:choose(version) end - end or nil, +-- ------------------------------------------------------------ game panel + +-- What this version's ROM situation is, as a plain table. The panel and the +-- per-game manage modal both read it, so the two can never disagree about +-- whether a ROM is present or what the import button should say. +-- state a headline, or nil when there is nothing to report (ready) +-- detail the paragraph under it +-- label the import button's caption +-- enabled whether that button may be pressed +-- progress 0-1 while an import for THIS version is running +local function romModel(imp, version, info, ready, locked) + local importLabel = imp.isNX and Strings("Scan again") or Strings("Import ROM") + if locked then + return { state = Strings("Not supported yet"), + detail = Strings("Support for this game is on the way."), + label = Strings("Import unavailable"), enabled = false } + end + local dropHint = imp.isNX and Strings("Copy the .gb/.gbc via MTP into imports/.") + or (imp.baseRomDiscovery and Strings("Or copy the .gb/.gbc into baseroms/.") + or (imp.android and Strings("Copy the .gb/.gbc via USB.") + or Strings("Or drop the .gb/.gbc file here."))) + local importing = imp.importing == version + local erroring = imp.workState == "error" and imp.errorVersion == version + local notice = imp.notice and imp.notice.version == version and imp.notice + local baseRom = imp.baseRoms and imp.baseRoms[version] + local scanning = imp.baseRomDiscovery and imp.baseRomScan + and imp.baseRomScan.state ~= "done" + if importing and (imp.workState == "working" or imp.workState == "complete") then + return { state = imp.status or Strings("Importing"), + detail = imp.detail or "", progress = imp.progress or 0 } + elseif erroring then + -- An import that FAILED is reported even on a ready game (a re-import + -- that could not read the new file): the failure is the only reason the + -- library still holds the old cache, and it must not be silent (the + -- "Import failed with no explanation" report). + return { state = Strings("Import failed"), + detail = imp.detail or Strings("That ROM could not be imported."), + label = importLabel, enabled = true } + elseif ready then + return { label = Strings("Re-import ROM"), enabled = true } + elseif notice then + return { state = Strings("No ROM imported"), + detail = ((notice.status or "") .. " " .. (notice.detail or "")) + :gsub("^%s+", ""):gsub("%s+$", ""), + label = importLabel, enabled = true } + elseif baseRom then + return { state = Strings("Compatible ROM found"), + detail = Strings("Found in baseroms/: %s", baseRom.name), + label = Strings("Import detected ROM"), enabled = true } + elseif scanning then + return { state = Strings("Checking baseroms..."), + detail = Strings("Looking for compatible Red, Blue, and Yellow ROMs."), + label = Strings("Import ROM"), enabled = false } + elseif imp.returning[version] then + return { state = Strings("Update required"), + detail = Strings("This build needs a few more things from your ") + .. info.label .. Strings(" ROM. Re-import to continue."), + label = Strings("Re-import ROM"), enabled = true } + end + return { state = Strings("No ROM imported"), + detail = Strings("The ROM is verified before any files are created. ") + .. dropHint, + label = importLabel, enabled = true } +end + +-- The import action behind whichever button carries it. +local function romAction(imp, version, mdl) + if not mdl.enabled then return nil end + return function() + if imp.ready[version] then imp:reimport(version) + else imp:choose(version) end + end +end + +-- The ROM card: the state headline, its paragraph, and the Import button. +-- It exists ONLY while there is something to report -- a game with a verified +-- ROM shows Play, not a card of file management (that moved behind the manage +-- button next to Play, and the save file controls moved into the slot card). +-- Returns the height it consumed, 0 when it drew nothing. +local function buildRomCard(imp, x, y, w, m, version, mdl, maxH) + if not (mdl.state or mdl.progress) then return 0 end + local pad = math.floor(14 * m.s) + local iw = w - 2 * pad + local lineH = Kit.textHeight("small") + local hasButton = mdl.progress == nil and mdl.label ~= nil + -- Pads and the button are fixed furniture that always fits; the detail + -- paragraph is the elastic part and gets trimmed to whatever lines the + -- budget leaves. Without that trim the card overflowed and got clipped + -- mid-button, which is the failure a no-scroll layout must design out. + local fixedH = pad + Kit.textHeight("button") + math.floor(4 * m.s) + + math.floor(10 * m.s) + + ((hasButton or mdl.progress) and (m.btnH + math.floor(2 * m.s)) or 0) + + pad + local detailLines = 3 + if maxH then + detailLines = math.max(0, + math.min(detailLines, math.floor((maxH - fixedH) / lineH))) + end + local detailH = Kit.wrapHeight("small", mdl.detail or "", iw, detailLines) + local h = fixedH + detailH + + Kit.card(x, y, w, h) + local cy = y + pad + Kit.text("button", Kit.ellipsize("button", mdl.state or "", iw), x + pad, cy, + PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(4 * m.s) + cy = cy + Kit.textWrapped("small", mdl.detail or "", x + pad, cy, iw, + PAL.detail, detailLines) + cy = cy + math.floor(10 * m.s) + if mdl.progress ~= nil then + Kit.progress(x + pad, cy + (m.btnH - math.floor(10 * m.s)) / 2, iw, + math.floor(10 * m.s), mdl.progress) + elseif hasButton then + btn(imp, x + pad, cy, iw, m.btnH, "rom-" .. version, mdl.label, { + kind = "accent", enabled = mdl.enabled, + action = romAction(imp, version, mdl), }) end + return h end -local function buildSaveFilesCard(imp, parent, m, version, ready, locked) - local sfImportEnabled, sfExportEnabled = false, false - if not locked then - imp:_ensureSlots(version) - sfImportEnabled = ready and true or false - local activeId = imp.activeSlot[version] - for _, sl in ipairs(imp.slots[version] or {}) do - if sl.id == activeId and sl.exists then sfExportEnabled = true break end +-- Save slots, PAGINATED. This was a fixed-height scroller with momentum; it +-- is now a page of rows sized to whatever height the column has left, which +-- is why 40 slots cost exactly what 4 do. +-- Lay a row's action chips out right-aligned, wrapping onto further lines +-- when they cannot all fit across the row. A narrow window (the 150%-scaled +-- desktop and the portrait phone in the reports) could not fit four chips on +-- one line, and a fixed right-to-left cluster simply walked them off the left +-- edge and under the row's own text. Returns an array of lines, each an +-- array of chips, so the caller can size the row BEFORE drawing it. +local function chipLines(chips, inner, gap) + local lines, line, used = {}, {}, 0 + for _, c in ipairs(chips) do + if #line > 0 and used + gap + c.w > inner then + lines[#lines + 1] = line + line, used = {}, 0 end + used = used + ((#line > 0) and gap or 0) + c.w + line[#line + 1] = c end - local sfNotice = (not locked) and imp.saveNotice[version] or nil - local hintText, hintCol - if sfNotice then - hintText, hintCol = sfNotice.text, (sfNotice.ok and "green" or "danger") - elseif locked then - hintText, hintCol = Strings("Not available yet."), "warn" - else - hintText = imp:_savesDefaultHint(version) - hintCol = "warn" - end - local savImportLabel = imp.isNX and Strings("Scan again") or Strings("Import save") - - local c = card(parent, { padding = m.cardPad, gap = 8 * m.s }) - label(c, "SAVE FILES", 12 * m.s + 1, C("gray")) - local row = mk({ parent = c, width = "100%", - positioning = "flex", flexDirection = "horizontal", gap = 10 * m.s }) - -- explicit halves rather than flex growth, which mis-distributed inside - -- an auto-height card - local halfW = math.floor((m.colW - 32 - 10 * m.s) / 2) - button(imp, row, "sav-import-" .. version, savImportLabel, { - w = halfW, h = m.btnH, size = 13 * m.s + 1, - kind = sfImportEnabled and "neutral" or "disabled", - action = sfImportEnabled and function() - imp:chooseSaveImport(version) - end or nil, - }) - button(imp, row, "sav-export-" .. version, Strings("Export save"), { - w = halfW, h = m.btnH, size = 13 * m.s + 1, - kind = sfExportEnabled and "neutral" or "disabled", - action = sfExportEnabled and function() - imp:exportSave(version) - end or nil, - }) - label(c, hintText, 12 * m.s + 1, C(hintCol)) - if sfNotice and sfNotice.dir then - local dir = sfNotice.dir - label(c, Strings("Open folder"), 12 * m.s + 1, - C("link", imp._hot["sav-folder-" .. version] and 1 or 0.85), { - onEvent = handler(imp, "sav-folder-" .. version, function() - love.system.openURL(imp:fileUrl(dir)) - end), - }) - end + if #line > 0 then lines[#lines + 1] = line end + return lines end -local function buildSlotCard(imp, parent, m, version) +-- The width a chip needs for its caption, at the row-chip font. +local function chipWidth(label, m) + return Kit.textWidth("small", label) + math.floor(20 * m.s) +end + +local function buildSlotCard(imp, x, y, w, availH, m, version, ready) imp:_ensureSlots(version) local slots = imp.slots[version] or {} local active = imp.activeSlot[version] local n = #slots + local pad = math.floor(14 * m.s) + local iw = w - 2 * pad + local gap = math.floor(8 * m.s) - local c = card(parent, { padding = m.cardPad, gap = 10 * m.s }) - local head = mk({ parent = c, width = "100%", - positioning = "flex", flexDirection = "horizontal", - justifyContent = "space-between", alignItems = "center" }) - label(head, "SAVE SLOT", 12 * m.s + 1, C("gray"), { textWrap = false }) - label(head, n == 1 and Strings("1 slot") or Strings("%d slots", n), - 12 * m.s + 1, C("gray"), { textWrap = false }) + -- A slot row: name + LOADED tag, meta line, then the action chips. The + -- chip set is measured against the WIDEST possible row (every chip present) + -- so every row on the page is the same height even though an empty slot + -- offers fewer -- pagination derives its row count from a uniform height. + local chipH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local rowInner = iw - math.floor(20 * m.s) + local maxChips = { + { w = chipWidth(Strings("Export"), m) }, + { w = chipWidth(Strings("Rename"), m) }, + { w = chipWidth(Strings("Edit"), m) }, + -- Delete's width is pinned to the WIDER of its two captions so arming to + -- "Sure?" never reflows the row under the pointer (#433). + { w = math.max(chipWidth(DELETE_LABEL(false), m), + chipWidth(DELETE_LABEL(true), m)) }, + } + local chipGap = math.floor(6 * m.s) + local maxChipsW = 0 + for i, c in ipairs(maxChips) do + maxChipsW = maxChipsW + c.w + ((i > 1) and chipGap or 0) + end + -- BESIDE the text when the row is wide enough to hold both and still leave + -- the name and meta lines a readable share, UNDER it when it is not. A + -- desktop row costs one text block instead of a text block plus a button + -- strip, which is what lets a two-column window show several slots per page + -- instead of one; a phone row keeps the taller shape rather than squeezing + -- four chips and a name into one line. + local textH = Kit.textHeight("button") + math.floor(4 * m.s) + + Kit.textHeight("small") + -- The threshold is what the TEXT needs, not a fraction of the row: a slot + -- name plus its badges/time/dex line wants about this much before it starts + -- ellipsizing anything a player came to read. + local textMinW = math.floor(150 * m.s) + local sideBySide = + (rowInner - maxChipsW - math.floor(12 * m.s)) >= textMinW + local chipRowCount = #chipLines(maxChips, rowInner, chipGap) + local chipBlockH = chipRowCount * chipH + + math.max(0, chipRowCount - 1) * chipGap + local rowH + if sideBySide then + rowH = math.floor(8 * m.s) + math.max(textH, chipH) + math.floor(8 * m.s) + else + rowH = math.floor(8 * m.s) + textH + math.floor(8 * m.s) + chipBlockH + + math.floor(8 * m.s) + end + + -- The header carries "Import save": a .sav import CREATES a slot, so it + -- belongs to the slot list rather than to the ROM card it used to sit in. + local headH = math.max(Kit.textHeight("caption"), m.btnH) + math.floor(8 * m.s) + local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local newBtnH = m.btnH + local sfNotice = imp.saveNotice[version] + local hintText, hintCol + if sfNotice then + hintText, hintCol = sfNotice.text, (sfNotice.ok and PAL.green or PAL.red) + else + hintText, hintCol = nil, PAL.muted + end + local hintH = hintText + and (Kit.wrapHeight("small", hintText, iw, 2) + math.floor(8 * m.s)) or 0 + local folderRow = sfNotice and sfNotice.dir + if folderRow then hintH = hintH + Kit.textHeight("small") + math.floor(4 * m.s) end + + -- Rows get whatever is left after the card's fixed furniture. + local listH = availH + - (pad * 2 + headH + hintH + pagerH + gap + newBtnH + gap) + local perPage = Kit.rowsThatFit(listH, rowH, gap, 1, 12) + local pageKey = "slots-" .. version + local first, last, cur, pages = Kit.pageBounds(page(imp, pageKey), n, perPage) + setPage(imp, pageKey, cur) + + local shown = math.max(0, last - first + 1) + local usedListH = (n == 0) and math.floor(70 * m.s) + or (shown * rowH + math.max(0, shown - 1) * gap) + local h = pad + headH + usedListH + gap + hintH + + (pages > 1 and (pagerH + gap) or 0) + newBtnH + pad + + Kit.card(x, y, w, h) + local cy = y + pad + local capY = cy + math.floor((m.btnH - Kit.textHeight("caption")) / 2) + Kit.caption(x + pad, capY, Strings("SAVE SLOT")) + local savImportLabel = imp.isNX and Strings("Scan again") + or Strings("Import save") + local impW = chipWidth(savImportLabel, m) + math.floor(8 * m.s) + btn(imp, x + w - pad - impW, cy, impW, m.btnH, "sav-import-" .. version, + savImportLabel, { + kind = "accent", font = "small", enabled = ready and true or false, + action = ready and function() imp:chooseSaveImport(version) end or nil, + }) + local countW = (x + w - pad - impW - math.floor(8 * m.s)) + - (x + pad + Kit.captionWidth(Strings("SAVE SLOT")) + math.floor(8 * m.s)) + if countW > 0 then + Kit.textRight("small", + n == 1 and Strings("1 slot") or Strings("%d slots", n), + x + w - pad - impW - math.floor(8 * m.s), capY, PAL.muted) + end + cy = cy + headH if n == 0 then - local box = mk({ - parent = c, width = "100%", height = 90 * m.s, - border = 1, borderColor = C("border", 0.45), cornerRadius = 12, - positioning = "flex", justifyContent = "center", alignItems = "center", - padding = { horizontal = 12 }, - }) - label(box, Strings("No saves yet - start a new game or import one."), - 12 * m.s + 2, C("warn"), { textAlign = "center" }) + Kit.emptyBox(x + pad, cy, iw, usedListH, + Strings("No saves yet - start a new game or import one.")) + cy = cy + usedListH + gap + else + -- Wheel over the list turns pages; the page index is bounded, so there is + -- no scroll offset to interpolate and nothing to clamp against content. + setPage(imp, pageKey, + Kit.wheelPage(x + pad, cy, iw, usedListH, cur, n, perPage)) + for i = first, last do + local slot = slots[i] + local selected = slot.id == active + local rowKey = "slot-" .. version .. "-" .. slot.id + local ry = cy + (i - first) * (rowH + gap) + local ink = rowHit(imp, x + pad, ry, iw, rowH, selected, rowKey, + function() imp:_selectSlot(version, slot.id) end) + + local px = x + pad + math.floor(10 * m.s) + local inner = iw - math.floor(20 * m.s) + -- Beside the chips, the text block only owns what they leave; under + -- them it owns the row. Either way the width is fixed before anything + -- prints, so the name ellipsizes into its own space rather than into + -- a button. + local textW = sideBySide + and (inner - maxChipsW - math.floor(12 * m.s)) or inner + local ly = ry + math.floor(8 * m.s) + + (sideBySide and math.floor((math.max(textH, chipH) - textH) / 2) or 0) + local name = slot.label or slot.name or Strings("NEW GAME") + local tagW = 0 + if selected then + tagW = Kit.textWidth("micro", Strings("LOADED")) + math.floor(16 * m.s) + Kit.tag(px + textW - tagW, ly, tagW, Kit.textHeight("button"), + Strings("LOADED"), PAL.inverse) + tagW = tagW + math.floor(8 * m.s) + end + Kit.text("button", Kit.ellipsize("button", name, textW - tagW), px, ly, ink) + ly = ly + Kit.textHeight("button") + math.floor(4 * m.s) + local metaTxt + if slot.exists and slot.meta then + metaTxt = Strings("%d badges - %s - %d caught", slot.meta.badges or 0, + slot.meta.timeText or "0:00", slot.meta.dexCount or 0) + else + metaTxt = Strings("empty slot") + end + Kit.text("small", Kit.ellipsize("small", metaTxt, textW), px, ly, + selected and PAL.inverse or PAL.muted) + -- Where the chip block starts: centred on the row beside the text, or + -- on its own line under it. + ly = sideBySide and (ry + (rowH - chipBlockH) / 2) + or (ly + Kit.textHeight("small") + math.floor(8 * m.s)) + + -- Action chips, right-aligned and wrapped onto as many lines as the row + -- width needs. Export lives HERE rather than beside the ROM buttons: + -- an export is a property of a slot, so the control belongs on the slot + -- it exports (it selects the row first, since the exporter writes + -- whichever slot is active). + local armed = deleteArmed(imp, "slot", slot.id, version) + local chips = {} + if slot.exists then + chips[#chips + 1] = { label = Strings("Export"), kind = "accent", + key = rowKey .. "-export", + action = function() + imp:_selectSlot(version, slot.id) + imp:exportSave(version) + end } + end + if not imp.android then + chips[#chips + 1] = { label = Strings("Rename"), kind = "accent", + key = rowKey .. "-rename", + action = function() imp:_beginRename(version, slot.id) end } + end + if imp.onEditSave and slot.exists then + chips[#chips + 1] = { label = Strings("Edit"), kind = "accent", + key = rowKey .. "-edit", + action = function() imp.onEditSave(version, slot.id) end } + end + chips[#chips + 1] = { label = DELETE_LABEL(armed), kind = "danger", + keepArm = true, key = rowKey .. "-del", + -- Pinned width, so arming to "Sure?" cannot reflow the cluster. + w = math.max(chipWidth(DELETE_LABEL(false), m), + chipWidth(DELETE_LABEL(true), m)), + action = function() + imp:pressDelete("slot", slot.id, version, function() + imp:_deleteSlot(version, slot.id) + end) + end } + for _, c in ipairs(chips) do c.w = c.w or chipWidth(c.label, m) end + for li, line in ipairs(chipLines(chips, inner, chipGap)) do + local total = 0 + for i, c in ipairs(line) do + total = total + c.w + ((i > 1) and chipGap or 0) + end + local cx = px + inner - total + local cly = ly + (li - 1) * (chipH + chipGap) + for _, c in ipairs(line) do + btn(imp, cx, cly, c.w, chipH, c.key, c.label, { + kind = c.kind, font = "small", keepArm = c.keepArm, + action = c.action, + }) + cx = cx + c.w + chipGap + end + end + end + cy = cy + usedListH + gap end - -- Taller stacked rows: name + LOADED line, meta line, then the action - -- buttons on their own full-width line, so no control can ever clip - -- against the card's right edge at any scale. Every line height is - -- measured, and the row height is their explicit sum: the engine's - -- auto-height came up short on some displays and let the button row fall - -- out of the card. - local chipSize = math.floor(11 * m.s + 1.5) - local nameSize = math.floor(14 * m.s + 2.5) - local metaSize = math.floor(11 * m.s + 2.5) - local pillSize = math.floor(10 * m.s + 1.5) - local btnH = math.ceil(textHeight(chipSize)) + 14 - local headH = math.max(math.ceil(textHeight(nameSize)), - math.ceil(textHeight(pillSize)) + 8) - local metaH = math.ceil(textHeight(metaSize)) - local rowH = 10 + headH + 5 + metaH + 8 + btnH + 10 - -- Fixed-height scroller so 40 slots actually overflow (page-level flex - -- scroll alone was growing with content, leaving nothing to drag). - local listParent = c - if n > 0 then - local listH = math.floor(clamp(m.h * (m.twoCol and 0.58 or 0.42), 200, 720)) - listParent = mk({ - parent = c, id = "slots-" .. version, width = "100%", height = listH, - overflowY = "scroll", hideScrollbars = true, - positioning = "flex", flexDirection = "vertical", gap = 10 * m.s, - padding = { right = 4 }, - }) - end - for _, slot in ipairs(slots) do - local selected = slot.id == active - local rowKey = "slot-" .. version .. "-" .. slot.id - local row = mk({ - parent = listParent, width = "100%", height = rowH, - backgroundColor = C("rowBg", imp._hot[rowKey] and 0.85 or 0.6), - border = 1, - borderColor = selected and C("green", 0.9) or C("border", 0.25), - cornerRadius = 12, - positioning = "flex", flexDirection = "vertical", gap = 5, - padding = { horizontal = 12, vertical = 10 }, - onEvent = handler(imp, rowKey, function() - imp:_selectSlot(version, slot.id) - end), - }) - local rowInner = row._innerW - local headRow = mk({ parent = row, width = rowInner, height = headH, - positioning = "flex", flexDirection = "horizontal", - justifyContent = "space-between", alignItems = "center" }) - local name = slot.label or slot.name or Strings("NEW GAME") - local pillW = selected - and (math.ceil(textWidth(pillSize, Strings("LOADED"))) + 20) or 0 - label(headRow, name, nameSize, C("white"), - { width = rowInner - pillW - 8, - textWrap = false, textOverflow = "ellipsis" }) - if selected then pill(headRow, Strings("LOADED"), "green", pillSize) end - local metaTxt - if slot.exists and slot.meta then - metaTxt = Strings("%d badges - %s - %d caught", slot.meta.badges or 0, - slot.meta.timeText or "0:00", slot.meta.dexCount or 0) - else - metaTxt = Strings("empty slot") - end - label(row, metaTxt, metaSize, C("warn"), - { width = rowInner, textWrap = false, textOverflow = "ellipsis" }) - - local btnRow = mk({ parent = row, width = rowInner, height = btnH, - positioning = "flex", flexDirection = "horizontal", - justifyContent = "flex-end", gap = 6 }) - if not imp.android then - button(imp, btnRow, rowKey .. "-rename", Strings("Rename"), { - size = chipSize, kind = "neutral", - action = function() imp:_beginRename(version, slot.id) end, - }) - end - if imp.onEditSave and slot.exists then - button(imp, btnRow, rowKey .. "-edit", Strings("Edit"), { - size = chipSize, kind = "accent", - action = function() imp.onEditSave(version, slot.id) end, - }) - end - local armed = deleteArmed(imp, "slot", slot.id, version) - -- width pinned to the unarmed label so arming to "Sure?" never reflows - -- the row under the pointer (#433) - button(imp, btnRow, rowKey .. "-del", DELETE_LABEL(armed), { - w = math.ceil(textWidth(chipSize, DELETE_LABEL(false))) + 26, - size = chipSize, kind = armed and "dangerArmed" or "danger", - keepArm = true, - action = function() - imp:pressDelete("slot", slot.id, version, function() - imp:_deleteSlot(version, slot.id) + -- The save-file notice (import/export result) lands in this card now that + -- the buttons that produce it do. + if hintText then + cy = cy + Kit.textWrapped("small", hintText, x + pad, cy, iw, hintCol, 2) + if folderRow then + cy = cy + math.floor(4 * m.s) + local key = "sav-folder-" .. version + local label = Strings("Open folder") + local lw = Kit.textWidth("small", label) + local lh = Kit.textHeight("small") + Kit.focusable(key, x + pad, cy, lw, lh) + Kit.text("small", label, x + pad, cy, PAL.blue) + Theme.fill(x + pad, cy + lh - 1, lw, 1, PAL.blue, 0.6) + if Kit.press(x + pad, cy, lw, lh) or Kit._activateId == key then + local dir = sfNotice.dir + queueAction(imp, key, function() + love.system.openURL(imp:fileUrl(dir)) end) - end, - }) + end + cy = cy + lh + end + cy = cy + math.floor(8 * m.s) end - button(imp, c, "slot-new-" .. version, Strings("+ New save slot"), { - w = "100%", h = m.btnH, size = 13 * m.s + 1, kind = "neutral", - action = function() imp:_newSlot(version) end, - }) + if pages > 1 then + local newPage = Kit.pager(x + pad, cy, iw, cur, n, perPage, pageKey) + setPage(imp, pageKey, newPage) + cy = cy + pagerH + gap + end + btn(imp, x + pad, cy, iw, newBtnH, "slot-new-" .. version, + Strings("+ New save slot"), { + kind = "good", + action = function() imp:_newSlot(version) end, + }) + return h end -local function buildGamePanel(imp, parent, m, version) +local function buildGamePanel(imp, x, y, w, availH, m, version) imp.panelVersion = version local info = GameVersion.info(version) local locked = info == nil @@ -957,341 +945,341 @@ local function buildGamePanel(imp, parent, m, version) or tostring(version) local ready = (not locked) and imp.ready[version] or false - -- header: name + status pill - local head = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 12 * m.s }) - label(head, gameName, 22 * m.s + 4, C("white"), { textWrap = false }) - if ready then pill(head, Strings("GOOD TO GO"), "green", 11 * m.s + 1) - elseif locked then pill(head, Strings("COMING SOON"), "disabled", 11 * m.s + 1) - else pill(head, Strings("ROM REQUIRED"), "gold", 11 * m.s + 1) end + -- title + status tag + local titleH = Kit.textHeight("title") + Kit.text("title", Kit.ellipsize("title", gameName, w * 0.6), x, y, PAL.heading) + local tagText, tagCol + if ready then tagText, tagCol = Strings("GOOD TO GO"), PAL.green + elseif imp.baseRoms and imp.baseRoms[version] then + tagText, tagCol = Strings("ROM FOUND"), PAL.green + elseif locked then tagText, tagCol = Strings("COMING SOON"), PAL.steel + else tagText, tagCol = Strings("ROM REQUIRED"), PAL.yellow end + local tagW = Kit.textWidth("micro", tagText) + math.floor(18 * m.s) + local tagH = Kit.textHeight("micro") + math.floor(10 * m.s) + Kit.tag(x + Kit.textWidth("title", Kit.ellipsize("title", gameName, w * 0.6)) + + math.floor(12 * m.s), y + (titleH - tagH) / 2, tagW, tagH, tagText, tagCol) + local cy = y + titleH + math.floor(12 * m.s) + local remaining = availH - (titleH + math.floor(12 * m.s)) - -- Two columns get explicit pixel widths (percentage children inside a - -- flex-grown column do not resolve, LayoutEngine LAY_004). Single-column - -- mode adds the cards straight to the page instead of nesting columns: - -- the engine under-measures a vertical column-of-columns' auto height, - -- which pushed the footer up over the save-slot card on phone shapes. - local grid, left, right + local gap = m.gap + local lx, lw, rx2, rw if m.twoCol then - grid = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", - gap = m.colGap, alignItems = "flex-start" }) - left = mk({ parent = grid, width = m.colW, - positioning = "flex", flexDirection = "vertical", gap = 12 * m.s }) - right = mk({ parent = grid, width = m.colW, - positioning = "flex", flexDirection = "vertical" }) + lx, lw = x, m.colW + rx2, rw = x + m.colW + m.colGap, m.colW else - left, right = parent, parent + lx, lw, rx2, rw = x, w, x, w end - buildRomCard(imp, left, m, version, info, ready, locked) - buildSaveFilesCard(imp, left, m, version, ready, locked) - if imp.onEditTouchControls then - button(imp, left, "touch-controls", Strings("Touch Controls"), { - w = "100%", h = m.btnH, size = 13 * m.s + 1, kind = "neutral", - action = function() imp.onEditTouchControls() end, - }) - end - button(imp, left, "play-" .. version, - ready and (Strings("Play ") .. gameName) - or (locked and Strings("Coming soon") or Strings("Import a ROM to play")), - { - w = "100%", h = math.max(48, 52 * m.s), size = 18 * m.s + 2, - kind = ready and "primary" or "disabled", - action = ready and function() imp:play(version) end or nil, - }) + -- LEFT COLUMN, laid out DOWNWARD from the top. It used to pin Play and a + -- Touch-Controls/Reset-rebinds pair to the BOTTOM and fill the cards + -- downward into whatever was left, which meant the column's height was + -- whatever its text happened to need -- and on any window shorter than + -- that pile the pinned block simply left the window (Play was measurably + -- off-screen at 1280x720 and on every phone shape). The controls pair has + -- moved behind the gear (they are global settings, not per-game), the ROM + -- and save file management moved into the manage modal and the slot card, + -- and what is left is short enough to lay out top-down and always fit. + local mdl = romModel(imp, version, info, ready, locked) + local ly = cy + + if ready then + -- Play IS the panel: it takes the space the ROM buttons used to hold, at + -- the top of the column where the eye lands, wearing this game's own + -- cartridge colour rather than a generic green. + -- Play grows into the room the column has: it is the one thing on this + -- screen the player came for, and the space freed by moving ROM and + -- control management out belongs to it rather than to a gap. Clamped at + -- both ends so a short window still gets a real button and a tall one + -- does not get a billboard. + local playH = math.floor(clamp(remaining * 0.30, 64 * m.s, 132 * m.s)) + local mgW = playH + local bgap = math.floor(8 * m.s) + btn(imp, lx, ly, lw - mgW - bgap, playH, "play-" .. version, + Strings("Play ") .. gameName, { + fill = cartColor(version), ink = PAL.inverse, font = "stat", + action = function() imp:play(version) end, + }) + imp._gearIcon = imp._gearIcon + or love.graphics.newImage("assets/launcher/gear.png") + iconButton(imp, "manage-" .. version, lx + lw - mgW, ly, playH, + imp._gearIcon, function() imp._gameManage = version end) + ly = ly + playH + gap + end + + -- The ROM card, which now only exists while there is something to report: + -- no ROM, a failed import, an import in flight, or an unsupported game. + local romH = buildRomCard(imp, lx, ly, lw, m, version, mdl, + m.twoCol and remaining or math.floor(remaining * 0.5)) + if romH > 0 then ly = ly + romH + gap end + + -- Save slots. Two columns put them beside the left stack; ONE column + -- stacks them underneath. Either way the card is clipped to the room it + -- actually has, and sizes its own list to that budget. if not locked then - buildSlotCard(imp, right, m, version) - end - -- The right subtree may have grown after FlexLove last measured its - -- grandparent. Refresh only the desktop grid once both columns are complete. - if grid then refreshAutoHeight(grid) end -end - --- ------- mods panel - -local function buildModsPanel(imp, parent, m) - imp:_ensureMods() - local ModUpdate = require("src.mods.ModUpdate") - local mods = imp.mods or {} - local enabledCount = 0 - for _, mod in ipairs(mods) do - if mod.enabled then enabledCount = enabledCount + 1 end - end - - local head = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", flexWrap = "wrap", - alignItems = "center", gap = 10 * m.s }) - label(head, Strings("Mods"), 22 * m.s + 4, C("white"), { textWrap = false }) - label(head, Strings("%d of %d enabled", enabledCount, #mods), - 12 * m.s + 2, C("warn"), { textWrap = false }) - mk({ parent = head, flex = 1 }) - if #mods > 0 then - button(imp, head, "mods-enable-all", Strings("Enable all"), { - size = 11 * m.s + 1, kind = "neutral", - action = function() imp:_setAllMods(true) end, - }) - button(imp, head, "mods-disable-all", Strings("Disable all"), { - size = 11 * m.s + 1, kind = "neutral", - action = function() imp:_setAllMods(false) end, - }) - end - button(imp, head, "mods-import", imp:_modsImportButtonLabel(), { - h = m.btnH, size = 13 * m.s + 1, kind = "neutral", - action = function() imp:chooseMod() end, - }) - - if imp.modNotice then - label(parent, imp.modNotice.text, 12 * m.s + 2, - C(imp.modNotice.ok and "green" or "danger")) - else - label(parent, imp:_modsDefaultHint(), 12 * m.s + 2, C("warn")) - end - - if #mods == 0 then - local box = mk({ - parent = parent, width = "100%", height = 110 * m.s, - backgroundColor = C("card", 0.4), - border = 1, borderColor = C("border", 0.3), cornerRadius = 14, - positioning = "flex", justifyContent = "center", alignItems = "center", - padding = { horizontal = 16 }, - }) - label(box, imp:_modsEmptyHint(), - math.floor(12 * m.s + 2.5), C("detail"), { textAlign = "center" }) - return - end - - -- Sort row: Name / Popularity / Release date / Last updated. The choice - -- persists in options.modSort; data-less mods (no github field, or a - -- cache that predates the feature) sink to the bottom of data sorts. - local sortKey = imp.modSort or "name" - if imp.modSort == nil then - local ok, opts = pcall(require("src.core.SaveData").loadOptions) - if ok and type(opts) == "table" and type(opts.modSort) == "string" then - sortKey = opts.modSort - imp.modSort = sortKey + local slotY = m.twoCol and cy or ly + local slotAvail = m.twoCol and remaining or (cy + remaining - ly) + if slotAvail > 80 * m.s then + Kit.pushClip(rx2, slotY, rw, math.max(0, slotAvail)) + buildSlotCard(imp, rx2, slotY, rw, slotAvail, m, version, ready) + Kit.popClip() end end - local sortRow = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", - flexWrap = "wrap", alignItems = "center", gap = 6 * m.s }) - label(sortRow, Strings("Sort:"), 11 * m.s + 2, C("detail"), { textWrap = false }) - local sorts = { +end + +-- --------------------------------------------------------------- mods panel + +-- One line of { text, color } segments, ellipsized as a whole: each segment +-- gets whatever width the previous ones left, and the first segment that has +-- to ellipsize ends the line. Lets the download count sit green inside an +-- otherwise muted stats line without two competing ellipsis passes. +local function segLine(fontName, segs, x, y, maxW) + local sx = x + for _, seg in ipairs(segs) do + local text = seg[1] + local avail = maxW - (sx - x) + if avail <= 0 then break end + local shown = Kit.ellipsize(fontName, text, avail) + Kit.text(fontName, shown, sx, y, seg[2]) + if shown ~= text then break end + sx = sx + Kit.textWidth(fontName, text) + end +end + +-- The persisted sort choice both mod panels share. The chooser itself is a +-- popup (buildSortModal); panels just read the current key and offer a +-- "Sort" button, which is what freed the chip row's two lines of space. +local function sortDefs() + return { { key = "name", label = Strings("Name") }, { key = "popularity", label = Strings("Popularity") }, { key = "release", label = Strings("Release date") }, { key = "updated", label = Strings("Last updated") }, } - for _, s in ipairs(sorts) do - local active = sortKey == s.key - local key = "mod-sort-" .. s.key - mk({ - parent = sortRow, text = s.label, - textColor = active and C("green") - or (imp._hot[key] and C("white") or C("detail")), - textSize = 11 * m.s + 2, textAlign = "center-center", autoScaleText = false, - backgroundColor = active and C("green", 0.18) or C("border", 0.10), - border = 1, - borderColor = active and C("green", 0.6) or C("border", 0.35), - cornerRadius = 999, - padding = { horizontal = 10, vertical = 4 }, - onEvent = handler(imp, key, function() - imp.modSort = s.key - pcall(function() - local SaveData = require("src.core.SaveData") - local opts = SaveData.loadOptions() - opts.modSort = s.key - SaveData.saveOptions(opts) - end) - end), - }) - end - - local sorted = {} - for i, v in ipairs(mods) do sorted[i] = v end - table.sort(sorted, function(a, b) - local function value(mod) - if sortKey == "name" then return (mod.name or ""):lower() end - local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id) - if sortKey == "popularity" then - return info and info.downloads and info.downloads.total or -1 - end - local date = info and info.dates - if sortKey == "release" then - return date and date.first or "0000-00-00" - end - return date and date.latest or "0000-00-00" - end - local va, vb = value(a), value(b) - if va ~= vb then - if sortKey == "name" then return va < vb end - return va > vb -- data sorts newest / most popular first - end - return (a.name or ""):lower() < (b.name or ""):lower() - end) - mods = sorted - - -- Explicit column widths AND heights: a flex-grown container collapses - -- its children's layout in this engine, and card auto-height came up - -- short on some displays, dropping the bottom action row out of the card. - -- Everything is measured with the same integer-sized fonts the labels - -- render with, and the card gets the exact sum. - local innerW = m.contentW - 32 - local clusterW = math.max(96, math.floor(110 * m.s)) - local bodyW = innerW - clusterW - 10 - local nameSize = math.floor(15 * m.s + 2.5) - local smallSize = math.floor(12 * m.s + 1.5) - local badgeSize = math.floor(10 * m.s + 1.5) - local chipSize = math.floor(11 * m.s + 1.5) - local btnH = math.ceil(textHeight(chipSize)) + 14 - local badgeH = math.ceil(textHeight(badgeSize)) + 6 - local toggleH = math.floor(24 * m.s + 2) + 8 - local pillH = math.ceil(textHeight(chipSize)) + 8 - local clusterH = pillH + 6 + toggleH - for _, mod in ipairs(mods) do - local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id) - local checkLine, checkCol - if info and info.status == "available" then - checkLine = Strings("Checked for updates - v%s available", - tostring(info.latest)) - checkCol = "green" - elseif info and info.status == "current" then - checkLine, checkCol = Strings("Checked for updates - up to date"), "green" - elseif info and info.status == "error" then - checkLine, checkCol = Strings("Checked for updates - failed"), "danger" - elseif mod.github and mod.github ~= "" then - checkLine, checkCol = Strings("Not checked for updates yet"), "warn" - end - -- Total downloads plus first/latest release dates, all from the same - -- cached release fetch. Only shown once that data actually carries - -- counts, so a pre-downloads cache entry costs the line, not a wrong "0". - local dlLine - if info and info.downloads then - local d = info.dates - dlLine = ModUpdate.statsLine(info.downloads.total, - d and d.first, d and d.latest) - end - - -- measure the body: name (with the badge beside it only when it fits), - -- version, check line, wrapped description - local badgeW = math.ceil(textWidth(badgeSize, mod.badge)) + 14 - local nameH = math.ceil(textHeight(nameSize)) - local badgeBesideName = - math.ceil(textWidth(nameSize, mod.name)) + 8 + badgeW <= bodyW - local bodyH = badgeBesideName and math.max(nameH, badgeH) - or (nameH + 4 + badgeH) - bodyH = bodyH + 4 + math.ceil(textHeight(smallSize)) - if checkLine then - bodyH = bodyH + 4 + wrapHeight(smallSize, checkLine, bodyW) - end - if dlLine then - bodyH = bodyH + 4 + wrapHeight(smallSize, dlLine, bodyW) - end - if mod.description ~= "" then - bodyH = bodyH + 4 + wrapHeight(smallSize, mod.description, bodyW) - end - local rowH = math.max(bodyH, clusterH) - - -- how many lines the right-aligned action row needs - local btnRowW = math.ceil(textWidth(chipSize, DELETE_LABEL(false))) + 26 - local updLabel, updKind = Strings("Check for updates"), "neutral" - if info and info.status == "available" then - updLabel, updKind = Strings("Update"), "accent" - elseif info and info.status == "current" then - updLabel = Strings("Check again") - end - if mod.github and mod.github ~= "" then - btnRowW = btnRowW + math.ceil(textWidth(chipSize, updLabel)) + 26 + 6 - + math.ceil(textWidth(chipSize, Strings("Versions"))) + 26 + 6 - end - local btnLines = math.max(1, math.ceil(btnRowW / innerW)) - local cardH = 28 + rowH + 8 + btnLines * btnH + (btnLines - 1) * 6 - - local c = card(parent, { padding = m.cardPad, gap = 8, height = cardH }) - local row = mk({ parent = c, width = "100%", height = rowH, - positioning = "flex", flexDirection = "horizontal", - gap = 10, alignItems = "flex-start" }) - local body = mk({ parent = row, width = bodyW, height = rowH, - positioning = "flex", flexDirection = "vertical", gap = 4 }) - local function badge(parent2) - mk({ - parent = parent2, text = mod.badge, autoScaleText = false, - textColor = mod.experimental and C("gold") or C("warn"), - textSize = badgeSize, textAlign = "center-center", - width = badgeW, height = badgeH, - border = 1, borderColor = C("border", 0.5), cornerRadius = 5, - }) - end - if badgeBesideName then - local nameRow = mk({ parent = body, width = "100%", - height = math.max(nameH, badgeH), - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 8 }) - label(nameRow, mod.name, nameSize, C("white"), { textWrap = false }) - badge(nameRow) - else - label(body, mod.name, nameSize, C("white"), - { width = "100%", textWrap = false, textOverflow = "ellipsis" }) - badge(body) - end - label(body, "v" .. tostring(mod.version or "?"), smallSize, C("detail")) - if checkLine then - label(body, checkLine, smallSize, C(checkCol), { width = "100%" }) - end - if dlLine then - label(body, dlLine, smallSize, C("gold"), { width = "100%" }) - end - if mod.description ~= "" then - label(body, mod.description, smallSize, C("detail"), { width = "100%" }) - end - - local cluster = mk({ parent = row, width = clusterW, height = clusterH, - positioning = "flex", flexDirection = "vertical", - alignItems = "flex-end", gap = 6 }) - local chipText, chipCol = modStatusChip(mod.status) - pill(cluster, chipText, chipCol, chipSize) - local togKey = "mod-toggle-" .. mod.id - local togWrap = mk({ parent = cluster, - width = math.floor(46 * m.s + 4) + 8, height = toggleH, - padding = 4, - onEvent = handler(imp, togKey, function() imp:_toggleMod(mod.id) end), - }) - toggleSwitch(togWrap, mod.enabled, math.floor(46 * m.s + 4), - math.floor(24 * m.s + 2), "tog:" .. mod.id) - - local btnRow = mk({ parent = c, width = "100%", - height = btnLines * btnH + (btnLines - 1) * 6, - positioning = "flex", flexDirection = "horizontal", - justifyContent = "flex-end", flexWrap = "wrap", gap = 6 }) - if mod.github and mod.github ~= "" then - button(imp, btnRow, "mod-upd-" .. mod.id, updLabel, { - size = chipSize, kind = updKind, - action = function() imp:_modGithubAction(mod.id, "update") end, - }) - button(imp, btnRow, "mod-ver-" .. mod.id, Strings("Versions"), { - size = chipSize, kind = "neutral", - action = function() imp:_modGithubAction(mod.id, "versions") end, - }) - end - local armed = deleteArmed(imp, "mod", mod.id, nil) - button(imp, btnRow, "mod-del-" .. mod.id, DELETE_LABEL(armed), { - w = math.ceil(textWidth(chipSize, DELETE_LABEL(false))) + 26, - size = chipSize, kind = armed and "dangerArmed" or "danger", - keepArm = true, - action = function() - imp:pressDelete("mod", mod.id, nil, function() - imp:_deleteMod(mod.id) - end) - end, - }) - end end --- ------- find mods panel +local function currentSort(imp) + local sortKey = imp.modSort + if sortKey == nil then + local ok, opts = pcall(require("src.core.SaveData").loadOptions) + if ok and type(opts) == "table" and type(opts.modSort) == "string" then + sortKey = opts.modSort + end + sortKey = sortKey or "popularity" + imp.modSort = sortKey + end + return sortKey +end -local function buildFindPanel(imp, parent, m) - imp._findThumbFetched = false - imp._findStatsFetched = false +-- A hand-drawn check mark: the UI font has no guaranteed glyph for one, and +-- a tofu box on the "you already have this" signal would be worse than none. +local function drawCheck(x, y, size, color) + love.graphics.push("all") + love.graphics.setColor(color) + love.graphics.setLineWidth(math.max(2, size * 0.16)) + love.graphics.setLineJoin("bevel") + love.graphics.line( + x, y + size * 0.55, + x + size * 0.35, y + size * 0.85, + x + size * 0.95, y + size * 0.15) + love.graphics.pop() +end + +local function buildModsPanel(imp, x, y, w, availH, m) + imp:_ensureMods() + local ModUpdate = require("src.mods.ModUpdate") + local mods = imp.mods or {} + local gap = m.gap + local cy = y + + -- header: just the action cluster, right-aligned. No "Mods" headline (the + -- active tab already says it) and no enabled count (the toggles show it). + local place = Layout.rightCluster(x, w, math.floor(6 * m.s)) + local bh = m.btnH + local importLabel = imp:_modsImportButtonLabel() + local iw2 = Kit.textWidth("small", importLabel) + math.floor(24 * m.s) + btn(imp, place(iw2), cy, iw2, bh, "mods-import", importLabel, { + kind = "accent", font = "small", + action = function() imp:chooseMod() end }) + if #mods > 0 then + local dw = Kit.textWidth("small", Strings("Disable all")) + math.floor(20 * m.s) + btn(imp, place(dw), cy, dw, bh, "mods-disable-all", Strings("Disable all"), { + kind = "warn", font = "small", + action = function() imp:_setAllMods(false) end }) + local ew = Kit.textWidth("small", Strings("Enable all")) + math.floor(20 * m.s) + btn(imp, place(ew), cy, ew, bh, "mods-enable-all", Strings("Enable all"), { + kind = "good", font = "small", + action = function() imp:_setAllMods(true) end }) + local sw = Kit.textWidth("small", Strings("Sort")) + math.floor(24 * m.s) + btn(imp, place(sw), cy, sw, bh, "mods-sort", Strings("Sort"), { + font = "small", + action = function() imp._sortPopup = true end }) + end + cy = cy + bh + math.floor(8 * m.s) + + -- notice line + local noticeText, noticeCol + if imp.modNotice then + noticeText = imp.modNotice.text + noticeCol = imp.modNotice.ok and PAL.green or PAL.red + else + noticeText, noticeCol = imp:_modsDefaultHint(), PAL.muted + end + cy = cy + Kit.textWrapped("small", noticeText, x, cy, w, noticeCol, 2) + + math.floor(8 * m.s) + + if #mods == 0 then + Kit.emptyBox(x, cy, w, math.floor(110 * m.s), imp:_modsEmptyHint()) + return + end + + local sortKey = currentSort(imp) + + -- Immediate mode paints this panel every frame; re-sorting the whole list + -- per frame (with lowercased-string allocations in the comparator) fed the + -- GC for nothing. Cache the sorted array, keyed on the list identity, the + -- sort mode, and the update-info revision the fetch pump bumps. + local cache = imp._modSortCache + if cache and cache.src == mods and cache.n == #mods + and cache.key == sortKey and cache.rev == (imp._modUpdateRev or 0) then + mods = cache.list + else + local sorted = {} + for i, v in ipairs(mods) do sorted[i] = v end + table.sort(sorted, function(a, b) + local function value(mod) + if sortKey == "name" then return (mod.name or ""):lower() end + local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id) + if sortKey == "popularity" then + return info and info.downloads and info.downloads.total or -1 + end + local date = info and info.dates + if sortKey == "release" then return date and date.first or "0000-00-00" end + return date and date.latest or "0000-00-00" + end + local va, vb = value(a), value(b) + if va ~= vb then + if sortKey == "name" then return va < vb end + return va > vb -- data sorts newest / most popular first + end + return (a.name or ""):lower() < (b.name or ""):lower() + end) + imp._modSortCache = { src = imp.mods, n = #mods, key = sortKey, + rev = imp._modUpdateRev or 0, list = sorted } + mods = sorted + end + + -- A mod row is a fixed height: name line, version + status line, one line + -- of description, and an action row. Fixed because a page of uniform rows + -- is what lets perPage come from the viewport. + local chipH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + -- Text block on the left, chips right-aligned beside it: one row, not a + -- text block with a button strip stacked under it. + local textH = Kit.textHeight("button") + math.floor(4 * m.s) + + Kit.textHeight("small") + math.floor(2 * m.s) + Kit.textHeight("small") + local rowH = math.floor(8 * m.s) + math.max(textH, chipH) + + math.floor(8 * m.s) + local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local listH = availH - (cy - y) - pagerH - gap + local perPage = Kit.rowsThatFit(listH, rowH, gap, 1, 20) + local first, last, cur, pages = Kit.pageBounds(page(imp, "mods"), #mods, perPage) + setPage(imp, "mods", cur) + local listTop = cy + setPage(imp, "mods", + Kit.wheelPage(x, listTop, w, listH, cur, #mods, perPage)) + + for i = first, last do + local mod = mods[i] + local ry = listTop + (i - first) * (rowH + gap) + local rowKey = "mod-row-" .. mod.id + -- The whole row is the control: it opens the per-mod actions popup + -- (update / versions / delete moved there). Only the enable toggle + -- stays inline, because flipping a mod on and off is the everyday act. + local focused = Kit.focusable(rowKey, x, ry, w, rowH) + local hot = focused or Kit.hover(x, ry, w, rowH) + Kit.card(x, ry, w, rowH, hot) + local pad = math.floor(12 * m.s) + local px, inner = x + pad, w - 2 * pad + local ly = ry + math.floor(10 * m.s) + + local togW = math.floor(56 * m.s) + local togH = math.floor(26 * m.s) + local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id) + + local togKey = "mod-toggle-" .. mod.id + -- The toggle reports its own new value, but the importer owns the state: + -- queue the flip and let _toggleMod (which may raise an experimental-mod + -- confirm) decide what actually happens. + local _, flipped = Kit.toggle(px + inner - togW, + ry + (rowH - togH) / 2, togW, togH, mod.enabled, togKey) + if flipped then + queueAction(imp, togKey, function() imp:_toggleMod(mod.id) end) + end + -- The toggle sits inside the row's rect, so its press also passes the + -- row's hit test; `flipped` gates the row action to everywhere else. + if not flipped + and (Kit.press(x, ry, w, rowH) or Kit._activateId == rowKey) then + local id = mod.id + queueAction(imp, rowKey, function() imp._modActions = id end) + end + local chipsW = togW + math.floor(6 * m.s) + local textW = inner - chipsW - math.floor(12 * m.s) + + local badgeW = Kit.textWidth("micro", mod.badge) + math.floor(12 * m.s) + local nameShown = Kit.ellipsize("button", mod.name, + textW - badgeW - math.floor(8 * m.s)) + Kit.text("button", nameShown, px, ly, PAL.heading) + Kit.tag(px + Kit.textWidth("button", nameShown) + math.floor(8 * m.s), ly, + badgeW, Kit.textHeight("button"), mod.badge, + mod.experimental and PAL.yellow or PAL.muted) + ly = ly + Kit.textHeight("button") + math.floor(4 * m.s) + + -- version + status + update state + local statusText, statusCol = modStatusColor(mod.status) + local line = "v" .. tostring(mod.version or "?") .. " " .. statusText + Kit.text("small", line, px, ly, statusCol) + local lx = px + Kit.textWidth("small", line) + math.floor(12 * m.s) + if imp:_modInfoPending(mod.id) then + -- An inline spinner, because this row's release check is genuinely in + -- flight -- the list stays usable while it resolves. + Loader.dot(lx, ly, Kit.textHeight("small")) + Kit.text("small", Strings("Checking..."), + lx + Kit.textHeight("small") + math.floor(6 * m.s), ly, PAL.muted) + elseif info and info.status == "available" then + Kit.text("small", Strings("v%s available", tostring(info.latest)), + lx, ly, PAL.yellow) + elseif info and info.status == "current" then + Kit.text("small", Strings("up to date"), lx, ly, PAL.muted) + elseif info and info.status == "error" then + Kit.text("small", Strings("check failed"), lx, ly, PAL.red) + end + ly = ly + Kit.textHeight("small") + math.floor(2 * m.s) + + -- one line of description, or the download stats when we have them + -- (download count in green so popularity reads at a glance) + if info and info.downloads then + local d = info.dates + local dl = ModUpdate.downloadsLine(info.downloads.total) + local dates = ModUpdate.datesLine(d and d.first, d and d.latest) + local segs = {} + if dl then segs[#segs + 1] = { dl, PAL.green } end + if dates then + segs[#segs + 1] = { (dl and " - " or "") .. dates, PAL.detail } + end + segLine("small", segs, px, ly, textW) + elseif (mod.description or "") ~= "" then + Kit.text("small", Kit.ellipsize("small", mod.description, textW), + px, ly, PAL.detail) + end + end + + local pagerY = listTop + (last - first + 1) * (rowH + gap) + local newPage = Kit.pager(x, pagerY, w, cur, #mods, perPage, "mods") + setPage(imp, "mods", newPage) +end + +-- ---------------------------------------------------------- find mods panel + +local function buildFindPanel(imp, x, y, w, availH, m) imp:_ensureFind() imp:_ensureMods() local ModIndex = require("src.mods.ModIndex") @@ -1299,364 +1287,226 @@ local function buildFindPanel(imp, parent, m) local sources = imp.findSources or {} local rows = imp:_findRows() local total = #((imp.findIndex and imp.findIndex.mods) or {}) + local gap = m.gap + local cy = y - local head = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", flexWrap = "wrap", - alignItems = "center", gap = 10 * m.s }) - label(head, Strings("Find Mods"), 22 * m.s + 4, C("white"), { textWrap = false }) - if #sources > 0 then - label(head, (#rows == total) and Strings("%d mods listed", total) - or Strings("%d of %d mods", #rows, total), 12 * m.s + 2, C("warn"), - { textWrap = false }) - end - mk({ parent = head, flex = 1 }) - if #sources > 0 then - button(imp, head, "find-refresh", Strings("Refresh"), { - h = m.btnH, size = 13 * m.s + 1, kind = "neutral", - action = function() - imp._findSearchFocus = false - imp:_disarmTextInput() - imp:_refreshFind(true) - end, - }) - end - button(imp, head, "find-add", - (#sources == 0) and Strings("Add an index") or Strings("Add index"), { - h = m.btnH, size = 13 * m.s + 1, kind = "neutral", - action = function() imp:_promptAddIndex() end, - }) - + -- No headline, no disclaimer paragraph: the active tab already names this + -- panel, and the index list, the category filter and the sort choice all + -- moved into popups (Indexes / Filter / Sort) so the space goes to rows. + -- Only a live action-feedback notice (Installed X / errors) earns a line. if imp.findNotice then - label(parent, imp.findNotice.text, 12 * m.s + 2, - C(imp.findNotice.ok and "green" or "danger")) - else - label(parent, Strings( - "Mods here are listed, not reviewed - read the source and trust the author."), - 12 * m.s + 2, C("warn")) + cy = cy + Kit.textWrapped("small", imp.findNotice.text, x, cy, w, + imp.findNotice.ok and PAL.green or PAL.red, 2) + math.floor(8 * m.s) end if #sources == 0 then - local box = mk({ - parent = parent, width = "100%", height = 150 * m.s, - border = 1, borderColor = C("border", 0.45), cornerRadius = 14, - positioning = "flex", flexDirection = "vertical", - justifyContent = "center", alignItems = "center", gap = 6 * m.s, - padding = { horizontal = 20 }, - }) - label(box, Strings("No mod index added"), 15 * m.s + 2, C("white"), - { textAlign = "center", width = "100%" }) - label(box, Strings( + local h = math.floor(140 * m.s) + Kit.card(x, cy, w, h) + Kit.textCenter("button", Strings("No mod index added"), x, + cy + math.floor(24 * m.s), w, PAL.heading) + Kit.textWrapped("small", Strings( "Add an index to browse mods. An index is a published list; paste its URL or its owner/repo."), - 12 * m.s + 1, C("warn"), { textAlign = "center", width = "100%" }) + x + math.floor(24 * m.s), cy + math.floor(54 * m.s), + w - math.floor(48 * m.s), PAL.muted, 2) + local aw = Kit.textWidth("small", Strings("Add an index")) + + math.floor(28 * m.s) + btn(imp, x + math.floor((w - aw) / 2), cy + h - m.btnH - math.floor(14 * m.s), + aw, m.btnH, "find-add", Strings("Add an index"), { + kind = "accent", font = "small", + action = function() imp._indexManage = true end }) return end - for _, source in ipairs(sources) do - local srow = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 8 * m.s }) - label(srow, source.label or source.feed, 12 * m.s + 1, C("detail"), - { width = m.contentW - - (math.ceil(textWidth(11 * m.s + 1, Strings("Remove"))) + 26) - - 8 * m.s, - textWrap = false, textOverflow = "ellipsis" }) - button(imp, srow, "find-src-rm-" .. tostring(source.feed), Strings("Remove"), { - size = 11 * m.s + 1, kind = "danger", - action = function() imp:_removeIndex(source.feed) end, - }) - end - - -- search field (hand-rolled text state, same routing as the rename modal) - textField(imp, parent, "find-search", - imp.findQuery or "", Strings("Search mods"), - imp._findSearchFocus == true, - function() - imp:_toggleFindSearchFocus() - end) - - local cats = (imp.findIndex and imp.findIndex.categories) or {} - if #cats > 0 then - local catRow = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", - flexWrap = "wrap", gap = 6 * m.s }) - local function catChip(name, id, active) - local key = "find-cat-" .. id - mk({ - parent = catRow, text = name, - textColor = active and C("green") - or (imp._hot[key] and C("white") or C("detail")), - textSize = 11 * m.s + 2, textAlign = "center-center", autoScaleText = false, - backgroundColor = active and C("green", 0.18) or C("border", 0.10), - border = 1, - borderColor = active and C("green", 0.6) or C("border", 0.35), - cornerRadius = 999, - padding = { horizontal = 10, vertical = 4 }, - onEvent = handler(imp, key, function() - imp.findCategory = (id ~= "" and imp.findCategory ~= id) and id or nil - end), - }) - end - catChip(Strings("All"), "", imp.findCategory == nil) - for _, cat in ipairs(cats) do - catChip(cat, cat, imp.findCategory == cat) - end - end + -- One row: the search field, then Filter / Sort / Indexes popup buttons. + local fieldH = math.max(Kit.tapMin(), math.floor(36 * m.s)) + local bgap = math.floor(6 * m.s) + local place = Layout.rightCluster(x, w, bgap) + local xw = Kit.textWidth("small", Strings("Indexes")) + math.floor(20 * m.s) + btn(imp, place(xw), cy, xw, fieldH, "find-indexes", Strings("Indexes"), { + font = "small", + action = function() imp._indexManage = true end }) + local sw = Kit.textWidth("small", Strings("Sort")) + math.floor(20 * m.s) + btn(imp, place(sw), cy, sw, fieldH, "find-sort", Strings("Sort"), { + font = "small", + action = function() imp._sortPopup = true end }) + -- The Filter button carries its state: blue while a category is active, + -- so a filtered-down list never reads as "the index shrank". + local fw = Kit.textWidth("small", Strings("Filter")) + math.floor(20 * m.s) + btn(imp, place(fw), cy, fw, fieldH, "find-filter", Strings("Filter"), { + kind = imp.findCategory and "accent" or "ghost", font = "small", + action = function() imp._filterPopup = true end }) + local searchW = place(0) - x - bgap + textField(imp, x, cy, searchW, fieldH, "find-search", imp.findQuery or "", + Strings("Search mods"), imp._findSearchFocus == true, + function() imp:_toggleFindSearchFocus() end) + cy = cy + fieldH + math.floor(8 * m.s) if #rows == 0 then - local box = mk({ - parent = parent, width = "100%", height = 130 * m.s, - backgroundColor = C("card", 0.4), - border = 1, borderColor = C("border", 0.3), cornerRadius = 14, - positioning = "flex", flexDirection = "vertical", - justifyContent = "center", alignItems = "center", gap = 8, - padding = { horizontal = 20 }, - }) - mk({ parent = box, width = math.floor(30 * m.s), - height = math.floor(30 * m.s), - image = imp._findIcon, objectFit = "contain", - imageTint = C("gray", 0.6) }) - label(box, (total == 0) and Strings("This index lists no mods yet.") - or Strings("No mods match that search."), - math.floor(13 * m.s + 1.5), C("detail"), { textAlign = "center" }) - if total > 0 then - label(box, - Strings("Try a different search, or clear the category filter."), - math.floor(11 * m.s + 1.5), C("warn"), { textAlign = "center" }) - end + Kit.emptyBox(x, cy, w, math.floor(110 * m.s), + (total == 0) and Strings("This index lists no mods yet.") + or Strings("No mods match that search.")) return end - -- Sort row: Name / Popularity / Release date / Last updated, the same - -- options the MODS tab offers, sharing its persisted choice - -- (options.modSort). Data comes from the same _findStats resolution the - -- cards use (feed-published, else the repo fetch); rows whose stats have - -- not resolved yet sink to the bottom of data sorts and rise as the - -- one-per-frame fetches complete. - local sortKey = imp.modSort or "name" - if imp.modSort == nil then - local ok, opts = pcall(require("src.core.SaveData").loadOptions) - if ok and type(opts) == "table" and type(opts.modSort) == "string" then - sortKey = opts.modSort - imp.modSort = sortKey - end - end - local sortRow = mk({ parent = parent, width = "100%", - positioning = "flex", flexDirection = "horizontal", - flexWrap = "wrap", alignItems = "center", gap = 6 * m.s }) - label(sortRow, Strings("Sort:"), 11 * m.s + 2, C("detail"), { textWrap = false }) - local sorts = { - { key = "name", label = Strings("Name") }, - { key = "popularity", label = Strings("Popularity") }, - { key = "release", label = Strings("Release date") }, - { key = "updated", label = Strings("Last updated") }, - } - for _, s in ipairs(sorts) do - local active = sortKey == s.key - local key = "find-sort-" .. s.key - mk({ - parent = sortRow, text = s.label, - textColor = active and C("green") - or (imp._hot[key] and C("white") or C("detail")), - textSize = 11 * m.s + 2, textAlign = "center-center", autoScaleText = false, - backgroundColor = active and C("green", 0.18) or C("border", 0.10), - border = 1, - borderColor = active and C("green", 0.6) or C("border", 0.35), - cornerRadius = 999, - padding = { horizontal = 10, vertical = 4 }, - onEvent = handler(imp, key, function() - imp.modSort = s.key - pcall(function() - local SaveData = require("src.core.SaveData") - local opts = SaveData.loadOptions() - opts.modSort = s.key - SaveData.saveOptions(opts) - end) - end), - }) - end + local sortKey = currentSort(imp) - local sorted = {} - for i, v in ipairs(rows) do sorted[i] = v end - table.sort(sorted, function(a, b) - local function value(entry) - if sortKey == "name" then - return (entry.title or entry.id or ""):lower() + -- Same caching rule as the MODS tab: the comparator allocates, so only + -- re-sort when the inputs actually change. + local fcache = imp._findSortCache + if fcache and fcache.src == rows and fcache.key == sortKey + and fcache.rev == (imp._findStatsRev or 0) then + rows = fcache.list + else + local sorted = {} + for i, v in ipairs(rows) do sorted[i] = v end + table.sort(sorted, function(a, b) + local function value(entry) + if sortKey == "name" then return (entry.title or entry.id or ""):lower() end + local stats = imp:_findStats(entry) + if sortKey == "popularity" then return stats and stats.total or -1 end + if sortKey == "release" then return stats and stats.first or "0000-00-00" end + return stats and stats.latest or "0000-00-00" end - local stats = imp:_findStats(entry) - if sortKey == "popularity" then - return stats and stats.total or -1 + local va, vb = value(a), value(b) + if va ~= vb then + if sortKey == "name" then return va < vb end + return va > vb end - if sortKey == "release" then - return stats and stats.first or "0000-00-00" - end - return stats and stats.latest or "0000-00-00" - end - local va, vb = value(a), value(b) - if va ~= vb then - if sortKey == "name" then return va < vb end - return va > vb -- data sorts newest / most popular first - end - return (a.title or a.id or ""):lower() < (b.title or b.id or ""):lower() - end) - rows = sorted + return (a.title or a.id or ""):lower() < (b.title or b.id or ""):lower() + end) + imp._findSortCache = { src = rows, key = sortKey, + rev = imp._findStatsRev or 0, list = sorted } + rows = sorted + end local installed = imp:_findInstalledMap() - local thumbW = 64 * m.s - -- Explicit measured widths AND heights, same reasoning as the mods card: - -- the engine's card auto-height dropped the Details/Source/Install row - -- past the card's bottom edge on some displays. - local innerW = m.contentW - 32 - local bodyW = innerW - thumbW - 10 - local titleSize = math.floor(15 * m.s + 2.5) - local smallSize = math.floor(12 * m.s + 1.5) - local chipSize = math.floor(11 * m.s + 1.5) - local btnH = math.ceil(textHeight(chipSize)) + 14 - for _, entry in ipairs(rows) do - local action, note = findActionFor(entry, installed[entry.id]) - -- Release stats for the row: feed-published when the feed carries - -- them, otherwise fetched from the mod's GitHub repo (one per frame, - -- cached six hours) exactly like the MODS tab does. - local stats = imp:_findStats(entry) - local statsLine - if stats and (stats.total ~= nil or stats.first or stats.latest) then - statsLine = ModUpdate.statsLine(stats.total, stats.first, stats.latest) + -- The thumbnail sits BESIDE the text and the action chips share the title + -- line's row, so a card is only as tall as its text block. The old layout + -- stacked chips under a 64px thumbnail and got ~2 rows per screen; this + -- fits roughly twice as many without shrinking a single tap target. + local thumb = math.floor(44 * m.s) + local chipH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + -- TWO text lines, not three: the version/author/category meta and the + -- download stats share a line. A third line cost every row ~20px, which + -- at this UI scale was the difference between one and two rows per page. + local textH = Kit.textHeight("button") + math.floor(4 * m.s) + + Kit.textHeight("small") + local rowH = math.floor(8 * m.s) + math.max(thumb, textH, chipH) + + math.floor(8 * m.s) + local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local listH = availH - (cy - y) - pagerH - gap + local perPage = Kit.rowsThatFit(listH, rowH, gap, 1, 20) + local first, last, cur, pages = Kit.pageBounds(page(imp, "find"), #rows, perPage) + setPage(imp, "find", cur) + local listTop = cy + setPage(imp, "find", Kit.wheelPage(x, listTop, w, listH, cur, #rows, perPage)) + + for i = first, last do + local entry = rows[i] + local ry = listTop + (i - first) * (rowH + gap) + local rowKey = "find-row-" .. entry.id + -- The whole row is the control: it opens the per-mod popup where + -- Install / Details / Source moved. The only inline signal left is a + -- green check when the mod is already installed. + local focused = Kit.focusable(rowKey, x, ry, w, rowH) + local hot = focused or Kit.hover(x, ry, w, rowH) + Kit.card(x, ry, w, rowH, hot) + local pad = math.floor(12 * m.s) + local px, inner = x + pad, w - 2 * pad + local ly = ry + math.floor(8 * m.s) + + if Kit.press(x, ry, w, rowH) or Kit._activateId == rowKey then + local e = entry + queueAction(imp, rowKey, function() imp._findEntry = e end) end - local bodyH = math.ceil(textHeight(titleSize)) - + 4 + math.ceil(textHeight(smallSize)) - if statsLine then bodyH = bodyH + 4 + wrapHeight(smallSize, statsLine, bodyW) end - if note then bodyH = bodyH + 4 + wrapHeight(smallSize, note, bodyW) end - if entry.summary and entry.summary ~= "" then - bodyH = bodyH + 4 + wrapHeight(smallSize, entry.summary, bodyW) + local _, note = findActionFor(entry, installed[entry.id]) + local chipsW = 0 + if installed[entry.id] then + local ck = math.floor(20 * m.s) + drawCheck(px + inner - ck, ry + (rowH - ck) / 2, ck, PAL.green) + chipsW = ck + math.floor(6 * m.s) end - local rowH = math.max(thumbW, bodyH) - local btnRowW = math.ceil(textWidth(chipSize, Strings("Details"))) + 26 - if entry.repo then - btnRowW = btnRowW + 6 + math.ceil(textWidth(chipSize, Strings("Source"))) + 26 - end - if action then - btnRowW = btnRowW + 6 + math.ceil(textWidth(chipSize, action)) + 26 - else - btnRowW = btnRowW + 6 - + math.ceil(textWidth(chipSize, Strings("Unavailable"))) + 20 - end - local btnLines = math.max(1, math.ceil(btnRowW / innerW)) - local cardH = 28 + rowH + 8 + btnLines * btnH + (btnLines - 1) * 6 - local c = card(parent, { padding = m.cardPad, gap = 8, height = cardH }) - local row = mk({ parent = c, width = "100%", height = rowH, - positioning = "flex", flexDirection = "horizontal", - gap = 10, alignItems = "flex-start" }) + -- thumbnail (or its placeholder while the async fetch is in flight) local image = imp:_findThumb(entry) if image then - mk({ parent = row, image = image, objectFit = "contain", - width = thumbW, height = thumbW, cornerRadius = 8 }) + local iw3, ih3 = image:getDimensions() + local s = math.min(thumb / iw3, thumb / ih3) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(image, Theme.snap(px), Theme.snap(ly), 0, s, s) else - mk({ parent = row, width = thumbW, height = thumbW, - backgroundColor = C("border", 0.18), cornerRadius = 8, - text = "MOD", textColor = C("disabled"), - textSize = math.floor(10 * m.s + 1.5), textAlign = "center-center", - autoScaleText = false }) + Theme.stroke(px, ly, thumb, thumb, PAL.line, Theme.A.hairline, 1) + Kit.textCenter("micro", "MOD", px, + ly + (thumb - Kit.textHeight("micro")) / 2, thumb, PAL.faint) end - local body = mk({ parent = row, width = bodyW, height = rowH, - positioning = "flex", flexDirection = "vertical", gap = 4 }) - label(body, entry.title or entry.id, titleSize, C("white"), - { width = "100%", textWrap = false, textOverflow = "ellipsis" }) - local meta = "v" .. tostring(ModIndex.displayVersion(entry)) - if entry.author then meta = meta .. " - " .. entry.author end + + local bx = px + thumb + math.floor(10 * m.s) + local bw = inner - thumb - math.floor(10 * m.s) - chipsW + Kit.text("button", Kit.ellipsize("button", entry.title or entry.id, bw), + bx, ly, PAL.heading) + local by2 = ly + Kit.textHeight("button") + math.floor(4 * m.s) + -- meta and stats on one line, the download count first (and green) + -- because it is what the default Popularity sort is ordering by: a + -- narrow window ellipsizes the tail, and the count must survive that. + local stats = imp:_findStats(entry) + local baseCol = note and PAL.green or PAL.detail + local lead = "v" .. tostring(ModIndex.displayVersion(entry)) + if note then lead = lead .. " - " .. note end + local dl = stats and ModUpdate.downloadsLine(stats.total) or nil + local dates = stats and ModUpdate.datesLine(stats.first, stats.latest) + or nil + local rest = {} + if entry.author then rest[#rest + 1] = entry.author end if entry.categories and entry.categories[1] then - meta = meta .. " - " .. entry.categories[1] + rest[#rest + 1] = entry.categories[1] end - label(body, meta, smallSize, C("detail"), - { width = "100%", textWrap = false, textOverflow = "ellipsis" }) - if statsLine then - label(body, statsLine, smallSize, C("gold"), { width = "100%" }) + if dates then + rest[#rest + 1] = dates + elseif not dl and (entry.summary or "") ~= "" then + rest[#rest + 1] = entry.summary end - if note then label(body, note, smallSize, C("green"), { width = "100%" }) end - if entry.summary and entry.summary ~= "" then - label(body, entry.summary, smallSize, C("detail"), { width = "100%" }) - end - - local btnRow = mk({ parent = c, width = "100%", - height = btnLines * btnH + (btnLines - 1) * 6, - positioning = "flex", flexDirection = "horizontal", - justifyContent = "flex-end", flexWrap = "wrap", gap = 6 }) - if not action then - pill(btnRow, Strings("Unavailable"), "gold", chipSize) - end - button(imp, btnRow, "find-det-" .. entry.id, Strings("Details"), { - size = chipSize, kind = "neutral", - action = function() imp:_findShowDetails(entry) end, - }) - if entry.repo then - button(imp, btnRow, "find-repo-" .. entry.id, Strings("Source"), { - size = chipSize, kind = "neutral", - action = function() love.system.openURL(entry.repo) end, - }) - end - if action then - button(imp, btnRow, "find-inst-" .. entry.id, action, { - size = chipSize, kind = "accent", - action = function() imp:_findConfirmInstall(entry) end, - }) + local segs = { { lead, baseCol } } + if dl then segs[#segs + 1] = { " - " .. dl, PAL.green } end + if #rest > 0 then + segs[#segs + 1] = { " - " .. table.concat(rest, " - "), baseCol } end + segLine("small", segs, bx, by2, bw) end + + local pagerY = listTop + (last - first + 1) * (rowH + gap) + setPage(imp, "find", Kit.pager(x, pagerY, w, cur, #rows, perPage, "find")) end --- ------- updater banner + footer - -local function buildBanner(imp, parent, m) - if not imp.Check then return end - local ok, st = pcall(imp.Check.state) - st = (ok and type(st) == "table") and st or nil - local status = st and st.status - if status ~= "available" and status ~= "downloading" - and status ~= "ready" and status ~= "needs_full" then - return - end - local c = card(parent, { - padding = { horizontal = 16, vertical = 12 }, - borderColor = C("gold", 0.5), gap = 8 * m.s, - }) - local row = mk({ parent = c, width = "100%", - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 10 * m.s }) - if status == "downloading" then - label(row, Strings("Downloading update"), 13 * m.s + 1, C("detail"), - { flex = 1 }) - progressBar(c, st.progress, "gold", math.max(8, 10 * m.s)) - else - local msg, btnLabel, action - if status == "available" then - msg = st.latest and (Strings("Update v") .. st.latest .. Strings(" available")) - or Strings("An update is available") - btnLabel = Strings("Update") - action = function() pcall(imp.Check.download) end - elseif status == "needs_full" then - msg = Strings("A new version needs a fresh download") - btnLabel = Strings("Open releases") - action = function() love.system.openURL(imp.Check.releaseUrl()) end - else - msg = Strings("Update downloaded") - btnLabel = Strings("Restart to update") - action = function() require("src.core.HostShell").restart() end - end - label(row, msg, 13 * m.s + 1, C("white"), { flex = 1 }) - button(imp, row, "updater", btnLabel, { - h = m.btnH, size = 13 * m.s, kind = "primary", action = action, - }) - end -end +-- ------------------------------------------------------------------ footer local TRUST_WARNING = "if you did not get this from bryanthaboi's github " .. "or a link from the discord that bryanthaboi himself posted, just know " .. "it might have been tampered with. go to the discord to verify " .. COMMUNITY_URL .. " (or click the logo above)" -local function buildFooter(imp, parent, m) - mk({ parent = parent, width = "100%", height = 1, - backgroundColor = C("border", 0.18) }) - -- The BCG mark is dark ink; invert it to white for the dark panel. +-- Pinned to the bottom of the window; returns the y it starts at, so the +-- panels above know how much room they have. +-- Deliberately compact: at a large UI scale the footer is pure overhead +-- competing with the panel for a short window's height, so the mark and the +-- link share one line and the trust warning is capped at a single line. +local function footerHeight(imp, m) + -- Top pad + mark/update row + gap + the FULL wrapped trust message + + -- bottom pad. The message wraps to as many lines as it needs: truncating + -- a trust warning defeats its purpose, and the bottom pad is not optional + -- either (without it the last line sits flush on the window edge and its + -- lower half clips off). The row is tapMin tall because the small update + -- button rides beside the mark. + local rowH = math.max(math.floor(22 * m.s), Kit.tapMin()) + return math.floor(8 * m.s) + rowH + math.floor(6 * m.s) + + Kit.wrapHeight("micro", TRUST_WARNING, m.contentW) + + math.floor(8 * m.s) +end + +local function buildFooter(imp, m, y) + Theme.fill(m.x, y, m.w, 1, PAL.line, Theme.A.hairline) + local cy = y + math.floor(8 * m.s) + -- The BCG mark is dark ink; invert it for the black field. imp.invertShader = imp.invertShader or love.graphics.newShader([[ vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) { vec4 p = Texel(tex, tc); @@ -1664,285 +1514,803 @@ local function buildFooter(imp, parent, m) } ]]) local bw, bh = imp.bcg:getDimensions() - local scale = math.min((180 * m.s) / bw, (44 * m.s) / bh) - mk({ - parent = parent, width = bw * scale, height = bh * scale, - alignSelf = "center", - customDraw = function(el) - love.graphics.setShader(imp.invertShader) - love.graphics.setColor(1, 1, 1, imp._hot.bcg and 1 or 0.85) - love.graphics.draw(imp.bcg, el.x, el.y, 0, - el.width / bw, el.height / bh) - love.graphics.setShader() - end, - onEvent = handler(imp, "bcg", function() - love.system.openURL(COMMUNITY_URL) - end), - }) - label(parent, TRUST_WARNING, 10 * m.s + 2, C("warn"), - { width = "100%", textAlign = "center" }) - -- the link gets a full-width, center-aligned row of its own: alignSelf on - -- an auto-width label was not honored and left it hugging the margin - local linkRow = mk({ parent = parent, width = "100%", - positioning = "flex", justifyContent = "center", - padding = { bottom = 18 } }) - label(linkRow, COMMUNITY_URL, 11 * m.s + 2, - C("link", imp._hot.bois and 1 or 0.85), { - textWrap = false, - onEvent = handler(imp, "bois", function() - love.system.openURL(COMMUNITY_URL) - end), - }) + local scale = math.min((130 * m.s) / bw, (22 * m.s) / bh) + local dw, dh = bw * scale, bh * scale + local rowH = math.max(math.floor(22 * m.s), Kit.tapMin()) + -- The mark and the small self-update control share the row, centred as a + -- group. The updater moved down here from the header, where it overlapped + -- the wordmark on a phone; small on purpose, its glow still carries the + -- "act on me" signal. + local upStatus, upLabel, upAction, upGlow = LauncherView._updateControl(imp) + -- Kit.button insets its label 16*scale per side, so the width must budget + -- more than that or the label ellipsizes ("Check for updat..."). + local uw = upStatus + and (Kit.textWidth("micro", upLabel) + math.floor(36 * m.s)) or 0 + local groupW = dw + (upStatus and (math.floor(10 * m.s) + uw) or 0) + local bx = m.x + math.floor((m.w - groupW) / 2) + local my = cy + math.floor((rowH - dh) / 2) + local hot = Kit.hover(bx, my, dw, dh) + love.graphics.setShader(imp.invertShader) + love.graphics.setColor(1, 1, 1, hot and 1 or 0.85) + love.graphics.draw(imp.bcg, Theme.snap(bx), Theme.snap(my), 0, scale, scale) + love.graphics.setShader() + love.graphics.setColor(1, 1, 1, 1) + if Kit.press(bx, my, dw, dh) then + queueAction(imp, "bcg", function() love.system.openURL(COMMUNITY_URL) end) + end + if upStatus then + btn(imp, bx + dw + math.floor(10 * m.s), cy, uw, rowH, "updater", + upLabel, { + kind = upGlow and "warn" or "ghost", font = "micro", + glow = upGlow, action = upAction, + }) + end + cy = cy + rowH + math.floor(6 * m.s) + -- The trust message wraps in full, each line centred under the mark, and + -- the URL inside it IS the link -- no separate link floating elsewhere. + -- font:getWrap never splits an unspaced word, so the URL stays whole on + -- one line and a plain substring find locates it. + local lines = Kit.wrapLines("micro", TRUST_WARNING, m.contentW) + local lh = Kit.textHeight("micro") + for i, line in ipairs(lines or {}) do + local lw = Kit.textWidth("micro", line) + local lx = m.contentX + math.floor((m.contentW - lw) / 2) + local ly = cy + (i - 1) * lh + local s0, e0 = line:find(COMMUNITY_URL, 1, true) + if s0 then + local pre = line:sub(1, s0 - 1) + local url = line:sub(s0, e0) + Kit.text("micro", pre, lx, ly, PAL.muted) + local ux = lx + Kit.textWidth("micro", pre) + local uw = Kit.textWidth("micro", url) + Kit.text("micro", url, ux, ly, PAL.blue) + Theme.fill(ux, ly + lh - 1, uw, 1, PAL.blue, 0.6) + if Kit.press(ux, ly, uw, lh) then + queueAction(imp, "bois", function() + love.system.openURL(COMMUNITY_URL) + end) + end + Kit.text("micro", line:sub(e0 + 1), ux + uw, ly, PAL.muted) + else + Kit.text("micro", line, lx, ly, PAL.muted) + end + end end --- ------- modals +-- ------------------------------------------------------------------ modals +-- A modal draws its own scrim, then raises Kit.blockClicks so everything +-- underneath is inert, then lowers it for its own panel. There is no +-- z-ordered hit test, so this ordering IS the z-order. -local function modalOverlay(imp, m, closeKey, onClose) - local overlay = mk({ - z = 1000, - x = 0, y = 0, width = m.W, height = m.H, - backgroundColor = rgba(4, 6, 16, 0.72), - positioning = "flex", justifyContent = "center", alignItems = "center", - onEvent = onClose and handler(imp, closeKey, onClose) or function() end, - }) - return overlay +local function modalPanel(m, w, h) + -- A near-opaque scrim, not a tint. At 0.82 the header and the wordmark + -- still read through the settings panel and the screen looked like two + -- layouts fighting rather than one panel on top ("the settings is covering + -- the logo"); at this weight the page behind is present but plainly out of + -- play, which is what a modal is supposed to say. + Theme.fill(0, 0, m.W, m.H, PAL.bg, 0.93) + Kit.blockClicks = true + local pw = math.floor(math.min(w, m.W - 2 * m.pad)) + local ph = math.floor(math.min(h, m.H - 2 * m.pad)) + local px = math.floor((m.W - pw) / 2) + local py = math.floor((m.H - ph) / 2) + Kit.card(px, py, pw, ph, true) + Kit.blockClicks = false + return px, py, pw, ph end -local function modalPanel(overlay, m, w, props) - local p = { - parent = overlay, - width = math.min(w, m.W - 24), - backgroundColor = rgba(12, 17, 38, 0.98), - border = 1, borderColor = C("border", 0.5), - cornerRadius = 12, - positioning = "flex", flexDirection = "vertical", - gap = 10 * m.s, padding = { horizontal = 16, vertical = 14 }, - -- swallow clicks so the overlay's close handler stays outside the panel - onEvent = function() end, - } - for k, v in pairs(props or {}) do p[k] = v end - return mk(p) -end - --- Shared prompt: title, hand-rolled text field, hint, action row. +-- Shared prompt: title, read-only field over the importer's text, buttons. local function buildPrompt(imp, m, spec) - local overlay = modalOverlay(imp, m, spec.key .. "-out") - local panel = modalPanel(overlay, m, spec.w or 460 * m.s) - label(panel, spec.title, 15 * m.s + 2, C("white")) + local pad = math.floor(18 * m.s) + local fieldH = math.max(Kit.tapMin(), math.floor(36 * m.s)) + local w = math.floor(460 * m.s) + local hintH = spec.hint and (Kit.wrapHeight("small", spec.hint, + w - 2 * pad, 2) + math.floor(6 * m.s)) or 0 + local footH = spec.footnote and (Kit.textHeight("micro") + + math.floor(8 * m.s)) or 0 + local h = pad + Kit.textHeight("button") + math.floor(10 * m.s) + hintH + + fieldH + math.floor(12 * m.s) + m.btnH + footH + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", spec.title, px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(10 * m.s) if spec.hint then - label(panel, spec.hint, 12 * m.s + 1, C("detail")) + cy = cy + Kit.textWrapped("small", spec.hint, px + pad, cy, + pw - 2 * pad, PAL.detail, 2) + math.floor(6 * m.s) end - textField(imp, panel, spec.key .. "-field", spec.text or "", nil, true) - local btnRow = mk({ parent = panel, width = "100%", - positioning = "flex", flexDirection = "horizontal", - justifyContent = "flex-end", gap = 8 * m.s }) + textField(imp, px + pad, cy, pw - 2 * pad, fieldH, spec.key .. "-field", + spec.text or "", nil, true) + cy = cy + fieldH + math.floor(12 * m.s) + + local place = Layout.rightCluster(px + pad, pw - 2 * pad, math.floor(8 * m.s)) + local okW = Kit.textWidth("small", spec.okLabel or Strings("Save")) + + math.floor(28 * m.s) + btn(imp, place(okW), cy, okW, m.btnH, spec.key .. "-ok", + spec.okLabel or Strings("Save"), + { kind = "primary", font = "small", action = spec.commit }) + local cw = Kit.textWidth("small", Strings("Cancel")) + math.floor(28 * m.s) + btn(imp, place(cw), cy, cw, m.btnH, spec.key .. "-cancel", Strings("Cancel"), + { font = "small", action = spec.cancel }) if spec.paste then - button(imp, btnRow, spec.key .. "-paste", Strings("Paste"), { - size = 12 * m.s + 1, kind = "accent", action = spec.paste, - }) - mk({ parent = btnRow, flex = 1 }) + local pwid = Kit.textWidth("small", Strings("Paste")) + math.floor(28 * m.s) + btn(imp, px + pad, cy, pwid, m.btnH, spec.key .. "-paste", Strings("Paste"), + { kind = "accent", font = "small", action = spec.paste }) end - button(imp, btnRow, spec.key .. "-cancel", Strings("Cancel"), { - size = 12 * m.s + 1, kind = "neutral", action = spec.cancel, - }) - button(imp, btnRow, spec.key .. "-ok", spec.okLabel or Strings("Save"), { - size = 12 * m.s + 1, kind = "primary", action = spec.commit, - }) + cy = cy + m.btnH + math.floor(8 * m.s) if spec.footnote then - label(panel, spec.footnote, 11 * m.s + 1, C("warn")) + Kit.text("micro", spec.footnote, px + pad, cy, PAL.muted) end end local function buildConfirmModal(imp, m) local c = imp._modConfirm - local overlay = modalOverlay(imp, m, "confirm-out") - local panel = modalPanel(overlay, m, 420 * m.s) - label(panel, c.title or Strings("Confirm"), 15 * m.s + 2, C("white")) + local pad = math.floor(22 * m.s) + local w = math.floor(520 * m.s) + local lineH = Kit.textHeight("small") + math.floor(4 * m.s) + local h = pad + Kit.textHeight("stat") + math.floor(12 * m.s) + + #(c.lines or {}) * lineH + math.floor(12 * m.s) + m.btnH + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("stat", c.title or Strings("Confirm"), px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("stat") + math.floor(12 * m.s) for _, line in ipairs(c.lines or {}) do - label(panel, line, 12 * m.s + 1, C("detail")) + Kit.text("small", Kit.ellipsize("small", line, pw - 2 * pad), + px + pad, cy, PAL.detail) + cy = cy + lineH end - local btnRow = mk({ parent = panel, width = "100%", - positioning = "flex", flexDirection = "horizontal", gap = 10 * m.s }) - button(imp, btnRow, "confirm-yes", c.yesLabel or Strings("OK"), { - flex = 1, h = m.btnH, size = 13 * m.s + 1, kind = "primary", - action = function() - imp._modConfirm = nil - if c.indexEntry then - imp:_findInstall(c.indexEntry) - elseif c.kind == "update" then - imp:_confirmModUpdate(c.id, c.release) - elseif c.kind == "enableAll" then - imp:_setAllMods(true, true) - else - imp:_toggleMod(c.id, true) - end - end, - }) - button(imp, btnRow, "confirm-no", Strings("Cancel"), { - flex = 1, h = m.btnH, size = 13 * m.s + 1, kind = "neutral", - action = function() imp._modConfirm = nil end, - }) + cy = cy + math.floor(12 * m.s) + local gap = math.floor(10 * m.s) + local halfW = math.floor((pw - 2 * pad - gap) / 2) + btn(imp, px + pad, cy, halfW, m.btnH, "confirm-yes", + c.yesLabel or Strings("OK"), { + kind = "primary", font = "small", + action = function() + imp._modConfirm = nil + if c.indexEntry then + imp:_findInstall(c.indexEntry) + elseif c.kind == "update" then + imp:_confirmModUpdate(c.id, c.release) + elseif c.kind == "enableAll" then + imp:_setAllMods(true, true) + elseif c.kind == "importOversize" then + imp:_importSave(c.version, c.source, true) + else + imp:_toggleMod(c.id, true) + end + end, + }) + btn(imp, px + pad + halfW + gap, cy, halfW, m.btnH, "confirm-no", + Strings("Cancel"), { font = "small", + action = function() imp._modConfirm = nil end }) end -local function buildTextModal(imp, m, title, body, closeFn, scrollId) - local overlay = modalOverlay(imp, m, "textmodal-out") - local panel = modalPanel(overlay, m, 520 * m.s) - label(panel, title, 15 * m.s + 2, C("white")) - local scroller = mk({ - parent = panel, id = scrollId, width = "100%", - height = math.min(m.H * 0.5, 340 * m.s), - overflowY = "scroll", hideScrollbars = true, - positioning = "flex", flexDirection = "vertical", - padding = { right = 8 }, - }) - label(scroller, body, 12 * m.s + 1, C("detail"), { width = "100%" }) - button(imp, panel, "textmodal-close", Strings("Close"), { - w = "100%", h = m.btnH, size = 13 * m.s, kind = "neutral", - action = closeFn, - }) +-- A body of text, paginated rather than scrolled (release notes, mod +-- descriptions). Long-form text is the one place a scrollbar was genuinely +-- convenient, so the pager here moves a LINE window instead of a row window. +local function buildTextModal(imp, m, key, title, body, closeFn) + local pad = math.floor(18 * m.s) + local w = math.floor(520 * m.s) + local h = math.floor(math.min(m.H - 2 * m.pad, 460 * m.s)) + local px, py, pw, ph = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Kit.ellipsize("button", title, pw - 2 * pad), + px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(10 * m.s) + + local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local bodyH = (py + ph - pad) - cy - m.btnH - math.floor(10 * m.s) + - pagerH - math.floor(8 * m.s) + local lineH = Kit.textHeight("small") + local perPage = math.max(1, math.floor(bodyH / lineH)) + local lines = Kit.wrapLines("small", body, pw - 2 * pad) or { "" } + local first, last, cur = Kit.pageBounds(page(imp, key), #lines, perPage) + setPage(imp, key, cur) + setPage(imp, key, Kit.wheelPage(px, cy, pw, bodyH, cur, #lines, perPage)) + for i = first, last do + Kit.text("small", lines[i], px + pad, cy + (i - first) * lineH, PAL.detail) + end + cy = cy + bodyH + math.floor(8 * m.s) + setPage(imp, key, Kit.pager(px + pad, cy, pw - 2 * pad, cur, #lines, + perPage, key)) + cy = cy + pagerH + math.floor(10 * m.s) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, key .. "-close", + Strings("Close"), { font = "small", action = closeFn }) end local function buildVersionsModal(imp, m) local ModUpdate = require("src.mods.ModUpdate") local v = imp._modVersions - local overlay = modalOverlay(imp, m, "versions-out") - local panel = modalPanel(overlay, m, 520 * m.s) - label(panel, Strings("Other versions: ") .. tostring(v.name), - 15 * m.s + 2, C("white")) + local pad = math.floor(18 * m.s) + local w = math.floor(520 * m.s) + local h = math.floor(math.min(m.H - 2 * m.pad, 480 * m.s)) + local px, py, pw, ph = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Kit.ellipsize("button", + Strings("Other versions: ") .. tostring(v.name), pw - 2 * pad), + px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(6 * m.s) + local info = imp:_modUpdateInfo(v.id) local statusTxt = Strings("Installed: v") .. tostring(v.current) - local statusCol = "detail" + local statusCol = PAL.detail if info and info.status == "available" then statusTxt = statusTxt .. " - " .. Strings("Update v") .. tostring(info.latest) - statusCol = "green" + statusCol = PAL.yellow elseif info and info.status == "current" then statusTxt = statusTxt .. " - " .. Strings("Up to date") - statusCol = "green" + statusCol = PAL.green end - label(panel, statusTxt, 12 * m.s + 1, C(statusCol)) - local scroller = mk({ - parent = panel, id = "modversions", width = "100%", - height = math.min(m.H * 0.5, 320 * m.s), overflowY = "scroll", hideScrollbars = true, - positioning = "flex", flexDirection = "vertical", gap = 6 * m.s, - padding = { right = 8 }, - }) - for i, rel in ipairs(v.releases) do - local row = mk({ parent = scroller, width = "100%", - backgroundColor = C("bg", 0.5), - border = 1, borderColor = C("border", 0.35), cornerRadius = 8, - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 8 * m.s, - padding = { horizontal = 10, vertical = 8 } }) + Kit.text("small", statusTxt, px + pad, cy, statusCol) + cy = cy + Kit.textHeight("small") + math.floor(10 * m.s) + + local chipH = math.max(Kit.tapMin(), math.floor(28 * m.s)) + local rowH = math.floor(8 * m.s) + Kit.textHeight("small") + + math.floor(4 * m.s) + chipH + math.floor(8 * m.s) + local gap = math.floor(6 * m.s) + local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local listH = (py + ph - pad) - cy - m.btnH - math.floor(10 * m.s) + - pagerH - math.floor(8 * m.s) + local perPage = Kit.rowsThatFit(listH, rowH, gap, 1, 12) + local n = #v.releases + local first, last, cur = Kit.pageBounds(page(imp, "versions"), n, perPage) + setPage(imp, "versions", cur) + setPage(imp, "versions", + Kit.wheelPage(px, cy, pw, listH, cur, n, perPage)) + + for i = first, last do + local rel = v.releases[i] + local ry = cy + (i - first) * (rowH + gap) + Theme.stroke(px + pad, ry, pw - 2 * pad, rowH, PAL.line, Theme.A.hairline, 1) + local ix = px + pad + math.floor(10 * m.s) + local inner = pw - 2 * pad - math.floor(20 * m.s) local text = "v" .. rel.version if rel.version == v.current then text = text .. Strings(" (installed)") end if rel.prerelease then text = text .. " pre" end - local body = mk({ parent = row, flex = 1, - positioning = "flex", flexDirection = "vertical", gap = 2 * m.s }) - label(body, text, 12 * m.s + 1, - rel.version == v.current and C("warn") or C("white"), - { textWrap = false }) + Kit.text("small", text, ix, ry + math.floor(8 * m.s), + rel.version == v.current and PAL.yellow or PAL.heading) local preview = ModUpdate.previewLine(rel.body or "", 90) if preview ~= "" then - label(body, preview, 11 * m.s + 1, C("detail"), - { textWrap = false, textOverflow = "ellipsis" }) + Kit.text("micro", Kit.ellipsize("micro", preview, + inner - math.floor(180 * m.s)), + ix + Kit.textWidth("small", text) + math.floor(10 * m.s), + ry + math.floor(8 * m.s), PAL.muted) + end + local ly = ry + math.floor(8 * m.s) + Kit.textHeight("small") + + math.floor(4 * m.s) + local place = Layout.rightCluster(ix, inner, math.floor(6 * m.s)) + if rel.version ~= v.current then + local iw5 = Kit.textWidth("small", Strings("Install")) + math.floor(20 * m.s) + btn(imp, place(iw5), ly, iw5, chipH, "ver-inst-" .. i, Strings("Install"), { + kind = "accent", font = "small", + action = function() imp:_installModVersion(v.id, rel) end }) end if type(rel.body) == "string" and rel.body:match("%S") then - button(imp, row, "ver-notes-" .. i, Strings("Read more"), { - size = 11 * m.s, kind = "neutral", + local rw = Kit.textWidth("small", Strings("Read more")) + math.floor(20 * m.s) + btn(imp, place(rw), ly, rw, chipH, "ver-notes-" .. i, Strings("Read more"), { + kind = "accent", font = "small", action = function() - imp._modReleaseNotes = { version = rel.version, - body = rel.body or "", scroll = 0 } - end, - }) - end - if rel.version ~= v.current then - button(imp, row, "ver-inst-" .. i, Strings("Install"), { - size = 11 * m.s, kind = "accent", - action = function() imp:_installModVersion(v.id, rel) end, - }) + imp._modReleaseNotes = { version = rel.version, body = rel.body or "" } + end }) end end - button(imp, panel, "versions-close", Strings("Close"), { - w = "100%", h = m.btnH, size = 13 * m.s, kind = "neutral", - action = function() imp._modVersions = nil end, - }) + cy = cy + listH + math.floor(8 * m.s) + setPage(imp, "versions", + Kit.pager(px + pad, cy, pw - 2 * pad, cur, n, perPage, "versions")) + cy = cy + pagerH + math.floor(10 * m.s) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "versions-close", + Strings("Close"), { font = "small", + action = function() imp._modVersions = nil end }) +end + +-- Sort chooser, shared by the MODS and FIND MODS tabs (they share the +-- persisted key, so one popup serves both). +local function buildSortModal(imp, m) + local defs = sortDefs() + local pad = math.floor(18 * m.s) + local w = math.floor(360 * m.s) + local gap = math.floor(8 * m.s) + local h = pad + Kit.textHeight("button") + math.floor(12 * m.s) + + #defs * (m.btnH + gap) + m.btnH + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Strings("Sort by"), px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(12 * m.s) + local cur = currentSort(imp) + for _, s in ipairs(defs) do + local key = s.key + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "sortpop-" .. key, s.label, { + kind = (cur == key) and "primary" or "ghost", font = "small", + action = function() + imp.modSort = key + imp._sortPopup = nil + pcall(function() + local SaveData = require("src.core.SaveData") + local opts = SaveData.loadOptions() + opts.modSort = key + SaveData.saveOptions(opts) + end) + end }) + cy = cy + m.btnH + gap + end + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "sortpop-close", + Strings("Close"), { font = "small", + action = function() imp._sortPopup = nil end }) +end + +-- Category filter for FIND MODS. Two columns, because an index can list +-- enough categories to overflow a single stacked column on a short window. +local function buildFilterModal(imp, m) + local cats = (imp.findIndex and imp.findIndex.categories) or {} + local items = { { key = nil, label = Strings("All") } } + for _, c in ipairs(cats) do items[#items + 1] = { key = c, label = c } end + local pad = math.floor(18 * m.s) + local w = math.floor(440 * m.s) + local gap = math.floor(8 * m.s) + local nrows = math.ceil(#items / 2) + local h = pad + Kit.textHeight("button") + math.floor(12 * m.s) + + nrows * (m.btnH + gap) + m.btnH + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Strings("Filter by category"), px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(12 * m.s) + local colW = math.floor((pw - 2 * pad - gap) / 2) + for i, it in ipairs(items) do + local bx = px + pad + ((i - 1) % 2) * (colW + gap) + local by = cy + math.floor((i - 1) / 2) * (m.btnH + gap) + local key = it.key + btn(imp, bx, by, colW, m.btnH, "filterpop-" .. (key or "all"), it.label, { + kind = (imp.findCategory == key) and "primary" or "ghost", + font = "small", + action = function() + imp.findCategory = key + setPage(imp, "find", 1) + imp._filterPopup = nil + end }) + end + cy = cy + nrows * (m.btnH + gap) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "filterpop-close", + Strings("Close"), { font = "small", + action = function() imp._filterPopup = nil end }) +end + +-- Index manager: every source with its Remove, plus Add and Refresh all. +-- This replaces both the old always-visible source rows above the search +-- field and the lone "Add index" header button. +local function buildIndexesModal(imp, m) + local sources = imp.findSources or {} + local pad = math.floor(18 * m.s) + local w = math.floor(520 * m.s) + local gap = math.floor(6 * m.s) + local rowH = math.max(Kit.tapMin(), math.floor(34 * m.s)) + local listH = (#sources > 0) and #sources * (rowH + gap) + or (Kit.textHeight("small") + gap) + local h = pad + Kit.textHeight("button") + math.floor(12 * m.s) + listH + + math.floor(6 * m.s) + 3 * (m.btnH + math.floor(8 * m.s)) + - math.floor(8 * m.s) + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Strings("Mod indexes"), px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(12 * m.s) + if #sources == 0 then + Kit.text("small", Strings("No index added yet."), px + pad, cy, PAL.muted) + cy = cy + Kit.textHeight("small") + gap + else + for _, source in ipairs(sources) do + local feed = source.feed + local rmW = Kit.textWidth("small", Strings("Remove")) + + math.floor(20 * m.s) + Kit.text("small", Kit.ellipsize("small", source.label or feed, + pw - 2 * pad - rmW - math.floor(12 * m.s)), px + pad, + cy + (rowH - Kit.textHeight("small")) / 2, PAL.detail) + btn(imp, px + pw - pad - rmW, cy, rmW, rowH, + "idx-rm-" .. tostring(feed), Strings("Remove"), { + kind = "danger", font = "small", + action = function() imp:_removeIndex(feed) end }) + cy = cy + rowH + gap + end + end + cy = cy + math.floor(6 * m.s) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "idx-add", + Strings("Add index"), { kind = "accent", font = "small", + action = function() imp:_promptAddIndex() end }) + cy = cy + m.btnH + math.floor(8 * m.s) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "idx-refresh", + Strings("Refresh all"), { + kind = "accent", font = "small", enabled = #sources > 0, + action = function() + imp._findSearchFocus = false + imp:_disarmTextInput() + imp:_refreshFind(true) + end }) + cy = cy + m.btnH + math.floor(8 * m.s) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "idx-close", + Strings("Close"), { font = "small", + action = function() imp._indexManage = nil end }) +end + +-- Per-mod actions for the MODS tab: the row itself only carries the enable +-- toggle, everything episodic (update check, versions, delete) lives here. +local function buildModActionsModal(imp, m) + local mod + for _, mm in ipairs(imp.mods or {}) do + if mm.id == imp._modActions then mod = mm break end + end + if not mod then imp._modActions = nil return end + local hasGit = mod.github and mod.github ~= "" + local info = hasGit and imp:_modUpdateInfo(mod.id) + local pad = math.floor(18 * m.s) + local w = math.floor(440 * m.s) + local gap = math.floor(8 * m.s) + local nBtns = (hasGit and 2 or 0) + 2 + local h = pad + Kit.textHeight("button") + math.floor(4 * m.s) + + Kit.textHeight("small") + math.floor(12 * m.s) + + nBtns * (m.btnH + gap) - gap + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Kit.ellipsize("button", mod.name, pw - 2 * pad), + px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(4 * m.s) + local statusText, statusCol = modStatusColor(mod.status) + local line = "v" .. tostring(mod.version or "?") .. " " .. statusText + if info and info.status == "available" then + line = line .. " " .. Strings("v%s available", tostring(info.latest)) + elseif info and info.status == "current" then + line = line .. " " .. Strings("up to date") + end + Kit.text("small", Kit.ellipsize("small", line, pw - 2 * pad), + px + pad, cy, statusCol) + cy = cy + Kit.textHeight("small") + math.floor(12 * m.s) + local id = mod.id + if hasGit then + local updLabel, updKind = Strings("Check for updates"), "ghost" + if info and info.status == "available" then + updLabel, updKind = Strings("Update"), "warn" + elseif info and info.status == "current" then + updLabel = Strings("Check again") + end + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "modact-upd", updLabel, { + kind = updKind, font = "small", + action = function() imp:_modGithubAction(id, "update") end }) + cy = cy + m.btnH + gap + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "modact-ver", + Strings("Versions"), { kind = "accent", font = "small", + action = function() imp:_modGithubAction(id, "versions") end }) + cy = cy + m.btnH + gap + end + local armed = deleteArmed(imp, "mod", id, nil) + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "modact-del", + DELETE_LABEL(armed), { + kind = "danger", font = "small", keepArm = true, + action = function() + imp:pressDelete("mod", id, nil, function() + imp:_deleteMod(id) + imp._modActions = nil + end) + end }) + cy = cy + m.btnH + gap + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "modact-close", + Strings("Close"), { font = "small", + action = function() imp._modActions = nil end }) +end + +-- Per-mod popup for FIND MODS: the row is a plain click, and Install / +-- Details / Source live here instead of crowding every row. +local function buildFindEntryModal(imp, m) + local ModIndex = require("src.mods.ModIndex") + local ModUpdate = require("src.mods.ModUpdate") + local entry = imp._findEntry + local installed = imp:_findInstalledMap() + local action, note = findActionFor(entry, installed[entry.id]) + local pad = math.floor(18 * m.s) + local w = math.floor(460 * m.s) + local gap = math.floor(8 * m.s) + local nBtns = 3 -- install row, details/source row, close row + local noteH = note and (Kit.textHeight("small") + math.floor(4 * m.s)) or 0 + local h = pad + Kit.textHeight("button") + math.floor(4 * m.s) + + Kit.textHeight("small") + noteH + math.floor(12 * m.s) + + nBtns * (m.btnH + gap) - gap + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + Kit.text("button", Kit.ellipsize("button", entry.title or entry.id, + pw - 2 * pad), px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(4 * m.s) + local stats = imp:_findStats(entry) + local lead = "v" .. tostring(ModIndex.displayVersion(entry)) + if entry.author then lead = lead .. " - " .. entry.author end + if entry.categories and entry.categories[1] then + lead = lead .. " - " .. entry.categories[1] + end + local dl = stats and ModUpdate.downloadsLine(stats.total) or nil + local segs = { { lead, PAL.detail } } + if dl then segs[#segs + 1] = { " - " .. dl, PAL.green } end + segLine("small", segs, px + pad, cy, pw - 2 * pad) + cy = cy + Kit.textHeight("small") + if note then + cy = cy + math.floor(4 * m.s) + Kit.text("small", note, px + pad, cy, PAL.green) + cy = cy + Kit.textHeight("small") + end + cy = cy + math.floor(12 * m.s) + if action then + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "findpop-inst", action, { + kind = "primary", font = "small", + action = function() + imp._findEntry = nil + imp:_findConfirmInstall(entry) + end }) + else + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "findpop-inst", + Strings("Not installable from this index"), + { font = "small", enabled = false }) + end + cy = cy + m.btnH + gap + local half = entry.repo and math.floor((pw - 2 * pad - gap) / 2) + or (pw - 2 * pad) + btn(imp, px + pad, cy, half, m.btnH, "findpop-det", Strings("Details"), { + kind = "accent", font = "small", + action = function() imp:_findShowDetails(entry) end }) + if entry.repo then + local repo = entry.repo + btn(imp, px + pad + half + gap, cy, half, m.btnH, "findpop-src", + Strings("Source"), { kind = "accent", font = "small", + action = function() love.system.openURL(repo) end }) + end + cy = cy + m.btnH + gap + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "findpop-close", + Strings("Close"), { font = "small", + action = function() imp._findEntry = nil end }) +end + +-- Per-game file management, behind the manage button beside Play. A ready +-- game's panel is Play and its saves; everything episodic about the FILES -- +-- swapping the ROM out, finding them on disk -- lives here instead of taking +-- two permanent buttons out of a column that has to fit on a phone. +local function buildGameManageModal(imp, m) + local version = imp._gameManage + local info = GameVersion.info(version) + local ready = imp.ready[version] or false + local mdl = romModel(imp, version, info, ready, info == nil) + local gameName = info and (info.launcherName or info.displayName) + or tostring(version) + local saveDir = love.filesystem.getSaveDirectory + and love.filesystem.getSaveDirectory() or nil + -- The folder link is desktop-only: Android and NX have no browsable path to + -- open, and both already print their own transfer hint on the slot card. + local canOpenFolder = saveDir and not imp.android and not imp.isNX + + local pad = math.floor(18 * m.s) + local w = math.floor(460 * m.s) + local gap = math.floor(8 * m.s) + local bodyW = w - 2 * pad + local detailH = Kit.wrapHeight("small", + mdl.detail or Strings("The ROM for this game is imported and verified."), + bodyW, 3) + local pathH = saveDir + and (Kit.textHeight("micro") + math.floor(8 * m.s)) or 0 + local nBtns = 1 + (canOpenFolder and 1 or 0) + 1 + local h = pad + Kit.textHeight("button") + math.floor(8 * m.s) + detailH + + math.floor(12 * m.s) + pathH + nBtns * (m.btnH + gap) - gap + pad + local px, py, pw = modalPanel(m, w, h) + local cy = py + pad + + Kit.text("button", Kit.ellipsize("button", + Strings("Manage ") .. gameName, pw - 2 * pad), px + pad, cy, PAL.heading) + cy = cy + Kit.textHeight("button") + math.floor(8 * m.s) + cy = cy + Kit.textWrapped("small", + mdl.detail or Strings("The ROM for this game is imported and verified."), + px + pad, cy, pw - 2 * pad, mdl.state and PAL.detail or PAL.green, 3) + cy = cy + math.floor(12 * m.s) + if saveDir then + -- Truncated from the LEFT: the tail of a save path is the part that + -- identifies it. + Kit.text("micro", Kit.ellipsizeLeft("micro", saveDir, pw - 2 * pad), + px + pad, cy, PAL.faint) + cy = cy + Kit.textHeight("micro") + math.floor(8 * m.s) + end + + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "manage-rom", + mdl.label or Strings("Re-import ROM"), { + kind = "accent", font = "small", enabled = mdl.enabled ~= false, + action = (mdl.enabled ~= false) and function() + imp._gameManage = nil + local fn = romAction(imp, version, mdl) + if fn then fn() end + end or nil }) + cy = cy + m.btnH + gap + if canOpenFolder then + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "manage-folder", + Strings("Open folder"), { kind = "accent", font = "small", + action = function() love.system.openURL(imp:fileUrl(saveDir)) end }) + cy = cy + m.btnH + gap + end + btn(imp, px + pad, cy, pw - 2 * pad, m.btnH, "manage-close", + Strings("Close"), { font = "small", + action = function() imp._gameManage = nil end }) end local function buildSettingsModal(imp, m) local model = imp._settings - local overlay = modalOverlay(imp, m, "settings-out") - local panel = modalPanel(overlay, m, 640 * m.s, { - height = math.min(m.H - 40, m.H * 0.88), - }) - local head = mk({ parent = panel, width = "100%", - positioning = "flex", flexDirection = "horizontal", - justifyContent = "space-between", alignItems = "center" }) - label(head, Strings("Settings"), 17 * m.s + 3, C("white"), { textWrap = false }) - button(imp, head, "settings-close", Strings("Close"), { - size = 12 * m.s + 1, kind = "neutral", - action = function() imp:_closeSettings() end, - }) - label(panel, Strings( + local pad = math.floor(18 * m.s) + local w = math.floor(640 * m.s) + local h = math.floor(math.min(m.H - 2 * m.pad, m.H * 0.9)) + local px, py, pw, ph = modalPanel(m, w, h) + local cy = py + pad + + Kit.text("stat", Strings("Settings"), px + pad, cy, PAL.heading) + local cw = Kit.textWidth("small", Strings("Close")) + math.floor(24 * m.s) + btn(imp, px + pw - pad - cw, cy, cw, m.btnH, "settings-close", + Strings("Close"), { font = "small", + action = function() imp:_closeSettings() end }) + cy = cy + math.max(Kit.textHeight("stat"), m.btnH) + math.floor(6 * m.s) + -- WRAPPED, not printed flat: on a portrait panel this line ran straight off + -- the right edge and the sentence ended mid-word at the card border. + cy = cy + Kit.textWrapped("micro", Strings( "Saved to your options file; the game applies these on its next start."), - 11 * m.s + 2, C("warn")) - local scroller = mk({ - parent = panel, id = "settings-scroll", width = "100%", - flex = 1, overflowY = "scroll", hideScrollbars = true, - positioning = "flex", flexDirection = "vertical", gap = 6 * m.s, - padding = { right = 8 }, - }) - for si, section in ipairs(model.sections) do - label(scroller, section.title, 12 * m.s + 2, C("gray"), { - width = "100%", - margin = { top = si == 1 and 0 or 12 }, - }) - for ri, row in ipairs(section.rows) do - local key = "set-" .. si .. "-" .. ri - local rowEl = mk({ parent = scroller, width = "100%", - backgroundColor = C("rowBg", 0.6), - border = 1, borderColor = C("border", 0.22), cornerRadius = 8, - positioning = "flex", flexDirection = "horizontal", - alignItems = "center", gap = 8 * m.s, - padding = { horizontal = 12, vertical = 8 } }) - label(rowEl, row.label, 13 * m.s + 1, C("white"), - { flex = 1, textWrap = false, textOverflow = "ellipsis" }) - if row.editText then - label(rowEl, row.value(), 13 * m.s + 1, C("detail"), { textWrap = false }) - button(imp, rowEl, key .. "-edit", Strings("Edit"), { - size = 11 * m.s + 1, kind = "accent", - action = function() - imp._settingsText = { row = row, text = tostring(row.value() or ""), - maxLen = row.editText.maxLen } - imp:_armTextInput() - end, - }) + px + pad, cy, pw - 2 * pad, PAL.muted, 2) + + math.floor(10 * m.s) + + -- Settings rows are PAGINATED, flattened across sections so a page is a + -- uniform run of rows. Section titles ride along as their own entry. + local flat = imp._settingsFlat + if not flat or flat.model ~= model then + flat = { model = model } + for _, section in ipairs(model.sections) do + flat[#flat + 1] = { header = section.title } + for _, row in ipairs(section.rows) do + flat[#flat + 1] = { row = row } + end + end + imp._settingsFlat = flat + end + -- The widest label in the whole model decides the row shape (below), so it + -- is measured once per model rather than per row per frame. Measuring the + -- WIDEST rather than each row keeps every row the same height, which is + -- what lets the list paginate off a uniform row. + if not flat.labelW or flat.labelFont ~= Kit.fonts.scale then + local widest = 0 + for _, item in ipairs(flat) do + if item.row then + widest = math.max(widest, Kit.textWidth("small", item.row.label)) + end + end + flat.labelW, flat.labelFont = widest, Kit.fonts.scale + end + + local stepW = math.floor(34 * m.s) + local valW = math.floor(140 * m.s) + local inner = pw - 2 * pad - math.floor(24 * m.s) + -- STACKED ROWS. Side by side, a row spends most of its width on the value + -- ladder and leaves the label whatever remains -- on a portrait phone that + -- was three characters and an ellipsis ("TEX...", "BAT...", "BAT..."), so + -- the panel listed a dozen settings none of which could be identified. + -- When the widest label does not fit beside its control, every row puts the + -- label on its own line ABOVE the control instead. All-or-nothing, because + -- a list that switches shape row by row is harder to scan than either form. + local stacked = flat.labelW + > (inner - 2 * stepW - valW - math.floor(24 * m.s)) + local rowH + if stacked then + rowH = Kit.textHeight("small") + math.floor(4 * m.s) + m.btnH + + math.floor(10 * m.s) + else + rowH = math.max(Kit.tapMin(), math.floor(36 * m.s)) + end + local gap = math.floor(4 * m.s) + local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) + local listH = (py + ph - pad) - cy - pagerH - math.floor(8 * m.s) + local perPage = Kit.rowsThatFit(listH, rowH, gap, 1, 24) + local n = #flat + -- POKEPORT_LAUNCHER_SETTINGS_PAGE jumps straight to a page, so a shot can + -- capture a row that is not on page one. + local wanted = tonumber(os.getenv("POKEPORT_LAUNCHER_SETTINGS_PAGE") or "") + if wanted and not imp._settingsPaged then + imp._settingsPaged = true + setPage(imp, "settings", wanted) + end + local first, last, cur = Kit.pageBounds(page(imp, "settings"), n, perPage) + setPage(imp, "settings", cur) + setPage(imp, "settings", Kit.wheelPage(px, cy, pw, listH, cur, n, perPage)) + + for i = first, last do + local item = flat[i] + local ry = cy + (i - first) * (rowH + gap) + if item.header then + Kit.caption(px + pad, ry + (rowH - Kit.textHeight("caption")) / 2, + item.header) + else + local row = item.row + local key = "set-" .. i + Theme.strokeRounded(px + pad, ry, pw - 2 * pad, rowH, PAL.line, + Theme.A.hairline, 1) + local ix = px + pad + math.floor(12 * m.s) + -- Where the label prints, and where the control band starts. Stacked: + -- label on its own full-width line, controls on the line below it. + -- Inline: both centred on one line, label left, controls right. + local labelY, ctlY, labelW + if stacked then + labelY = ry + math.floor(6 * m.s) + ctlY = labelY + Kit.textHeight("small") + math.floor(4 * m.s) + labelW = inner else - button(imp, rowEl, key .. "-prev", "<", { - size = 13 * m.s + 1, kind = "neutral", pad = { horizontal = 10, vertical = 4 }, - action = function() - if row.step and row.step(-1) then model.save() end - end, - }) - label(rowEl, row.value(), 13 * m.s + 1, C("green"), { - width = 110 * m.s, textAlign = "center", textWrap = false, - }) - button(imp, rowEl, key .. "-next", ">", { - size = 13 * m.s + 1, kind = "neutral", pad = { horizontal = 10, vertical = 4 }, - action = function() - if row.step and row.step(1) then model.save() end - end, - }) + labelY = ry + (rowH - Kit.textHeight("small")) / 2 + ctlY = ry + (rowH - m.btnH) / 2 + labelW = nil -- per-shape below: what the controls leave over + end + local rx = ix + inner + + if row.editText then + local ew = Kit.textWidth("small", Strings("Edit")) + math.floor(20 * m.s) + local vw = math.floor(160 * m.s) + Kit.text("small", Kit.ellipsize("small", row.label, + labelW or (inner - ew - vw - math.floor(20 * m.s))), + ix, labelY, PAL.text) + Kit.textRight("small", Kit.ellipsize("small", tostring(row.value()), vw), + rx - ew - math.floor(10 * m.s), + ctlY + (m.btnH - Kit.textHeight("small")) / 2, PAL.detail) + btn(imp, rx - ew, ctlY, ew, m.btnH, + key .. "-edit", Strings("Edit"), { kind = "accent", font = "small", + action = function() + imp._settingsText = { row = row, text = tostring(row.value() or ""), + maxLen = row.editText.maxLen } + imp:_armTextInput() + end }) + elseif row.action then + -- A plain action row (Reset rebinds, Touch controls): the whole right + -- side is one button rather than a value ladder. + local aw = Kit.textWidth("small", row.actionLabel or Strings("Run")) + + math.floor(24 * m.s) + Kit.text("small", Kit.ellipsize("small", row.label, + labelW or (inner - aw - math.floor(12 * m.s))), ix, labelY, PAL.text) + btn(imp, rx - aw, ctlY, aw, m.btnH, + key .. "-act", row.actionLabel or Strings("Run"), { + kind = row.danger and "danger" or "ghost", font = "small", + action = function() + if row.action() ~= false then model.save() end + end }) + else + Kit.text("small", Kit.ellipsize("small", row.label, + labelW or (inner - 2 * stepW - valW - math.floor(24 * m.s))), + ix, labelY, PAL.text) + -- Stacked rows give the value the whole span between the steppers, + -- which is where the extra width goes now that the label is not + -- competing for it. + local vw = stacked and (inner - 2 * stepW - math.floor(16 * m.s)) + or valW + btn(imp, rx - stepW, ctlY, stepW, m.btnH, + key .. "-next", ">", { font = "small", + action = function() if row.step and row.step(1) then model.save() end end }) + Kit.textCenter("small", Kit.ellipsize("small", tostring(row.value()), vw), + rx - stepW - vw, ctlY + (m.btnH - Kit.textHeight("small")) / 2, vw, + PAL.heading) + btn(imp, rx - stepW - vw - stepW, ctlY, stepW, + m.btnH, key .. "-prev", "<", { font = "small", + action = function() if row.step and row.step(-1) then model.save() end end }) end end end + cy = cy + listH + math.floor(8 * m.s) + setPage(imp, "settings", + Kit.pager(px + pad, cy, pw - 2 * pad, cur, n, perPage, "settings")) +end + +-- Whether ANY modal will draw this frame. draw() consults this BEFORE the +-- panels build: immediate mode hit-tests each control as it draws, so the +-- panels underneath a modal must run with Kit.blockClicks already raised or +-- a click on the scrim lands on whatever button happens to be behind it. +-- Keep this list in sync with buildModals below. +local function modalUp(imp) + return (imp._settingsText or imp._settings or imp._rename + or imp._indexPrompt or imp._modConfirm or imp._modReleaseNotes + or imp._findDetails or imp._modVersions or imp._sortPopup + or imp._filterPopup or imp._indexManage or imp._modActions + or imp._findEntry or imp._gameManage) ~= nil end local function buildModals(imp, m) if imp._settingsText then local st = imp._settingsText buildPrompt(imp, m, { - key = "settext", title = st.row.label, - text = st.text, + key = "settext", title = st.row.label, text = st.text, okLabel = Strings("Save"), commit = function() imp:_commitSettingsText() end, cancel = function() @@ -1951,17 +2319,13 @@ local function buildModals(imp, m) end, footnote = Strings("Enter to save - Esc to cancel"), }) - return - end - if imp._settings then - buildSettingsModal(imp, m) - return + return true end + if imp._settings then buildSettingsModal(imp, m) return true end if imp._rename then buildPrompt(imp, m, { key = "rename", title = Strings("Name save slot"), - text = imp._rename.text, - okLabel = Strings("Save"), + text = imp._rename.text, okLabel = Strings("Save"), commit = function() imp:_commitRename() end, cancel = function() imp._rename = nil @@ -1969,14 +2333,13 @@ local function buildModals(imp, m) end, footnote = Strings("Enter to save - Esc to cancel - empty clears"), }) - return + return true end if imp._indexPrompt then buildPrompt(imp, m, { key = "index", title = Strings("Add a mod index"), hint = Strings("Paste the index URL, or its owner/repo."), - text = imp._indexPrompt.text or "", - okLabel = Strings("Add"), + text = imp._indexPrompt.text or "", okLabel = Strings("Add"), commit = function() imp:_commitAddIndex() end, cancel = function() imp._indexPrompt = nil @@ -1985,37 +2348,67 @@ local function buildModals(imp, m) paste = function() imp:_pasteIndexUrl() end, footnote = Strings("Enter to add - Esc to cancel"), }) - return - end - if imp._modConfirm then - buildConfirmModal(imp, m) - return + return true end + if imp._modConfirm then buildConfirmModal(imp, m) return true end if imp._modReleaseNotes then local ModUpdate = require("src.mods.ModUpdate") local n = imp._modReleaseNotes local body = ModUpdate.cleanBody(n.body or "", 0) if body == "" then body = Strings("(No release notes.)") end - buildTextModal(imp, m, "v" .. tostring(n.version) .. Strings(" notes"), - body, function() imp._modReleaseNotes = nil end, "release-notes") - return + buildTextModal(imp, m, "release-notes", + "v" .. tostring(n.version) .. Strings(" notes"), body, + function() imp._modReleaseNotes = nil end) + return true end if imp._findDetails then local ModUpdate = require("src.mods.ModUpdate") local d = imp._findDetails local body = ModUpdate.cleanBody(d.body or "", 0) if body == "" then body = Strings("(No description.)") end - buildTextModal(imp, m, d.title, body, - function() imp._findDetails = nil end, "find-details") - return - end - if imp._modVersions then - buildVersionsModal(imp, m) - return + buildTextModal(imp, m, "find-details", d.title, body, + function() imp._findDetails = nil end) + return true end + if imp._modVersions then buildVersionsModal(imp, m) return true end + -- The lighter popups come after the deep ones on purpose: opening + -- Versions or Details from inside an actions popup draws the deeper modal + -- while the popup's own state stays set, so closing the deep one drops + -- you back where you were. + if imp._sortPopup then buildSortModal(imp, m) return true end + if imp._filterPopup then buildFilterModal(imp, m) return true end + if imp._indexManage then buildIndexesModal(imp, m) return true end + if imp._modActions then buildModActionsModal(imp, m) return true end + if imp._findEntry then buildFindEntryModal(imp, m) return true end + if imp._gameManage then buildGameManageModal(imp, m) return true end + return false end --- ------- pad cursor overlay (drawn after FlexLove, plain love.graphics) +-- --------------------------------------------------------------- overlays + +-- The blocking loader. imp.workState drives the ROM import (which reports +-- real progress); imp._busy drives every async network operation. +local function loaderSpec(imp) + if imp.workState == "working" then + return { + title = imp.status or Strings("Working"), + detail = imp.detail, + progress = imp.progress, + } + end + local b = imp._busy + if b then + return { title = b.title, detail = b.detail, progress = b.progress, + onCancel = b.cancel } + end + -- The boot prewarm runs without an overlay (the user did not ask for it and + -- must be able to use the launcher meanwhile), but if they reach the Find + -- Mods tab before it lands, THEN they are waiting on it and it earns one. + if imp.tab == "find" and imp._findFetch and not imp.findLoaded then + return { title = Strings("Loading mod index") } + end + return nil +end local function drawPadCursor(imp) if not imp._padCursorActive then return end @@ -2036,105 +2429,121 @@ local function drawPadCursor(imp) love.graphics.polygon("fill", x, y, x, y + 20, x + 6, y + 14, x + 12, y + 24, x + 16, y + 22, x + 9, y + 12, x + 18, y + 12) - love.graphics.setColor(0.05, 0.07, 0.12, 1) + love.graphics.setColor(0, 0, 0, 1) love.graphics.polygon("line", x, y, x, y + 20, x + 6, y + 14, x + 12, y + 24, x + 16, y + 22, x + 9, y + 12, x + 18, y + 12) love.graphics.pop() end --- ------- frame assembly +-- ------------------------------------------------------------ frame assembly + +-- Mirror of buildHeader's vertical arithmetic, so the frame can decide +-- whether the window is tall enough BEFORE anything draws. Keep in sync +-- with buildHeader (rail, logo row, tab row, hairline pad). +local function headerHeight(m) + return m.railH + m.logoH + math.floor(12 * m.s) + math.floor(6 * m.s) + + m.chip + math.floor(8 * m.s) + math.floor(10 * m.s) +end + +-- The panel space a tab needs to lay out without crushing itself. Below +-- this the page SCROLLS (wheel / touch drag) instead of compressing: the +-- pinned Play block used to walk up over the cards on a short window, which +-- is unusable, and the footer simply lives below the fold until scrolled to. +local function minPanelHeight(m) + -- One column stacks the ROM card and the slot card in a single pile, so it + -- needs more room than the side-by-side layout; two columns only have to + -- fit the taller of the two. Both numbers came DOWN sharply when the + -- pinned Touch-Controls / Reset-rebinds pair moved behind the gear and the + -- save-file buttons moved into the slot card: the pile they used to sit on + -- top of was what forced 460/660 (#852), and a threshold larger than the + -- content pushes the whole page below the fold on windows that could have + -- shown it outright (a 1280x720 desktop was scrolling for 93px of nothing). + -- Whatever a window still cannot show, the page scroll above reaches. + return math.floor((m.twoCol and 340 or 470) * m.s) +end function LauncherView.draw(imp) - ensureFlex(imp) + ensureState(imp) + local m = Layout.metrics(1200) - local W, H = love.graphics.getDimensions() - if imp._lastW ~= W or imp._lastH ~= H then - imp._lastW, imp._lastH = W, H - pcall(FlexLove.resize) - -- Persisted immediate-mode state (scroll offsets and the scroll - -- manager's cached geometry) survives a resize keyed by element id, so - -- the old window's scissors and scrollbar metrics kept clipping the new - -- layout. Drop it all; losing the scroll position on a resize is the - -- lesser cost. - pcall(FlexLove.clearAllStates) + -- The pointer is the pad cursor while it is active, so the ring, hover and + -- clicks all agree on where "the pointer" is. + local mx, my = 0, 0 + if imp._padCursorActive then + mx, my = imp._padCursor.x, imp._padCursor.y + elseif love.mouse and love.mouse.getPosition then + mx, my = love.mouse.getPosition() end + local click = imp._clickPt + if click then mx, my = click.x, click.y end - -- flat backdrop, painted before the element tree renders over it - love.graphics.setColor(C("bg"):toRGBA()) - love.graphics.rectangle("fill", 0, 0, W, H) - love.graphics.setColor(1, 1, 1, 1) + -- SHORT-WINDOW SCROLL. When the space between header and footer falls + -- under the panel minimum, the whole page (header included) scrolls by a + -- plain y offset: layout runs off a shifted m.top, so hit tests, focus + -- rects and drawing all agree with the real pointer and no transform is + -- involved. Modals and the loader keep the REAL metrics and stay + -- centred in the window. + local footH = footerHeight(imp, m) + local naturalAvail = m.h - headerHeight(m) - footH - m.gap + local scrollMax = math.max(0, minPanelHeight(m) - naturalAvail) + local scroll = math.max(0, math.min(imp._pageScroll or 0, scrollMax)) + if scrollMax > 0 and (imp._wheelY or 0) ~= 0 then + scroll = math.max(0, math.min( + scroll - imp._wheelY * math.floor(48 * m.s), scrollMax)) + imp._wheelY = 0 -- the page consumed the wheel; lists page by tap here + end + imp._pageScroll, imp._pageScrollMax = scroll, scrollMax - local ox, oy, sw, sh = SafeArea.rect() - local s = clamp(sh / 768, 0.62, 1.5) - local appW = math.min(sw, 1200 * s) - local m = { - W = W, H = H, s = s, - x = ox + (sw - appW) / 2, top = oy, - w = appW, h = sh, - pad = clamp(appW * 0.03, 10, 24), - chip = math.max(34, 42 * s), - logoH = clamp(sh * 0.11, 40, 96), - btnH = math.max(34, 40 * s), - cardPad = { horizontal = 16, vertical = 14 }, - twoCol = appW >= 640, - } - m.colGap = 16 * m.s - -- scrollbars are hidden (wheel and touch drag still scroll); the slim - -- gutter is breathing room so content never touches the window edge - m.gutter = 8 - m.contentW = appW - 2 * m.pad - m.gutter - m.colW = m.twoCol and math.floor((m.contentW - m.colGap) / 2) or m.contentW + Kit.beginFrame(mx, my, click ~= nil, imp._wheelY or 0) + imp._clickPt = nil + imp._wheelY = 0 - local root = mk({ - x = m.x, y = m.top, width = m.w, height = m.h, - positioning = "flex", flexDirection = "vertical", - }) - buildHeader(imp, root, m) + Theme.field() - -- One scroll region per tab (stable id keeps its offset across frames and - -- separate per tab), holding the panel, the updater banner and the footer. - -- flexShrink/minHeight keep this viewport-sized so overflowY can scroll. - local page = mk({ - parent = root, id = "page-" .. imp.tab, - width = "100%", flex = 1, flexShrink = 1, minHeight = 0, - overflowY = "scroll", hideScrollbars = true, - positioning = "flex", flexDirection = "vertical", - gap = 12 * m.s, - padding = { left = m.pad, right = m.pad + m.gutter, - top = 14 * m.s, bottom = 10 * m.s }, - }) - if imp.tab == "mods" then - buildModsPanel(imp, page, m) - elseif imp.tab == "find" then - buildFindPanel(imp, page, m) + -- Everything from here to buildModals sits UNDER any open modal, so the + -- whole stage draws shielded (no clicks, no hover, no focus ring) while + -- one is up; buildModals lowers the shield for the modal's own controls. + Kit.blockClicks = modalUp(imp) + + local ms = m + if scroll > 0 then + ms = setmetatable({ top = m.top - scroll }, { __index = m }) + end + local contentY = buildHeader(imp, ms) + local footY, availH + if scrollMax > 0 then + availH = minPanelHeight(m) + footY = contentY + availH + m.gap else - buildGamePanel(imp, page, m, imp.tab) + footY = m.top + m.h - footH + availH = footY - contentY - m.gap end - buildBanner(imp, page, m) - buildFooter(imp, page, m) + local x, w = m.contentX, m.contentW + if imp.tab == "mods" then + buildModsPanel(imp, x, contentY, w, availH, m) + elseif imp.tab == "find" then + buildFindPanel(imp, x, contentY, w, availH, m) + else + buildGamePanel(imp, x, contentY, w, availH, m, imp.tab) + end + + buildFooter(imp, m, footY) + Kit.blockClicks = false buildModals(imp, m) - FlexLove.draw() - drawPadCursor(imp) - - -- Dev harness: POKEPORT_LAUNCHER_DUMP=1 prints the laid-out tree once - -- (id/text, x, y, w, h) so geometry bugs are read off numbers instead of - -- guessed from screenshots. - if os.getenv("POKEPORT_LAUNCHER_DUMP") == "1" and not imp._dumped - and imp._shotTimer and imp._shotTimer > 1.0 then - imp._dumped = true - local function walk(el, depth) - local tag = el.id or (el.text and ("%q"):format( - tostring(el.text):sub(1, 24))) or "-" - print(("%s%s x=%.0f y=%.0f w=%.0f h=%.0f"):format( - (" "):rep(depth), tag, el.x or -1, el.y or -1, - el.width or -1, el.height or -1)) - for _, ch in ipairs(el.children or {}) do walk(ch, depth + 1) end + -- The loader sits above everything, including modals: it is the one thing + -- that must never be clicked around. + local spec = loaderSpec(imp) + if spec then + if Loader.overlay(m, spec) and spec.onCancel then + queueAction(imp, "loader-cancel", spec.onCancel) end - for _, el in ipairs(FlexLove.topElements or {}) do walk(el, 0) end end + + Kit.endFrame() + drawPadCursor(imp) end return LauncherView diff --git a/src/import/RomExtractor.lua b/src/import/RomExtractor.lua index 3d5e8e77..6c4319cc 100644 --- a/src/import/RomExtractor.lua +++ b/src/import/RomExtractor.lua @@ -198,6 +198,12 @@ function RomExtractor:extractTilesets() out[constName] = { id = constName, source = ("ROM:Tilesets[%d]"):format(index - 1), + -- The raw Tilesets row, verbatim. A .sav export has to reproduce what + -- LoadTilesetHeader (engine/overworld/tilesets.asm) would have left in + -- wTilesetBank..wGrassTile, because a Continue never re-runs it -- see + -- src/save_convert/MapContext.lua (#889). Byte 12 is the tile + -- animation id, which rides in sTileAnimations. + header = self.rom:bytes(headers.bank, rowAddress, 12), image = "assets/generated/tilesets/" .. base .. ".png", imageWidth = spec.imageWidth, imageHeight = spec.imageHeight, @@ -268,6 +274,11 @@ function RomExtractor:extractMaps() assert(tilesetId < #tilesets, constName .. ": unknown tileset id") local blockPointer = self.rom:word(header.bank, address + 3) local connectionFlags = self.rom:byte(header.bank, address + 9) + -- wCurMapHeader verbatim (tileset, height, width, data/text/script + -- pointers, connection flags). A save restores this window instead of + -- rebuilding it, so an export has to carry the real bytes (#889). + local headerBytes = self.rom:bytes(header.bank, address, 10) + local connectionStart = address + 10 address = address + 10 local connections = {} @@ -289,6 +300,8 @@ function RomExtractor:extractMaps() end assert(bit.band(connectionFlags, 0xF0) == 0, constName .. ": unknown connection flags") + local connectionBytes = self.rom:bytes( + header.bank, connectionStart, address - connectionStart) local objectPointer = self.rom:word(header.bank, address) local objectAddress = objectPointer local borderBlock = self.rom:byte(header.bank, objectAddress) @@ -376,6 +389,17 @@ function RomExtractor:extractMaps() width = width, height = height, blocks = blocks, borderBlock = borderBlock, connections = connections, warps = warps, signs = signs, objects = objects, + -- Raw ROM bytes a .sav export replays through LoadMapHeader's WRAM + -- writes (src/save_convert/MapContext.lua, #889). Kept as the original + -- bytes rather than re-encoded from the decoded tables above: the + -- pointers in them (wCurMapDataPtr, the connection strip src/dest + -- addresses, sign text ids) have no equivalent in the port's own model. + sram = { + header = headerBytes, + connections = connectionBytes, + objects = self.rom:bytes( + header.bank, objectPointer, objectAddress - objectPointer), + }, } self:tick("Maps", mapIndex, #keys) end @@ -1639,9 +1663,100 @@ function RomExtractor:raw1bpp(label, width, height, relative, transparent) return image end +-- Trading animation art: gfx/trade.asm TradingAnimationGraphics is one +-- 49-tile atlas (game_boy.2bpp, built with --remove-duplicates, then +-- link_cable.2bpp), and the Game Boy and open-cable plates are painted out +-- of it through the tilemaps in data/tilemaps.asm (GameBoyTiles 6x8, +-- LinkCableTiles 12x3), whose ids are absolute vChars2 ids starting at $31 +-- because trade.asm reaches them through +-- CopyTileIDsFromList_ZeroBaseTileID. Only the developer-only Python path +-- ever wrote these files, so an imported cache had none of them and +-- TradeAnim drew the whole cinematic as plain rectangles (#750). +function RomExtractor:extractTradeArt() + local BASE, COUNT = 0x31, 49 + local gfx = self:symbol("TradingAnimationGraphics") + local atlas = ImageWriter.decode2bpp( + self.rom:bytes(gfx.bank, gfx.address, COUNT * 16), COUNT * 8, 8) + local function tileX(id) + local index = id - BASE + assert(index >= 0 and index < COUNT, + ("trade tile $%02X is outside the animation atlas"):format(id)) + return index * 8 + end + local function plate(label, tilesWide, tilesHigh, relative, matte) + local map = self:symbol(label) + local ids = self.rom:bytes(map.bank, map.address, tilesWide * tilesHigh) + local image = ImageWriter.blank(tilesWide * 8, tilesHigh * 8, 1, 1, 1, 1) + for index, id in ipairs(ids) do + ImageWriter.blit(image, atlas, + (index - 1) % tilesWide * 8, + math.floor((index - 1) / tilesWide) * 8, tileX(id), 0, 8, 8) + end + if matte then image = ImageWriter.matteColor0(image) end + self:save(image, relative) + end + plate("GameBoyTiles", 6, 8, "trade/game_boy.png", true) + plate("LinkCableTiles", 12, 3, "trade/open_cable.png", false) + for _, spec in ipairs({ + { 0x5D, "cable_conn" }, { 0x5E, "cable_seg" }, { 0x5F, "cable_corner" }, + { 0x60, "cable_end" }, { 0x61, "cable_vert" }, + }) do + local tile = ImageWriter.blank(8, 8, 1, 1, 1, 1) + ImageWriter.blit(tile, atlas, 0, 0, tileX(spec[1]), 0, 8, 8) + self:save(tile, "trade/" .. spec[2] .. ".png") + end + -- Trade_DrawCableAcrossScreen fills a whole 20-tile row with tile $5e. + local horizontal = ImageWriter.blank(160, 8, 1, 1, 1, 1) + for column = 0, 19 do + ImageWriter.blit(horizontal, atlas, column * 8, 0, tileX(0x5E), 0, 8, 8) + end + self:save(horizontal, "trade/cable_horiz.png") + + -- Trade_BallInsideLinkCableOAMBlock draws one tile four times with the + -- X/Y flips, so each of the two frames -- $7e travelling, $7f bulging, + -- the bottom row of TradingAnimationGraphics2 -- makes a 16x16 ball. + local ball = self:symbol("TradingAnimationGraphics2") + local frames = ImageWriter.decode2bpp( + self.rom:bytes(ball.bank, ball.address, 64), 16, 16, true) + for index, name in ipairs({ "cable_ball", "cable_ball_alt" }) do + local image = ImageWriter.blank(16, 16, 1, 1, 1, 0) + for y = 0, 7 do + for x = 0, 7 do + local r, g, b, a = frames:getPixel((index - 1) * 8 + x, 8 + y) + image:setPixel(x, y, r, g, b, a) + image:setPixel(15 - x, y, r, g, b, a) + image:setPixel(x, 15 - y, r, g, b, a) + image:setPixel(15 - x, 15 - y, r, g, b, a) + end + end + self:save(image, "trade/" .. name .. ".png") + end + -- The ring around the travelling mon: one 16x16 quadrant per animation + -- frame (engine/gfx/mon_icons.asm TradeBubbleIconGFX), mirrored into a + -- 32x32 circle by the OAM attributes in Trade_CircleOAMBlocks. + local bubble = self:symbol("TradeBubbleIconGFX") + self:write2bpp(self.rom:bytes(bubble.bank, bubble.address, 128), + 16, 32, "trade/bubble.png", true) + + return { + gameBoy = "assets/generated/trade/game_boy.png", + openCable = "assets/generated/trade/open_cable.png", + cableHoriz = "assets/generated/trade/cable_horiz.png", + cableConn = "assets/generated/trade/cable_conn.png", + cableVert = "assets/generated/trade/cable_vert.png", + cableCorner = "assets/generated/trade/cable_corner.png", + cableEnd = "assets/generated/trade/cable_end.png", + cableBall = "assets/generated/trade/cable_ball.png", + cableBallAlt = "assets/generated/trade/cable_ball_alt.png", + bubble = "assets/generated/trade/bubble.png", + source = "ROM:TradingAnimationGraphics + ROM:TradeBubbleIconGFX" + .. " (engine/movie/trade.asm InternalClockTradeAnim)", + } +end + function RomExtractor:extractField() self:beginStage("Interface artwork") - local done, total = 0, 49 + local done, total = 0, 51 local function tick() done = done + 1 self:tick("Interface artwork", math.min(done, total), total) @@ -1657,6 +1772,16 @@ function RomExtractor:extractField() "title/copyright.png"); tick() self:raw2bpp("GameFreakLogoGraphics", 72, 8, "title/gamefreak_inc.png"); tick() + + do + local gf = self.symbols["GameFreakLogoGraphics"] + local tb = self.symbols["TextBoxGraphics"] + if gf and tb and tb[2] == gf[2] + 9 * 16 + 16 then + local raw = self.rom:bytes(gf[1], gf[2] + 9 * 16, 16) + self:save(ImageWriter.decode2bpp(raw, 8, 8, false), "title/nine.png") + end + end + tick() -- Yellow fixed Pikachu title art (no-op on Red/Blue manifests). self:extractYellowTitleArt(); tick() @@ -1832,6 +1957,8 @@ function RomExtractor:extractField() end self:save(emotes, "emotes.png"); tick() + local tradeArt = self:extractTradeArt(); tick() + -- Yellow-only: the Surfing Pikachu minigame sheets -- (gfx/surfing_pikachu.asm) at pret's canvas widths, so -- src/ui/SurfingMinigame.lua's quads can be read off the source pngs. @@ -1962,6 +2089,7 @@ function RomExtractor:extractField() local converted = {} for index, values in pairs(adjacency) do converted[tonumber(index)] = values end data.hiddenExtras.trashCans.adjacent = converted + data.tradeArt = tradeArt data.source = "canonical Pokemon Red ROM + bundled port metadata" self:write("field", data) self:tick("Interface artwork", total, total) diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index e67ae314..a38c5b0e 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -30,9 +30,13 @@ end -- Cache generation tag; bump to force every imported version to re-extract. -- v9: Yellow audio re-anchored on pokeyellow.sym (#522) -- stale caches -- carry Red's bank $1f header, wave-table, and CryData offsets. -local CACHE_FORMAT = "rom-cache-v9:" +-- v10: maps carry their raw map-header/connection/object bytes and tilesets +-- their Tilesets row (#889), which a .sav export replays so a Continue on +-- real hardware has a map to load; a v9 cache has none of them and exports +-- the same unbootable save as before. +local CACHE_FORMAT = "rom-cache-v10:" -- The completion marker is written under each version's cache prefix --- (rom-cache.complete for Red, blue/rom-cache.complete for Blue). +-- (red/rom-cache.complete, blue/rom-cache.complete, ...). local MARKER_PATH = "rom-cache.complete" -- The marker a finished import writes for a version: the generation tag plus @@ -57,6 +61,10 @@ local REQUIRED_FILES = { "assets/generated/battle/anims/move_anim_0.png", "assets/generated/battle/anims/move_anim_1.png", "assets/generated/audio/programs.bin", + -- The trade cinematic's Game Boy / cable art. Caches built before #750 + -- carry none of it and fall back to plain rectangles, so listing one of + -- the files re-imports them without a CACHE_FORMAT bump. + "assets/generated/trade/game_boy.png", } -- Files only one version's cache carries. A version that predates one of @@ -131,8 +139,8 @@ local PAL = { -- CacheFs.exists checks the game folder directly for a portable install, -- otherwise the save directory through love.filesystem. It honors --- CacheFs.prefix, so we point it at the version's cache subtree (Red at the --- root, Blue under blue/). +-- CacheFs.prefix, so we point it at the version's cache subtree (red/, +-- blue/, yellow/). local function allRequiredFilesExist(version) local CacheFs = require("src.import.CacheFs") local saved = CacheFs.prefix @@ -148,14 +156,22 @@ local function allRequiredFilesExist(version) return ok end --- A developer checkout / Python build leaves Red's generated data in the --- physfs SOURCE at the un-prefixed root; that is always current. Only Red --- ships this way (Blue is import-only), so this stays a Red-root check. -local function sourceTreeHasData() - if not allRequiredFilesExist("red") or not love.filesystem.getRealDirectory then - return false +-- A developer checkout / Python build leaves generated data in the physfs +-- source: Red at the historical root, Blue/Yellow in their versioned trees. +-- Imported Red caches still live under red/. Check source paths directly so +-- that cache prefix cannot hide Red's source tree, and keep save-dir caches +-- from counting as current source data. +local function sourceTreeHasData(version) + if not love.filesystem.getRealDirectory then return false end + local prefix = version == "red" and "" or GameVersion.cachePrefix(version) + for _, path in ipairs(REQUIRED_FILES) do + if love.filesystem.getInfo(prefix .. path, "file") == nil then return false end end - local real = love.filesystem.getRealDirectory(REQUIRED_FILES[1]) + for _, path in ipairs(VERSION_REQUIRED_FILES[version] or {}) do + if love.filesystem.getInfo(prefix .. path, "file") == nil then return false end + end + local path = prefix .. REQUIRED_FILES[1] + local real = love.filesystem.getRealDirectory(path) return real == love.filesystem.getSource() end @@ -210,8 +226,8 @@ local function purgeSaveDirCache() f:close() return true end - -- Purge each version's stale save-directory copy (Red at the root, Blue - -- under blue/) so it cannot shadow the portable game-folder cache. + -- Purge each version's stale save-directory copy (under its red/ / blue/ + -- / yellow/ prefix) so it cannot shadow the portable game-folder cache. for _, version in ipairs(GameVersion.ORDER) do local prefix = GameVersion.cachePrefix(version) if saveDirHas(prefix .. MARKER_PATH) or saveDirHas(prefix .. REQUIRED_FILES[1]) then @@ -232,10 +248,8 @@ function RomImporter.isReady(version) -- save-directory copy that would otherwise shadow it at runtime. purgeSaveDirCache() end - -- Red generated data in the physfs source (developer checkout / Python - -- build) is always current; Blue is import-only and falls through to the - -- version-marker gate. - if version == "red" and sourceTreeHasData() then return true end + -- Generated data in a developer checkout / Python build is always current. + if sourceTreeHasData(version) then return true end local saved = CacheFs.prefix CacheFs.prefix = GameVersion.cachePrefix(version) local marker = CacheFs.read(MARKER_PATH) @@ -311,36 +325,29 @@ end -- keyboard-navigates (keyboard focus is a separate grab) but ignores the -- mouse entirely -- issue #254 on Linux. Whether it bites is a race with how -- long the click was held, which is why the same build picks one ROM fine and --- then hangs the mouse on the next. So pump until no button is held, letting --- SDL see the release and let go first; bounded, so a stuck button costs a --- moment and never the launcher. pump() only drains OS events into LOVE's --- queue -- it dispatches nothing -- so there is no reentry into mousepressed --- and the release is still delivered normally on the next frame. -local function releasePointerGrab() - if not (love.mouse and love.mouse.isDown and love.event and love.event.pump - and love.timer) then - return - end - local deadline = love.timer.getTime() + 1 - while love.mouse.isDown(1, 2, 3) do - love.event.pump() - if love.timer.getTime() > deadline then break end - love.timer.sleep(0.005) - end -end +-- then hangs the mouse on the next. +-- +-- The release itself now lives in HostShell.releasePointerGrab, called from +-- HostShell.popen, so every host spawn inherits it and not just the three +-- pickers here. It stays a single release point on purpose: this file used +-- to run its own copy first, and each copy carries its own one-second bound, +-- so keeping both made a stuck button cost two seconds instead of one. local function commandOutput(command) if not Platform.canSpawnProcess() then return nil end - releasePointerGrab() local pipe = HostShell.popen(command) if not pipe then return nil end local result = pipe:read("*a") - pipe:close() + -- HostShell.pclose, never pipe:close(): closing a pipe outside the spawn + -- lock can free a FILE while a worker thread's popen is walking the stream + -- list, which deadlocks that thread for good (see HostShell). + HostShell.pclose(pipe) result = trim(result) return result ~= "" and result or nil end local IMPORTS_DIR = "imports" +local BASE_ROMS_DIR = "baseroms" local MODS_INBOX_DIR = "imports/mods" local SAVES_INBOX_DIR = "imports/saves" local ROM_BYTES = 1024 * 1024 @@ -474,6 +481,65 @@ local function listRomPaths(dir) return paths end +local function baseRomScanSatisfied(self) + for _, version in ipairs(GameVersion.ORDER) do + if not self.ready[version] and not self.baseRoms[version] then + return false + end + end + return true +end + +function RomImporter:_queueBaseRomScan() + if not self.baseRomDiscovery then return end + if baseRomScanSatisfied(self) then + self.baseRomScan = { state = "done" } + return + end + self.baseRomScan = { state = "queued", index = 1 } +end + +function RomImporter:_stepBaseRomScan() + local scan = self.baseRomScan + if not scan or scan.state == "done" or self.workState == "working" then + return + end + if scan.state == "queued" then + local info = love.filesystem.getInfo(BASE_ROMS_DIR) + if not info and love.filesystem.createDirectory then + love.filesystem.createDirectory(BASE_ROMS_DIR) + end + scan.paths = listRomPaths(BASE_ROMS_DIR) + table.sort(scan.paths) + scan.state = "running" + end + + local path = scan.paths[scan.index] + if not path then + scan.state = "done" + return + end + scan.index = scan.index + 1 + + local info = love.filesystem.getInfo(path, "file") + if info and info.size == ROM_BYTES then + local data = love.filesystem.read(path) + if type(data) == "string" and #data == ROM_BYTES then + local version = GameVersion.forSha1(sha1(data)) + if version and not self.ready[version] and not self.baseRoms[version] then + self.baseRoms[version] = { + path = path, + name = path:match("[^/\\]+$") or path, + } + end + end + end + + if baseRomScanSatisfied(self) or not scan.paths[scan.index] then + scan.state = "done" + end +end + local function listZipPaths(dir) local paths = {} for _, name in ipairs(love.filesystem.getDirectoryItems(dir) or {}) do @@ -993,6 +1059,25 @@ local function updaterAllowed() return true end +-- #835: which column the launcher opens on. `tab` starts at the --game +-- shortcut's version (LaunchOptions.pendingTab) or Red; this then prefers the +-- game play() last handed off, so relaunching lands on the game that was last +-- played instead of always Red. An explicit --game still wins, and a +-- remembered version whose cache is gone or stale is ignored, since opening a +-- column with no Play button would read as the launcher losing the import. +-- Called from new() once self.ready is filled, which is what that check needs. +function RomImporter:_applyLastVersionTab() + local okLO, LO = pcall(require, "src.core.LaunchOptions") + if okLO and LO.pendingTab then return end + local okOpt, opts = pcall(function() + return require("src.core.SaveData").loadOptions() + end) + local last = okOpt and opts and opts.lastVersion + if last and GameVersion.VERSIONS[last] and self.ready[last] then + self.tab = last + end +end + -- The launcher runs each GameVersion as an independent tab. Each dropped or -- chosen ROM is routed to its version by SHA-1, extracted into that version's -- own cache (Red at the root, Blue under blue/, Yellow under yellow/), so all @@ -1030,6 +1115,10 @@ function RomImporter.new(onComplete, opts) mobileFileBridge = mobileFileBridge, android = android, ios = mobileOS == "iOS", + nativePicker = romImportMode == "native-picker", + baseRomDiscovery = opts.launcher and Platform.isUWP(), + baseRoms = {}, + baseRomScan = nil, -- One startup poll pass on both mobiles. iOS: files dropped through the -- Files app are swept into the save dir before Lua boots (GRBootstrap) with -- no love.focus event necessarily following. Android: the SAF picker is a @@ -1045,7 +1134,13 @@ function RomImporter.new(onComplete, opts) -- for click hit-testing inside EventHandler. touchPollable = mobileFileBridge and love.touch ~= nil and love.touch.getTouches ~= nil and love.touch.getPosition ~= nil, - tab = "red", -- active launcher tab: "red"/"blue"/"yellow"/"mods" + -- Active launcher tab: "red"/"blue"/"yellow"/"mods"/"find". A --game + -- shortcut for a version that is not importable yet lands here, so the + -- player at least arrives on the tab they asked for (src/core/LaunchOptions). + tab = (function() + local okLO, LO = pcall(require, "src.core.LaunchOptions") + return (okLO and LO.pendingTab) or "red" + end)(), logo = love.graphics.newImage("assets/logo/logo.png"), bcg = love.graphics.newImage("assets/logo/bcg.png"), ready = {}, returning = {}, romName = {}, @@ -1089,8 +1184,8 @@ function RomImporter.new(onComplete, opts) -- Android SAF create-document: which game's SAVE FILES card should show -- "Save exported." when export_done.flag appears on focus. androidPendingExportVersion = nil, - iosPendingKind = nil, - iosPendingVersion = nil, + pickerPendingKind = nil, + pickerPendingVersion = nil, -- Virtual pointer for handhelds / gamepads (Anbernic stock OS has no -- mouse). D-pad + left stick move it; A clicks; shoulders cycle tabs; -- right stick scrolls the save-slot / mods lists. @@ -1102,6 +1197,11 @@ function RomImporter.new(onComplete, opts) _padInited = false, }, RomImporter) + -- Pre-#899 installs keep Red's extracted cache at the save-dir root; move + -- it under red/ before the readiness loop looks for red/ paths, or every + -- such install would read as "never imported" and demand the ROM again. + CacheFs.migrateLegacyRedCache() + for _, version in ipairs(GameVersion.ORDER) do local info = GameVersion.info(version) local ready = RomImporter.isReady(version) and not self.forceImport @@ -1117,6 +1217,8 @@ function RomImporter.new(onComplete, opts) self.romName[version] = "pokemon_" .. info.id .. (info.id == "yellow" and ".gbc" or ".gb") end + self:_applyLastVersionTab() + self:_queueBaseRomScan() -- Android: import a save-dir .gb/.gbc that is not yet ready (USB drop or a -- leftover SAF pick), routed by SHA-1. Already-imported carts are skipped @@ -1155,9 +1257,9 @@ function RomImporter.new(onComplete, opts) end -- Self-updater: the interactive launcher on a real fused build kicks off one - -- async release check as it comes up; draw() polls Check.state() to render an - -- unobtrusive banner beneath the columns. Held behind pcall so a broken or - -- absent updater can never take the launcher down with it. + -- async release check as it comes up; the top-right update control polls + -- Check.state() and glows when there is something to do. Held behind pcall + -- so a broken or absent updater can never take the launcher down with it. if self.launcher and updaterAllowed() then local ok, Check = pcall(require, "src.update.Check") if ok and Check then @@ -1166,6 +1268,26 @@ function RomImporter.new(onComplete, opts) end end + -- PREWARM. Start the mod-index fetch at boot rather than when the Find + -- Mods tab is first opened. The work is identical either way, but doing it + -- now means it overlaps the time the user spends looking at the game tab, + -- so the tab is already populated when they reach it instead of greeting + -- them with a loader. Nothing here blocks: the fetch pool is off-thread + -- and _pumpFindFetch collects the result whenever it lands. + -- + -- Deliberately NOT behind the blocking overlay: the user did not ask for + -- this and must be able to use the launcher while it runs, so _busy is + -- cleared straight back out. An explicit Refresh press still shows one. + if self.launcher then + pcall(function() + self:_refreshFindSources() + if #(self.findSources or {}) > 0 then + self:_refreshFind(false) + self:_clearBusy() + end + end) + end + -- On Linux handhelds / NX a gamepad is usually already connected at boot; -- arm the virtual cursor immediately so the player does not have to press a -- button before seeing something move. Desktop keeps the cursor latent @@ -1489,10 +1611,10 @@ function RomImporter:chooseMod() self:rescanModsAction() return end - if self.ios and love.system.getPickedFile then - self.iosPendingKind = "mod" + if self.nativePicker and love.system.getPickedFile then + self.pickerPendingKind = "mod" if not pickFile("mod") then - self.iosPendingKind = nil + self.pickerPendingKind = nil self.modNotice = { ok = false, text = "Could not open the file picker." } end return @@ -1533,7 +1655,7 @@ end -- the target tab forward so the notice (and, on success, the new active slot) -- is visible. Requires the ROM to be imported first, since a save is only -- playable with its game's data present. -function RomImporter:_importSave(version, source) +function RomImporter:_importSave(version, source, force) if self.workState == "working" then return end if GameVersion.VERSIONS[self.tab] or self.tab == "mods" then self.tab = version @@ -1543,15 +1665,35 @@ function RomImporter:_importSave(version, source) .. GameVersion.info(version).displayName .. " ROM before importing a save." } return end - local ok, res = require("src.import.SaveFileIO").importToSlot(source, version) + local ok, res, info = require("src.import.SaveFileIO").importToSlot(source, version, force) if ok then self:_refreshSlots(version) self.activeSlot[version] = res self.slotScroll[version] = math.huge -- pin the new row on screen (clamped in draw) self.saveNotice[version] = { ok = true, text = "Imported save into " .. tostring(res) .. "." } - else - self.saveNotice[version] = { ok = false, text = tostring(res) } + return end + if res == nil and info and info.needsConfirm then + -- A .sav larger than 32 KB whose first 32768 bytes checksum: the surplus + -- is almost certainly an emulator RTC footer, so ask before truncating. + -- The yes arm re-enters with force=true; cancel leaves the file untouched. + self._modConfirm = { + kind = "importOversize", + version = version, + source = source, + title = "Oversized save file", + lines = { + ("This save is %d bytes; a cartridge save is exactly %d bytes (32 KB).") + :format(info.size, 32768), + "It may come from a ROM that saved the battery image with an emulator.", + "The extra bytes would be discarded.", + "Import it anyway?", + }, + yesLabel = "Import anyway", + } + return + end + self.saveNotice[version] = { ok = false, text = tostring(res) } end -- "Import save" button: open a native .sav picker and import the pick. @@ -1565,12 +1707,12 @@ function RomImporter:chooseSaveImport(version) self:rescanSavesAction(version) return end - if self.ios and love.system.getPickedFile then - self.iosPendingKind = "sav" - self.iosPendingVersion = version + if self.nativePicker and love.system.getPickedFile then + self.pickerPendingKind = "sav" + self.pickerPendingVersion = version if not pickFile("sav") then - self.iosPendingKind = nil - self.iosPendingVersion = nil + self.pickerPendingKind = nil + self.pickerPendingVersion = nil self.saveNotice[version] = { ok = false, text = "Could not open the file picker." } end return @@ -1683,10 +1825,25 @@ function RomImporter:choose(version) self:rescanAction(self.chooseVersion) return end - if self.ios and love.system.getPickedFile then - self.iosPendingKind = "rom" + local baseRom = self.baseRomDiscovery and self.baseRoms[self.chooseVersion] + if baseRom then + self.baseRoms[self.chooseVersion] = nil + local data = love.filesystem.read(baseRom.path) + if not data then + self.notice = { + version = self.chooseVersion, + status = "The detected ROM is no longer available.", + detail = "Choose Import ROM to select it another way.", + } + return + end + self:startData(data, baseRom.name) + return + end + if self.nativePicker and love.system.getPickedFile then + self.pickerPendingKind = "rom" if not pickFile("rom") then - self.iosPendingKind = nil + self.pickerPendingKind = nil self:setError("Could not open the file picker.") end return @@ -1805,12 +1962,24 @@ end function RomImporter:update(dt) self.pulse = self.pulse + dt self:_updatePadCursor(dt) + self:_stepBaseRomScan() -- Pump the FlexLove view (input polling + the queued click actions). The -- flag is only set once draw() has built a tree, so headless runs and the -- test tier never touch the toolkit. if self._flex then require("src.import.LauncherView").update(self, dt) end + -- Drive every in-flight async fetch. These are the operations that used to + -- run synchronously inside draw and freeze the window; each pump is a + -- non-blocking channel poll, so a frame with nothing in flight costs + -- nothing. They run whether or not the view is up, so a refresh started + -- before a tab switch still completes. + self:_pumpFindFetch() + self:_pumpModInfoFetch() + self:_pumpFindStats() + self:_pumpFindThumbs() + self:_pumpModCheck() + self:_pumpModInstall() -- Dev harness: POKEPORT_LAUNCHER_SHOT=/path.png resizes the window from -- POKEPORT_WIN=WxH, lets the view settle, then captures one frame and -- quits, so a scripted run can see the real launcher at any window shape @@ -1826,6 +1995,24 @@ function RomImporter:update(dt) end local tab = os.getenv("POKEPORT_LAUNCHER_TAB") if tab and tab ~= "" then self:_switchTab(tab) end + -- POKEPORT_LAUNCHER_CONFIRM=1 arms a representative install confirm so + -- a capture can see the modal (it is otherwise only reachable by click) + if os.getenv("POKEPORT_LAUNCHER_CONFIRM") == "1" then + self._modConfirm = { + kind = "update", + title = "Install mod", + yesLabel = "Install", + lines = { "JP GREEN - Poketto Monsuta Midori v0.4.4", + "by bryanthaboi", + "Mods are not reviewed - trust the author." }, + } + end + -- POKEPORT_LAUNCHER_SETTINGS=1 opens the gear panel, the other layout + -- a capture cannot otherwise reach without a click. Pair it with + -- POKEPORT_LAUNCHER_SETTINGS_PAGE to land on a page past the first. + if os.getenv("POKEPORT_LAUNCHER_SETTINGS") == "1" then + self:_openSettings() + end local query = os.getenv("POKEPORT_LAUNCHER_QUERY") if query and query ~= "" then self.findQuery = query @@ -1873,27 +2060,35 @@ function RomImporter:update(dt) end) end end - if self.ios and love.system.getPickedFile and self.workState ~= "working" then + if self.nativePicker and love.system.getPickedFile and self.workState ~= "working" then local path = love.system.getPickedFile() if path then - local kind = self.iosPendingKind or "rom" - local version = self.iosPendingVersion - self.iosPendingKind = nil - self.iosPendingVersion = nil + local kind = self.pickerPendingKind or "rom" + local version = self.pickerPendingVersion + self.pickerPendingKind = nil + self.pickerPendingVersion = nil if kind == "mod" then self:_installMod(path) + if Platform.isUWP() and self.modNotice and self.modNotice.ok then + os.remove(path) + end elseif kind == "sav" then - self:_importSave(version or self:_savedropTarget(), path) + local target = version or self:_savedropTarget() + self:_importSave(target, path) + if Platform.isUWP() and self.saveNotice[target] and self.saveNotice[target].ok then + os.remove(path) + end else self:startPath(path) + if Platform.isUWP() then os.remove(path) end end elseif love.system.getPickError then local errorText = love.system.getPickError() if errorText then - local kind = self.iosPendingKind or "rom" - local version = self.iosPendingVersion or self:_savedropTarget() - self.iosPendingKind = nil - self.iosPendingVersion = nil + local kind = self.pickerPendingKind or "rom" + local version = self.pickerPendingVersion or self:_savedropTarget() + self.pickerPendingKind = nil + self.pickerPendingVersion = nil if kind == "mod" then self.modNotice = { ok = false, text = errorText } elseif kind == "sav" then @@ -2158,6 +2353,17 @@ function RomImporter:play(version) if self.workState == "working" then return end if not self.ready[version] then return end self._handedOff = true + -- #835: remember the game being launched so the next launcher start opens on + -- its column (_applyLastVersionTab). It rides options.lua rather than a file + -- of its own, so portable installs and POKEPORT_IDENTITY sandboxes keep it + -- with the rest of the launcher's persisted state. A failed write only + -- costs the memory of the choice, so it must never block the boot. + pcall(function() + local SaveData = require("src.core.SaveData") + local opts = SaveData.loadOptions() + opts.lastVersion = version + SaveData.saveOptions(opts) + end) resetPointerCursor(self) -- The game draws with raw love.graphics from here on; drop the view's -- element tree and canvases before the handoff. @@ -2173,6 +2379,10 @@ function RomImporter:reimport(version) self.ready[version] = false self.returning[version] = false self.chooseVersion = version + if self.baseRomDiscovery then + self.baseRoms[version] = nil + self:_queueBaseRomScan() + end end local function clamp(v, lo, hi) @@ -2219,8 +2429,28 @@ end -- it rebuilds the element tree from this importer's state every frame and -- renders it. Required lazily so a headless test require of this module -- never loads the UI toolkit. +-- Dev harness: POKEPORT_LAUNCHER_PROF= times the view's build+draw +-- for that many frames, prints mean/median/p95/worst to stdout and quits. +-- Pair with POKEPORT_LAUNCHER_TAB / POKEPORT_WIN to profile a specific panel. +local profN, profSamples = tonumber(os.getenv("POKEPORT_LAUNCHER_PROF") or ""), {} + function RomImporter:draw() - require("src.import.LauncherView").draw(self) + local View = require("src.import.LauncherView") + if not profN then return View.draw(self) end + local t0 = love.timer.getTime() + View.draw(self) + profSamples[#profSamples + 1] = (love.timer.getTime() - t0) * 1000 + if #profSamples >= profN + 30 then + local s = {} + for i = 31, #profSamples do s[#s + 1] = profSamples[i] end -- drop warmup + table.sort(s) + local sum = 0 + for _, v in ipairs(s) do sum = sum + v end + io.stderr:write(("PROF frames=%d mean=%.2fms median=%.2fms p95=%.2fms worst=%.2fms\n") + :format(#s, sum / #s, s[math.ceil(#s * 0.5)], s[math.ceil(#s * 0.95)], s[#s])) + io.stderr:flush() + love.event.quit() + end end -- Nothing in the launcher can undo a delete, so every Delete control asks @@ -2242,11 +2472,52 @@ function RomImporter:pressDelete(kind, id, version, commit) return false end +-- Drain one frame's queued launcher actions; LauncherView.update hands the +-- batch straight over. A touch tap fires on EVERY element whose bounds hold +-- the finger, not only the topmost one: FlexLove gates its mouse path on +-- Context.findInteractiveAtPosition (libs/flexlove/modules/behaviors/ +-- Clickable.lua) but polls touches per element with a bare bounds test +-- (EventHandler:processTouchEvents), so a phone tap on a save row's Delete +-- chip also lands on the row behind it. Control keys inside a row are the +-- row's key plus "-", so a row's own action is dropped whenever a +-- control inside that row queued in the same batch, and #433's disarm runs +-- here instead of at queue time. Without both halves an Android tap on +-- Delete selected the slot and wiped the arm it had just set, so a secondary +-- slot became the loaded one and could never be deleted (#780). +function RomImporter:runActions(queue) + for i = 1, #queue do + local entry = queue[i] + local key = type(entry.key) == "string" and entry.key or "" + local superseded = false + for j = 1, #queue do + local other = queue[j] + if j ~= i and type(other.key) == "string" + and other.key:sub(1, #key + 1) == key .. "-" then + superseded = true + break + end + end + if not superseded then + if not entry.keepArm then self._confirmDelete = nil end + local ok, err = pcall(entry.fn) + if not ok then print("launcher action error: " .. tostring(err)) end + end + end +end + -- Clicks are polled inside FlexLove (mouse + love.touch); host-forwarded --- mousepressed stays inert so Android's synthesized mouse path cannot --- double-fire a tap (#553). Touch move/press/release must still reach +-- mousepressed mints no click, so Android's synthesized mouse path cannot +-- double-fire a tap (#553). It DOES hand the pointer back from the pad +-- cursor (#781): a Linux boot with a joystick present arms it (see the +-- getJoystickCount block in new()), and while it is active +-- LauncherView.update refuses to mint mouse clicks, so a real press must +-- win the pointer back even when the polled motion yield misses (X11 +-- multi-monitor coords). Same contract as PadCursor.yieldToPointer for +-- the overlay hosts. Touch move/press/release must still reach -- FlexLove.touch* or scroll containers never drag on phones. -function RomImporter:mousepressed() end +function RomImporter:mousepressed() + self._padCursorActive = false +end function RomImporter:touchpressed(id, x, y, dx, dy, pressure) if not self._flex then return end @@ -2287,12 +2558,30 @@ end -- ------- settings gear (options.lua + enabled mods' option schemas) function RomImporter:_openSettings() + -- The touch-overlay editor is a host screen, so the model gets it as a + -- hook rather than reaching for main.lua's handler itself. Closing the + -- settings panel FIRST persists the pending edits (_closeSettings saves) + -- and leaves no modal behind the editor to return to. + local hooks = {} + if self.onEditTouchControls then + hooks.editTouchControls = function() + self:_closeSettings() + self.onEditTouchControls() + end + end local ok, model = pcall(function() - return require("src.import.LauncherSettings").open() + return require("src.import.LauncherSettings").open(hooks) end) if ok and model then self._settings = model end end +-- Quit from the launcher's own X. It goes through love.event.quit so main.lua's +-- love.quit hook still runs: that is where the worker threads are shut down +-- (#339) and where a launcher close is told apart from a running game's (#785). +function RomImporter:_quitApp() + if love.event and love.event.quit then love.event.quit() end +end + function RomImporter:_closeSettings() if self._settings then self._settings.save() end self._settings = nil @@ -2381,6 +2670,12 @@ function RomImporter:keypressed(key) return end if self.workState == "working" then return end + -- Keyboard focus ring: arrows move it, Enter activates it -- but only once + -- the arrows have been used, so the long-standing "Enter plays the visible + -- game" shortcut below still works for anyone who never touches the ring. + if self._flex and require("src.import.LauncherView").keypressed(self, key) then + return + end if key == "return" or key == "space" or key == "kpenter" then -- Enter acts on the visible game tab: Play if its ROM is ready, otherwise -- open its picker. The mods tab has no keyboard action. @@ -2565,52 +2860,86 @@ end -- Resolve cached (or freshly fetched) GitHub status for every mod that -- declares a github field. force=true bypasses the 6h cache on every repo. -- Results live on self.modUpdateInfo[id] = { status, latest, best, releases }. +-- ASYNC (was synchronous). This runs on every _refreshMods -- boot, and any +-- toggle or install -- and used to make one blocking curl call per mod with a +-- github field, in a loop, on the render thread. A handful of mods was a +-- multi-second freeze of the whole launcher. Now each mod gets a handle and +-- they resolve together across later frames; a mod whose cache is still fresh +-- resolves on the first pump with no network at all. function RomImporter:_syncModUpdateInfo(force) local ModUpdate = require("src.mods.ModUpdate") self.modUpdateInfo = self.modUpdateInfo or {} + local pending = {} for _, m in ipairs(self.mods or {}) do if m.github and m.github ~= "" then - local ok, packed = pcall(function() - local releases, err, meta = ModUpdate.fetchReleases(m.github, m.id, { - force = force == true, - }) - local cached = ModUpdate.readCache(m.github) - return { - releases = releases, - err = err, - meta = meta, - checkedAt = (cached and cached.checkedAt) or os.time(), - } - end) - if not ok then - self.modUpdateInfo[m.id] = { - status = "error", err = tostring(packed), - } - elseif packed.releases then - local status, best = ModUpdate.statusFor(m.version, packed.releases) - self.modUpdateInfo[m.id] = { - status = status, - latest = best and best.version or nil, - best = best, - releases = packed.releases, - downloads = ModUpdate.totalDownloads(packed.releases), - dates = ModUpdate.releaseDates(packed.releases), - err = nil, - checkedAt = packed.checkedAt or os.time(), - } - else - self.modUpdateInfo[m.id] = { - status = "error", - latest = nil, - best = nil, - releases = nil, - err = tostring(packed.err), - } - end + pending[#pending + 1] = { mod = m, + h = ModUpdate.beginFetchReleases(m.github, m.id, { force = force == true }) } else self.modUpdateInfo[m.id] = nil end end + self._modInfoFetch = (#pending > 0) and pending or nil + -- Bump immediately so a mod that lost its github field (or a list that + -- shrank) is reflected without waiting on the network. + self._modUpdateRev = (self._modUpdateRev or 0) + 1 +end + +-- Drive in-flight release checks one frame at a time. Called from update(). +-- Deliberately NOT behind the blocking overlay: this is background enrichment +-- of rows that are already usable, so the list stays interactive while the +-- download counts and update badges fill in. Individual rows show their own +-- inline spinner instead. +function RomImporter:_pumpModInfoFetch() + local pending = self._modInfoFetch + if not pending then return end + local ModUpdate = require("src.mods.ModUpdate") + local remaining, changed = {}, false + for _, item in ipairs(pending) do + local m = item.mod + local ok, done, releases, err = pcall(ModUpdate.pumpFetchReleases, item.h) + if not ok then + self.modUpdateInfo[m.id] = { status = "error", err = tostring(done) } + changed = true + elseif done then + changed = true + if releases then + local status, best = ModUpdate.statusFor(m.version, releases) + local cached = ModUpdate.readCache(m.github) + self.modUpdateInfo[m.id] = { + status = status, + latest = best and best.version or nil, + best = best, + releases = releases, + downloads = ModUpdate.totalDownloads(releases), + dates = ModUpdate.releaseDates(releases), + err = nil, + checkedAt = (cached and cached.checkedAt) or os.time(), + } + else + self.modUpdateInfo[m.id] = { + status = "error", latest = nil, best = nil, releases = nil, + err = tostring(err), + } + end + else + remaining[#remaining + 1] = item + end + end + self._modInfoFetch = (#remaining > 0) and remaining or nil + if changed then + -- Bump so the view's sorted-list cache (keyed on this revision) rebuilds + -- when release/download data actually changes, not every frame. + self._modUpdateRev = (self._modUpdateRev or 0) + 1 + end +end + +-- True while any mod's release check is still in flight, so a row can show +-- an inline spinner instead of "Not checked for updates yet". +function RomImporter:_modInfoPending(id) + for _, item in ipairs(self._modInfoFetch or {}) do + if item.mod.id == id then return true end + end + return false end function RomImporter:_modUpdateInfo(id) @@ -2696,46 +3025,29 @@ end -- Update button: when a newer release is known, confirm then install; when -- already current, force-refresh the 6h cache and report / offer update. function RomImporter:_modGithubAction(id, action) - if not Platform.networkValidated() then + -- canFetchRemote, not networkValidated: the self-updater's gate used to + -- stand in for this one, which cost Xbox the whole mod catalog rather than + -- just the self-update it actually cannot do (#876). Say what still works + -- while we are here, since the native picker is live on every platform that + -- lands in this branch. + if not Platform.canFetchRemote() then self.modNotice = { ok = false, - text = "Remote mod download is unavailable on this platform." } + text = "Remote mod download is unavailable on this platform. Install a mod .zip from storage instead." } + return + end + local ModUpdate = require("src.mods.ModUpdate") + local row + for _, m in ipairs(self.mods or {}) do + if m.id == id then row = m; break end + end + if not row or not row.github then + self.modNotice = { ok = false, text = "This mod has no github field" } return end - local ran, err = pcall(function() - local ModUpdate = require("src.mods.ModUpdate") - local row - for _, m in ipairs(self.mods or {}) do - if m.id == id then row = m; break end - end - if not row or not row.github then - self.modNotice = { ok = false, text = "This mod has no github field" } - return - end - if action == "versions" then - self.modNotice = { ok = true, text = "Loading versions..." } - local releases, fetchErr = ModUpdate.fetchReleases(row.github, row.id, {}) - if not releases then - self.modNotice = { ok = false, text = tostring(fetchErr) } - return - end - local status, best = ModUpdate.statusFor(row.version, releases) - self.modUpdateInfo = self.modUpdateInfo or {} - self.modUpdateInfo[row.id] = { - status = status, latest = best and best.version, best = best, - releases = releases, - downloads = ModUpdate.totalDownloads(releases), - dates = ModUpdate.releaseDates(releases), - } - self._modVersions = { - id = row.id, name = row.name, current = row.version, - releases = releases, scroll = 0, - } - self.modNotice = nil - return - end - - -- update / check + -- Update, when we already know a newer release exists, needs no network: + -- confirm straight away off the cached info. + if action ~= "versions" then local info = self:_modUpdateInfo(id) if info and info.status == "available" and info.best then self._modConfirm = { @@ -2750,51 +3062,177 @@ function RomImporter:_modGithubAction(id, action) } return end + end - -- Manual check (or first click when status is current/unknown/error) - self.modNotice = { ok = true, text = "Checking " .. row.github .. "..." } - local releases, fetchErr = ModUpdate.fetchReleases(row.github, row.id, { - force = true, - }) - if not releases then - self.modNotice = { ok = false, text = tostring(fetchErr) } - return - end - if #releases == 0 then - self.modNotice = { ok = false, text = "No .zip releases found" } - return - end - local status, best = ModUpdate.statusFor(row.version, releases) - self.modUpdateInfo = self.modUpdateInfo or {} - self.modUpdateInfo[row.id] = { - status = status, latest = best and best.version, best = best, - releases = releases, checkedAt = os.time(), - downloads = ModUpdate.totalDownloads(releases), - dates = ModUpdate.releaseDates(releases), - } - if status == "available" and best then - self.modNotice = { ok = true, - text = row.name .. ": new version available (v" .. best.version .. ")" } - self._modConfirm = { - kind = "update", id = row.id, release = best, - title = "Update available", - yesLabel = "Update", - lines = { - "Update " .. row.name .. "?", - "Installed v" .. tostring(row.version), - "Latest v" .. tostring(best.version), - }, - } - else - self.modNotice = { ok = true, - text = row.name .. " is up to date (v" - .. tostring(row.version) .. ")" } - end - end) - if not ran then + -- ASYNC (was a blocking fetch). Both remaining paths -- listing versions + -- and a manual re-check -- hit the GitHub API, which is exactly the call + -- that used to freeze the launcher mid-click. One job at a time. + if self._modCheck then return end + self._modCheck = { + id = row.id, name = row.name, github = row.github, + version = row.version, action = action, + h = ModUpdate.beginFetchReleases(row.github, row.id, + { force = action ~= "versions" }), + } + self:_setBusy(action == "versions" and Strings("Loading versions") + or Strings("Checking for updates"), row.name) +end + +-- Drive the in-flight per-mod release check. Called from _pumpModInfoFetch's +-- neighbourhood in update(); kept separate because this one IS behind the +-- blocking overlay (the user pressed a button and is waiting on the answer). +function RomImporter:_pumpModCheck() + local job = self._modCheck + if not job then return end + local ModUpdate = require("src.mods.ModUpdate") + local ok, done, releases, err = pcall(ModUpdate.pumpFetchReleases, job.h) + if ok and not done then return end + self._modCheck = nil + self:_clearBusy() + if not ok then self._modVersions = nil - self.modNotice = { ok = false, - text = "Update failed: " .. tostring(err) } + self.modNotice = { ok = false, text = "Update failed: " .. tostring(done) } + return + end + if not releases then + self.modNotice = { ok = false, text = tostring(err) } + return + end + if #releases == 0 then + self.modNotice = { ok = false, text = "No .zip releases found" } + return + end + + local status, best = ModUpdate.statusFor(job.version, releases) + self.modUpdateInfo = self.modUpdateInfo or {} + self.modUpdateInfo[job.id] = { + status = status, latest = best and best.version, best = best, + releases = releases, checkedAt = os.time(), + downloads = ModUpdate.totalDownloads(releases), + dates = ModUpdate.releaseDates(releases), + } + self._modUpdateRev = (self._modUpdateRev or 0) + 1 + + if job.action == "versions" then + self._modVersions = { + id = job.id, name = job.name, current = job.version, + releases = releases, page = 1, + } + self.modNotice = nil + return + end + + if status == "available" and best then + self.modNotice = { ok = true, + text = job.name .. ": new version available (v" .. best.version .. ")" } + self._modConfirm = { + kind = "update", id = job.id, release = best, + title = "Update available", + yesLabel = "Update", + lines = { + "Update " .. job.name .. "?", + "Installed v" .. tostring(job.version), + "Latest v" .. tostring(best.version), + }, + } + else + self.modNotice = { ok = true, + text = job.name .. " is up to date (v" .. tostring(job.version) .. ")" } + end +end + +-- ------- mod install / update (async download, blocking unzip) +-- +-- The download is the slow half and now runs on the fetch pool behind a +-- non-dismissable loader; unzipping the finished archive is fast and stays +-- on the main thread, where love.filesystem belongs. All three entry points +-- (update a mod, install a specific version, install from an index) funnel +-- into one in-flight job, so two installs can never race for the same id. +-- +-- `spec` = { modId, name, release, notice = "mod"|"find", verb, entry } +function RomImporter:_beginModInstall(spec) + if self._modInstall then return end + local ModIndex = require("src.mods.ModIndex") + local release = spec.release + -- An index entry only tells us WHERE the zip is; resolving that is + -- ModIndex's job, exactly as in the synchronous path. + if not release and spec.entry then + local resolved, why = ModIndex.releaseFor(spec.entry) + if not resolved then + self:_modInstallFailed(spec, why or "this mod cannot be installed") + return + end + release = resolved + end + if type(release) ~= "table" or not release.zip or not release.zip.url then + self:_modInstallFailed(spec, "release has no downloadable .zip") + return + end + local version = release.version or os.time() + local tmpName = ("mod_update_%s_%s.zip"):format(tostring(spec.modId), + tostring(version)) + local ModUpdate = require("src.mods.ModUpdate") + self._modInstall = { + spec = spec, release = release, version = release.version, + h = ModUpdate.beginDownloadZip(release.zip.url, tmpName, + release.zip.size), + } + self:_setBusy(Strings("Downloading %s", tostring(spec.name or spec.modId)), + "v" .. tostring(release.version or "?")) +end + +function RomImporter:_modInstallFailed(spec, msg) + local notice = { ok = false, text = tostring(msg) } + if spec.notice == "find" then self.findNotice = notice + else self.modNotice = notice end + self:_clearBusy() +end + +function RomImporter:_pumpModInstall() + local job = self._modInstall + if not job then return end + local ModUpdate = require("src.mods.ModUpdate") + local ok, done, path, err, progress = pcall(ModUpdate.pumpDownloadZip, job.h) + if ok and not done then + -- Feed real download progress into the overlay when the size is known. + if progress and self._busy then self._busy.progress = progress end + return + end + self._modInstall = nil + local spec = job.spec + if not ok then + self:_modInstallFailed(spec, "download failed: " .. tostring(done)) + return + end + if not path then + self:_modInstallFailed(spec, err or "download failed") + return + end + -- Unzip + manifest check. Fast, and it must run here: love.filesystem + -- writes are main-thread only. + self:_setBusy(Strings("Installing %s", tostring(spec.name or spec.modId))) + local LauncherMods = require("src.mods.LauncherMods") + local ran, res, resErr = pcall(LauncherMods.installDownloadedZip, + spec.modId, path, job.version) + self:_clearBusy() + if not ran then + self:_modInstallFailed(spec, "install failed: " .. tostring(res)) + return + end + if not res then + self:_modInstallFailed(spec, resErr or "install failed") + return + end + -- The installed list is what the Install / Installed labels read, so it has + -- to be re-derived before the next paint or the card lies. + pcall(self._refreshMods, self) + local shown = tostring(resErr or job.version or "") + local text = ("%s %s %s"):format(spec.verb or "Installed", + tostring(spec.name or spec.modId), shown) + if spec.notice == "find" then + self.findNotice = { ok = true, text = text } + else + self.modNotice = { ok = true, text = text } end end @@ -2803,52 +3241,26 @@ function RomImporter:_confirmModUpdate(modId, release) for _, m in ipairs(self.mods or {}) do if m.id == modId then row = m; break end end - local name = row and row.name or modId - self.modNotice = { ok = true, - text = "Downloading " .. tostring(release and release.version or "?") .. "..." } - local ran, err = pcall(function() - local LauncherMods = require("src.mods.LauncherMods") - local ok, res = LauncherMods.installFromRelease(modId, release) - if ok then - pcall(self._refreshMods, self) - self.modNotice = { ok = true, - text = "Updated " .. name .. " to " .. tostring(res) } - else - self.modNotice = { ok = false, text = tostring(res) } - end - end) - if not ran then - self.modNotice = { ok = false, text = "Update failed: " .. tostring(err) } - end + self:_beginModInstall({ + modId = modId, name = row and row.name or modId, + release = release, verb = "Updated", notice = "mod", + }) end function RomImporter:_installModVersion(modId, release) self._modVersions = nil self._modReleaseNotes = nil - local version = release and release.version or "?" - self.modNotice = { ok = true, text = "Downloading " .. tostring(version) .. "..." } - local ran, err = pcall(function() - local LauncherMods = require("src.mods.LauncherMods") - local ok, res = LauncherMods.installFromRelease(modId, release) - if ok then - pcall(self._refreshMods, self) - self.modNotice = { ok = true, - text = "Installed " .. tostring(modId) .. " " .. tostring(res) } - else - self.modNotice = { ok = false, text = tostring(res) } - end - end) - if not ran then - self.modNotice = { ok = false, - text = "Install failed: " .. tostring(err) } - end + self:_beginModInstall({ + modId = modId, name = modId, release = release, + verb = "Installed", notice = "mod", + }) end -- NX / desktop / Android labels and inbox hints for the FlexLove view. function RomImporter:_modsImportButtonLabel() if self.isNX then return Strings("Scan again") end - return "Import mod .zip" + return Strings("Import mod .zip") end function RomImporter:_modsDefaultHint() @@ -2859,7 +3271,7 @@ function RomImporter:_modsDefaultHint() return Strings("Copy a .zip via MTP into %s/imports/mods/\n" .. "DBI MTP → 1: SD Card/%simports/mods/", saveDir, rel) end - if self.android then return "Or copy a mod .zip via USB." end + if self.android then return Strings("Or copy a mod .zip via USB.") end return Strings("Or drop a mod .zip onto the window.") end @@ -2875,7 +3287,7 @@ function RomImporter:_savesDefaultHint(version) .. "DBI MTP → 1: SD Card/%s%s/", game, saveDir, inbox, rel, inbox) end if self.android then - return "Import or export a .sav with the system file picker." + return Strings("Import or export a .sav with the system file picker.") end return Strings("Import a .sav to a new slot, or export the active slot.") end @@ -2917,56 +3329,145 @@ end -- there is one "nuzlocke" as far as the installer is concerned, so the panel -- must not offer two. Per-source failures are collected rather than fatal: an -- index that is down should cost its own rows, not everybody else's. +-- ASYNC (was synchronous). Every source used to be fetched with a blocking +-- curl call inside the draw path, so opening Find Mods froze the window for +-- as long as the slowest index took -- measured at over two minutes on a +-- cold open, with no spinner, because the frame that would have drawn one +-- never ran. The fetch now starts here and completes across later frames in +-- _pumpFindFetch; the loader overlay is up for the whole flight. function RomImporter:_refreshFind(force) - if not Platform.networkValidated() then + -- The notice is the fix, not the gate (#876). This branch used to return an + -- empty listing silently, and because the player had by then added a source, + -- the panel skipped its "No mod index added" card and rendered the merged + -- listing empty state instead: a valid feed reported as "This index lists no + -- mods yet." Every other failure on this panel surfaces through findNotice, + -- and this one has to as well, or adding an index looks like it worked and + -- the index looks empty. + if not Platform.canFetchRemote() then self.findLoaded = true self.findIndex = { mods = {}, categories = {} } + self.findNotice = { ok = false, + text = "Mod indexes cannot be fetched on this platform. Install a mod .zip from storage instead." } return end local ModIndex = require("src.mods.ModIndex") self:_refreshFindSources() - local mods, seen, cats, catSeen, errs = {}, {}, {}, {}, {} - local stale, oldest = false, nil - for _, source in ipairs(self.findSources or {}) do - local ok, index, err, meta = pcall(function() - return ModIndex.fetch(source, { force = force == true }) - end) - if not ok then - errs[#errs + 1] = (source.label or source.feed) .. ": " .. tostring(index) - elseif not index then - errs[#errs + 1] = (source.label or source.feed) .. ": " .. tostring(err) - else - if meta and meta.stale then stale = true end - if meta and meta.checkedAt then - oldest = math.min(oldest or meta.checkedAt, meta.checkedAt) - end - for _, entry in ipairs(index.mods or {}) do - if not seen[entry.id] then - seen[entry.id] = true - entry._source = source.label or source.feed - entry._base = source.base - mods[#mods + 1] = entry + local sources = self.findSources or {} + if #sources == 0 then + self.findIndex = { mods = {}, categories = {} } + self.findLoaded = true + return + end + -- One in-flight refresh at a time: a second Refresh press while the first + -- is running would double-count every row into the merge. + if self._findFetch then return end + local handles = {} + for i, source in ipairs(sources) do + handles[i] = { source = source, + h = ModIndex.beginFetch(source, { force = force == true }) } + end + self._findFetch = { + handles = handles, force = force == true, + mods = {}, seen = {}, cats = {}, catSeen = {}, errs = {}, + stale = false, oldest = nil, at = 1, + } + self:_setBusy(Strings("Fetching mod index"), + #sources == 1 and (sources[1].label or sources[1].feed) + or Strings("%d indexes", #sources)) +end + +-- Drive the in-flight index fetch one frame at a time. Called from update(). +function RomImporter:_pumpFindFetch() + local f = self._findFetch + if not f then return end + local ModIndex = require("src.mods.ModIndex") + + -- Pump every handle each frame; they run concurrently on the fetch pool. + local allDone = true + for _, item in ipairs(f.handles) do + if not item.done then + local ok, done, index, err, meta = pcall(ModIndex.pumpFetch, item.h) + if not ok then + item.done = true + f.errs[#f.errs + 1] = (item.source.label or item.source.feed) + .. ": " .. tostring(done) + elseif done then + item.done = true + if not index then + f.errs[#f.errs + 1] = (item.source.label or item.source.feed) + .. ": " .. tostring(err) + else + item.index, item.meta = index, meta end - end - for _, c in ipairs(ModIndex.categoriesIn(index)) do - if not catSeen[c] then catSeen[c] = true; cats[#cats + 1] = c end + else + allDone = false end end end - self.findIndex = { mods = mods, categories = cats, stale = stale, - checkedAt = oldest } + self._busyCount = nil + if not allDone then return end + + -- Merge in SOURCE ORDER, not completion order: first source wins on a + -- duplicate id, matching how the mod loader resolves two mods with one id, + -- and that rule has to be stable regardless of which index answered first. + for _, item in ipairs(f.handles) do + local index, meta, source = item.index, item.meta, item.source + if index then + if meta and meta.stale then f.stale = true end + if meta and meta.checkedAt then + f.oldest = math.min(f.oldest or meta.checkedAt, meta.checkedAt) + end + for _, entry in ipairs(index.mods or {}) do + if not f.seen[entry.id] then + f.seen[entry.id] = true + entry._source = source.label or source.feed + entry._base = source.base + f.mods[#f.mods + 1] = entry + end + end + for _, c in ipairs(ModIndex.categoriesIn(index)) do + if not f.catSeen[c] then + f.catSeen[c] = true + f.cats[#f.cats + 1] = c + end + end + end + end + + self.findIndex = { mods = f.mods, categories = f.cats, stale = f.stale, + checkedAt = f.oldest } self.findLoaded = true - if #errs > 0 then - self.findNotice = { ok = false, text = table.concat(errs, " - ") } - elseif force then + if #f.errs > 0 then + self.findNotice = { ok = false, text = table.concat(f.errs, " - ") } + elseif f.force then self.findNotice = { ok = true, - text = Strings("Refreshed - %d mods listed", #mods) } + text = Strings("Refreshed - %d mods listed", #f.mods) } end -- A category that no longer exists after a refresh would filter everything -- away with no way back except guessing. - if self.findCategory and not catSeen[self.findCategory] then + if self.findCategory and not f.catSeen[self.findCategory] then self.findCategory = nil end + self.findPage = 1 + self._findFetch = nil + self:_clearBusy() +end + +-- Clearing rebinds used to live here, behind a button on the game panel. It +-- is now the RESET REBINDS row of the settings model +-- (src/import/LauncherSettings.lua), which edits the same options table the +-- rest of that panel does and saves through the same save() -- one control +-- for a setting that was never per-game in the first place. + +-- ------- busy state (drives the non-dismissable loader overlay) +-- Anything that makes the user wait sets this; LauncherView renders it as a +-- blocking overlay so no operation can ever run invisibly. +function RomImporter:_setBusy(title, detail, cancel) + self._busy = { title = title, detail = detail, cancel = cancel } +end + +function RomImporter:_clearBusy() + self._busy = nil end function RomImporter:_ensureFind() @@ -2986,12 +3487,22 @@ end -- The rows the filters leave, and the installed-mod context the compatibility -- warnings are judged against. function RomImporter:_findRows() - local ModIndex = require("src.mods.ModIndex") local all = (self.findIndex and self.findIndex.mods) or {} - return ModIndex.filter(all, { + -- The view asks every frame (immediate mode); only re-filter when the + -- index, query, or category actually changed. + local c = self._findRowsCache + if c and c.src == all and c.query == self.findQuery + and c.category == self.findCategory then + return c.rows + end + local ModIndex = require("src.mods.ModIndex") + local rows = ModIndex.filter(all, { query = self.findQuery, category = self.findCategory, }) + self._findRowsCache = { src = all, query = self.findQuery, + category = self.findCategory, rows = rows } + return rows end function RomImporter:_findInstalledMap() @@ -3008,21 +3519,55 @@ function RomImporter:_findThumb(entry) self._findThumbs = self._findThumbs or {} local cached = self._findThumbs[entry.id] if cached ~= nil then return cached or nil end - if self._findThumbFetched then return nil end -- budget spent this frame local ModIndex = require("src.mods.ModIndex") local url = ModIndex.joinUrl(entry._base, entry.thumbnail) if not url then self._findThumbs[entry.id] = false return nil end - self._findThumbFetched = true - local ok, image = pcall(function() - local path, err = ModIndex.downloadThumbnail(url, entry.id) - if not path then error(err or "download failed", 0) end - return love.graphics.newImage(path) - end) - self._findThumbs[entry.id] = ok and image or false - return ok and image or nil + -- ASYNC (was one blocking download per frame). Only rows on the current + -- page ever ask, so pagination already bounds this to a page's worth of + -- requests; the fetch pool runs them off-thread and the card shows its + -- placeholder until the image lands. + self._findThumbFetch = self._findThumbFetch or {} + if not self._findThumbFetch[entry.id] then + local ext = url:match("%.(%a%a%a?%a?)$") or "png" + local name = ("mod_thumb_%s.%s") + :format(tostring(entry.id):gsub("[^%w%-_]", "_"), ext) + local Fetch = require("src.net.Fetch") + self._findThumbFetch[entry.id] = { + -- A short ceiling on purpose: a page of these is queued at once, and + -- each one's ceiling is part of the worst case for closing the window + -- (Fetch.shutdown). A thumbnail that has not arrived in 15s is not + -- worth holding the process open for -- the card shows its placeholder. + job = Fetch.download(url, name, + { userAgent = "gen1recomp-mod-index", maxSeconds = 15 }), + } + end + return nil +end + +-- Turn finished thumbnail downloads into images. Called from update(), so +-- love.graphics.newImage runs on the render thread where it belongs. +function RomImporter:_pumpFindThumbs() + local pending = self._findThumbFetch + if not pending then return end + local Fetch = require("src.net.Fetch") + for id, item in pairs(pending) do + local st = Fetch.poll(item.job) + if st.status ~= "pending" then + Fetch.release(item.job) + pending[id] = nil + local image + if st.status == "ok" and st.path then + local ok, img = pcall(love.graphics.newImage, st.path) + image = ok and img or nil + end + self._findThumbs = self._findThumbs or {} + self._findThumbs[id] = image or false + end + end + if next(pending) == nil then self._findThumbFetch = nil end end -- Release stats for a FIND MODS row, resolved the same way the MODS tab @@ -3048,31 +3593,58 @@ function RomImporter:_findStats(entry) self._findStatsCache[entry.id] = cached return cached end - if self._findStatsFetched then return nil end -- budget spent this frame if not entry.github or entry.github == "" then cached = { done = true } self._findStatsCache[entry.id] = cached return cached end - self._findStatsFetched = true - local ModUpdate = require("src.mods.ModUpdate") - local list, fetchErr - local ok = pcall(function() - list, fetchErr = ModUpdate.fetchReleases(entry.github, entry.id, {}) - end) - local stats = list and ModUpdate.statsForReleases(list) or nil - if stats then - cached = { total = stats.total, first = stats.first, - latest = stats.latest, done = true } - else - -- A repo that does not exist is permanent; every other failure (the - -- hourly API rate limit, a hiccup) is retried in a minute so rows can - -- recover without restarting the launcher. - local permanent = tostring(fetchErr):find("Not Found", 1, true) ~= nil - cached = { done = permanent, retryAt = os.time() + 60 } + -- ASYNC (was a blocking fetch, one row per frame). "One per frame" bounded + -- how many stalls happened at once, not how long each one lasted: every + -- frame that started a fetch blocked for the whole round trip, so scrolling + -- a listing juddered once per row. Rows now queue a handle and fill in + -- when it lands; until then the row simply has no stats line. + self._findStatsPending = self._findStatsPending or {} + if not self._findStatsPending[entry.id] then + local ModUpdate = require("src.mods.ModUpdate") + self._findStatsPending[entry.id] = { + id = entry.id, + h = ModUpdate.beginFetchReleases(entry.github, entry.id, {}), + } end - self._findStatsCache[entry.id] = cached - return cached + return nil +end + +-- Drive in-flight FIND MODS stats lookups. Called from update(). +function RomImporter:_pumpFindStats() + local pending = self._findStatsPending + if not pending then return end + local ModUpdate = require("src.mods.ModUpdate") + for id, item in pairs(pending) do + local ok, done, releases, err = pcall(ModUpdate.pumpFetchReleases, item.h) + if not ok or done then + pending[id] = nil + local stats = (ok and releases) and ModUpdate.statsForReleases(releases) or nil + local cached + if stats then + cached = { total = stats.total, first = stats.first, + latest = stats.latest, done = true } + else + -- A repo that does not exist is permanent; every other failure (the + -- hourly API rate limit, a hiccup) is retried in a minute so rows can + -- recover without restarting the launcher. + local permanent = tostring(ok and err or done) + :find("Not Found", 1, true) ~= nil + cached = { done = permanent, retryAt = os.time() + 60 } + end + self._findStatsCache = self._findStatsCache or {} + self._findStatsCache[id] = cached + -- The FIND list's sort cache is keyed on this revision; without the + -- bump a Popularity/date sort stays frozen in the order of the first + -- frame (no stats yet = name order) even after every fetch lands. + self._findStatsRev = (self._findStatsRev or 0) + 1 + end + end + if next(pending) == nil then self._findStatsPending = nil end end -- Open the "add an index" text prompt. Deliberately a typed URL rather than a @@ -3169,24 +3741,10 @@ function RomImporter:_findConfirmInstall(entry) end function RomImporter:_findInstall(entry) - local name = entry.title or entry.id - self.findNotice = { ok = true, text = Strings("Downloading %s...", name) } - local ran, err = pcall(function() - local LauncherMods = require("src.mods.LauncherMods") - local ok, res = LauncherMods.installFromIndex(entry) - if ok then - -- The installed list is what the Install / Installed labels read, so it - -- has to be re-derived before the next paint or the card lies. - pcall(self._refreshMods, self) - self.findNotice = { ok = true, - text = Strings("Installed %s %s", name, tostring(res)) } - else - self.findNotice = { ok = false, text = tostring(res) } - end - end) - if not ran then - self.findNotice = { ok = false, text = "Install failed: " .. tostring(err) } - end + self:_beginModInstall({ + modId = entry.id, name = entry.title or entry.id, entry = entry, + verb = "Installed", notice = "find", + }) end return RomImporter diff --git a/src/import/SaveFileIO.lua b/src/import/SaveFileIO.lua index 76a0ebf1..c9a149f5 100644 --- a/src/import/SaveFileIO.lua +++ b/src/import/SaveFileIO.lua @@ -6,8 +6,10 @@ -- bytes), runs them through SaveConvert.importSav (32768-byte + checksum -- validated), then registers a fresh slot, writes it, and makes it active. -- Export loads the active slot, encodes it back to a 32768-byte SRAM image, and --- drops it in the save directory's exports// folder, returning the --- absolute path so the launcher can offer an "open folder" affordance. +-- drops it in exports// under the same root SaveData's persistFs +-- writes slots to -- the portable game folder when portable.txt marks the +-- install, otherwise the LOVE save directory (#752) -- returning the absolute +-- path so the launcher can offer an "open folder" affordance. -- -- Every failure returns false + a friendly one-line message (never raises), so -- the card can surface it as a red notice line rather than crashing. @@ -62,18 +64,34 @@ local function readSource(source) return nil, "could not read the save file: " .. tostring(openErr) end --- importToSlot(source, version) -> ok, slotIdOrErr --- source: an absolute path, a LOVE DroppedFile, or raw 32768 bytes. On success +-- importToSlot(source, version, force) -> ok, slotIdOrErr | (false, nil, info) +-- source: an absolute path, a LOVE DroppedFile, or raw bytes. On success -- registers a new slot for the version, writes the imported save into it, makes -- it the active slot, and returns true + the new slot id. On any failure --- returns false + a friendly message. -function SaveFileIO.importToSlot(source, version) +-- returns false + a friendly message. force only matters for a file LARGER +-- than 32768 bytes whose first 32768 bytes carry a valid main-data checksum +-- (i.e. a cartridge save padded with an emulator RTC footer): without force +-- this returns false, nil, { needsConfirm = true, size = #bytes } so the +-- launcher can ask the player before truncating; with force the extra bytes +-- are dropped and the 32768-byte save imports. +function SaveFileIO.importToSlot(source, version, force) version = version or GameVersion.get() local bytes, readErr = readSource(source) if not bytes then return false, readErr end if #bytes ~= SAVE_SIZE then - return false, ("A save file must be %d bytes (32 KB); this one is %d.") - :format(SAVE_SIZE, #bytes) + local check = SaveConvert.mainChecksumValid(bytes) + if check == nil then + return false, ("A save file must be %d bytes (32 KB); this one is %d.") + :format(SAVE_SIZE, #bytes) + end + if check == false then + return false, "save data checksum invalid (main data checksum mismatch)" + end + if #bytes > SAVE_SIZE and not force then + return false, nil, { needsConfirm = true, size = #bytes } + end + bytes = #bytes > SAVE_SIZE and bytes:sub(1, SAVE_SIZE) + or (bytes .. string.rep("\0", SAVE_SIZE - #bytes)) end -- 3rd arg: the crosswalk has to come from THIS game's ROM cache. The -- launcher imports before the cache is mounted on the un-prefixed paths, so @@ -99,9 +117,10 @@ end -- exportActiveSlot(version) -> ok, pathOrErr -- Loads the version's active slot save (SaveData.load semantics), encodes it -- back to a 32768-byte SRAM image, and writes it to --- exports//gen1recomp--.sav in the save directory --- (created if absent). Returns true + the absolute path on success, false + a --- friendly message otherwise. +-- exports//gen1recomp--.sav under the portable game +-- folder when portable mode is on, otherwise the save directory (created if +-- absent). Returns true + the absolute path on success, false + a friendly +-- message otherwise. function SaveFileIO.exportActiveSlot(version) version = version or GameVersion.get() local save = SaveData.load(version) @@ -109,7 +128,14 @@ function SaveFileIO.exportActiveSlot(version) local bytes, exportErr = SaveConvert.exportSav(save, version) if not bytes then return false, exportErr end local slotId = SaveData.activeSlot(version) or "save" - local fs = love and love.filesystem + -- Portable mode is the same seam SaveData's own persistFs uses: when + -- portable.txt marks the install every persistent write leaves the OS save + -- directory for the game folder, and an export is no exception. Writing + -- through love.filesystem here dropped the .sav in AppData while the slots + -- it came from lived on the stick, and the desktop "Open folder" affordance + -- (RomImporter:exportSave) followed the returned path straight there (#752). + local portableFs = SaveData.portableFs() + local fs = portableFs or (love and love.filesystem) if not (fs and fs.write) then return false, "no filesystem available to export to" end if fs.createDirectory then fs.createDirectory("exports") @@ -119,6 +145,14 @@ function SaveFileIO.exportActiveSlot(version) local rel = ("exports/%s/gen1recomp-%s-%s.sav"):format(version, version, slotId) local ok, writeErr = fs.write(rel, bytes) if not ok then return false, "could not write the export: " .. tostring(writeErr) end + -- Absolute path for the notice line, resolved against whichever root took + -- the write. Portable paths use the OS separator (slotDiskPath does the + -- same); LOVE save-directory paths stay "/"-joined as before. + local portableBase = SaveData.portableBaseDir() + if portableBase then + local sep = package.config:sub(1, 1) + return true, portableBase .. sep .. rel:gsub("/", sep) + end local base = fs.getSaveDirectory and fs.getSaveDirectory() or "" if base ~= "" then return true, base .. "/" .. rel end return true, rel diff --git a/src/inventory/ItemEffects.lua b/src/inventory/ItemEffects.lua index ad1e2165..2dd49e1d 100644 --- a/src/inventory/ItemEffects.lua +++ b/src/inventory/ItemEffects.lua @@ -12,9 +12,31 @@ local Flags = require("src.script.Flags") local Strings = require("src.core.Strings") +local romText = require("src.core.RomText") local ItemEffects = {} +local function notTime(data, save) + return romText(data, "_ItemUseNotTimeText", + "OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) +end + +local function noEffect(data) + return romText(data, "_ItemUseNoEffectText", "It won't have\nany effect.") +end + +local function registeredEffect(data, itemDef) + if not data or not itemDef or not itemDef.effect then + return nil + end + + if not data.item_effects then + return nil + end + + return data.item_effects[itemDef.effect] +end + local HEAL_AMOUNT = { POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200, FRESH_WATER = 50, SODA_POP = 60, LEMONADE = 80, @@ -41,6 +63,10 @@ local STONES = { local VITAMINS = { HP_UP = "hp", PROTEIN = "attack", IRON = "defense", CARBOS = "speed", CALCIUM = "special" } +-- REPEL / SUPER_REPEL / MAX_REPEL all funnel through ItemUseRepelCommon, +-- which refuses mid-battle before writing wRepelRemainingSteps (#894) +local REPELS = { REPEL = true, SUPER_REPEL = true, MAX_REPEL = true } + ItemEffects.BALLS = BALLS function ItemEffects.isBall(id) return BALLS[id] or false end @@ -58,7 +84,18 @@ function ItemEffects.healsHP(id) end -- Does this item need a party-member target? -function ItemEffects.needsTarget(id, itemDef) +-- 'data' is optional for compat purposes; targeting falls back to itemDef/vanilla detection +function ItemEffects.needsTarget(id, itemDef, data) + if itemDef and itemDef.needsTarget ~= nil then + return itemDef.needsTarget + end + + local effect = registeredEffect(data, itemDef) + + if effect and effect.needsTarget ~= nil then + return effect.needsTarget + end + return HEAL_AMOUNT[id] or STATUS_HEAL[id] or id == "MAX_POTION" or id == "FULL_RESTORE" or id == "REVIVE" or id == "MAX_REVIVE" or id == "RARE_CANDY" or STONES[id] @@ -82,6 +119,14 @@ local function cureActiveToxic(battle, target) end end +-- the per-item cure lines (item_effects.asm .cureStatusAilment picks the +-- text by item id); FULL_RESTORE lands here too when it acts as a cure +local CURE_TEXT = { + ANTIDOTE = "_AntidoteText", BURN_HEAL = "_BurnHealText", + ICE_HEAL = "_IceHealText", AWAKENING = "_AwakeningText", + PARLYZ_HEAL = "_ParlyzHealText", FULL_HEAL = "_FullHealText", +} + -- battle-only stat boosters (engine/items/item_effects.asm ItemUseXStat) local X_ITEMS = { X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed", @@ -125,13 +170,36 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) local itemDef = data.items[itemId] local name = itemDef and itemDef.name or itemId + local effectDef = registeredEffect(data, itemDef) + if effectDef then + if battle and effectDef.battle == false then + return "failed", { notTime(data, save) } + end + + if not battle and effectDef.field == false then + return "failed", { notTime(data, save) } + end + + return effectDef.use({ + data = data, + save = save, + itemId = itemId, + item = itemDef, + target = target, + battle = battle, + moveIndex = moveIndex, + overworld = ow, + }) + end + -- ItemUseVitamin / ItemUsePPUp / ItemUseEvoStone / ItemUseCoinCase / - -- ItemUseTMHM all refuse mid-battle (jp nz, ItemUseNotTime) + -- ItemUseTMHM / ItemUseRepelCommon all refuse mid-battle + -- (jp nz, ItemUseNotTime) if battle and (VITAMINS[itemId] or STONES[itemId] or itemId == "PP_UP" or itemId == "RARE_CANDY" or itemId == "COIN_CASE" + or REPELS[itemId] or (itemDef and itemDef.machine)) then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", - save.player.name) } + return "failed", { notTime(data, save) } end if BALLS[itemId] then @@ -148,13 +216,14 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- data/scripts/story.lua's snorlaxWake) local mapId, npc = adjacentSleepingSnorlax(save, ow) if npc then - return "flute_wake", { data.text._PlayedFluteHadEffectText - or Strings("{PLAYER} played the\nPOKé FLUTE.") }, + return "flute_wake", { romText(data, "_PlayedFluteHadEffectText", + "{PLAYER} played the\nPOKé FLUTE.") }, { mapId = mapId, npc = npc } end -- otherwise: play the tune, nothing happens (ItemUsePokeFlute's -- PlayedFluteNoEffectText branch) - return "flute_field", { Strings("Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") } + return "flute_field", { romText(data, "_PlayedFluteNoEffectText", + "Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") } end local woke = false local function wake(mon) @@ -169,17 +238,20 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- WakeUpEntireParty runs on the enemy's bench too for _, mon in ipairs(battle.enemyParty or {}) do wake(mon) end if not woke then - return "failed", { Strings("Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") } + return "failed", { romText(data, "_PlayedFluteNoEffectText", + "Played the POKé\nFLUTE.\fNow, that's a\ncatchy tune!") } end - return "flute", { Strings("%s played the\nPOKé FLUTE.", save.player.name), - Strings("All sleeping\nPOKéMON woke up!") } + return "flute", { romText(data, "_PlayedFluteHadEffectText", + "%s played the\nPOKé FLUTE.", save.player.name), + romText(data, "_FluteWokeUpText", + "All sleeping\nPOKéMON woke up!") } end -- battle-only items if X_ITEMS[itemId] or itemId == "DIRE_HIT" or itemId == "GUARD_SPEC" or itemId == "POKE_DOLL" then if not battle then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end local b = battle.player -- PIKAHAPPY_USEDXITEM (item_effects.asm ItemUseXAccuracy / @@ -201,7 +273,8 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- effect, so at +6 it is still consumed and StatModifierUpEffect -- just prints "Nothing happened!" if cur >= 6 then - return "consumed", { Strings("Nothing happened!") } + return "consumed", { romText(data, "_NothingHappenedText", + "Nothing happened!") } end b.stages[stat] = cur + 1 return "consumed", { Strings("%s's\n%s rose!", b.name, stat:upper()) } @@ -210,7 +283,8 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- the item, even when it is already active if itemId == "DIRE_HIT" then b.focusEnergy = true - return "consumed", { Strings("%s's\ngetting pumped!", b.name) } + return "consumed", { romText(data, "_GettingPumpedText", + "%s's\ngetting pumped!", b.name) } end if itemId == "GUARD_SPEC" then b.mist = true @@ -219,10 +293,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) if itemId == "POKE_DOLL" then if battle.kind ~= "wild" then -- ItemUsePokeDoll jumps to ItemUseNotTime in trainer battles - return "failed", { Strings( - "OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end - return "consumed_escape", { Strings("The wild POKéMON\nran away!") } + return "consumed_escape", { romText(data, "_WildRanText", + "The wild POKéMON\nran away!", battle.enemy and battle.enemy.name) } end end @@ -231,7 +305,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- restore every move with no menu. if itemId == "ETHER" or itemId == "MAX_ETHER" or itemId == "ELIXER" or itemId == "MAX_ELIXER" then - if not target then return "failed", { Strings("It won't have\nany effect.") } end + if not target then return "failed", { noEffect(data) } end local restored = false local full = itemId == "MAX_ETHER" or itemId == "MAX_ELIXER" local allMoves = itemId == "ELIXER" or itemId == "MAX_ELIXER" @@ -253,9 +327,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) restored = mv and restore(mv) or false end if not restored then - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end - return "consumed", { Strings("%s's PP\nwas restored!", monName(data, target)) } + -- pokered's line names no mon, so the extracted text takes no args + return "consumed", { romText(data, "_PPRestoredText", "PP was restored.") } end -- PIKAHAPPY_USEDITEM (item_effects.asm ItemUseMedicine, item id up to @@ -280,10 +355,11 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) target.status = nil cureActiveToxic(battle, target) require("src.core.Sound").play(data, "Heal_Ailment") - return "consumed", { Strings("%s's\nstatus returned\nto normal!", monName(data, target)) } + return "consumed", { romText(data, CURE_TEXT.FULL_HEAL, + "%s's\nstatus returned\nto normal!", monName(data, target)) } end if not target or target.hp <= 0 or target.hp >= target.stats.hp then - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end -- wHPBarOldHP: the bar animation starts from the HP the mon had BEFORE -- the item landed (item_effects.asm latches it with the party menu still @@ -295,7 +371,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) else target.hp = math.min(target.stats.hp, target.hp + heal) end - local msgs = { Strings("%s's HP\nwas restored!", monName(data, target)) } + -- _PotionText's second slot is the recovered amount ({NUM: + -- wHPBarHPDifference}); the engine fallback never prints it + local msgs = { romText(data, "_PotionText", "%s's HP\nwas restored!", + monName(data, target), target.hp - before) } if itemId == "FULL_RESTORE" then target.status = nil cureActiveToxic(battle, target) @@ -307,17 +386,18 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) local cures = STATUS_HEAL[itemId] if cures then if not target or not target.status or not cures[target.status] then - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end target.status = nil cureActiveToxic(battle, target) require("src.core.Sound").play(data, "Heal_Ailment") - return "consumed", { Strings("%s's\nstatus returned\nto normal!", monName(data, target)) } + return "consumed", { romText(data, CURE_TEXT[itemId], + "%s's\nstatus returned\nto normal!", monName(data, target)) } end if itemId == "REVIVE" or itemId == "MAX_REVIVE" then if not target or target.hp > 0 then - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end target.status = nil target.hp = itemId == "REVIVE" and math.floor(target.stats.hp / 2) or target.stats.hp @@ -329,13 +409,14 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) if battle and battle.participants then battle.participants[target] = true end - return "consumed", { Strings("%s\nis revitalized!", monName(data, target)) }, + return "consumed", { romText(data, "_ReviveText", + "%s\nis revitalized!", monName(data, target)) }, { healedFrom = 0 } end if itemId == "RARE_CANDY" then if not target or target.level >= 100 then - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end local Growth = require("src.pokemon.Growth") local Stats = require("src.pokemon.Stats") @@ -348,12 +429,13 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- PIKAHAPPY_LEVELUP on a candy level (item_effects.asm:1540) require("src.world.PikachuFollower") .modifyHappiness(save, "LEVELUP", target) - return "consumed", { Strings("%s grew\nto level %d!", monName(data, target), target.level) }, + return "consumed", { romText(data, "_RareCandyText", + "%s grew\nto level %d!", monName(data, target), target.level) }, { leveledTo = target.level } end if STONES[itemId] then - if not target then return "failed", { Strings("It won't have\nany effect.") } end + if not target then return "failed", { noEffect(data) } end -- Yellow's starter Pikachu never evolves: ItemUseEvoStone runs -- IsThisPartyMonStarterPikachu (OT identity match) before -- TryEvolvingMon and bails with the voiced cry + RefusingText. @@ -363,10 +445,8 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) and target.ot == save.player.name and target.otId == save.player.id then require("src.core.Sound").playCry(data, "PIKACHU") - local raw = data.text and data.text._RefusingText - local line = raw and raw:gsub("{RAM:[^}]*}", monName(data, target)) - or Strings("%s\nis refusing!", monName(data, target)) - return "failed", { line } + return "failed", { romText(data, "_RefusingText", + "%s\nis refusing!", monName(data, target)) } end local speciesDef = data.pokemon[target.species] for _, evo in ipairs(speciesDef.evolutions) do @@ -374,44 +454,48 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) return "consumed", nil, { evolveTo = evo.species } end end - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end -- vitamins: +2560 stat exp, refused at 25600+ (ItemUseVitamin, -- engine/items/item_effects.asm) local vitaminStat = VITAMINS[itemId] if vitaminStat then - if not target then return "failed", { Strings("It won't have\nany effect.") } end + if not target then return "failed", { noEffect(data) } end target.statExp = target.statExp or {} local cur = target.statExp[vitaminStat] or 0 if cur >= 25600 then - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end target.statExp[vitaminStat] = math.min(65535, cur + 2560) local Stats = require("src.pokemon.Stats") target.stats = Stats.calc(data.pokemon[target.species], target.level, target.dvs, target.statExp) target.hp = math.min(target.hp, target.stats.hp) + -- _VitaminStatRoseText's slot order is localization-dependent (the + -- Spanish ROM puts the stat before the name), so the extracted line + -- cannot be filled positionally; the engine wording stands return "consumed", { Strings("%s's %s\nrose!", monName(data, target), vitaminStat == "hp" and "HP" or vitaminStat:upper()) } end -- PP UP boosts the move the player picked (ItemUsePPUp's move menu) if itemId == "PP_UP" then - if not target then return "failed", { Strings("It won't have\nany effect.") } end + if not target then return "failed", { noEffect(data) } end local mv = target.moves[moveIndex or 1] local mdef = mv and data.moves[mv.id] if mdef and (mv.ppUps or 0) < 3 then mv.ppUps = (mv.ppUps or 0) + 1 -- each PP UP adds maxPP/5 uses on top of the base maximum mv.pp = mv.pp + math.floor(mdef.pp / 5) - return "consumed", { Strings("%s's PP\nincreased!", mdef.name) } + return "consumed", { romText(data, "_PPIncreasedText", + "%s's PP\nincreased!", mdef.name) } end - return "failed", { Strings("It won't have\nany effect.") } + return "failed", { noEffect(data) } end if itemDef and itemDef.machine then - if not target then return "failed", { Strings("It won't have\nany effect.") } end + if not target then return "failed", { noEffect(data) } end local speciesDef = data.pokemon[target.species] local ok = false for _, m in ipairs(speciesDef.tmhm) do @@ -422,11 +506,16 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) -- plays SFX_DENIED before MonCannotLearnMachineMoveText (the generic -- ItemUseNotTime/NoCyclingAllowedHere paths are silent) require("src.core.Sound").play(data, "Denied") - return "failed", { Strings("%s can't\nlearn that move!", monName(data, target)) } + local moveName = data.moves[itemDef.machine.move].name + return "failed", { romText(data, "_MonCannotLearnMachineMoveText", + "%s can't\nlearn that move!", + monName(data, target), moveName, moveName) } end for _, mv in ipairs(target.moves) do if mv.id == itemDef.machine.move then - return "failed", { Strings("It knows that\nmove already!") } + return "failed", { romText(data, "_AlreadyKnowsText", + "It knows that\nmove already!", + monName(data, target), data.moves[itemDef.machine.move].name) } end end -- HMs are never consumed; TMs are single-use @@ -435,21 +524,28 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) if itemId == "OLD_ROD" or itemId == "GOOD_ROD" or itemId == "SUPER_ROD" then if battle then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end -- FishingInit (engine/items/item_effects.asm): cp wWalkBikeSurfState, 2 -- (surfing) sets carry, and every ItemUseXRod does jp c, ItemUseNotTime -- on that carry -- surfing refuses the rod with the same OAK text as -- the mid-battle case above, no rod-specific message (#533) if ow and ow.player and ow.player.surfing then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end return "fish", itemId end if itemId == "BICYCLE" then if battle then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } + end + -- ItemUseBicycle (engine/items/item_effects.asm) opens with + -- `cp 2 ; is the player surfing?` -> jp z, ItemUseNotTime, so the + -- BICYCLE refuses on the water with the same OAK text as the rods + -- above (#846) + if ow and ow.player and ow.player.surfing then + return "failed", { notTime(data, save) } end return "bicycle" end @@ -459,26 +555,27 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow) end if itemId == "TOWN_MAP" then if battle then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end return "townmap" end if itemId == "ITEMFINDER" then if battle then - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end return "itemfinder" end if itemId == "COIN_CASE" then - return "failed", { Strings("Coin count:\n%d", save.coins or 0) } + return "failed", { romText(data, "_CoinCaseNumCoinsText", + "Coin count:\n%d", save.coins or 0) } end - if itemId == "REPEL" or itemId == "SUPER_REPEL" or itemId == "MAX_REPEL" then + if REPELS[itemId] then local steps = itemId == "REPEL" and 100 or itemId == "SUPER_REPEL" and 200 or 250 save.repelSteps = steps return "consumed", { Strings("%s used\n%s!", save.player.name, name) } end - return "failed", { Strings("OAK: %s!\nThis isn't the\ntime to use that!", save.player.name) } + return "failed", { notTime(data, save) } end return ItemEffects diff --git a/src/link/Json.lua b/src/link/Json.lua index 9ddb2e30..b6bd58a4 100644 --- a/src/link/Json.lua +++ b/src/link/Json.lua @@ -171,4 +171,26 @@ function Json.decode(s) return nil, v end +-- For an HTTP response that was meant to carry JSON but did not. Returns nil +-- when `s` starts like a JSON object or array (the only shapes the update and +-- index endpoints publish), otherwise a short message naming what the server +-- actually sent -- so callers surface "the response was an HTML page/plain +-- text, not JSON (it starts with ...)" instead of leaking the decoder's +-- low-level "unexpected character 'E'" assert at the first byte of an error +-- page or plain-text outage message. +function Json.describeUnexpected(s) + if type(s) ~= "string" then + return "the response had no body to decode" + end + local first = s:match("^%s*(.)") + if first == "{" or first == "[" then return nil end + local preview = s:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") + if preview == "" then + return "the response was empty, not JSON" + end + if #preview > 60 then preview = preview:sub(1, 57) .. "..." end + local kind = (first == "<") and "an HTML page" or "plain text" + return ("the response was %s, not JSON (it starts with %q)"):format(kind, preview) +end + return Json diff --git a/src/link/LinkState.lua b/src/link/LinkState.lua index 4e97133e..9f3da68d 100644 --- a/src/link/LinkState.lua +++ b/src/link/LinkState.lua @@ -10,6 +10,7 @@ local Net = require("src.link.Net") local Protocol = require("src.link.Protocol") local Runtime = require("src.mods.Runtime") local Screens = require("src.ui.Screens") +local Session = require("src.link.Session") local TextBox = require("src.render.TextBox") local Strings = require("src.core.Strings") @@ -44,10 +45,8 @@ local function forceLevelLabel(v) return (v == ANY or v == nil) and "ANY" or ("AUTO " .. tostring(v)) end --- stages before any Net object is meaningfully "this session's link" -- --- .net can still be a leftover failed attempt sitting on self, so error/ --- closed checks below skip these rather than keying off self.net's --- presence alone +-- stages before a successful transport has become this link's Session; +-- terminal checks skip them rather than keying off self.net's presence local PRE_CONNECT_STAGES = { menu = true, lanMenu = true, onlineMenu = true } -- how long the host waits for a v2 hello before deciding the peer predates @@ -70,6 +69,16 @@ local function ipDigits(ip) return digits end +local function openSession(role, connect) + local transport = Net.new() + if connect(transport) then + return Session.new(transport, { role = role, kind = "link" }) + end + local detail = transport.error or "?" + transport:close() + return nil, detail +end + function LinkState.new(game) local self = setmetatable({}, LinkState) self.game = game @@ -89,12 +98,15 @@ end -- "connecting with this code", same as if the player had typed it in function LinkState.newJoinOnline(game, code) local self = LinkState.new(game) - self.net = Net.new() - if self.net:joinOnline(nil, code) then + local session, detail = openSession("guest", function(transport) + return transport:joinOnline(nil, code) + end) + if session then + self.net = session self.stage = "onlineJoining" else self.stage = "menu" -- exitWith below needs a real stage to unwind from - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + self:exitWith(Strings("Link error:\n%s", detail)) end return self end @@ -163,21 +175,13 @@ end -- take the peer's hello out of the inbox without eating anything that -- shares the batch with it function LinkState:pollHello() - local msgs = self.net:poll() - local keep, got = {}, false - for _, msg in ipairs(msgs) do - if msg.type == "hello" and not self.peerHello then - self.peerHello = msg - self.peerName = msg.name - got = true - else - keep[#keep + 1] = msg - end + local message + if not self.peerHello then message = self.net:take("hello") end + if message then + self.peerHello = message + self.peerName = message.name end - for i = #keep, 1, -1 do - table.insert(self.net.inbox, 1, keep[i]) - end - return got, #keep > 0 + return message ~= nil, self.net:hasPending() end function LinkState:sendHello(mode) @@ -220,13 +224,15 @@ function LinkState:update(dt) local input = self.game.input if self.net then self.net:update() - if self.net.error and not PRE_CONNECT_STAGES[self.stage] then - self:exitWith(Strings("Link error:\n%s", self.net.error:sub(1, 60))) + local status = self.net:getStatus() + if status == "failed" and not PRE_CONNECT_STAGES[self.stage] then + self:exitWith(Strings("Link error:\n%s", + (self.net.error or "?"):sub(1, 60))) return end - -- the peer vanished without a bye (only once the inbox is drained, + -- the peer vanished without a bye (only once the session FIFO drains, -- so a final message travelling with the disconnect still counts) - if self.net.closed and #self.net.inbox == 0 + if status == "closed" and not PRE_CONNECT_STAGES[self.stage] and self.stage ~= "addrEntry" and self.stage ~= "codeEntry" and self.stage ~= "notice" and self.stage ~= "battleRunning" then @@ -269,12 +275,15 @@ function LinkState:update(dt) self.stage = "menu" self.index = 1 elseif input:wasPressed("a") then - self.net = Net.new() if self.index == 1 then - if self.net:host() then + local session, detail = openSession("host", function(transport) + return transport:host() + end) + if session then + self.net = session self.stage = "hosting" else - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + self:exitWith(Strings("Link error:\n%s", detail)) end else self.stage = "addrEntry" @@ -289,11 +298,14 @@ function LinkState:update(dt) self.index = 2 elseif input:wasPressed("a") then if self.index == 1 then - self.net = Net.new() - if self.net:hostOnline() then + local session, detail = openSession("host", function(transport) + return transport:hostOnline() + end) + if session then + self.net = session self.stage = "onlineHosting" else - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + self:exitWith(Strings("Link error:\n%s", detail)) end else self.stage = "codeEntry" @@ -327,11 +339,14 @@ function LinkState:update(dt) CodeEntry.right(self.codeEntry) elseif input:wasPressed("a") then local code = CodeEntry.text(self.codeEntry) - self.net = Net.new() - if self.net:joinOnline(nil, code) then + local session, detail = openSession("guest", function(transport) + return transport:joinOnline(nil, code) + end) + if session then + self.net = session self.stage = "onlineJoining" else - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + self:exitWith(Strings("Link error:\n%s", detail)) end end @@ -367,10 +382,15 @@ function LinkState:update(dt) + self.addr[base + 2] * 10 + self.addr[base + 3]) end - if self.net:join(table.concat(octets, ".")) then + local address = table.concat(octets, ".") + local session, detail = openSession("guest", function(transport) + return transport:join(address) + end) + if session then + self.net = session self.stage = "joining" else - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + self:exitWith(Strings("Link error:\n%s", detail)) end end @@ -428,19 +448,11 @@ function LinkState:update(dt) elseif self.stage == "waitMode" then -- guest waits for host's pick if input:wasPressed("b") then self:exitWith(nil) return end - local msgs = self.net:poll() - for i, msg in ipairs(msgs) do - if msg.type == "hello" then - self.peerHello = msg - self.peerName = msg.name - -- the host's next messages (party, ...) can share this batch; - -- put them back so the new stage's poll sees them - for j = #msgs, i + 1, -1 do - table.insert(self.net.inbox, 1, msgs[j]) - end - self:decideCompat(msg.mode, false) - break - end + local message = self.net:take("hello") + if message then + self.peerHello = message + self.peerName = message.name + self:decideCompat(message.mode, false) end elseif self.stage == "notice" then @@ -456,40 +468,34 @@ function LinkState:update(dt) elseif self.stage == "battleWait" then if input:wasPressed("b") then self:exitWith(nil) return end - local msgs = self.net:poll() - for i, msg in ipairs(msgs) do - if msg.type == "party" then - -- the host owns this rule (same as mode); the guest only learns - -- it here, off the host's own party message - if not self.isHost then self.forceLevel = msg.forceLevel end - local LinkBattle = require("src.link.LinkBattle") - local opts = { - myParty = Protocol.packParty(self.game.save.party), - theirParty = msg.mons, - theirName = self.peerName or "FOE", - seed = self.isHost and self.linkSeed or msg.seed, - verdict = self.verdict, - strict = Handshake.strict(self.verdict), - forceLevel = self.forceLevel, - } - local battle, why - if self.isHost then - battle, why = LinkBattle.newHost(self.game, self.net, opts) - else - battle, why = LinkBattle.newGuest(self.game, self.net, opts) - end - if not battle then - self.net:send({ type = "bye" }) - self:exitWith(why or Strings("Link battle\ncan't start."), "error") - return - end - self.game.stack:push(battle) - self.stage = "battleRunning" - for j = #msgs, i + 1, -1 do - table.insert(self.net.inbox, 1, msgs[j]) - end - break + local message = self.net:take("party") + if message then + -- the host owns this rule (same as mode); the guest only learns + -- it here, off the host's own party message + if not self.isHost then self.forceLevel = message.forceLevel end + local LinkBattle = require("src.link.LinkBattle") + local opts = { + myParty = Protocol.packParty(self.game.save.party), + theirParty = message.mons, + theirName = self.peerName or "FOE", + seed = self.isHost and self.linkSeed or message.seed, + verdict = self.verdict, + strict = Handshake.strict(self.verdict), + forceLevel = self.forceLevel, + } + local battle, why + if self.isHost then + battle, why = LinkBattle.newHost(self.game, self.net, opts) + else + battle, why = LinkBattle.newGuest(self.game, self.net, opts) end + if not battle then + self.net:send({ type = "bye" }) + self:exitWith(why or Strings("Link battle\ncan't start."), "error") + return + end + self.game.stack:push(battle) + self.stage = "battleRunning" end elseif self.stage == "battleRunning" then diff --git a/src/link/Net.lua b/src/link/Net.lua index 950bee1e..b8d4654c 100644 --- a/src/link/Net.lua +++ b/src/link/Net.lua @@ -207,7 +207,7 @@ function Net:send(msg) end if self.peerEnd then -- loopback: re-encode through json like the wire local decoded = Json.decode(Json.encode(msg)) - if decoded and not self.peerEnd.closed then + if decoded ~= nil and not self.peerEnd.closed then table.insert(self.peerEnd.inbox, decoded) end return @@ -256,13 +256,12 @@ end function Net:handleTCPLine(line) local msg = Json.decode(line) - if not msg then + if msg == nil then Logger.warn("link: bad relay message %q", line:sub(1, 60)) return end - if not handleGenericRelayControl(self, msg) then - table.insert(self.inbox, msg) - end + if type(msg) == "table" and handleGenericRelayControl(self, msg) then return end + table.insert(self.inbox, msg) end -- pulls every complete "\n"-terminated line out of rxBuf (leaving a @@ -352,7 +351,7 @@ function Net:update() end elseif event.type == "receive" then local msg = Json.decode(event.data) - if msg then + if msg ~= nil then table.insert(self.inbox, msg) else Logger.warn("link: bad message %q", tostring(event.data):sub(1, 60)) diff --git a/src/link/Session.lua b/src/link/Session.lua new file mode 100644 index 00000000..ecb4a64b --- /dev/null +++ b/src/link/Session.lua @@ -0,0 +1,198 @@ +local Session = {} +Session.__index = Session + +local VALID_ROLES = { host = true, guest = true } +local REQUIRED_METHODS = { "update", "poll", "send", "close" } + +function Session.new(transport, options) + assert(type(transport) == "table", "Session.new requires a transport") + assert(type(options) == "table", "Session.new requires options") + assert(VALID_ROLES[options.role], "Session role must be host or guest") + assert(type(options.kind) == "string" and options.kind ~= "", + "Session kind must be a non-empty string") + for _, method in ipairs(REQUIRED_METHODS) do + assert(type(transport[method]) == "function", + "Session transport requires " .. method) + end + + local self = setmetatable({ + _transport = transport, + _role = options.role, + _kind = options.kind, + _inbox = {}, + _status = "connecting", + _terminal = nil, + _transportCloseCalled = false, + paired = false, + closed = false, + error = nil, + code = nil, + address = nil, + target = nil, + }, Session) + self:_syncMetadata() + self:_refreshStatus() + return self +end + +function Session:_syncMetadata() + local transport = self._transport + self.paired = transport.paired == true + self.code = transport.code + self.address = transport.address + self.target = transport.target +end + +function Session:_refreshStatus() + if not self._terminal then + self._status = self.paired and "paired" or "connecting" + self.closed = false + self.error = nil + return + end + if #self._inbox > 0 then + self._status = "draining" + self.closed = false + self.error = nil + return + end + self._status = self._terminal.status + self.closed = true + self.error = self._terminal.status == "failed" + and (self._terminal.detail or self._terminal.reason) or nil +end + +function Session:_latchTerminal(status, reason, detail) + if self._terminal then return false end + self._terminal = { status = status, reason = reason, detail = detail } + self:_refreshStatus() + return true +end + +function Session:_closeTransport() + if self._transportCloseCalled then return true end + self._transportCloseCalled = true + local ok, detail = pcall(self._transport.close, self._transport) + return ok, ok and nil or tostring(detail) +end + +function Session:getRole() return self._role end +function Session:getKind() return self._kind end +function Session:getStatus() return self._status end +function Session:getFailure() + if not self._terminal or self._terminal.status ~= "failed" then + return nil, nil + end + return self._terminal.reason, self._terminal.detail +end +function Session:hasPending() return #self._inbox > 0 end + +function Session:send(message) + if self._terminal then return nil end + return self._transport:send(message) +end + +function Session:update() + if self._terminal then + self:_refreshStatus() + return + end + + local failureReason, failureDetail + local updateOk, updateDetail = pcall(self._transport.update, self._transport) + self:_syncMetadata() + if not updateOk then + failureReason, failureDetail = "transport_error", tostring(updateDetail) + elseif self._transport.error then + failureReason = "transport_error" + failureDetail = tostring(self._transport.error) + end + + local pollOk, messages = pcall(self._transport.poll, self._transport) + if not pollOk then + if not failureReason then + failureReason, failureDetail = "transport_error", tostring(messages) + end + elseif type(messages) ~= "table" then + if not failureReason then + failureReason, failureDetail = "transport_error", + "transport poll returned non-table" + end + else + for index = 1, #messages do + local message = messages[index] + if type(message) ~= "table" or type(message.type) ~= "string" then + if not failureReason then + failureReason = "protocol_error" + failureDetail = ("message %d must be a table with string type") + :format(index) + end + break + end + self._inbox[#self._inbox + 1] = message + end + end + + self:_syncMetadata() + if failureReason then + self:_latchTerminal("failed", failureReason, failureDetail) + self:_closeTransport() + elseif self._transport.closed then + local closeOk, closeDetail = self:_closeTransport() + if closeOk then + self:_latchTerminal("closed") + else + self:_latchTerminal("failed", "transport_error", closeDetail) + end + end + self:_refreshStatus() +end + +local function finishRead(self) + self:_refreshStatus() +end + +function Session:take(messageType) + assert(type(messageType) == "string", "Session.take requires a message type") + for index, message in ipairs(self._inbox) do + if message.type == messageType then + local found = table.remove(self._inbox, index) + finishRead(self) + return found + end + end + return nil +end + +function Session:pollOne() + if #self._inbox == 0 then return nil end + local message = table.remove(self._inbox, 1) + finishRead(self) + return message +end + +function Session:poll() + local messages = self._inbox + self._inbox = {} + finishRead(self) + return messages +end + +function Session:close() + if self._status == "closed" or self._status == "failed" then return end + if self._terminal then + self:_closeTransport() + self:_refreshStatus() + return + end + local ok, detail = self:_closeTransport() + if ok then + self:_latchTerminal("closed") + else + self:_latchTerminal("failed", "transport_error", detail) + end + self:_syncMetadata() + self:_refreshStatus() +end + +return Session diff --git a/src/link/Tournament.lua b/src/link/Tournament.lua index cfe663f4..a8a06193 100644 --- a/src/link/Tournament.lua +++ b/src/link/Tournament.lua @@ -14,6 +14,7 @@ local Font = require("src.render.Font") local Handshake = require("src.link.Handshake") local LinkBattle = require("src.link.LinkBattle") local Net = require("src.link.Net") +local Session = require("src.link.Session") local Protocol = require("src.link.Protocol") local Runtime = require("src.mods.Runtime") local Sound = require("src.core.Sound") @@ -132,12 +133,23 @@ end -- host / join -- ------------------------------------------------------------------- +local function openSession(role) + local transport = Net.new() + if transport:connectTCP(Net.defaultRelayAddress()) then + return Session.new(transport, { role = role, kind = "tournament" }) + end + local detail = transport.error or "?" + transport:close() + return nil, detail +end + function Tournament:startHosting() - self.net = Net.new() - if not self.net:connectTCP(Net.defaultRelayAddress()) then - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + local session, detail = openSession("host") + if not session then + self:exitWith(Strings("Link error:\n%s", detail)) return end + self.net = session local size, minL, maxL = partyStats(self.game.save.party) self.isCreator = true self.participating = self.settings.participating @@ -156,11 +168,12 @@ function Tournament:startHosting() end function Tournament:startJoining(code) - self.net = Net.new() - if not self.net:connectTCP(Net.defaultRelayAddress()) then - self:exitWith(Strings("Link error:\n%s", self.net.error or "?")) + local session, detail = openSession("guest") + if not session then + self:exitWith(Strings("Link error:\n%s", detail)) return end + self.net = session local size, minL, maxL = partyStats(self.game.save.party) self.isCreator = false self.participating = true -- joining is always to compete; only hosting can opt out @@ -256,22 +269,14 @@ function Tournament:sendHello(mode) end function Tournament:pollHello() - local msgs = self.net:poll() - local keep, got = {}, false - for _, msg in ipairs(msgs) do - if msg.type == "hello" and not self.peerHello then - self.peerHello = msg - got = true - else - keep[#keep + 1] = msg - end - end - for i = #keep, 1, -1 do - table.insert(self.net.inbox, 1, keep[i]) - end - return got + local message + if not self.peerHello then message = self.net:take("hello") end + if message then self.peerHello = message end + return message ~= nil end +-- The relay assigns a side for each match; this can differ from the immutable +-- tournament creator/joiner role held by Session. function Tournament:enterMatch(msg) self.isHost = (msg.role == "host") self.opponentName = msg.opponent @@ -356,12 +361,13 @@ function Tournament:update(dt) if self.net then self.net:update() - if self.net.error and self.stage ~= "menu" and self.stage ~= "hostSettings" + local status = self.net:getStatus() + if status == "failed" and self.stage ~= "menu" and self.stage ~= "hostSettings" and self.stage ~= "codeEntry" then - self:exitWith(Strings("Link error:\n%s", self.net.error:sub(1, 60))) + self:exitWith(Strings("Link error:\n%s", (self.net.error or "?"):sub(1, 60))) return end - if self.net.closed and self.stage ~= "done" then + if status == "closed" and self.stage ~= "done" then self:exitWith(Strings("The tournament\nconnection was\nlost.")) return end @@ -370,24 +376,23 @@ function Tournament:update(dt) if self.stage == "matchHello" then if input:wasPressed("b") then self:exitWith(nil) return end self:pollHello() - if self.peerHello then self:beginMatchBattle() end - for _, msg in ipairs(self.net:poll()) do self:handleMessage(msg) end + if self.peerHello then + self:beginMatchBattle() + return + end + for _, message in ipairs(self.net:poll()) do self:handleMessage(message) end return elseif self.stage == "matchWaitParty" then if input:wasPressed("b") then self:exitWith(nil) return end - local msgs = self.net:poll() - for i, msg in ipairs(msgs) do - if msg.type == "party" then - self.pendingBattleOpts.theirParty = msg.mons + while self.net:hasPending() do + local message = self.net:pollOne() + if message.type == "party" then + self.pendingBattleOpts.theirParty = message.mons if self.isHost then self.pendingBattleOpts.seed = self.pendingBattleOpts.seed or self.linkSeed else - self.pendingBattleOpts.seed = msg.seed + self.pendingBattleOpts.seed = message.seed end - -- Split rather than `cond and newHost() or newGuest()`: the and/or - -- idiom truncates a call to its first result, so the second return - -- (the specific reason) was always dropped and every failure showed - -- the generic fallback instead of "same mods on both games" etc. local battle, why if self.isHost then battle, why = LinkBattle.newHost(self.game, self.net, self.pendingBattleOpts) @@ -398,27 +403,22 @@ function Tournament:update(dt) self:exitWith(why or Strings("Link battle\ncan't start.")) return end - -- anything after `party` in this same batch belongs to the - -- battle now, not to Tournament -- put it back for its own poll() - for j = #msgs, i + 1, -1 do - table.insert(self.net.inbox, 1, msgs[j]) - end self.activeBattle = battle self.game.stack:push(battle) self.stage = "matchRunning" return else - self:handleMessage(msg) + self:handleMessage(message) end end return elseif self.stage == "spectateWait" then if input:wasPressed("b") then self:exitWith(nil) return end - local msgs = self.net:poll() - for i, msg in ipairs(msgs) do - if msg.type == "spectate" and msg.msg.type == "party" then - local inner = msg.msg - if msg.side == "host" then + while self.net:hasPending() do + local message = self.net:pollOne() + if message.type == "spectate" and message.msg.type == "party" then + local inner = message.msg + if message.side == "host" then self.spectate.hostParty = inner.mons self.spectate.seed = inner.seed else @@ -426,8 +426,10 @@ function Tournament:update(dt) end if self.spectate.hostParty and self.spectate.guestParty then local battle, why = LinkBattle.newSpectator(self.game, self.net, { - hostParty = self.spectate.hostParty, guestParty = self.spectate.guestParty, - hostName = self.spectate.hostName, guestName = self.spectate.guestName, + hostParty = self.spectate.hostParty, + guestParty = self.spectate.guestParty, + hostName = self.spectate.hostName, + guestName = self.spectate.guestName, seed = self.spectate.seed, forceLevel = levelForWire(self.settings.forceLevel), }) @@ -435,16 +437,13 @@ function Tournament:update(dt) self:exitWith(why or Strings("Can't watch this\nmatch.")) return end - for j = #msgs, i + 1, -1 do - table.insert(self.net.inbox, 1, msgs[j]) - end self.activeBattle = battle self.game.stack:push(battle) self.stage = "spectateRunning" return end else - self:handleMessage(msg) + self:handleMessage(message) end end return diff --git a/src/mods/LauncherMods.lua b/src/mods/LauncherMods.lua index 2091af91..e078b232 100644 --- a/src/mods/LauncherMods.lua +++ b/src/mods/LauncherMods.lua @@ -254,6 +254,80 @@ function LauncherMods.list() return result or {} end +-- ------- pre-boot translation strings +-- +-- The launcher draws before Game:load, so the loader has not run and Strings +-- has no catalog. #767/#791 routed the launcher's text through Strings, but +-- nothing filled the catalog this early, so a translation mod still could not +-- reach the launcher however complete it was -- and no restart helped, because +-- the ordering is the same on every launch. +-- +-- This fills it, and deliberately does the smallest thing that can: one +-- declarative file per enabled mod, lang/strings.lua, and never the entry +-- chunk. That keeps the promise the rest of this module is built on -- no mod +-- behaviour runs before the game boots -- because a catalog is data. +-- +-- It is still a mod-authored chunk, so it runs with an empty environment: a +-- plain `return { ... }` evaluates fine, while anything reaching for love, io +-- or os raises and is skipped rather than being trusted this early. +-- +-- Game:load calls Strings.load(Data) again after the real merge, which +-- replaces whatever this installed, so the two never disagree for long. +local STRINGS_CATALOG = "lang/strings.lua" + +local function readStringsCatalog(path) + local fs = love and love.filesystem + if not (fs and fs.read) then return nil end + local rel = path .. "/" .. STRINGS_CATALOG + local raw = fs.read(rel) + if type(raw) ~= "string" or raw == "" then return nil end + local chunk = loadstring(raw, "@" .. rel) + if not chunk then return nil end + -- Lua 5.1/LuaJIT: no _ENV, so setfenv is the sandbox. + if setfenv then setfenv(chunk, {}) end + local ok, result = pcall(chunk) + if not ok or type(result) ~= "table" then return nil end + return result +end + +-- deriveStrings(rows, byId, read) -> the merged catalog, pure. +-- rows is deriveList's output, byId the id -> manifest map, and read(path) a +-- reader returning that mod's catalog table (or nil). Split out so the engine +-- tier can table-drive the enable/precedence rules with no filesystem. +function LauncherMods.deriveStrings(rows, byId, read) + local out, any = {}, false + for _, row in ipairs(rows or {}) do + local manifest = row.enabled and byId and byId[row.id] or nil + local catalog = manifest and manifest.path and read(manifest.path) + for source, value in pairs(catalog or {}) do + -- an empty value means "not translated yet", never "translate to + -- blank" -- the same rule the mod's own loader applies + if type(source) == "string" and type(value) == "string" + and value ~= "" then + out[source] = value + any = true + end + end + end + return any and out or nil +end + +-- translationStrings() -> a source -> translation map for the launcher, or nil +-- when no enabled mod ships one. Enable-state and ordering are deriveList's, +-- so a mod that wins a key here wins it at boot too. +function LauncherMods.translationStrings() + local ok, merged = pcall(function() + local manifests = discover() + if #manifests == 0 then return nil end + local rows = LauncherMods.deriveList(manifests, SaveData.loadOptions()) + local byId = {} + for _, m in ipairs(manifests) do byId[m.id] = m end + return LauncherMods.deriveStrings(rows, byId, readStringsCatalog) + end) + if not ok then return nil end + return merged +end + -- setEnabled(id, enabled): persist options.mods[id] in the exact shape -- Loader:_saveState writes (a plain boolean), so the running game and the -- in-game ManagerState pick it up unchanged. @@ -400,6 +474,33 @@ local function removeTree(path) fs.remove(path) end +-- Every mods/ folder currently holding this id, plus the bare mods/ tree +-- even when its manifest is missing or unreadable. Second return: whether any +-- of them carries a manifest the panel can actually list. An install names +-- its dest after the manifest id, but a hand-unzipped copy keeps whatever +-- folder name the archive carried, and discover()'s first-id-wins rule means +-- whichever folder physfs happens to enumerate first is the one the panel and +-- the loader really use. Replacing only mods/ let an update report +-- success while the old copy kept winning that race (#801); and a +-- manifest-less mods/ left by an interrupted copy blocked every re-import +-- as "already installed" while showing nowhere the player could see (#834). +local function sameIdTrees(fs, id) + local out, installed = {}, false + if not fs.getInfo("mods") then return out, installed end + for _, name in ipairs(fs.getDirectoryItems("mods")) do + local path = "mods/" .. name + local raw = fs.read(path .. "/manifest.json") + local manifest = raw and decodeManifest(raw, path) + if manifest and manifest.id == id then + out[#out + 1] = path + installed = true + elseif name == id and fs.getInfo(path) then + out[#out + 1] = path + end + end + return out, installed +end + -- ------- strays: mods dropped beside the game that it cannot see -- love.filesystem looks in two places for "mods/": the save directory, and -- @@ -592,16 +693,21 @@ function LauncherMods._installZipInner(source, opts) end local dest = "mods/" .. manifest.id - if fs.getInfo(dest) then - if not opts.replace then - cleanup() - return nil, "a mod named '" .. manifest.id .. "' is already installed" - end - -- drop the old tree before copy; enable-flag is preserved (uninstall - -- would clear it, which would surprise an update) + local existing, installedSomewhere = sameIdTrees(fs, manifest.id) + if installedSomewhere and not opts.replace then + cleanup() + return nil, "a mod named '" .. manifest.id .. "' is already installed" + end + if #existing > 0 then + -- drop every old tree before copy -- mods/ and any same-id folder + -- under another name, or the survivor keeps winning discover()'s + -- first-id-wins race after the "successful" update (#801). A tree with + -- no readable manifest is debris from an interrupted copy: it never + -- refuses the install, it only gets cleared (#834). Enable-flag is + -- preserved (uninstall would clear it, which would surprise an update). local savedPrefix = CacheFs.prefix CacheFs.prefix = "" - removeTree(dest) + for _, path in ipairs(existing) do removeTree(path) end CacheFs.prefix = savedPrefix end @@ -652,6 +758,30 @@ function LauncherMods.installFromRelease(modId, release) return result, err end +-- The install half of installFromRelease, split out so the launcher can run +-- the DOWNLOAD half asynchronously (src/net/Fetch.lua) and still land in the +-- same place. `localPath` is a love.filesystem-relative path to an already +-- downloaded zip; it is consumed (removed) either way. +-- Returns true, version | nil, errString. +function LauncherMods.installDownloadedZip(modId, localPath, version) + local ok, result, err = pcall(function() + if type(modId) ~= "string" or modId == "" then + return nil, "missing mod id" + end + if type(localPath) ~= "string" or localPath == "" then + return nil, "missing downloaded archive" + end + local installed, res = LauncherMods.installZip(localPath, { + replace = true, expectId = modId, + }) + pcall(love.filesystem.remove, localPath) + if not installed then return nil, res end + return true, version or res + end) + if not ok then return nil, "install failed: " .. tostring(result) end + return result, err +end + -- Install a mod listed in a community index (src/mods/ModIndex.lua). -- The index only ever tells us WHERE the zip is; resolving that URL is -- ModIndex's job and installing it is installFromRelease's, so this is the @@ -691,14 +821,17 @@ function LauncherMods.uninstall(id) return nil, "mod uninstall needs LOVE" end local fs = love.filesystem - local dest = "mods/" .. id - if not fs.getInfo(dest) then + local trees = sameIdTrees(fs, id) + if #trees == 0 then return nil, "mod '" .. id .. "' is not installed" end - -- same root pin as installZip: the mods tree is not version-prefixed (#330) + -- same root pin as installZip: the mods tree is not version-prefixed (#330). + -- Every same-id tree goes, folder name notwithstanding, so Delete works on a + -- hand-unzipped copy too and cannot leave a shadow copy for discover()'s + -- first-id-wins rule to resurrect on the next boot (#801) local savedPrefix = CacheFs.prefix CacheFs.prefix = "" - removeTree(dest) + for _, path in ipairs(trees) do removeTree(path) end CacheFs.prefix = savedPrefix -- Drop the enable flag so a reinstall of the same id starts from the -- loader's default (enabled) rather than a stale false. diff --git a/src/mods/Loader.lua b/src/mods/Loader.lua index ba92fa95..fa6f6a34 100644 --- a/src/mods/Loader.lua +++ b/src/mods/Loader.lua @@ -138,6 +138,7 @@ function Loader.new(opts) events = Events.new(), hooks = Hooks.new(), content = {}, assets = {}, exports = {}, migrations = {}, order = {}, modSave = {}, modOptions = {}, optionSchemas = {}, imageCache = {}, + modInput = {}, fs = (opts and opts.fs) or (love and love.filesystem), dev = dev, }, Loader) @@ -527,9 +528,50 @@ function Loader:_registerCommand(modId, verb, fn) return self.content.commands:register(verb, fn, modId) end +-- the GB buttons mod.input may drive (#807) +local GB_BUTTONS = { + up = true, down = true, left = true, right = true, + a = true, b = true, start = true, select = true, +} + +-- per-mod mod.input ledger (#807): seq numbers this mod's Input sources, +-- tokens maps each opaque press token to what release must undo. Living +-- on the loader (not the api closure) is what lets rollback, hot reload +-- and input recovery retire a mod's holds from outside the mod's own code. +function Loader:_modInput(modId) + local bucket = self.modInput[modId] + if not bucket then + bucket = { seq = 0, tokens = {} } + self.modInput[modId] = bucket + end + return bucket +end + +-- Release every outstanding mod.input hold: one mod's on entry-chunk +-- rollback, everyone's (no argument) on hot reload and input recovery +-- (#807). When Input:reset already dropped the sources these releases +-- are no-ops; the point is the stale tokens die with the code that took +-- them, so a later mod.input:release on one is refused instead of +-- touching a button someone else now holds. +function Loader:releaseModInput(modId) + if modId == nil then + for id in pairs(self.modInput) do self:releaseModInput(id) end + return + end + local bucket = self.modInput[modId] + if not bucket then return end + self.modInput[modId] = nil + for _, rec in pairs(bucket.tokens) do + rec.input:sourceRelease(rec.btn, rec.source) + end +end + function Loader:_api(mod) local loader = self local modId = mod.manifest.id + local Storage = engineRequire("src.mods.Storage") + local storage = Storage and Storage.new(modId, loader.fs) + local Checkpoint = engineRequire("src.core.Checkpoint") local api = { id = modId, version = mod.manifest.version, @@ -559,6 +601,45 @@ function Loader:_api(mod) hooks = { wrap = function(_, name, callback, priority) return loader.hooks:wrap(name, callback, priority, modId) end }, + -- source-safe scripted GB input (#807): tap queues exactly one + -- wasPressed edge for the next fixed step with no held state; press + -- holds until release. Every call is its own "mod::" source in + -- game.input, so releasing a token can never drop a button the + -- keyboard, a pad, the touch overlay, or another mod still holds. + input = { + tap = function(_, game, btn) + local input = game and game.input + assert(input, "mod.input needs the live game (see game.ready)") + assert(GB_BUTTONS[btn], "unknown GB button: " .. tostring(btn)) + local bucket = loader:_modInput(modId) + bucket.seq = bucket.seq + 1 + local source = "mod:" .. modId .. ":" .. bucket.seq + input:sourcePress(btn, source) + input:sourceRelease(btn, source) + end, + press = function(_, game, btn) + local input = game and game.input + assert(input, "mod.input needs the live game (see game.ready)") + assert(GB_BUTTONS[btn], "unknown GB button: " .. tostring(btn)) + local bucket = loader:_modInput(modId) + bucket.seq = bucket.seq + 1 + local source = "mod:" .. modId .. ":" .. bucket.seq + input:sourcePress(btn, source) + local token = {} + bucket.tokens[token] = { input = input, btn = btn, source = source } + return token + end, + -- idempotent, and a token another mod took is simply not in this + -- ledger, so cross-mod release is refused by construction + release = function(_, token) + local bucket = loader.modInput[modId] + local rec = bucket and bucket.tokens[token] + if not rec then return false end + bucket.tokens[token] = nil + rec.input:sourceRelease(rec.btn, rec.source) + return true + end, + }, -- the widget toolkit facade (12 4.5) is one shared surface, not -- per-mod state; each widget inside it loads on first touch ui = ModUI, @@ -580,6 +661,25 @@ function Loader:_api(mod) bucket[key] = value end, }, + -- Data-only state independent of the vanilla progress checkpoint. The + -- engine binds version/playthrough/mod scope and portable persistence; + -- callers never receive paths or a raw filesystem handle. + storage = { + context = function(_, game) return storage:context(game) end, + write = function(_, game, key, value) return storage:write(game, key, value) end, + read = function(_, game, key) return storage:read(game, key) end, + list = function(_, game, prefix) return storage:list(game, prefix) end, + delete = function(_, game, key) return storage:delete(game, key) end, + }, + -- Runtime safety and reconstruction stay engine-owned. Checkpoints contain + -- data only; no controller, stack, coroutine or renderer object crosses out. + checkpoints = { + inspect = function(_, game) return Checkpoint.inspect(game) end, + capture = function(_, game) return Checkpoint.capture(game) end, + restore = function(_, game, checkpoint) + return Checkpoint.restore(game, checkpoint) + end, + }, options = { define = function(_, schema) assert(type(schema) == "table", "options schema must be a table of rows") @@ -714,6 +814,7 @@ function Loader:_rollback(modId) end self.events:removeOwner(modId) self.hooks:removeOwner(modId) + self:releaseModInput(modId) self.exports[modId] = nil self.optionSchemas[modId] = nil self.migrations[modId] = nil @@ -850,6 +951,16 @@ function Loader:load(data) end end end + -- A manifest may name an env var that force-enables it regardless of a + -- saved disable in options.mods -- generic, not tied to any mod id, for + -- a mod (e.g. a native-launcher bridge) that cannot function disabled on + -- the one build where its env var is set. + for id, mod in pairs(self.mods) do + local envName = mod.manifest.force_enable_env + if envName and os.getenv(envName) == "1" then + self.disabled[id] = nil + end + end for id, mod in pairs(self.mods) do mod.enabled = not self.disabled[id] mod.state = mod.enabled and "pending" or "disabled" diff --git a/src/mods/Manifest.lua b/src/mods/Manifest.lua index 97bed0ac..9d1378a6 100644 --- a/src/mods/Manifest.lua +++ b/src/mods/Manifest.lua @@ -229,6 +229,7 @@ function Manifest.validate(raw, path) permissionSet = permissionSet, options_schema = optionalFile(raw.options_schema, "options_schema"), assets_transforms = optionalFile(raw.assets_transforms, "assets_transforms"), + force_enable_env = optionalFile(raw.force_enable_env, "force_enable_env"), path = path, raw = raw, } diff --git a/src/mods/ModIndex.lua b/src/mods/ModIndex.lua index 9bb71542..3988b9a9 100644 --- a/src/mods/ModIndex.lua +++ b/src/mods/ModIndex.lua @@ -191,8 +191,10 @@ end -- Never throws: a truncated download, an HTML error page, or a feed from a -- future schema all come back as a message the panel can print. function ModIndex.parse(jsonText, Json) + Json = Json or require("src.link.Json") + local notJson = Json.describeUnexpected(jsonText) + if notJson then return nil, notJson end local ok, result, err = pcall(function() - Json = Json or require("src.link.Json") local doc, decodeErr = Json.decode(jsonText) if type(doc) ~= "table" then return nil, decodeErr or "index.json is not an object" @@ -592,6 +594,101 @@ function ModIndex.fetch(source, opts) return index, nil, { fromCache = false, checkedAt = os.time() } end +-- ------- async fetch (the launcher's path; ModIndex.fetch above stays as the +-- synchronous one for tests and non-UI callers) +-- +-- ModIndex.fetch blocks on curl, which on the render thread froze the Find +-- Mods tab for as long as the server took. These three functions are the +-- same state machine driven a frame at a time over src/net/Fetch.lua: +-- local h = ModIndex.beginFetch(source, { force = true }) +-- -- every frame: +-- local done, index, err, meta = ModIndex.pumpFetch(h) +-- pumpFetch returns done=false while the request is in flight. The cache +-- rules are identical to the sync path: a fresh cache short-circuits the +-- network entirely (so the handle completes on its first pump), a failed +-- live fetch falls back to stale cache, and the fallback mirror gets one try +-- before the feed counts as an outage. +function ModIndex.beginFetch(source, opts) + opts = opts or {} + local h = { source = source, opts = opts, stage = "start" } + if type(source) ~= "table" or type(source.feed) ~= "string" then + h.stage, h.err = "done", "missing index source" + return h + end + return h +end + +-- Shared with the sync path's `cached` closure: read whatever is in the +-- options cache and shape it like a parsed index. +local function cachedIndex(feed, stale) + local entry = ModIndex.readCache(feed) + if not entry then return nil end + return { + schemaVersion = ModIndex.SCHEMA_VERSION, + generatedAt = entry.generatedAt, + categories = entry.categories or {}, + mods = entry.mods or {}, + }, nil, { fromCache = true, stale = stale, checkedAt = entry.checkedAt } +end + +-- Returns done, index, err, meta. +function ModIndex.pumpFetch(h) + if not h then return true, nil, "no handle" end + local Fetch = require("src.net.Fetch") + local feed = h.source and h.source.feed + + if h.stage == "done" then + return true, h.index, h.err, h.meta + end + + if h.stage == "start" then + if not h.opts.force then + local entry = ModIndex.readCache(feed) + if ModIndex.cacheFresh(entry) then + h.index, h.err, h.meta = cachedIndex(feed, false) + h.stage = "done" + return true, h.index, h.err, h.meta + end + end + h.job = Fetch.get(feed, { userAgent = "gen1recomp-mod-index" }) + h.stage = "feed" + return false + end + + local st = Fetch.poll(h.job) + if st.status == "pending" then return false end + Fetch.release(h.job) + + if st.status == "ok" and st.body then + local index, parseErr = ModIndex.parse(st.body) + if index then + ModIndex.writeCache(feed, index) + h.index, h.meta = index, { fromCache = false, checkedAt = os.time() } + h.stage = "done" + return true, h.index, nil, h.meta + end + -- A feed that parses badly is an outage as far as the UI is concerned. + h.parseErr = parseErr + end + + -- Pages deploys trail a push; the raw mirror is the same file, so a feed + -- that fails right after a release is worth one retry elsewhere. + if h.stage == "feed" and h.source.fallback then + h.job = Fetch.get(h.source.fallback, { userAgent = "gen1recomp-mod-index" }) + h.stage = "fallback" + return false + end + + local index, _, meta = cachedIndex(feed, true) + h.stage = "done" + if index then + h.index, h.meta = index, meta + return true, index, nil, meta + end + h.err = h.parseErr or st.err or "index fetch failed" + return true, nil, h.err +end + -- Fetch a description_url / any index-relative text file. Returns the raw -- markdown; callers run it through ModUpdate.cleanBody for display. function ModIndex.fetchText(url) diff --git a/src/mods/ModUpdate.lua b/src/mods/ModUpdate.lua index d9051663..4768bf70 100644 --- a/src/mods/ModUpdate.lua +++ b/src/mods/ModUpdate.lua @@ -140,8 +140,10 @@ end -- Decode a releases array (GET /repos/.../releases) into a sorted list -- (newest first). Releases without a .zip asset are dropped. Never throws. function ModUpdate.parseReleases(jsonText, modId, Json) + Json = Json or require("src.link.Json") + local notJson = Json.describeUnexpected(jsonText) + if notJson then return nil, notJson end local ok, result, err = pcall(function() - Json = Json or require("src.link.Json") local doc, decodeErr = Json.decode(jsonText) if type(doc) ~= "table" then return nil, decodeErr or "releases json is not an array" @@ -257,16 +259,21 @@ end -- across all releases - Released 2024-05-31 - Updated 2026-07-01". -- Each part is optional; nil everywhere means nil, so a row with no data -- shows no line rather than a wrong "0". +function ModUpdate.downloadsLine(total) + if total == nil then return nil end + return Strings("%s downloads across all releases", + ModUpdate.formatCount(total)) +end + +function ModUpdate.datesLine(first, latest) + if not (first or latest) then return nil end + return Strings("Released %s - Updated %s", first or "?", latest or "?") +end + function ModUpdate.statsLine(total, first, latest) local parts = {} - if total ~= nil then - parts[#parts + 1] = Strings("%s downloads across all releases", - ModUpdate.formatCount(total)) - end - if first or latest then - parts[#parts + 1] = Strings("Released %s - Updated %s", - first or "?", latest or "?") - end + parts[#parts + 1] = ModUpdate.downloadsLine(total) + parts[#parts + 1] = ModUpdate.datesLine(first, latest) if #parts == 0 then return nil end return table.concat(parts, " - ") end @@ -416,6 +423,117 @@ function ModUpdate.fetchReleases(repo, modId, opts) return list, nil, { fromCache = false } end +-- ------- async siblings (the launcher's path; the sync functions above stay +-- for tests and non-UI callers) +-- +-- Same cache and fallback rules as fetchReleases, driven one frame at a time +-- over src/net/Fetch.lua so a release check never stalls the render thread. +-- local h = ModUpdate.beginFetchReleases(repo, modId, { force = true }) +-- local done, releases, err, meta = ModUpdate.pumpFetchReleases(h) +function ModUpdate.beginFetchReleases(repo, modId, opts) + opts = opts or {} + local h = { repo = repo, modId = modId, opts = opts, stage = "start" } + if type(repo) ~= "string" or repo == "" then + h.stage, h.err = "done", "missing github repo" + end + return h +end + +local function staleReleases(repo) + local cached = ModUpdate.readCache(repo) + if cached and cached.releases then + return cached.releases, nil, { fromCache = true, stale = true } + end + return nil +end + +-- Returns done, releases, err, meta. +function ModUpdate.pumpFetchReleases(h) + if not h then return true, nil, "no handle" end + if h.stage == "done" then return true, h.releases, h.err, h.meta end + local Fetch = require("src.net.Fetch") + + if h.stage == "start" then + if not h.opts.force then + local cached = ModUpdate.readCache(h.repo) + if ModUpdate.cacheFresh(cached) and ModUpdate.cacheUsable(cached) then + h.releases, h.meta = cached.releases, { fromCache = true } + h.stage = "done" + return true, h.releases, nil, h.meta + end + end + h.job = Fetch.get(ModUpdate.apiReleasesUrl(h.repo), { + userAgent = "gen1recomp-mod-updater", + accept = "application/vnd.github+json", + }) + h.stage = "fetching" + return false + end + + local st = Fetch.poll(h.job) + if st.status == "pending" then return false end + Fetch.release(h.job) + h.stage = "done" + + if st.status == "ok" and st.body then + local list, parseErr = ModUpdate.parseReleases(st.body, h.modId) + if list then + ModUpdate.writeCache(h.repo, list) + h.releases, h.meta = list, { fromCache = false } + return true, list, nil, h.meta + end + h.err = parseErr + return true, nil, parseErr + end + + -- Offline or a failed call: stale cache beats an empty list. + local rel, _, meta = staleReleases(h.repo) + if rel then + h.releases, h.meta = rel, meta + return true, rel, nil, meta + end + h.err = st.err or "release check failed" + return true, nil, h.err +end + +-- Async download of a mod zip into the save directory. Returns a handle; +-- pump it for done, savePath, err. +function ModUpdate.beginDownloadZip(url, destName, size) + local h = { stage = "done" } + if type(url) ~= "string" or url == "" then + h.err = "missing download url" + return h + end + if not (love and love.filesystem) then + h.err = "download needs LOVE" + return h + end + local name = destName or ("mod_update_" .. tostring(os.time()) .. ".zip") + name = tostring(name):gsub("[/\\]", "_") + local Fetch = require("src.net.Fetch") + h.name = name + h.stage = "fetching" + h.job = Fetch.download(url, name, { + size = size, userAgent = "gen1recomp-mod-updater" }) + return h +end + +function ModUpdate.pumpDownloadZip(h) + if not h then return true, nil, "no handle" end + if h.stage == "done" then return true, h.path, h.err end + local Fetch = require("src.net.Fetch") + local st = Fetch.poll(h.job) + if st.status == "pending" then return false, nil, nil, st.progress end + Fetch.release(h.job) + h.stage = "done" + if st.status == "ok" then + h.path = st.path or h.name + return true, h.path + end + h.err = st.err or "download failed" + return true, nil, h.err +end + function ModUpdate.downloadZip(url, destName) if type(url) ~= "string" or url == "" then return nil, "missing download url" diff --git a/src/mods/Schemas.lua b/src/mods/Schemas.lua index 42f5bc66..3c033bbe 100644 --- a/src/mods/Schemas.lua +++ b/src/mods/Schemas.lua @@ -74,7 +74,11 @@ function f.map(key, value) desc = ("map of %s -> %s"):format(key.desc, value.desc) } end -function f.rec(fields) +-- opts.strict closes the record to unknown fields even at the extensible +-- top level. A union alternative whose fields are ALL optional needs this: +-- with top-level leniency it matches every table, so the union stops +-- rejecting anything (the font "ttf" shape was the first such alternative). +function f.rec(fields, opts) local names = {} for name in pairs(fields) do names[#names + 1] = name end table.sort(names) @@ -83,7 +87,7 @@ function f.rec(fields) local ft = fields[name] parts[#parts + 1] = name .. (ft.kind == "opt" and "?" or "") end - return { kind = "rec", fields = fields, + return { kind = "rec", fields = fields, strict = opts and opts.strict or nil, desc = "{" .. table.concat(parts, ", ") .. "}" } end @@ -175,7 +179,7 @@ checkValue = function(t, value, path, patchMode, errors, top) -- unknown keys are preserved unless they read as a typo of a known -- field. Nested recs stay strict, that is where typos hide. local hint = suggest(t.fields, key) - if hint or not top then + if hint or not top or t.strict then errors[#errors + 1] = ("%s.%s: unknown field%s"):format(path, tostring(key), hint and (' (did you mean "' .. hint .. '"?)') or "") end @@ -573,6 +577,9 @@ R.trainers = { aiMods = f.opt(f.any), aiClass = f.opt(f.id("ai_classes")), brain = f.opt(f.fn), + -- Per-trainer battle theme (an audio.songs id): overrides the + -- kind-based default (wild/trainer/gym/final) for this trainer's + -- battles. The victory jingle stays kind-based. battleTheme = f.opt(f.id("music")), }, example = 'mod.content.trainers:patch("OPP_BROCK", { baseMoney = 99 })', @@ -585,6 +592,13 @@ R.sprites = { image = f.path, frames = f.int(1), walker = f.opt(f.bool), + -- Optional sheet geometry for mod actors. Defaults match the vanilla + -- 16x16 grounded walker; anchors are measured from each frame's + -- top-left in pixels (default: bottom-center). + frameWidth = f.opt(f.int(1)), + frameHeight = f.opt(f.int(1)), + anchorX = f.opt(f.num), + anchorY = f.opt(f.num), trueColor = f.opt(f.bool), -- Mod art can opt into an existing ROM sprite's Advanced-mode OBJ -- palette assignment without claiming that the image itself came from @@ -1064,6 +1078,12 @@ local function fontIsCharmap(id) return tostring(id):match("^charmap:.+$") ~= nil end +-- the third id form: "ttf" switches text rendering to a real TTF (the +-- bundled Plain Pixel when `file` is omitted -- src/render/Font.lua) +local function fontIsTtf(id) + return tostring(id) == "ttf" +end + R.font = { semantics = "record", target = "font", value = f.union{ @@ -1071,6 +1091,17 @@ R.font = { advance = f.opt(f.int(1)), charmap = f.opt(f.list(f.rec{ code = f.int(0), seq = f.str })) }, f.rec{ seq = f.str, code = f.int(0) }, + -- strict: every field here is optional ({} is a legal "ttf" entry), so + -- with the usual top-level leniency this alternative would match ANY + -- table and let malformed pages through the union unchecked + f.rec({ file = f.opt(f.path), size = f.opt(f.int(1)), + spacing = f.opt(f.num), yOffset = f.opt(f.num), + bold = f.opt(f.bool), + -- characters that keep their ROM tile instead of coming from the + -- TTF: a string of them, or a list when a multi-character charmap + -- sequence is meant (src/render/Font.lua) + tiles = f.opt(f.union{ f.str, f.list(f.str) }) }, + { strict = true }), }, extra = function(id, value) if fontIsCharmap(id) then @@ -1080,12 +1111,18 @@ R.font = { if type(value.code) ~= "number" then return "a charmap: entry needs a code" end + elseif fontIsTtf(id) then + -- every field optional: {} is "the bundled font at its native size" + if value.image ~= nil or value.base ~= nil then + return 'the "ttf" entry takes file/size/spacing/yOffset/bold/tiles, not a page' + end elseif value.image == nil or value.base == nil then return "a font page needs an image and a base" end end, baseAt = function(base, id) if fontIsCharmap(id) then return nil end + if fontIsTtf(id) then return base.ttf end return base.pages and base.pages[id] or nil end, baseIds = function(base) @@ -1096,6 +1133,9 @@ R.font = { write = function(target, registry) local pages = target.pages or {} target.pages = pages + -- the extractor never emits a ttf entry, so like the charmap rows it is + -- rebuilt from the registry each merge: disabling the mod disables it + target.ttf = nil -- the extractor's rows have no id and stay put; the registry's own are -- rebuilt every merge so a re-merge replaces them instead of stacking local rows = {} @@ -1108,6 +1148,8 @@ R.font = { if value ~= nil then rows[#rows + 1] = { id = id, seq = value.seq, code = value.code } end + elseif fontIsTtf(id) then + target.ttf = value else pages[id] = value end diff --git a/src/mods/Storage.lua b/src/mods/Storage.lua new file mode 100644 index 00000000..e529106c --- /dev/null +++ b/src/mods/Storage.lua @@ -0,0 +1,220 @@ +-- Data-only per-mod persistence, scoped by game version and opaque playthrough. +-- This module is engine-private; Loader exposes only the bound facade methods. + +local SaveData = require("src.core.SaveData") +local SaveSerializer = require("src.core.SaveSerializer") +local Version = require("src.core.Version") + +local Storage = {} +Storage.__index = Storage + +local ROOT = "mod_storage" + +local function failure(code, message) + return nil, code, message +end + +local function validSegment(value) + return type(value) == "string" and value ~= "" + and value:match("^[%w_-]+$") ~= nil +end + +local function validKey(key, allowEmpty) + if type(key) ~= "string" or (key == "" and not allowEmpty) then return false end + if key == "" then return true end + if key:sub(1, 1) == "/" or key:sub(-1) == "/" or key:find("//", 1, true) then + return false + end + for segment in key:gmatch("[^/]+") do + if not validSegment(segment) then return false end + end + return true +end + +local function ensureParent(fs, path) + local dir = path:match("^(.*)/[^/]+$") + if dir and fs.createDirectory then fs.createDirectory(dir) end +end + +local function remove(fs, path) + if fs.remove then fs.remove(path) end +end + +local function decodeAt(fs, path) + if not (fs.getInfo and fs.getInfo(path)) then return nil end + local body = fs.read and fs.read(path) + if type(body) ~= "string" then return nil end + local data = SaveSerializer.decode(body) + if not data then return nil end + return data, body +end + +function Storage.new(modId, fs) + assert(validSegment(modId), "Storage.new needs a safe mod id") + return setmetatable({ modId = modId, injectedFs = fs }, Storage) +end + +function Storage:_scope(game) + local save = game and game.save + local meta = save and save.meta + local version = save and save.version + if not (save and validSegment(version)) then + return failure("not_in_playthrough", + "Storage is available only inside an identified playthrough.") + end + local playthroughId = meta and meta.playthroughId + if not validSegment(playthroughId) then + playthroughId = SaveData.ensurePlaythroughId(save, self.injectedFs) + end + if not validSegment(playthroughId) then + return failure("not_in_playthrough", + "Storage could not identify the active playthrough.") + end + local fs = SaveData.persistenceFs(self.injectedFs) + if not (fs and fs.read and fs.write and fs.getInfo) then + return failure("storage_unavailable", "The persistence backend is unavailable.") + end + local base = table.concat({ ROOT, version, playthroughId, self.modId }, "/") + return { gameVersion = version, playthroughId = playthroughId, + base = base, fs = fs } +end + +function Storage:context(game) + local scope, code, message = self:_scope(game) + if not scope then return nil, code, message end + return { + engineVersion = Version.engine, + gameVersion = scope.gameVersion, + playthroughId = scope.playthroughId, + } +end + +function Storage:_names(game, key, allowEmpty) + if not validKey(key, allowEmpty) then + return failure("invalid_key", + "Storage keys use nonempty letters, numbers, underscore, dash and slash segments.") + end + local scope, code, message = self:_scope(game) + if not scope then return nil, code, message end + local path = scope.base .. (key ~= "" and ("/" .. key) or "") + return scope, path .. ".lua", path .. ".lua.bak", path .. ".lua.tmp" +end + +function Storage:write(game, key, value) + local scope, main, bak, tmp = self:_names(game, key, false) + if not scope then return false, main, bak end + if type(value) ~= "table" then + return false, "encode_failed", "Storage values must be data-only tables." + end + local encodedOk, encoded = pcall(SaveSerializer.encode, value) + if not encodedOk then + return false, "encode_failed", "Storage value is not serializable data: " + .. tostring(encoded) + end + + local fs = scope.fs + ensureParent(fs, main) + local _, previous = decodeAt(fs, main) + if not previous then _, previous = decodeAt(fs, bak) end + + local ok, err = fs.write(tmp, encoded) + if not ok then + return false, "write_failed", "Could not stage storage data: " .. tostring(err) + end + local staged = decodeAt(fs, tmp) + if not staged then + remove(fs, tmp) + return false, "verify_failed", "Staged storage data could not be verified." + end + + if previous then fs.write(bak, previous) end + ok, err = fs.write(main, encoded) + if not ok then + remove(fs, tmp) + return false, "write_failed", "Could not replace storage data: " .. tostring(err) + end + local verified = decodeAt(fs, main) + if not verified then + remove(fs, main) + remove(fs, tmp) + return false, "verify_failed", "Replacement storage data could not be verified." + end + + -- At rest both main and backup hold the newest verified record. If a later + -- write dies after rolling this copy aside, one verified generation remains. + fs.write(bak, encoded) + remove(fs, tmp) + return true +end + +function Storage:read(game, key) + local scope, main, bak, tmp = self:_names(game, key, false) + if not scope then return nil, main, bak end + local fs = scope.fs + local data, body = decodeAt(fs, main) + if data then return data end + + data, body = decodeAt(fs, tmp) + if not data then data, body = decodeAt(fs, bak) end + if not data then + return nil, "not_found", "No valid stored value exists for this key." + end + + -- Best-effort healing. The recovered copy remains in tmp/bak if promotion + -- cannot land, so returning it is still safe and the next read can retry. + ensureParent(fs, main) + if fs.write(main, body) then fs.write(bak, body) end + remove(fs, tmp) + return data +end + +function Storage:list(game, prefix) + prefix = prefix or "" + local scope, main, codeOrBak = self:_names(game, prefix, true) + if not scope then return nil, main, codeOrBak end + local fs = scope.fs + if not fs.getDirectoryItems then + return nil, "storage_unavailable", "The persistence backend cannot enumerate keys." + end + + local base = scope.base + local start = prefix == "" and base or (base .. "/" .. prefix) + local out = {} + + local function walk(path, logical) + local info = fs.getInfo(path) + if not info then return end + if info.type == "file" then + if path:sub(-4) == ".lua" then out[#out + 1] = logical:sub(1, -5) end + return + end + for _, child in ipairs(fs.getDirectoryItems(path) or {}) do + local childLogical = logical == "" and child or (logical .. "/" .. child) + walk(path .. "/" .. child, childLogical) + end + end + + -- A prefix may identify one exact key or a directory of keys. + if fs.getInfo(start .. ".lua") then + out[#out + 1] = prefix + else + walk(start, prefix) + end + table.sort(out) + return out +end + +function Storage:delete(game, key) + local scope, main, bak, tmp = self:_names(game, key, false) + if not scope then return false, main, bak end + local fs = scope.fs + if not (fs.getInfo(main) or fs.getInfo(bak) or fs.getInfo(tmp)) then + return false, "not_found", "No stored value exists for this key." + end + remove(fs, main) + remove(fs, bak) + remove(fs, tmp) + return true +end + +return Storage diff --git a/src/net/Fetch.lua b/src/net/Fetch.lua new file mode 100644 index 00000000..370ad1a4 --- /dev/null +++ b/src/net/Fetch.lua @@ -0,0 +1,213 @@ +-- Async HTTP for the launcher: a small job queue over a pool of love.thread +-- workers. +-- +-- WHY THIS EXISTS. Every network call in the launcher used to run on the +-- render thread. HostShell.httpGet shells out to curl through io.popen and +-- reads the pipe to EOF, so refreshing the mod index, checking one mod's +-- releases, or opening the Find Mods tab froze the window for as long as the +-- server took -- measured at over two minutes on a cold Find Mods open, with +-- no spinner, no progress and no way to cancel, because the frame that would +-- have drawn them never ran. The self-updater already did this correctly on +-- a worker (src/update/check_worker.lua); this generalises that pattern so +-- everything else can follow it. +-- +-- CONTRACT. Callers get an opaque job id back immediately and poll it: +-- local job = Fetch.get(url) +-- ... +-- local st = Fetch.poll(job) -- { status, body, err, progress } +-- if st.status == "ok" then ... end +-- status is: pending | ok | error | cancelled. poll() never blocks and +-- never throws. A job's result is retained until Fetch.release(job), so a +-- caller that polls once per frame cannot miss it. +-- +-- DEGRADATION. With no love.thread (the headless test stub), no curl and no +-- Android bridge, jobs complete immediately with status "error" and a reason. +-- The UI shows that as a failed fetch, which is the same path an offline +-- machine takes -- there is no code path where the launcher waits forever. + +local Fetch = {} + +local CMD = "fetch_cmd" +local RESULT = "fetch_result" +local QUIT = "fetch_quit" + +-- Worker count. Three is enough to overlap the common burst (a mod index +-- refresh plus a couple of per-mod release checks) without spawning a thread +-- per row on a 200-mod list; extra jobs queue on the channel. +local POOL = 3 + +local workers = {} +local cmdCh, resCh, quitCh +local ready -- nil = untried, true = running, false = unavailable +local jobs = {} -- id -> { status, body, err, progress, path } +local nextId = 0 +local unavailableReason + +local function ensureWorkers() + if ready ~= nil then return ready end + if not (love and love.thread and love.thread.newThread) then + ready, unavailableReason = false, "background threads unavailable" + return false + end + cmdCh = love.thread.getChannel(CMD) + resCh = love.thread.getChannel(RESULT) + quitCh = love.thread.getChannel(QUIT) + -- Channels outlive a pool (they are global to the process, keyed by name), + -- so a pool started after a shutdown -- the save editor opens from a live + -- launcher and hands the screen back -- must clear the previous round's + -- flag and leftovers or its workers quit on their first job. + quitCh:clear() + cmdCh:clear() + for i = 1, POOL do + local ok, th = pcall(love.thread.newThread, "src/net/fetch_worker.lua") + if ok and th and pcall(function() th:start() end) then + workers[#workers + 1] = th + end + end + if #workers == 0 then + ready, unavailableReason = false, "could not start fetch workers" + return false + end + ready = true + return true +end + +-- Move every finished result off the channel into the job table. Called by +-- poll() and pending(), so a caller that polls any job drains all of them. +local function drain() + if not resCh then return end + local msg = resCh:pop() + while msg do + if type(msg) == "table" and msg.id then + local j = jobs[msg.id] + if j and j.status == "pending" then + if msg.progress and not msg.done then + j.progress = msg.progress + else + j.status = msg.ok and "ok" or "error" + j.body, j.err, j.path = msg.body, msg.err, msg.path + j.progress = msg.ok and 1 or j.progress + end + end + end + msg = resCh:pop() + end + -- A worker that died takes its in-flight job with it; surface that rather + -- than leaving the job pending forever (which would hang a loader overlay). + for _, th in ipairs(workers) do + local err = th:getError() + if err then + for _, j in pairs(jobs) do + if j.status == "pending" then + j.status, j.err = "error", tostring(err) + end + end + break + end + end +end + +local function submit(cmd) + nextId = nextId + 1 + local id = nextId + cmd.id = id + jobs[id] = { status = "pending", progress = 0 } + if not ensureWorkers() then + jobs[id].status = "error" + jobs[id].err = unavailableReason + return id + end + cmdCh:push(cmd) + return id +end + +-- GET a URL, returning the body as a string. +-- opts: { userAgent, accept, maxSeconds } +-- maxSeconds is the transfer ceiling, and it is also this job's worst-case +-- contribution to how long closing the window takes (see Fetch.shutdown). +function Fetch.get(url, opts) + opts = opts or {} + return submit({ kind = "get", url = url, + userAgent = opts.userAgent or "gen1recomp", + accept = opts.accept, maxSeconds = opts.maxSeconds }) +end + +-- Download a URL to `saveRel`, a path relative to the LOVE save directory. +-- Progress is reported as a 0..1 fraction when `size` is known. +function Fetch.download(url, saveRel, opts) + opts = opts or {} + return submit({ kind = "download", url = url, dest = saveRel, + size = opts.size, + userAgent = opts.userAgent or "gen1recomp", + accept = opts.accept, maxSeconds = opts.maxSeconds }) +end + +-- Non-blocking status. Returns a table; never nil, even for an unknown id +-- (an unknown id reads as an error, so a caller that dropped its handle +-- cannot deadlock a loader). +local MISSING = { status = "error", err = "unknown job" } +function Fetch.poll(id) + drain() + return jobs[id] or MISSING +end + +function Fetch.isPending(id) + return Fetch.poll(id).status == "pending" +end + +-- Forget a finished job. Callers should do this once they have consumed the +-- result, or the table grows for the life of the process. +function Fetch.release(id) + jobs[id] = nil +end + +-- Mark a job cancelled on the main thread. The worker's curl is NOT killed +-- (there is no portable way to signal it), but the result is dropped when it +-- lands, so a cancelled download cannot resurrect a closed overlay. +function Fetch.cancel(id) + local j = jobs[id] + if j and j.status == "pending" then j.status = "cancelled" end +end + +-- True while any job is still running -- drives the "working" indicator in +-- the launcher chrome. +function Fetch.busy() + drain() + for _, j in pairs(jobs) do + if j.status == "pending" then return true end + end + return false +end + +function Fetch.available() + return ensureWorkers() +end + +-- End every worker. Their command loops sit in Channel:demand(), which never +-- returns on its own, and LOVE waits for every live love.thread before the +-- process exits (#339). +-- +-- ORDER MATTERS, and getting it wrong is what froze the launcher on close +-- after a visit to the mod tabs. A quit pushed as an ordinary command is +-- just another item in a FIFO the workers are already chewing through: a page +-- of thumbnail downloads sits in front of it, and th:wait() below blocks the +-- main thread until every one of them finishes. So: +-- 1. raise the quit FLAG, which workers check after every demand(), +-- 2. CLEAR the queue -- nobody will read those results, and dropping them +-- is what turns "wait for the backlog" into "wait for what is in flight", +-- 3. push one wake sentinel per worker, because a worker idling inside +-- demand() has nothing to check the flag on until something arrives. +-- What remains is at most one transfer per worker, bounded by the caller's +-- maxSeconds; there is no portable way to interrupt a running curl. +function Fetch.shutdown() + if quitCh then quitCh:push(true) end + if cmdCh then + cmdCh:clear() + for _ = 1, #workers do cmdCh:push({ kind = "quit" }) end + end + for _, th in ipairs(workers) do pcall(function() th:wait() end) end + workers = {} + cmdCh, resCh, quitCh, ready = nil, nil, nil, false +end + +return Fetch diff --git a/src/net/fetch_worker.lua b/src/net/fetch_worker.lua new file mode 100644 index 00000000..f86d78f1 --- /dev/null +++ b/src/net/fetch_worker.lua @@ -0,0 +1,122 @@ +-- Worker thread behind src/net/Fetch.lua. Several of these run as a pool. +-- +-- Pulls jobs off the shared "fetch_cmd" channel and pushes results onto +-- "fetch_result". Every job is wrapped in pcall: a worker that dies takes +-- its in-flight job with it, and Fetch surfaces that as an error rather than +-- leaving a loader overlay spinning forever. +-- +-- Transport is HostShell, so this inherits the platform matrix that already +-- exists (curl on desktop, the JNI bridge on Android). Fresh love threads do +-- not carry the "src.*" package searcher, so HostShell is pulled in with +-- love.filesystem.load exactly like src/update/check_worker.lua does. + +require("love.thread") +require("love.filesystem") +require("love.timer") +require("love.system") + +local function loadModule(path) + local ok, chunk = pcall(love.filesystem.load, path) + if not ok or type(chunk) ~= "function" then return nil end + local ok2, mod = pcall(chunk) + if not ok2 then return nil end + return mod +end + +local HostShell = loadModule("src/core/HostShell.lua") + +local cmdCh = love.thread.getChannel("fetch_cmd") +local resCh = love.thread.getChannel("fetch_result") +-- Raised by Fetch.shutdown BEFORE the wake sentinels go out. A worker checks +-- it after every demand() and drops whatever it just pulled, so a quit does +-- not have to wait its turn behind a queue of jobs nobody will ever read the +-- results of. +local quitCh = love.thread.getChannel("fetch_quit") + +local saveDir = love.filesystem.getSaveDirectory() + +-- See the note in doGet: these bound how long a quit can block. A mod index +-- or a release list is a small JSON document, and a mod zip is a few MB; the +-- old 300s download ceiling was sized for the self-updater's whole payload, +-- which does not come through this pool. Callers may pass a shorter one +-- (job.maxSeconds) -- a thumbnail has no business holding the process open +-- for as long as a mod install does. +local GET_MAX_SECONDS = 20 +local DOWNLOAD_MAX_SECONDS = 90 + +local function quitting() + return quitCh:peek() ~= nil +end + +local function post(t) resCh:push(t) end + +local function doGet(job) + if not HostShell then + post({ id = job.id, ok = false, err = "no transport" }) + return + end + -- Bounded transfer time: a worker inside a blocking curl cannot see a quit + -- command, and LOVE waits for live threads before exiting (#339), so this + -- ceiling is also the worst case for how long closing the window can take. + local body, err = HostShell.httpGet(job.url, job.userAgent, job.accept, + tonumber(job.maxSeconds) or GET_MAX_SECONDS) + if not body then + post({ id = job.id, ok = false, err = err or "fetch failed" }) + return + end + post({ id = job.id, ok = true, body = body }) +end + +-- Downloads go straight to the save directory. HostShell.httpDownload +-- blocks until curl exits, which is fine here -- this is the whole reason +-- the work is on a worker -- but it means progress cannot be sampled from +-- inside the call. Where the caller knows the expected size we poll the +-- growing file from a second pass instead; where it does not, the job simply +-- reports indeterminate progress and the UI shows a spinner. +local function doDownload(job) + if not HostShell then + post({ id = job.id, ok = false, err = "no transport" }) + return + end + local rel = job.dest + local abs = saveDir .. "/" .. rel + local dir = rel:match("^(.*)/[^/]*$") + if dir then love.filesystem.createDirectory(dir) end + love.filesystem.remove(rel) + + local ok, err = HostShell.httpDownload(job.url, abs, job.userAgent, + job.accept, tonumber(job.maxSeconds) or DOWNLOAD_MAX_SECONDS) + if not ok then + post({ id = job.id, ok = false, err = err or "download failed" }) + return + end + local info = love.filesystem.getInfo(rel) + if not info or (info.size or 0) == 0 then + love.filesystem.remove(rel) + post({ id = job.id, ok = false, err = "empty download" }) + return + end + post({ id = job.id, ok = true, path = rel, done = true }) +end + +while true do + local job = cmdCh:demand() + -- The flag is checked before the job's KIND, so a worker woken by a + -- sentinel abandons whatever real job it happened to pull instead of + -- running it. Without this the quit commands queued behind a page of + -- thumbnail downloads and closing the window blocked for as long as those + -- transfers took -- the launcher froze on close after a visit to the mods + -- tabs, which is exactly what LOVE waiting on live threads looks like. + if quitting() then break end + if type(job) == "table" then + if job.kind == "quit" then + break + elseif job.kind == "get" then + local ok, err = pcall(doGet, job) + if not ok then post({ id = job.id, ok = false, err = tostring(err) }) end + elseif job.kind == "download" then + local ok, err = pcall(doDownload, job) + if not ok then post({ id = job.id, ok = false, err = tostring(err) }) end + end + end +end diff --git a/src/pokemon/Evolution.lua b/src/pokemon/Evolution.lua index c2df1741..b92352b6 100644 --- a/src/pokemon/Evolution.lua +++ b/src/pokemon/Evolution.lua @@ -15,6 +15,7 @@ local Screens = require("src.ui.Screens") local Stats = require("src.pokemon.Stats") local TextBox = require("src.render.TextBox") local Strings = require("src.core.Strings") +local romText = require("src.core.RomText") local Evolution = {} @@ -140,7 +141,8 @@ function Evolution.learnEvolutionMoves(game, mon, onDone) table.insert(mon.moves, { id = moveId, pp = mdef.pp }) Runtime.emit("pokemon.move_learned", { mon = mon, moveId = moveId }) game.stack:push(TextBox.new(game, - Strings("%s learned\n%s!", name, mdef.name), nextStep)) + romText(game.data, "_LearnedMove1Text", + "%s learned\n%s!", name, mdef.name), nextStep)) else -- LearnMoveFromLevelUp with a full moveset: the forget UI Screens.push(game, "MoveLearnMenu", mon, moveId, nextStep) @@ -161,8 +163,12 @@ function Evolution.evolve(game, mon, newSpecies, onDone, via) Music.play(game.data, Music.special(game.data, "evolution")) local oldName = mon.nickname or game.data.pokemon[mon.species].name Evolution.apply(game, mon, newSpecies, via) - local msg = Strings("What?\n%s is\nevolving!\fCongratulations!\nYour %s\nevolved into\n%s!", - oldName, oldName, game.data.pokemon[newSpecies].name) + -- the congrats page keeps the engine wording: _EvolvedText extracts + -- truncated (it stops at a dynamic marker the decoder does not follow) + local msg = romText(game.data, "_IsEvolvingText", + "What?\n%s is\nevolving!", oldName) + .. "\f" .. Strings("Congratulations!\nYour %s\nevolved into\n%s!", + oldName, game.data.pokemon[newSpecies].name) game.stack:push(TextBox.new(game, msg, function() Music.restoreMap(game.data) -- re-run the evolved species' level-up learn check before onDone diff --git a/src/render/Font.lua b/src/render/Font.lua index 33f1a97a..3b59cffb 100644 --- a/src/render/Font.lua +++ b/src/render/Font.lua @@ -6,6 +6,13 @@ -- for variable-width text; the default is the GB's flat 8px. -- The charmap is matched greedily (longest sequence first) so multi-byte -- UTF-8 chars and ligature glyphs like 'd 'l 's map to single glyphs. +-- +-- A translation may instead set data.font.ttf and render its text through a +-- real TTF (the bundled Plain Pixel by default), so a script that would need +-- hundreds of page tiles works out of the box. Single characters then draw +-- from the TTF; multi-character charmap sequences (, the 'd ligatures) +-- and the sub-0x80 chrome glyphs (borders, arrows) keep their tiles, which +-- is why the box border never depends on the TTF's coverage. local Assets = require("src.render.Assets") @@ -13,6 +20,19 @@ local Font = {} local GLYPH = 8 +-- TTF glyph codes are the Unicode codepoint offset far above any page base, +-- so they flow through the same span/encode/drawCode pipeline as tiles. +local TTF_BASE = 0x400000 +Font.TTF_BASE = TTF_BASE + +-- The engine's bundled TTF (assets/fonts/plainpixel/README.md: CC-BY 4.0, +-- Douglas Vautour). data.font.ttf.file overrides for a mod-shipped font. +-- 15 is the font's design em: its glyphs only rasterize at their true +-- pixel size (5x11 base, 11x11 double-width) at multiples of 15; at any +-- other size they downscale unevenly (at 11, M comes out 4px and A 5px). +Font.PLAINPIXEL = "assets/fonts/plainpixel/PlainPixel-Regular.ttf" +Font.PLAINPIXEL_SIZE = 15 + local state local loadedFrom @@ -35,6 +55,34 @@ local function pagesOf(def) return pages end +-- ttf.tiles as a lookup keyed by charmap sequence. Accepts a plain string +-- ("0123456789"), which is split into UTF-8 characters, or a list of +-- sequences ({ "0", "1", "" }) when a multi-character macro is meant. +local function tileSet(spec) + local set = {} + if type(spec) == "table" then + for _, seq in ipairs(spec) do set[tostring(seq)] = true end + elseif type(spec) == "string" then + -- split on UTF-8 lead bytes rather than utf8Decode, which this file + -- declares further down and would be nil here + local i, n = 1, #spec + while i <= n do + local last = i + if spec:byte(i) >= 0xC0 then + local k = i + 1 + while k <= n do + local b = spec:byte(k) + if b < 0x80 or b > 0xBF then break end + last, k = k, k + 1 + end + end + set[spec:sub(i, last)] = true + i = last + 1 + end + end + return set +end + function Font.load(data) loadedFrom = data local def = data.font @@ -83,6 +131,53 @@ function Font.load(data) Font.BORDER = {} for key, code in pairs(Font.DEFAULT_BORDER) do Font.BORDER[key] = code end for key, code in pairs(def.border or {}) do Font.BORDER[key] = code end + + -- TTF mode. All fields optional: {} means "the bundled Plain Pixel at + -- its native 11px". A failed load logs and falls back to tiles, so a + -- typo'd path degrades exactly like a missing page image does above. + if type(def.ttf) == "table" then + local file = def.ttf.file or Font.PLAINPIXEL + local size = def.ttf.size or Font.PLAINPIXEL_SIZE + -- The game renders into a pixel-exact canvas, so keep the TTF rasterizer + -- on that same 1x grid instead of inheriting the window DPI on mobile. + local ok, obj = pcall(love.graphics.newFont, file, size, "mono", 1) + if ok and obj then + -- nearest keeps the pixel font crisp under the integer UI scale + if obj.setFilter then pcall(obj.setFilter, obj, "nearest", "nearest") end + state.ttf = { + font = obj, file = file, + -- the font's own advances already carry a 1px gap at its design + -- size; spacing adds to (or, negative, takes from) every advance + spacing = def.ttf.spacing or 0, + -- bold double-prints each glyph at a 1px offset, for fonts whose + -- single-pixel strokes read too light against the tile art + bold = def.ttf.bold == true, + -- glyphs are taller than the 8px cell (11px base, and the em box + -- reserves even more for vertical extension); anchor the font's + -- baseline to the tile font's, which sits on row 7 of the cell, so + -- caps line up and descenders hang below as the GB font's own do + yOffset = def.ttf.yOffset or (obj.getBaseline + and (GLYPH - 1 - obj:getBaseline()) or (GLYPH - obj:getHeight())), + -- Single characters that keep their ROM tile instead of coming from + -- the TTF. A CJK translation sizes the font so a kana fills the 8px + -- cell, which leaves Latin narrower than the tile font it replaces: + -- the numbers in a right-aligned column (the party menu's ":L12" over + -- "34/ 34") then no longer land where the 8px-per-character layout put + -- them. Naming "0123456789" here keeps digits on the vanilla tiles -- + -- identical to the English build -- while kana still come from the + -- font. Sequence keys, so "é" or a "" macro can be listed too. + tiles = tileSet(def.ttf.tiles), + widths = {}, chars = {}, + } + else + require("src.core.Logger").warn("font: could not load ttf %q (%s)", + tostring(file), tostring(obj)) + end + end +end + +function Font.ttfActive() + return state ~= nil and state.ttf ~= nil end -- re-run load against the data it last saw, so hot reload picks up an @@ -104,6 +199,49 @@ end local SPACE = 0x7F +-- Decode one UTF-8 sequence: codepoint and the index of its last byte, or +-- nil on a malformed lead/continuation (the caller falls back to bytes). +local function utf8Decode(text, i) + local b = text:byte(i) + if not b then return nil, i end + if b < 0x80 then return b, i end + local cont, cp + if b >= 0xF0 then cont, cp = 3, b - 0xF0 + elseif b >= 0xE0 then cont, cp = 2, b - 0xE0 + elseif b >= 0xC0 then cont, cp = 1, b - 0xC0 + else return nil, i end + for k = i + 1, i + cont do + local c = text:byte(k) + if not c or c < 0x80 or c > 0xBF then return nil, i end + cp = cp * 64 + (c - 0x80) + end + return cp, i + cont +end + +local function utf8Encode(cp) + if cp < 0x80 then return string.char(cp) end + if cp < 0x800 then + return string.char(0xC0 + math.floor(cp / 64), 0x80 + cp % 64) + end + if cp < 0x10000 then + return string.char(0xE0 + math.floor(cp / 4096), + 0x80 + math.floor(cp / 64) % 64, 0x80 + cp % 64) + end + return string.char(0xF0 + math.floor(cp / 262144), + 0x80 + math.floor(cp / 4096) % 64, + 0x80 + math.floor(cp / 64) % 64, 0x80 + cp % 64) +end + +-- the character a TTF code draws, cached per code +local function ttfChar(ttf, code) + local ch = ttf.chars[code] + if not ch then + ch = utf8Encode(code - TTF_BASE) + ttf.chars[code] = ch + end + return ch +end + -- Segment text into glyph spans: `{ from, to, code }` byte ranges, one per -- drawn glyph, code nil when the charmap has nothing. A span is a whole -- charmap sequence, so a multi-byte char ("é", "♂") and an ASCII ligature @@ -119,6 +257,7 @@ local SPACE = 0x7F -- boundaries, which is all a headless paginate needs. function Font.split(text) local spans = {} + local ttf = state and state.ttf local i, n = 1, #text while i <= n do local span @@ -127,11 +266,24 @@ function Font.split(text) for _, entry in ipairs(candidates) do local len = #entry.seq if text:sub(i, i + len - 1) == entry.seq then + if ttf and not ttf.tiles[entry.seq] then + -- single characters belong to the TTF; only multi-character + -- sequences (ligatures, macros) keep their tile mapping, + -- plus anything the mod named in ttf.tiles (see Font.load) + local cp, last = utf8Decode(entry.seq, 1) + if cp and last == len then break end + end span = { from = i, to = i + len - 1, code = entry.code } break end end end + if not span and ttf then + local cp, last = utf8Decode(text, i) + if cp and cp >= 0x20 then + span = { from = i, to = last, code = TTF_BASE + cp } + end + end if not span then -- Nothing matched. Still keep a UTF-8 sequence whole, so a cut never -- lands mid-character even for a glyph we cannot draw. @@ -185,14 +337,37 @@ function Font.encode(text) end function Font.drawCode(code, x, y) + local ttf = state and state.ttf + if ttf and code >= TTF_BASE then + local prev = love.graphics.getFont() + love.graphics.setFont(ttf.font) + local ch = ttfChar(ttf, code) + love.graphics.print(ch, x, y + ttf.yOffset) + if ttf.bold then love.graphics.print(ch, x + 1, y + ttf.yOffset) end + if prev then love.graphics.setFont(prev) end + return + end local page = pageFor(code) if not page then return end local quad = page.quads[code - page.base] if quad then love.graphics.draw(page.image, quad, x, y) end end --- how far the pen moves past a glyph; 8 unless its page says otherwise +-- how far the pen moves past a glyph; 8 unless its page says otherwise. +-- TTF glyphs answer with the font's own metrics (5px base, 11px for +-- double-width kana/CJK in Plain Pixel), which is what makes TextBox's +-- pixel-budget pagination fit more of a narrow script per line. function Font.advanceOf(code) + local ttf = state and state.ttf + if ttf and code >= TTF_BASE then + local w = ttf.widths[code] + if not w then + w = ttf.font:getWidth(ttfChar(ttf, code)) + ttf.spacing + + (ttf.bold and 1 or 0) + ttf.widths[code] = w + end + return w + end local page = pageFor(code) return page and page.advance or GLYPH end @@ -232,8 +407,18 @@ for key, code in pairs(Font.DEFAULT_BORDER) do Font.BORDER[key] = code end -- Draw a Game Boy style bordered box in tile coordinates. function Font.drawBox(tx, ty, tw, th) + -- The white interior is a fill, so it needs the color; everything after it + -- is a glyph and needs the caller's. Restoring is not cosmetic: the tile + -- pages are black glyphs on transparent, so they come out black whatever + -- the color is, and leaking white here was invisible for as long as every + -- glyph was a tile. TTF text is not immune -- it draws in the current + -- color -- so a leaked white left every label printed after a box white on + -- white. On the summary screen that erased ATTACK/DEFENSE/SPEED/SPECIAL + -- and TYPE1/TYPE2 while the numbers beside them, still tiles, stayed put. + local r, g, b, a = love.graphics.getColor() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", tx * 8, ty * 8, tw * 8, th * 8) + love.graphics.setColor(r, g, b, a) local B = Font.BORDER Font.drawCode(B.tl, tx * 8, ty * 8) Font.drawCode(B.tr, (tx + tw - 1) * 8, ty * 8) diff --git a/src/render/PaletteFX.lua b/src/render/PaletteFX.lua index 9de4f271..ea654d9d 100644 --- a/src/render/PaletteFX.lua +++ b/src/render/PaletteFX.lua @@ -868,4 +868,20 @@ function PaletteFX.sendColors(shader, c) shader:send("c3", { c[4][1] / 255, c[4][2] / 255, c[4][3] / 255 }) end +-- The same send with NO display-mode substitution and no shade map: the four +-- colors reach the shader exactly as given. Only for an INTERMEDIATE pass +-- whose output is re-thresholded downstream -- the classic battle's zone pass +-- under a forced-mono mode, where ensureZones' whole-screen zone already +-- substitutes once at blit time and doing it again here applies the mode +-- twice (#822). Everything that draws a final pixel wants sendColors. +function PaletteFX.sendShades(shader, c) + -- headless (no love.graphics) leaves shader() nil; sendColors reaches the + -- same no-op through effectiveColors returning nil for an absent palette + if not shader or not c then return end + shader:send("c0", { c[1][1] / 255, c[1][2] / 255, c[1][3] / 255 }) + shader:send("c1", { c[2][1] / 255, c[2][2] / 255, c[2][3] / 255 }) + shader:send("c2", { c[3][1] / 255, c[3][2] / 255, c[3][3] / 255 }) + shader:send("c3", { c[4][1] / 255, c[4][2] / 255, c[4][3] / 255 }) +end + return PaletteFX diff --git a/src/render/Renderer.lua b/src/render/Renderer.lua index 28618cb2..3b69e2d0 100644 --- a/src/render/Renderer.lua +++ b/src/render/Renderer.lua @@ -564,16 +564,24 @@ function Renderer:drawTiltedWorld(zoneList, sx, sy, wox, woy, target) return true end +-- LÖVE 11 truncates scissor arguments to framebuffer pixels; the half-pixel +-- bias keeps values divided back through a fractional DPI scale from landing +-- one short. LÖVE 12 passes fractional arguments through and rounds in the +-- graphics backend instead, where that bias shifts each origin by one pixel +-- and extends its far edge by two (#673). +local SCISSOR_PIXEL_BIAS = 0.5 +if love and love.getVersion and select(1, love.getVersion()) >= 12 then + SCISSOR_PIXEL_BIAS = 0 +end + -- Clamp a scissor rect to the viewport box, then round it outward to whole --- framebuffer pixels. love.graphics.setScissor truncates x, y, w and h to --- pixels independently, so a rect with fractional unit edges (Android's --- non-integer DPI puts fitScale/dpi in Sx/Sy) loses up to a pixel per side --- and two adjacent SGB zones stop sharing an edge: the letterbox clear shows --- through as a horizontal seam at every zone boundary (#373). Rounding --- outward makes neighbours overlap by at most one row instead -- the overlap --- redraws the same canvas pixels one palette later, and past the canvas edge --- there is nothing to draw. The half pixel keeps LOVE's truncation on the --- snapped edge rather than one short of it. +-- framebuffer pixels. On LÖVE 11, x, y, w and h are truncated independently, +-- so a rect with fractional unit edges (Android's non-integer DPI puts +-- fitScale/dpi in Sx/Sy) loses up to a pixel per side and two adjacent SGB +-- zones stop sharing an edge: the letterbox clear shows through as a seam at +-- every zone boundary (#373). Rounding outward makes neighbours overlap by +-- at most one row instead; SCISSOR_PIXEL_BIAS preserves that result across the +-- LÖVE 11 and 12 conversion rules. local function scissorClamped(x, y, w, h, ox, oy, vpw, vph, dpiX, dpiY) local x2, y2 = math.min(x + w, ox + vpw), math.min(y + h, oy + vph) x, y = math.max(x, ox), math.max(y, oy) @@ -581,9 +589,10 @@ local function scissorClamped(x, y, w, h, ox, oy, vpw, vph, dpiX, dpiY) dpiX, dpiY = dpiX or 1, dpiY or 1 local px1, py1 = math.floor(x * dpiX), math.floor(y * dpiY) local px2, py2 = math.ceil(x2 * dpiX), math.ceil(y2 * dpiY) - love.graphics.setScissor((px1 + 0.5) / dpiX, (py1 + 0.5) / dpiY, - (px2 - px1 + 0.5) / dpiX, - (py2 - py1 + 0.5) / dpiY) + local b = SCISSOR_PIXEL_BIAS + love.graphics.setScissor((px1 + b) / dpiX, (py1 + b) / dpiY, + (px2 - px1 + b) / dpiX, + (py2 - py1 + b) / dpiY) return true end @@ -811,8 +820,13 @@ function Renderer:endFrame(zones, worldZones) -- non-battle state that opts in) uses the paper shade. "world" never -- reaches here -- it makes the battle non-opaque, so the world pass is -- active and this whole branch is skipped. + -- FAITHFUL RATIO's mobile lock promises the display outside the GB + -- screen stays black (src/core/FaithfulRes.lua); the paper surround + -- painted the whole phone white on New Game and in battle (#864), so + -- the lock keeps the default black bars. if state and state.letterboxWhite - and not (state.bgMode and state:bgMode() == "black") then + and not (state.bgMode and state:bgMode() == "black") + and not FaithfulRes.scaleCap() then clearR, clearG, clearB = PaletteFX.paperShade(Game and Game.data) end end @@ -941,9 +955,22 @@ function Renderer:endFrame(zones, worldZones) -- reads as the foreground instead of competing with a fully lit map. Goes -- here rather than in the letterbox clear because with the world pass -- active there is no clear -- the world already covers the surface. + -- + -- The veil covers the voids ONLY, never the battle's own letterbox: "world" + -- changes what surrounds the battle and leaves the battle screen alone + -- (BattleState:bgMode). On hardware there is no "behind the battle" to dim + -- at all -- _InitBattleCommon calls ClearScreen (pokered home/copy2.asm), + -- which blanks the whole tilemap before the battle draws. A whole-surface + -- fill was invisible only because the classic battle paints an opaque paper + -- field over it a few lines below; a render pipeline that stages the fight + -- on the map and keys that field out got the veil straight onto its + -- sprites, HP bars and HUD, which is the 55% whole-window dim of #777 + -- (and its duplicate #772). if self.battleDim and self.battleDim > 0 then love.graphics.setColor(0, 0, 0, self.battleDim) - love.graphics.rectangle("fill", 0, 0, ww, wh) + for _, r in ipairs(subtractRect({ { 0, 0, ww, wh } }, uox, uoy, uvpw, uvph)) do + love.graphics.rectangle("fill", r[1], r[2], r[3], r[4]) + end love.graphics.setColor(1, 1, 1, 1) end @@ -1007,7 +1034,17 @@ function Renderer:endFrame(zones, worldZones) local veil = self.screenVeil if veil and veil[2] > 0 then love.graphics.setColor(veil[1], veil[1], veil[1], veil[2]) - love.graphics.rectangle("fill", 0, 0, ww, wh) + -- FAITHFUL RATIO's mobile lock: the surface the player sees is the + -- locked viewport and the bars around it are dead display, not screen + -- (src/core/FaithfulRes.lua). A whole-window veil lit the entire phone + -- for the battle flash and the post-battle fade (#864), so under the + -- lock the veil stops at the letterbox. The desktop lock is unaffected: + -- there the window IS the viewport. + if FaithfulRes.scaleCap() then + love.graphics.rectangle("fill", ox, oy, vpw, vph) + else + love.graphics.rectangle("fill", 0, 0, ww, wh) + end love.graphics.setColor(1, 1, 1, 1) end diff --git a/src/render/SecondScreen.lua b/src/render/SecondScreen.lua index c8a6efc3..2e5fb3b5 100644 --- a/src/render/SecondScreen.lua +++ b/src/render/SecondScreen.lua @@ -5,13 +5,15 @@ local SecondScreen = {} local C = nil +local ffi = nil local function log(msg) pcall(function() require("src.core.Logger").info("SecondScreen: %s", msg) end) end do - local ok, ffi = pcall(require, "ffi") + local ok + ok, ffi = pcall(require, "ffi") if not (ok and ffi) then log("ffi unavailable (not LuaJIT); second display disabled") else @@ -19,6 +21,7 @@ do int love_android_secondary_ready(); void love_android_push_secondary(const void *rgba, int w, int h); void love_android_secondary_enable(int on); + const char *love_android_poll_secondary_touch(); ]]) local okLib, lib = pcall(ffi.load, "love") if okLib and lib and pcall(function() return lib.love_android_secondary_ready end) then @@ -51,6 +54,17 @@ function SecondScreen.push(imageData, w, h) end) end +-- Returns the oldest queued secondary-display event as "action,x,y", where +-- coordinates are in the submitted frame's pixel space. +function SecondScreen.pollTouch() + if not C then return nil end + local ok, event = pcall(function() + return C.love_android_poll_secondary_touch() + end) + if not ok or event == nil or event == ffi.NULL then return nil end + return ffi.string(event) +end + function SecondScreen.setEnabled(on) if not C then return end pcall(function() C.love_android_secondary_enable(on and 1 or 0) end) diff --git a/src/render/SpriteRenderer.lua b/src/render/SpriteRenderer.lua index 3f39f1d9..36ebb36e 100644 --- a/src/render/SpriteRenderer.lua +++ b/src/render/SpriteRenderer.lua @@ -1,7 +1,8 @@ --- Overworld character sprites. A 12-tile sheet (16x96 PNG) holds 6 16x16 --- frames: stand down/up/left, walk down/up/left (data/sprites/facings.asm). +-- Overworld character sprites. The vanilla 12-tile sheet (16x96 PNG) holds +-- 6 16x16 frames: stand down/up/left, walk down/up/left +-- (data/sprites/facings.asm). Mod records may opt into another frame size +-- and anchor; the defaults below preserve the original grounded placement. -- Right-facing frames are horizontal flips of the left frames. --- Sprites draw 4px above their cell, like the GB engine. local Assets = require("src.render.Assets") local PaletteFX = require("src.render.PaletteFX") @@ -76,6 +77,58 @@ local WALK = { down = 3, up = 4, left = 5, right = 5 } SpriteRenderer.STAND = STAND SpriteRenderer.WALK = WALK +-- Sprite records are anchored at the point where the actor stands in the +-- world. In the vanilla renderer that point is the bottom-center of a +-- 16x16 frame: the frame starts at (px, py - 4), so the ground point is +-- (px + 8, py + 12). Custom anchors are measured from the frame's top-left +-- in sheet pixels and may be fractional for a sub-pixel art style. +local DEFAULT_FRAME_WIDTH = 16 +local DEFAULT_FRAME_HEIGHT = 16 +local DEFAULT_ANCHOR_X = 8 +local DEFAULT_ANCHOR_Y = 16 +local WORLD_ANCHOR_X = 8 +local WORLD_ANCHOR_Y = 12 +SpriteRenderer.DEFAULT_FRAME_WIDTH = DEFAULT_FRAME_WIDTH +SpriteRenderer.DEFAULT_FRAME_HEIGHT = DEFAULT_FRAME_HEIGHT +SpriteRenderer.DEFAULT_ANCHOR_X = DEFAULT_ANCHOR_X +SpriteRenderer.DEFAULT_ANCHOR_Y = DEFAULT_ANCHOR_Y + +local function finiteNumber(value) + if type(value) ~= "number" or value ~= value + or value == math.huge or value == -math.huge then + return nil + end + return value +end + +local function positiveInteger(value, fallback) + value = finiteNumber(value) + if value and value >= 1 then return math.floor(value) end + return fallback +end + +local function numberOr(value, fallback) + return finiteNumber(value) or fallback +end + +local function pose(self, facing, walkPhase, stepFlip) + if self.frameCount <= 1 then return 0, false end + local frame = (self.def.walker and walkPhase == 1) + and WALK[facing] or STAND[facing] + frame = frame or 0 + -- Preserve the old fallback for a short custom sheet whose pose table + -- names a frame it does not provide. + if not self.frames[frame] then frame = 0 end + local flip = false + if facing == "right" then + flip = true + elseif (facing == "down" or facing == "up") + and walkPhase == 1 and stepFlip then + flip = true + end + return frame, flip +end + -- seed: any stable per-instance value (e.g. an NPC's `id`) used to resolve -- RED++'s per-instance "random" OBP sentinel (PaletteFX.spriteObp) function SpriteRenderer.new(spriteDef, seed) @@ -83,14 +136,62 @@ function SpriteRenderer.new(spriteDef, seed) self.def = spriteDef self.seed = seed self.image = getImage(spriteDef.image) + self.frameCount = positiveInteger(spriteDef.frames, 1) + self.frameWidth = positiveInteger(spriteDef.frameWidth, DEFAULT_FRAME_WIDTH) + self.frameHeight = positiveInteger(spriteDef.frameHeight, DEFAULT_FRAME_HEIGHT) + self.anchorX = numberOr(spriteDef.anchorX, self.frameWidth / 2) + self.anchorY = numberOr(spriteDef.anchorY, self.frameHeight) local iw, ih = self.image:getDimensions() self.frames = {} - for f = 0, spriteDef.frames - 1 do - self.frames[f] = love.graphics.newQuad(0, f * 16, 16, 16, iw, ih) + for f = 0, self.frameCount - 1 do + self.frames[f] = love.graphics.newQuad(0, f * self.frameHeight, + self.frameWidth, self.frameHeight, + iw, ih) end return self end +-- Return the sheet rectangle and top-left-relative anchor for a frame. The +-- result is a fresh table so a custom render pipeline may annotate it without +-- changing the renderer's shared definition. +function SpriteRenderer:getFrameGeometry(frame) + frame = math.floor(finiteNumber(frame) or 0) + if frame < 0 then frame = 0 end + if frame >= self.frameCount then frame = self.frameCount - 1 end + return { + frame = frame, + x = 0, + y = frame * self.frameHeight, + width = self.frameWidth, + height = self.frameHeight, + anchorX = self.anchorX, + anchorY = self.anchorY, + quad = self.frames[frame], + } +end + +-- Return the frame geometry selected by the ordinary 2D pose rules, plus the +-- horizontal mirror state that :draw applies. This is the supported hook for +-- custom render pipelines that need to draw actors with the same pose/flip. +function SpriteRenderer:getPoseGeometry(facing, walkPhase, stepFlip) + local frame, flip = pose(self, facing, walkPhase, stepFlip) + local geometry = self:getFrameGeometry(frame) + geometry.facing = facing + geometry.walkPhase = walkPhase + geometry.stepFlip = stepFlip + geometry.mirror = flip + return geometry +end + +-- Screen-space top-left for the actor's current world anchor. World-facing +-- effects such as fishing can use this instead of assuming a 16x16 frame. +function SpriteRenderer:getScreenOrigin(px, py, camX, camY) + local baseX = math.floor(px - camX) + WORLD_ANCHOR_X + local baseY = math.floor(py - camY) + WORLD_ANCHOR_Y + return math.floor(baseX - self.anchorX), + math.floor(baseY - self.anchorY) +end + -- The image this sprite would draw from right now: the plain sheet, or the -- OBP-recolored bake of it. Exposed so a render pipeline can texture its -- own geometry from the very same image -- the geometry carries sheet pixel @@ -125,27 +226,32 @@ end -- facing: down/up/left/right; walkPhase: 0 stand, 1 walk; flip: alternate -- steps mirror the walk frame for up/down (GB uses OAM flip for this). -local function blitFrame(image, quad, x, y, flip, redraw) +local function blitFrame(image, quad, x, y, flip, redraw, frameWidth) + frameWidth = frameWidth or DEFAULT_FRAME_WIDTH if flip then - love.graphics.draw(image, quad, x + 16, y, 0, -1, 1) - if redraw then PaletteFX.markSpriteRedraw(image, quad, x + 16, y, -1) end + love.graphics.draw(image, quad, x + frameWidth, y, 0, -1, 1) + if redraw then + PaletteFX.markSpriteRedraw(image, quad, x + frameWidth, y, -1) + end else love.graphics.draw(image, quad, x, y) if redraw then PaletteFX.markSpriteRedraw(image, quad, x, y, 1) end end end --- topHalf blits only the upper 8 rows of the frame: FishingAnim overwrites the --- bottom tile row of the standing frames with the fishing pose art, which the --- caller then draws itself through :drawTile (Player:draw, #384) +-- topHalf blits everything above the bottom 8-pixel tile row: FishingAnim +-- overwrites that row of the standing frames with fishing pose art, which the +-- caller then draws itself through :drawTile (Player:draw, #384). Vanilla +-- frames therefore still draw 8 rows, while taller frames keep their larger +-- body and reserve only the overlay row. function SpriteRenderer:draw(px, py, camX, camY, facing, walkPhase, stepFlip, topHalf) - local x = math.floor(px - camX) - local y = math.floor(py - camY) - 4 + local x, y = self:getScreenOrigin(px, py, camX, camY) local image = self.image local redraw = false - -- full-color art claims its 16x16 cell out of the shade-remap pass + -- True-color sheets bypass every palette bake; the screen-space exemption + -- is recorded below once the final frame/height is known. if self.def.trueColor then - PaletteFX.markTrueColor(x, y, 16, 16) + image = self.image elseif PaletteFX.usesGbcPack() then -- RED++: the world canvas is already true-color (TileRenderer bakes -- terrain, this bakes the sprite) and the world pass runs unshaded @@ -177,31 +283,28 @@ function SpriteRenderer:draw(px, py, camX, camY, facing, walkPhase, stepFlip, to -- being colorized by the zone IS the point (#301). image = getObpImage(self.def.image, PaletteFX.dmgObj()) end - -- single-frame sprites (item balls, fossils...) have one fixed pose; + -- Single-frame sprites (item balls, fossils...) have one fixed pose; -- still 3-frame sprites turn to face (the nurse at her machine, - -- facePlayer on STAY NPCs) but never show walk frames - if self.def.frames <= 1 then - blitFrame(image, self.frames[0], x, y, false, redraw) - return - end - local frame = (self.def.walker and walkPhase == 1) - and WALK[facing] or STAND[facing] - local flip = false - if facing == "right" then - flip = true - elseif (facing == "down" or facing == "up") and walkPhase == 1 and stepFlip then - flip = true - end - local quad = self.frames[frame] or self.frames[0] - if topHalf then + -- facePlayer on STAY NPCs) but never show walk frames. + local frame, flip = pose(self, facing, walkPhase, stepFlip) + local quad = self.frames[frame] + local drawHeight = self.frameHeight + if topHalf and self.frameCount > 1 then self.halfFrames = self.halfFrames or {} if not self.halfFrames[frame] then local iw, ih = self.image:getDimensions() - self.halfFrames[frame] = love.graphics.newQuad(0, frame * 16, 16, 8, iw, ih) + local topHeight = math.max(1, self.frameHeight - math.min(8, self.frameHeight)) + self.halfFrames[frame] = love.graphics.newQuad( + 0, frame * self.frameHeight, self.frameWidth, topHeight, iw, ih) end quad = self.halfFrames[frame] + drawHeight = math.max(1, self.frameHeight - math.min(8, self.frameHeight)) end - blitFrame(image, quad, x, y, flip, redraw) + -- Full-color art claims exactly the portion of the frame that was drawn. + if self.def.trueColor then + PaletteFX.markTrueColor(x, y, self.frameWidth, drawHeight) + end + blitFrame(image, quad, x, y, flip, redraw, self.frameWidth) end -- Blit a loose 16-wide fx tile at screen (x, y) wearing THIS sprite's OBJ @@ -225,7 +328,7 @@ function SpriteRenderer:drawTile(path, x, y, flip) self.tileQuads = self.tileQuads or {} self.tileQuads[path] = self.tileQuads[path] or love.graphics.newQuad(0, 0, iw, ih, iw, ih) - blitFrame(image, self.tileQuads[path], x, y, flip, redraw) + blitFrame(image, self.tileQuads[path], x, y, flip, redraw, iw) end return SpriteRenderer diff --git a/src/render/TextBox.lua b/src/render/TextBox.lua index df502512..290c4ecc 100644 --- a/src/render/TextBox.lua +++ b/src/render/TextBox.lua @@ -7,11 +7,13 @@ -- the text is exhausted and A is pressed, then calls onDone. local Font = require("src.render.Font") +local Runtime = require("src.mods.Runtime") local Theme = require("src.ui.Theme") local Timing = require("src.core.Timing") local TextBox = {} TextBox.__index = TextBox +TextBox.isTextBox = true -- theme-free fallbacks; geometry resolves against Theme.textBox at -- construction time, so an unthemed boot stays byte-identical @@ -344,6 +346,11 @@ function TextBox:update(dt) end function TextBox:draw() + if Runtime.wantsHook("battle.bottom_ui_visible") + and Runtime.call("battle.bottom_ui_visible", function() return true end, + self) == false then + return + end -- The dialogue box belongs against the bottom of the screen, not floating -- in the middle of a zoomed-out letterbox. Declared per frame; the -- renderer blits this region to the screen edge and the rest of the UI @@ -370,8 +377,12 @@ function TextBox:draw() local ys = { self.line1Y, self.line2Y } for i, line in ipairs(self.shown) do local y = (ys[i] or self.line2Y) + (i == 1 and off or 0) - for j, code in ipairs(line) do - Font.drawCode(code, self.textX + (j - 1) * 8, y) + -- the pen advances per glyph, matching the pixel budget paginate + -- measured with; every fixed-width page still lands on the 8px grid + local pen = self.textX + for _, code in ipairs(line) do + Font.drawCode(code, pen, y) + pen = pen + Font.advanceOf(code) end end if (self.waiting or (self.done and not self.choice and not self.auto diff --git a/src/render/Transition.lua b/src/render/Transition.lua index 23784ca0..f970b5f6 100644 --- a/src/render/Transition.lua +++ b/src/render/Transition.lua @@ -59,7 +59,13 @@ local function styleOf(game, id) return record or Transition.STYLES[id] end -function Transition.new(game, onMidpoint, onDone) +-- `warp` marks the map-change fade: PlayMapChangeSound's GBFadeOutToBlack +-- has no matching fade in (LoadGBPal restores the palettes in one write), so +-- warps land with framesIn 0. Script fades that bracket a HideObject +-- (ViridianGym.asm .afterBeat, RocketHideoutB4F BeatGiovanniScript) call +-- GBFadeOutToBlack -> GBFadeInFromBlack instead, so the default keeps the +-- symmetric 32-frame fade back in (home/fade.asm:21, b = 4). +function Transition.new(game, onMidpoint, onDone, warp) local self = setmetatable({}, Transition) self.game = game self.onMidpoint = onMidpoint @@ -68,9 +74,13 @@ function Transition.new(game, onMidpoint, onDone) self.phase = "out" local style = styleOf(game, "warp_fade") self.frames = style.frames or FRAMES - -- a style may still ask for a fade in (mods, and the record is data-driven); - -- the built-in warp is 0, matching hardware - self.framesIn = style.framesIn or FRAMES_IN + if warp then + -- a style may still ask for a fade in (mods, and the record is + -- data-driven); the built-in warp is 0, matching hardware + self.framesIn = style.framesIn or FRAMES_IN + else + self.framesIn = Timing.FADE_IN_FROM_BLACK + end return self end diff --git a/src/render/UiFont.lua b/src/render/UiFont.lua new file mode 100644 index 00000000..b0ec5f03 --- /dev/null +++ b/src/render/UiFont.lua @@ -0,0 +1,62 @@ +-- Per-glyph fallback for the launcher's UI faces. +-- +-- The launcher draws with LÖVE's default face, which covers Latin and little +-- else. That was invisible while the launcher was English-only, but its text +-- now goes through Strings (#767/#791) and a translation mod's catalog is +-- loaded before the first launcher frame (LauncherMods.translationStrings), so +-- the moment one is Japanese every kana lands as a tofu box. +-- +-- Font:setFallbacks fills ONLY the codepoints the primary face is missing, so +-- Latin keeps the launcher's own look and nothing about an English install +-- changes; only the glyphs it genuinely cannot draw come from the bundled +-- Plain Pixel (assets/fonts/plainpixel/README.md: CC-BY 4.0, Douglas +-- Vautour), which covers kana and CJK. That is the same face the in-game TTF +-- text mode uses, so a translated launcher and a translated game agree. +-- +-- Measuring and rendering must attach the same fallback or the launcher +-- measures a width it does not draw -- which is how buttons clip. + +local UiFont = {} + +local FALLBACK_PATH = "assets/fonts/plainpixel/PlainPixel-Regular.ttf" +-- Plain Pixel only rasterizes evenly at multiples of its 15px design em, so +-- snap rather than matching the primary size exactly: a fallback glyph a pixel +-- off its grid is far more obvious than one a pixel off its neighbours. +local DESIGN_EM = 15 + +local cache = {} +local unavailable = false + +local function fallbackFor(size) + if unavailable then return nil end + local snapped = math.max(DESIGN_EM, + math.floor(size / DESIGN_EM + 0.5) * DESIGN_EM) + local hit = cache[snapped] + if hit ~= nil then return hit or nil end + local ok, font = pcall(love.graphics.newFont, FALLBACK_PATH, snapped) + if not ok or not font then + -- one failure means the file is absent (a trimmed build); stop retrying + unavailable = true + return nil + end + if font.setFilter then pcall(font.setFilter, font, "nearest", "nearest") end + cache[snapped] = font + return font +end + +-- attach(font, size) -> font. Safe to call on every cache miss; a font whose +-- fallback is already set is left alone, and any failure is swallowed so a +-- missing fallback file can never take the launcher down with it. +function UiFont.attach(font, size) + if not (font and font.setFallbacks) then return font end + local fallback = fallbackFor(size or (font.getHeight and font:getHeight()) or DESIGN_EM) + if not fallback or rawequal(fallback, font) then return font end + pcall(font.setFallbacks, font, fallback) + return font +end + +function UiFont.clear() + cache, unavailable = {}, false +end + +return UiFont diff --git a/src/save_convert/GenSave.lua b/src/save_convert/GenSave.lua index 06d170fc..0f754359 100644 --- a/src/save_convert/GenSave.lua +++ b/src/save_convert/GenSave.lua @@ -27,6 +27,7 @@ -- game regenerates all of it from wCurMap on the next map load anyway. local bit = require("bit") +local MapContext = require("src.save_convert.MapContext") local GenSave = {} @@ -93,6 +94,12 @@ O.statusFlags1 = O.townVisited + 29 -- 1B O.statusFlags4 = O.townVisited + 35 -- 1B O.elite4Flags = O.townVisited + 41 -- 1B O.tradeFlags = O.townVisited + 44 -- 2B (flag_array NUM_NPC_TRADES) +-- wToggleableObjectFlags (ram/wram.asm, flag_array $100): the ShowObject/ +-- HideObject persistence, one bit per data/maps/toggleable_objects.asm entry, +-- set = hidden (engine/overworld/toggleable_objects.asm IsObjectHidden). +-- Sits 2 bytes (wPlayerCoins) past O.coins per the walk above; absolute +-- 0x2852 (#763, #857). +O.toggleObjectFlags = O.coins + 2 -- 32B -- Play time (wPlayTimeHours/Maxed/Minutes/Seconds/Frames) lives INSIDE the -- sMainData window (wMainDataStart..wMainDataEnd is copied verbatim into -- SRAM), 1866 bytes past wMainDataStart -- reached from the checksum-verified @@ -107,6 +114,15 @@ O.playTimeMaxed = O.mainData + 1867 -- 1B (set once past 2 O.playTimeMinutes = O.mainData + 1868 -- 1B (0-59) O.playTimeSeconds = O.mainData + 1869 -- 1B (0-59) O.playTimeFrames = O.mainData + 1870 -- 1B (0-59, 1/60s ticks) +-- wPikachuHappiness, Yellow only (pret/pokeyellow ram/wram.asm; no local +-- pokeyellow checkout, so verified against the pokeyellow symbol file +-- instead: d46f - wMainDataStart d2f6 = 377, the well-known absolute +-- 0x271C). In Red/Blue this byte is current-map scratch the game +-- regenerates on load, so the codec touches it only when the crosswalk +-- data set names the game "yellow" (#763, #838). Every other modeled +-- offset is identical between pokered and pokeyellow (same sram.asm, same +-- wMainData field spacing per both symbol files). +O.pikachuHappiness = O.mainData + 377 O.mainDataSize = 1929 -- wMainDataEnd - wMainDataStart O.spriteData = O.mainData + O.mainDataSize @@ -190,6 +206,18 @@ local function checksum(bytes, from, to) return bit.band(bit.bnot(sum), 0xFF) end +-- Main-data checksum gate used before an import policy is decided. Returns +-- nil when the buffer is too short to even carry the stored checksum byte +-- (offset O.mainChecksum, the last byte of wMainData), false on a mismatch, +-- true when it matches. Works on any length >= O.mainChecksum + 1, so a +-- caller can classify a truncated or footer-padded file without a full +-- decode -- the checksummed region (0x2598..0x3522) always sits entirely +-- inside the first 0x3524 bytes of a save. +function GenSave.mainChecksumValid(bytes) + if #bytes < O.mainChecksum + 1 then return nil end + return checksum(bytes, O.checksumStart, O.checksumEnd) == u8(bytes, O.mainChecksum) +end + -- flag_array packs LSB-first within each byte (bit 0 of byte 0 = index 0). -- This is pokered's runtime FlagAction convention (home/predef macros): it -- takes flag number N, addresses byte N/8, and builds the mask by rotating @@ -240,7 +268,7 @@ local function decodeName(bytes, off, len) return table.concat(out) end -local function encodeName(buf, off, len, text) +local function encodeName(buf, off, len, text, padTail) local i, pos = 0, 1 while i < len - 1 and pos <= #text do -- a bracketed control token (e.g. "", from decodeName reading a @@ -259,16 +287,17 @@ local function encodeName(buf, off, len, text) setByte(buf, off + i, charmap.byToken[ch] or charmap.byToken["?"] or 0x50) i, pos = i + 1, pos + clen end - -- Write exactly ONE $50 terminator and then $50-pad the rest of the - -- field: every real save the naming screen ever wrote fills the tail - -- with $50, and a zero tail is what PKHeX renders as garbage glyphs - -- after the name ("JOHN{}", #206). Template bytes that are NOT zero - -- stay untouched, so an unchanged name still round-trips - -- byte-identical and stale template data survives. + -- Write exactly ONE $50 terminator. The tail past it is $50-padded only + -- on a templateless (engine-origin) export, where the zero-filled buffer + -- is what PKHeX renders as garbage glyphs after the name ("JOHN{}", #206). + -- With a template the tail keeps the original save's bytes verbatim -- + -- real cartridge saves legitimately hold 0x00 (and other stale glyph) + -- bytes after the terminator, and rewriting any of them broke the + -- import->export byte-identical round trip (the game and PKHeX both stop + -- reading at the terminator, so preserved tails are always safe). if i < len then setByte(buf, off + i, 0x50) end - for j = i + 1, len - 1 do - local cur = buf[off + j + 1] - if cur == nil or cur:byte() == 0 then setByte(buf, off + j, 0x50) end + if padTail then + for j = i + 1, len - 1 do setByte(buf, off + j, 0x50) end end end @@ -716,6 +745,25 @@ function GenSave.decode(bytes, data, opts) if save.flags[vanillaName] then save.flags[portName] = true end end + -- wToggleableObjectFlags -> save.objectToggles (bit set = hidden). A few + -- of these are re-derived from flags on map entry (#106/#234 onEnter + -- re-applies), but most ShowObject/HideObject state -- the Mt Moon + -- fossils, the Cerulean guard swap -- has no flag to re-derive from, so + -- an import that drops the array resurrects taken fossils and blocking + -- guards (#763, #857). + local toggles = data.toggleObjects + if toggles then + save.objectToggles = {} + for bitIdx, e in pairs(toggles.byBit) do + local mapToggles = save.objectToggles[e[1]] + if not mapToggles then + mapToggles = {} + save.objectToggles[e[1]] = mapToggles + end + mapToggles[e[2]] = not bitGet(bytes, O.toggleObjectFlags, bitIdx) + end + end + -- FLY destinations. wTownVisitedFlag's bit index IS the town's map index: -- engine/items/town_map.asm BuildFlyLocationsList loads the 16-bit value -- into de and rotates it right one bit per iteration with b counting up @@ -761,6 +809,15 @@ function GenSave.decode(bytes, data, opts) + u8(bytes, O.playTimeSeconds) + u8(bytes, O.playTimeFrames) / 60 + -- Yellow starter friendship (save.pikachuHappiness, + -- src/world/PikachuFollower.lua reads it; pokeyellow's + -- init_player_data.asm seeds 90 on a new game), gated on the data set's + -- game because the byte is map scratch in Red/Blue (see + -- O.pikachuHappiness) (#763, #838). + if data.gameVersion == "yellow" then + save.pikachuHappiness = u8(bytes, O.pikachuHappiness) + end + save.warnings = warnings save.rawImport = bytes -- template for a later encode(); see file header return save @@ -781,8 +838,9 @@ function GenSave.encode(save, data, template) for i = 1, GenSave.SAVE_SIZE do buf[i] = zero end end - encodeName(buf, O.playerName, NAME_LENGTH, (save.player and save.player.name) or "RED") - encodeName(buf, O.rivalName, NAME_LENGTH, (save.player and save.player.rival) or "BLUE") + local padTail = not src + encodeName(buf, O.playerName, NAME_LENGTH, (save.player and save.player.name) or "RED", padTail) + encodeName(buf, O.rivalName, NAME_LENGTH, (save.player and save.player.rival) or "BLUE", padTail) setU16be(buf, O.playerId, (save.player and save.player.id) or 0) -- wOptions (engine/menus/main_menu.asm InitOptions): bit 7 = battle -- effects OFF, bit 6 = SET style, bits 2-0 = text speed -- the recomp's @@ -854,6 +912,41 @@ function GenSave.encode(save, data, template) end end + -- wToggleableObjectFlags, written both ways like the #396 extras: this + -- port's save is the authority, and vanilla folds three stores this port + -- keeps separate into these same bits -- script ShowObject/HideObject + -- (save.objectToggles), taken overworld items (engine/events/ + -- pick_up_item.asm -> save.itemsTaken) and beaten static encounters + -- (home/trainers.asm HideObject after battle -> save.defeatedTrainers) -- + -- so all three fold back in here or an exported save resurrects them + -- (#763, #857). + local toggleData = data.toggleObjects + if toggleData then + local objectToggles = save.objectToggles or {} + local itemsTaken = save.itemsTaken or {} + local beaten = save.defeatedTrainers or {} + for bitIdx, e in pairs(toggleData.byBit) do + local mapId, objName, visible = e[1], e[2], e[3] + local mapToggles = objectToggles[mapId] + if mapToggles and mapToggles[objName] ~= nil then + visible = mapToggles[objName] + end + if visible and data.maps and data.maps[mapId] then + for _, obj in ipairs(data.maps[mapId].objects or {}) do + if obj.name == objName then + local key = mapId .. "_obj_" .. obj.index + if (obj.item and itemsTaken[key]) + or (obj.pokemon and beaten[key]) then + visible = false + end + break + end + end + end + bitSet(buf, O.toggleObjectFlags, bitIdx, not visible) + end + end + -- FLY destinations back into wTownVisitedFlag (see the decode note), so a -- save exported from this port is flyable on hardware (#263). A save -- table with no `visited` key at all says nothing about the set, so leave @@ -875,11 +968,11 @@ function GenSave.encode(save, data, template) encodeMon(buf, O.partyMons + i * PARTY_STRUCT_SIZE, mon, true, cw) setByte(buf, O.partySpecies + i, cw.pokemonIndex[mon.species] or 0) encodeName(buf, O.partyMonOT + i * NAME_LENGTH, NAME_LENGTH, - mon.ot or (save.player and save.player.name) or "RED") + mon.ot or (save.player and save.player.name) or "RED", padTail) -- no nickname stores the species' DISPLAY name, not its ROM constant id -- ("NIDORAN_M" would charmap the "_" to "?") (#257) encodeName(buf, O.partyMonNicks + i * NAME_LENGTH, NAME_LENGTH, - mon.nickname or speciesName(cw, mon.species)) + mon.nickname or speciesName(cw, mon.species), padTail) end -- $FF-terminate the species index list right after the last real mon. The -- struct, OT-name and nickname bytes of the empty slots past partyN are left @@ -900,9 +993,9 @@ function GenSave.encode(save, data, template) encodeMon(buf, base + 22 + i * BOX_STRUCT_SIZE, mon, false, cw) setByte(buf, base + 1 + i, cw.pokemonIndex[mon.species] or 0) encodeName(buf, base + 22 + MONS_PER_BOX * BOX_STRUCT_SIZE + i * NAME_LENGTH, NAME_LENGTH, - mon.ot or (save.player and save.player.name) or "RED") + mon.ot or (save.player and save.player.name) or "RED", padTail) encodeName(buf, base + 22 + MONS_PER_BOX * (BOX_STRUCT_SIZE + NAME_LENGTH) + i * NAME_LENGTH, NAME_LENGTH, - mon.nickname or speciesName(cw, mon.species)) -- #257, as above + mon.nickname or speciesName(cw, mon.species), padTail) -- #257, as above end -- $FF-terminate the species list after the last real mon; empty slots past -- n keep their template bytes (byte-identical round-trip) or zero (fresh @@ -931,6 +1024,44 @@ function GenSave.encode(save, data, template) setByte(buf, O.lastMap, cw.mapsIndex[save.lastOutdoor.id] or 0) end + -- Current-map engine state (see src/save_convert/MapContext.lua). A + -- Continue restores this window from the save and never rebuilds it, so a + -- zero-filled one boots into a garbled map on a silent hang (#889). + -- + -- Rebuilt when there is no template at all (a save that began as a New Game + -- in this port), and when the template was saved on a DIFFERENT map than the + -- one the player is standing on now -- an imported save that has since been + -- played carries the old map's header, which is just as unbootable. A + -- template still on its own map keeps its bytes untouched: they are the + -- game's own, including live NPC positions, and preserving them is what + -- makes import -> export byte-identical. + local mapId = save.player and save.player.map + if mapId then + local rebuild = true + if src then + -- compare the way the byte was written (masked), and rebuild when the + -- map has no index at all rather than trusting a stale template + local index = cw.mapsIndex[mapId] + rebuild = index == nil or u8(src, O.curMap) ~= bit.band(index, 0xFF) + end + if rebuild then + local ctx = MapContext.build(data, mapId, + (save.player and save.player.x) or 0, (save.player and save.player.y) or 0) + if ctx then + for offset, values in pairs(ctx.writes) do + for i, value in ipairs(values) do + setByte(buf, O.mainData + offset + i - 1, value) + end + end + for i, value in ipairs(ctx.spriteData) do + setByte(buf, O.spriteData + i - 1, value) + end + -- sTileAnimations, the byte between sCurBoxData and the checksum + setByte(buf, O.checksumEnd - 1, ctx.tileAnimations) + end + end + end + -- play time: split save.playTime (seconds) back into H/M/S/F. The real -- game freezes the clock at 255h and sets wPlayTimeMaxed once past it, so -- mirror that cap rather than letting hours overflow a single byte. @@ -953,6 +1084,16 @@ function GenSave.encode(save, data, template) setByte(buf, O.playTimeFrames, rem - secs * 60) end + -- Yellow starter friendship back out (see O.pikachuHappiness); Red/Blue + -- data sets never reach this write. 90 is the fresh-game seed the + -- follower system itself uses when the save has never tracked it. + -- Placed before the checksum pass so the byte is covered by the + -- main-data checksum automatically (#763, #838). + if data.gameVersion == "yellow" then + local h = tonumber(save.pikachuHappiness) or 90 + setByte(buf, O.pikachuHappiness, math.max(0, math.min(255, math.floor(h)))) + end + local out = table.concat(buf) -- checksums, computed last over the now-final bytes local outBuf = {} diff --git a/src/save_convert/MapContext.lua b/src/save_convert/MapContext.lua new file mode 100644 index 00000000..0443a992 --- /dev/null +++ b/src/save_convert/MapContext.lua @@ -0,0 +1,263 @@ +-- MapContext -- the current map's engine state, as a vanilla Gen1 save has to +-- carry it (#889). +-- +-- Continuing a real save never re-loads the map header. LoadMainData +-- (engine/menus/save.asm) copies sMainData back into WRAM and then sets +-- BIT_NO_PREVIOUS_MAP in wCurMapTileset; LoadMapHeader (home/overworld.asm) +-- clears that bit and RETURNS IMMEDIATELY when it was set, so every byte it +-- would otherwise have written -- the map header, connections, warps, signs, +-- sprites, the tileset header, the map's music -- comes straight out of the +-- save file. A cartridge save always has them because the game saved them. +-- +-- An export from this port did not: GenSave models the player's progress, not +-- the engine scratch around it, and left that whole window zero-filled unless +-- it had an imported SRAM image to copy from. The game then continued into +-- tileset 0 with a $0000 map-data pointer and sound id 0, which is the +-- garbled overworld / silent hang users reported after exporting a save that +-- began as a New Game in the port. +-- +-- This module rebuilds the window from the extracted ROM data, replaying the +-- same writes LoadMapHeader makes, so a port-origin save boots on hardware. +-- The offsets are all relative to wMainDataStart (= sMainData in a .sav) and +-- were summed from ram/wram.asm, then checked byte-for-byte against a real +-- cartridge save: rebuilding its current map reproduces the bytes it already +-- held, everywhere except the stale tails of disabled connection slots (which +-- the engine never reads once the connected-map byte is $FF). +-- +-- Pure Lua, no love.*: shared by the runtime exporter, the CLI and the tests. + +local MapContext = {} + +-- wMainDataStart-relative offsets. Anchors: wCurMap is +103 (the offset +-- GenSave already decodes the player's map from), and the run from there is +-- wCurMap, wCurrentTileBlockMapViewPointer(2), wYCoord, wXCoord, wYBlockCoord, +-- wXBlockCoord, wLastMap, wUnusedLastMapWidth, wCurMapHeader -- so the header +-- lands at +112, and every later label follows from the declaration sizes in +-- ram/wram.asm (MAX_WARP_EVENTS 32, MAX_BG_EVENTS 16, MAX_OBJECT_EVENTS 16, +-- SPRITE_SET_LENGTH 11). +local O = { + mapMusicSoundID = 100, -- wMapMusicSoundID + mapMusicROMBank = 101, -- wMapMusicROMBank + viewPointer = 104, -- wCurrentTileBlockMapViewPointer (2) + yCoord = 106, + xCoord = 107, + yBlockCoord = 108, + xBlockCoord = 109, + curMapHeader = 112, -- wCurMapHeader (10) + connectionHeaders = 122, -- wNorth/South/West/EastConnectionHeader (4 x 11) + mapBackgroundTile = 182, + numberOfWarps = 183, + warpEntries = 184, -- MAX_WARP_EVENTS x 4 + numSigns = 441, + signCoords = 442, -- MAX_BG_EVENTS x 2 + signTextIDs = 474, -- MAX_BG_EVENTS + numSprites = 490, + mapSpriteData = 493, -- MAX_OBJECT_EVENTS x 2 + mapSpriteExtra = 525, -- MAX_OBJECT_EVENTS x 2 + currentMapHeight2 = 557, + currentMapWidth2 = 558, + tilesetHeader = 564, -- wTilesetBank .. wGrassTile (11) +} +MapContext.OFFSETS = O + +local MAX_WARP_EVENTS = 32 +local MAX_BG_EVENTS = 16 +local MAX_OBJECT_EVENTS = 16 +local CONNECTION_STRUCT = 11 -- map_connection_struct (macros/ram.asm) +local SPRITE_STRUCT = 16 -- one wSpriteStateData1/2 entry +local NUM_SPRITE_STRUCTS = 16 + +-- wOverworldMap, the tile-block map the view pointer indexes. Recovered from +-- the event_displacement formula below applied to a real save's coordinates, +-- and it is the same $C6E8 every Gen1 reference quotes. +local OVERWORLD_MAP = 0xC6E8 + +-- Audio header tables start at $4000 in each of the three audio banks, and +-- constants/music_constants.asm derives every song id arithmetically from +-- that base ("Song ids are calculated by address", music_const: +-- (Music_X - SFX_Headers_1) / 3). So the extracted header address IS the id, +-- and no separate MapSongBanks extraction is needed. +local SFX_HEADERS_BASE = 0x4000 + +local function soundId(address) + if type(address) ~= "number" then return nil end + local delta = address - SFX_HEADERS_BASE + if delta < 0 or delta % 3 ~= 0 then return nil end + local id = delta / 3 + if id > 0xFF then return nil end + return id +end + +-- LoadMapHeader parses the map's object data (data/maps/objects/*.asm) into +-- five separate WRAM arrays. Walk it exactly as the engine does; `bytes` is +-- the 1-based array of raw object-data bytes the extractor captured. +local function parseObjects(bytes) + local pos = 1 + local function take() + local b = bytes[pos] + pos = pos + 1 + return b + end + local out = { warps = {}, signCoords = {}, signTexts = {}, + spriteData = {}, spriteExtra = {}, sprites = {} } + + out.backgroundTile = take() + + local warpCount = take() + if warpCount == nil then return nil end + out.warpCount = warpCount + for _ = 1, math.min(warpCount, MAX_WARP_EVENTS) * 4 do + out.warps[#out.warps + 1] = take() + end + + local signCount = take() + if signCount == nil then return nil end + out.signCount = signCount + for _ = 1, math.min(signCount, MAX_BG_EVENTS) do + out.signCoords[#out.signCoords + 1] = take() -- Y + out.signCoords[#out.signCoords + 1] = take() -- X + out.signTexts[#out.signTexts + 1] = take() + end + + local spriteCount = take() + if spriteCount == nil then return nil end + out.spriteCount = spriteCount + for _ = 1, math.min(spriteCount, MAX_OBJECT_EVENTS) do + local picture, mapY, mapX = take(), take(), take() + local movement1, movement2, textId = take(), take(), take() + if textId == nil then return nil end + out.sprites[#out.sprites + 1] = { + picture = picture, mapY = mapY, mapX = mapX, movement1 = movement1, + } + -- wMapSpriteData: movement byte 2, then the text id with its trainer/item + -- flag bits masked off (LoadMapHeader's `and $3f`). + out.spriteData[#out.spriteData + 1] = movement2 + out.spriteData[#out.spriteData + 1] = textId % 0x40 + -- BIT_TRAINER ($40) is tested before BIT_ITEM ($80), as LoadMapHeader does + if textId % 0x80 >= 0x40 then -- trainer: class, then party id + out.spriteExtra[#out.spriteExtra + 1] = take() + out.spriteExtra[#out.spriteExtra + 1] = take() + elseif textId >= 0x80 then -- item ball: item id, second byte unused + out.spriteExtra[#out.spriteExtra + 1] = take() + out.spriteExtra[#out.spriteExtra + 1] = 0 + else + out.spriteExtra[#out.spriteExtra + 1] = 0 + out.spriteExtra[#out.spriteExtra + 1] = 0 + end + end + return out +end + +-- build(data, mapId, x, y) -> ctx, err +-- +-- ctx.writes [wMainDataStart-relative offset] = array of bytes +-- ctx.spriteData 512 bytes for sSpriteData (wSpriteStateData1 then 2) +-- ctx.tileAnimations the byte sTileAnimations holds +-- +-- Returns nil plus a reason when the data set predates the extractor fields +-- this needs (an older ROM cache), so callers can fall back rather than fail. +function MapContext.build(data, mapId, x, y) + local map = data and data.maps and data.maps[mapId] + if not map then return nil, "unknown map " .. tostring(mapId) end + local sram = map.sram + if not (sram and sram.header and sram.objects) then + return nil, "map cache has no saved-map bytes (re-import the ROM)" + end + x, y = math.floor(tonumber(x) or 0), math.floor(tonumber(y) or 0) + + local writes = {} + local header = sram.header + writes[O.curMapHeader] = header + local height, width = header[2], header[3] + local connectionFlags = header[10] + + -- Connections: LoadMapHeader writes $FF over all four connected-map bytes + -- and then copies 11 bytes per present direction, north/south/west/east. + -- The disabled slots' remaining bytes are never read. + local conn = {} + for i = 1, 4 * CONNECTION_STRUCT do conn[i] = 0 end + for slot = 0, 3 do conn[slot * CONNECTION_STRUCT + 1] = 0xFF end + local pos = 1 + for slot, flag in ipairs({ 0x08, 0x04, 0x02, 0x01 }) do + if math.floor(connectionFlags / flag) % 2 == 1 then + for i = 0, CONNECTION_STRUCT - 1 do + conn[(slot - 1) * CONNECTION_STRUCT + 1 + i] = (sram.connections or {})[pos + i] or 0 + end + pos = pos + CONNECTION_STRUCT + end + end + writes[O.connectionHeaders] = conn + + local parsed = parseObjects(sram.objects) + if not parsed then return nil, "malformed object data for " .. tostring(mapId) end + writes[O.mapBackgroundTile] = { parsed.backgroundTile } + writes[O.numberOfWarps] = { parsed.warpCount } + writes[O.warpEntries] = parsed.warps + writes[O.numSigns] = { parsed.signCount } + writes[O.signCoords] = parsed.signCoords + writes[O.signTextIDs] = parsed.signTexts + writes[O.numSprites] = { parsed.spriteCount } + writes[O.mapSpriteData] = parsed.spriteData + writes[O.mapSpriteExtra] = parsed.spriteExtra + + -- "map height/width in 2x2 tile blocks", doubled at the end of LoadMapHeader + writes[O.currentMapHeight2] = { (height * 2) % 256 } + writes[O.currentMapWidth2] = { (width * 2) % 256 } + + -- The tileset header, as predef LoadTilesetHeader would have copied it. + local tilesets = data.tilesets or {} + local tilesetDef = tilesets[map.tileset] + local tileAnimations = 0 + if tilesetDef and tilesetDef.header then + local row = {} + for i = 1, 11 do row[i] = tilesetDef.header[i] end + writes[O.tilesetHeader] = row + tileAnimations = tilesetDef.header[12] or 0 + end + + -- MapSongBanks: without it the game continues with sound id 0 and audio + -- bank 0, which is what actually hangs a Continue on a white screen. + local audio = data.audio + local songLabel = audio and audio.mapSongs and audio.mapSongs[mapId] + local song = songLabel and audio.songs and audio.songs[songLabel] + local id = song and soundId(song.address) + if id and song.bank then + writes[O.mapMusicSoundID] = { id } + writes[O.mapMusicROMBank] = { song.bank % 256 } + end + + -- Player position within its block, and the upper-left corner of the view. + -- The pointer is the same expression the warp_to tables are assembled with + -- (macros/coords.asm event_displacement). + writes[O.yBlockCoord] = { y % 2 } + writes[O.xBlockCoord] = { x % 2 } + local view = OVERWORLD_MAP + 7 + width + + (width + 6) * math.floor(y / 2) + math.floor(x / 2) + writes[O.viewPointer] = { view % 256, math.floor(view / 256) % 256 } + + -- sSpriteData. LoadMapHeader zeroes structs 1-15, sets their image index to + -- $ff (off screen until the engine places them), then fills in each map + -- object's picture id and its map Y/X plus movement byte 1. Struct 0 is the + -- player, which SpecialEnterMap rebuilds through ResetPlayerSpriteData. + local spriteData = {} + for i = 1, NUM_SPRITE_STRUCTS * SPRITE_STRUCT * 2 do spriteData[i] = 0 end + local data2 = NUM_SPRITE_STRUCTS * SPRITE_STRUCT + for slot = 1, NUM_SPRITE_STRUCTS - 1 do + spriteData[slot * SPRITE_STRUCT + 2 + 1] = 0xFF + end + for index, sprite in ipairs(parsed.sprites) do + local base = index * SPRITE_STRUCT + spriteData[base + 1] = sprite.picture + spriteData[data2 + base + 4 + 1] = sprite.mapY + spriteData[data2 + base + 5 + 1] = sprite.mapX + spriteData[data2 + base + 6 + 1] = sprite.movement1 + end + + return { + writes = writes, + spriteData = spriteData, + tileAnimations = tileAnimations, + } +end + +return MapContext diff --git a/src/save_convert/SaveConvert.lua b/src/save_convert/SaveConvert.lua index ef2547e3..3df6122f 100644 --- a/src/save_convert/SaveConvert.lua +++ b/src/save_convert/SaveConvert.lua @@ -25,6 +25,7 @@ local GenSave = require("src.save_convert.GenSave") local SaveConvert = {} SaveConvert.SAVE_SIZE = GenSave.SAVE_SIZE +SaveConvert.mainChecksumValid = GenSave.mainChecksumValid -- ------------------------------------------------------------------ -- Crosswalk data loading (cached). Mirrors src/core/Data.lua: prefer @@ -41,8 +42,27 @@ local DATA_MODULES = { moves = { "data.generated.moves", "data/generated/moves.lua" }, items = { "data.generated.items", "data/generated/items.lua" }, maps = { "data.generated.maps", "data/generated/maps.lua" }, + -- tilesets/audio are only read by src/save_convert/MapContext.lua, to + -- rebuild the current map's engine state on export (#889) + tilesets = { "data.generated.tilesets", "data/generated/tilesets.lua" }, + audio = { "data.generated.audio", "data/generated/audio.lua" }, charmap = { "src.save_convert.data.charmap", "src/save_convert/data/charmap.lua" }, eventFlags = { "src.save_convert.data.event_flags", "src/save_convert/data/event_flags.lua" }, + toggleObjects = { "src.save_convert.data.toggle_objects", "src/save_convert/data/toggle_objects.lua" }, +} + +local OPTIONAL_MODULES = { tilesets = true, audio = true } + +-- Yellow renumbers wEventFlags bits: pokeyellow's constants/event_constants.asm +-- inserts events pokered does not have (the Jessie & James fights, catch +-- training, the Officer Jenny Squirtle) and shifts the Mt Moon 3 / Silph Co +-- 11F block, so writing a Yellow save through the Red table lands bits on the +-- wrong events and drops every Yellow-only flag. Kept outside DATA_MODULES so +-- the ensureData loop never loads it as a crosswalk of its own -- it +-- substitutes for `eventFlags` when the caller names Yellow (#838). +local YELLOW_EVENT_FLAGS = { + "src.save_convert.data.event_flags_yellow", + "src/save_convert/data/event_flags_yellow.lua", } local function loadTable(requirePath, filePath) @@ -111,15 +131,25 @@ local function ensureData(gameVersion) local data = {} for name, spec in pairs(DATA_MODULES) do if name ~= "charmap" then + if name == "eventFlags" and gameVersion == "yellow" then + spec = YELLOW_EVENT_FLAGS -- Yellow's bit numbering differs (#838) + end local mod = loadCacheTable(gameVersion, spec[2]) if not mod then local e mod, e = loadTable(spec[1], spec[2]) - if not mod then return nil, e end + -- tilesets/audio only sharpen the export (MapContext); a cache + -- without them still imports and exports, just without the + -- rebuilt map window, so they must not fail the whole load + if not mod and not OPTIONAL_MODULES[name] then return nil, e end end data[name] = mod end end + -- record which game's tables these are: the codec gates Yellow-only + -- bytes (wPikachuFriendship) on it, since those offsets are map + -- scratch in Red/Blue (#763, #838) + data.gameVersion = gameVersion crosswalks[key] = data end if not charmapReady then diff --git a/src/save_convert/data/event_flags_yellow.lua b/src/save_convert/data/event_flags_yellow.lua new file mode 100644 index 00000000..5b65b445 --- /dev/null +++ b/src/save_convert/data/event_flags_yellow.lua @@ -0,0 +1,1057 @@ +-- Generated by tools/build_data.py. DO NOT EDIT. +-- wEventFlags bit index <-> EVENT_* name (pokeyellow numbering; see +-- pokeyellow ram/wram.asm wEventFlags, a flat NUM_EVENTS-bit +-- array). byBit only has entries for bits with a name -- +-- reserved/padding bits are intentionally absent. +return { + byBit = { + [0] = "EVENT_FOLLOWED_OAK_INTO_LAB", + [3] = "EVENT_HALL_OF_FAME_DEX_RATING", + [5] = "EVENT_PLAYER_AT_RIGHT_EXIT_TO_PALLET_TOWN", + [6] = "EVENT_PALLET_AFTER_GETTING_POKEBALLS", + [24] = "EVENT_GOT_TOWN_MAP", + [25] = "EVENT_ENTERED_BLUES_HOUSE", + [26] = "EVENT_DAISY_WALKING", + [32] = "EVENT_FOLLOWED_OAK_INTO_LAB_2", + [33] = "EVENT_OAK_ASKED_TO_CHOOSE_MON", + [34] = "EVENT_GOT_STARTER", + [35] = "EVENT_BATTLED_RIVAL_IN_OAKS_LAB", + [36] = "EVENT_GOT_POKEBALLS_FROM_OAK", + [37] = "EVENT_GOT_POKEDEX", + [38] = "EVENT_PALLET_AFTER_GETTING_POKEBALLS_2", + [39] = "EVENT_OAK_APPEARED_IN_PALLET", + [40] = "EVENT_VIRIDIAN_GYM_OPEN", + [41] = "EVENT_GOT_TM42", + [44] = "EVENT_SPAWNED_OLD_MAN_1", + [45] = "EVENT_COMPLETED_CATCH_TRAINING", + [46] = "EVENT_COMPLETED_CATCH_TRAINING_AGAIN", + [47] = "EVENT_INITIAL_CATCH_TRAINING", + [56] = "EVENT_OAK_GOT_PARCEL", + [57] = "EVENT_GOT_OAKS_PARCEL", + [80] = "EVENT_GOT_TM27", + [81] = "EVENT_BEAT_VIRIDIAN_GYM_GIOVANNI", + [82] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_0", + [83] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_1", + [84] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_2", + [85] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_3", + [86] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_4", + [87] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_5", + [88] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_6", + [89] = "EVENT_BEAT_VIRIDIAN_GYM_TRAINER_7", + [104] = "EVENT_BOUGHT_MUSEUM_TICKET", + [105] = "EVENT_GOT_OLD_AMBER", + [114] = "EVENT_BEAT_PEWTER_GYM_TRAINER_0", + [118] = "EVENT_GOT_TM34", + [119] = "EVENT_BEAT_BROCK", + [152] = "EVENT_BEAT_CERULEAN_RIVAL", + [167] = "EVENT_BEAT_CERULEAN_ROCKET_THIEF", + [168] = "EVENT_GOT_BULBASAUR_IN_CERULEAN", + [186] = "EVENT_BEAT_CERULEAN_GYM_TRAINER_0", + [187] = "EVENT_BEAT_CERULEAN_GYM_TRAINER_1", + [190] = "EVENT_GOT_TM11", + [191] = "EVENT_BEAT_MISTY", + [192] = "EVENT_GOT_BICYCLE", + [238] = "EVENT_POKEMON_TOWER_RIVAL_ON_LEFT", + [239] = "EVENT_BEAT_POKEMON_TOWER_RIVAL", + [241] = "EVENT_BEAT_POKEMONTOWER_3_TRAINER_0", + [242] = "EVENT_BEAT_POKEMONTOWER_3_TRAINER_1", + [243] = "EVENT_BEAT_POKEMONTOWER_3_TRAINER_2", + [249] = "EVENT_BEAT_POKEMONTOWER_4_TRAINER_0", + [250] = "EVENT_BEAT_POKEMONTOWER_4_TRAINER_1", + [251] = "EVENT_BEAT_POKEMONTOWER_4_TRAINER_2", + [258] = "EVENT_BEAT_POKEMONTOWER_5_TRAINER_0", + [259] = "EVENT_BEAT_POKEMONTOWER_5_TRAINER_1", + [260] = "EVENT_BEAT_POKEMONTOWER_5_TRAINER_2", + [261] = "EVENT_BEAT_POKEMONTOWER_5_TRAINER_3", + [263] = "EVENT_IN_PURIFIED_ZONE", + [265] = "EVENT_BEAT_POKEMONTOWER_6_TRAINER_0", + [266] = "EVENT_BEAT_POKEMONTOWER_6_TRAINER_1", + [267] = "EVENT_BEAT_POKEMONTOWER_6_TRAINER_2", + [271] = "EVENT_BEAT_GHOST_MAROWAK", + [273] = "EVENT_BEAT_POKEMONTOWER_7_JESSIE_JAMES", + [274] = "EVENT_POKEMONTOWER_7_JESSIE_JAMES_ON_LEFT", + [279] = "EVENT_RESCUED_MR_FUJI_2", + [296] = "EVENT_GOT_POKE_FLUTE", + [327] = "EVENT_GOT_SQUIRTLE_FROM_OFFICER_JENNY", + [337] = "EVENT_GOT_BIKE_VOUCHER", + [338] = "EVENT_LEFT_FANCLUB_AFTER_BIKE_VOUCHER", + [342] = "EVENT_SEEL_FAN_BOAST", + [343] = "EVENT_PIKACHU_FAN_BOAST", + [352] = "EVENT_2ND_LOCK_OPENED", + [353] = "EVENT_1ST_LOCK_OPENED", + [354] = "EVENT_BEAT_VERMILION_GYM_TRAINER_0", + [355] = "EVENT_BEAT_VERMILION_GYM_TRAINER_1", + [356] = "EVENT_BEAT_VERMILION_GYM_TRAINER_2", + [358] = "EVENT_GOT_TM24", + [359] = "EVENT_BEAT_LT_SURGE", + [384] = "EVENT_GOT_TM41", + [396] = "EVENT_GOT_TM13", + [397] = "EVENT_GOT_TM48", + [398] = "EVENT_GOT_TM49", + [399] = "EVENT_GOT_TM18", + [424] = "EVENT_GOT_TM21", + [425] = "EVENT_BEAT_ERIKA", + [426] = "EVENT_BEAT_CELADON_GYM_TRAINER_0", + [427] = "EVENT_BEAT_CELADON_GYM_TRAINER_1", + [428] = "EVENT_BEAT_CELADON_GYM_TRAINER_2", + [429] = "EVENT_BEAT_CELADON_GYM_TRAINER_3", + [430] = "EVENT_BEAT_CELADON_GYM_TRAINER_4", + [431] = "EVENT_BEAT_CELADON_GYM_TRAINER_5", + [432] = "EVENT_BEAT_CELADON_GYM_TRAINER_6", + [440] = "EVENT_1B8", + [441] = "EVENT_FOUND_ROCKET_HIDEOUT", + [442] = "EVENT_GOT_10_COINS", + [443] = "EVENT_GOT_20_COINS", + [444] = "EVENT_GOT_20_COINS_2", + [447] = "EVENT_1BF", + [480] = "EVENT_GOT_COIN_CASE", + [568] = "EVENT_GOT_HM04", + [569] = "EVENT_GAVE_GOLD_TEETH", + [590] = "EVENT_SAFARI_GAME_OVER", + [591] = "EVENT_IN_SAFARI_ZONE", + [600] = "EVENT_GOT_TM06", + [601] = "EVENT_BEAT_KOGA", + [602] = "EVENT_BEAT_FUCHSIA_GYM_TRAINER_0", + [603] = "EVENT_BEAT_FUCHSIA_GYM_TRAINER_1", + [604] = "EVENT_BEAT_FUCHSIA_GYM_TRAINER_2", + [605] = "EVENT_BEAT_FUCHSIA_GYM_TRAINER_3", + [606] = "EVENT_BEAT_FUCHSIA_GYM_TRAINER_4", + [607] = "EVENT_BEAT_FUCHSIA_GYM_TRAINER_5", + [632] = "EVENT_MANSION_SWITCH_ON", + [649] = "EVENT_BEAT_MANSION_1_TRAINER_0", + [664] = "EVENT_GOT_TM38", + [665] = "EVENT_BEAT_BLAINE", + [666] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_0", + [667] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_1", + [668] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_2", + [669] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_3", + [670] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_4", + [671] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_5", + [672] = "EVENT_BEAT_CINNABAR_GYM_TRAINER_6", + [679] = "EVENT_2A7", + [680] = "EVENT_CINNABAR_GYM_GATE0_UNLOCKED", + [681] = "EVENT_CINNABAR_GYM_GATE1_UNLOCKED", + [682] = "EVENT_CINNABAR_GYM_GATE2_UNLOCKED", + [683] = "EVENT_CINNABAR_GYM_GATE3_UNLOCKED", + [684] = "EVENT_CINNABAR_GYM_GATE4_UNLOCKED", + [685] = "EVENT_CINNABAR_GYM_GATE5_UNLOCKED", + [686] = "EVENT_CINNABAR_GYM_GATE6_UNLOCKED", + [727] = "EVENT_GOT_TM35", + [736] = "EVENT_GAVE_FOSSIL_TO_LAB", + [737] = "EVENT_LAB_STILL_REVIVING_FOSSIL", + [738] = "EVENT_LAB_HANDING_OVER_FOSSIL_MON", + [832] = "EVENT_GOT_TM31", + [848] = "EVENT_DEFEATED_FIGHTING_DOJO", + [849] = "EVENT_BEAT_KARATE_MASTER", + [850] = "EVENT_BEAT_FIGHTING_DOJO_TRAINER_0", + [851] = "EVENT_BEAT_FIGHTING_DOJO_TRAINER_1", + [852] = "EVENT_BEAT_FIGHTING_DOJO_TRAINER_2", + [853] = "EVENT_BEAT_FIGHTING_DOJO_TRAINER_3", + [854] = "EVENT_GOT_HITMONLEE", + [855] = "EVENT_GOT_HITMONCHAN", + [864] = "EVENT_GOT_TM46", + [865] = "EVENT_BEAT_SABRINA", + [866] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_0", + [867] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_1", + [868] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_2", + [869] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_3", + [870] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_4", + [871] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_5", + [872] = "EVENT_BEAT_SAFFRON_GYM_TRAINER_6", + [919] = "EVENT_SILPH_CO_RECEPTIONIST_AT_DESK", + [944] = "EVENT_GOT_TM29", + [960] = "EVENT_GOT_POTION_SAMPLE", + [984] = "EVENT_GOT_HM05", + [994] = "EVENT_BEAT_ROUTE_3_TRAINER_0", + [995] = "EVENT_BEAT_ROUTE_3_TRAINER_1", + [996] = "EVENT_BEAT_ROUTE_3_TRAINER_2", + [997] = "EVENT_BEAT_ROUTE_3_TRAINER_3", + [998] = "EVENT_BEAT_ROUTE_3_TRAINER_4", + [999] = "EVENT_BEAT_ROUTE_3_TRAINER_5", + [1000] = "EVENT_BEAT_ROUTE_3_TRAINER_6", + [1001] = "EVENT_BEAT_ROUTE_3_TRAINER_7", + [1010] = "EVENT_BEAT_ROUTE_4_TRAINER_0", + [1023] = "EVENT_BOUGHT_MAGIKARP", + [1041] = "EVENT_BEAT_ROUTE_6_TRAINER_0", + [1042] = "EVENT_BEAT_ROUTE_6_TRAINER_1", + [1043] = "EVENT_BEAT_ROUTE_6_TRAINER_2", + [1044] = "EVENT_BEAT_ROUTE_6_TRAINER_3", + [1045] = "EVENT_BEAT_ROUTE_6_TRAINER_4", + [1046] = "EVENT_BEAT_ROUTE_6_TRAINER_5", + [1073] = "EVENT_BEAT_ROUTE_8_TRAINER_0", + [1074] = "EVENT_BEAT_ROUTE_8_TRAINER_1", + [1075] = "EVENT_BEAT_ROUTE_8_TRAINER_2", + [1076] = "EVENT_BEAT_ROUTE_8_TRAINER_3", + [1077] = "EVENT_BEAT_ROUTE_8_TRAINER_4", + [1078] = "EVENT_BEAT_ROUTE_8_TRAINER_5", + [1079] = "EVENT_BEAT_ROUTE_8_TRAINER_6", + [1080] = "EVENT_BEAT_ROUTE_8_TRAINER_7", + [1081] = "EVENT_BEAT_ROUTE_8_TRAINER_8", + [1089] = "EVENT_BEAT_ROUTE_9_TRAINER_0", + [1090] = "EVENT_BEAT_ROUTE_9_TRAINER_1", + [1091] = "EVENT_BEAT_ROUTE_9_TRAINER_2", + [1092] = "EVENT_BEAT_ROUTE_9_TRAINER_3", + [1093] = "EVENT_BEAT_ROUTE_9_TRAINER_4", + [1094] = "EVENT_BEAT_ROUTE_9_TRAINER_5", + [1095] = "EVENT_BEAT_ROUTE_9_TRAINER_6", + [1096] = "EVENT_BEAT_ROUTE_9_TRAINER_7", + [1097] = "EVENT_BEAT_ROUTE_9_TRAINER_8", + [1105] = "EVENT_BEAT_ROUTE_10_TRAINER_0", + [1106] = "EVENT_BEAT_ROUTE_10_TRAINER_1", + [1107] = "EVENT_BEAT_ROUTE_10_TRAINER_2", + [1108] = "EVENT_BEAT_ROUTE_10_TRAINER_3", + [1109] = "EVENT_BEAT_ROUTE_10_TRAINER_4", + [1110] = "EVENT_BEAT_ROUTE_10_TRAINER_5", + [1113] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_0", + [1114] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_1", + [1115] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_2", + [1116] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_3", + [1117] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_4", + [1118] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_5", + [1119] = "EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_6", + [1121] = "EVENT_BEAT_POWER_PLANT_VOLTORB_0", + [1122] = "EVENT_BEAT_POWER_PLANT_VOLTORB_1", + [1123] = "EVENT_BEAT_POWER_PLANT_VOLTORB_2", + [1124] = "EVENT_BEAT_POWER_PLANT_VOLTORB_3", + [1125] = "EVENT_BEAT_POWER_PLANT_VOLTORB_4", + [1126] = "EVENT_BEAT_POWER_PLANT_VOLTORB_5", + [1127] = "EVENT_BEAT_POWER_PLANT_VOLTORB_6", + [1128] = "EVENT_BEAT_POWER_PLANT_VOLTORB_7", + [1129] = "EVENT_BEAT_ZAPDOS", + [1137] = "EVENT_BEAT_ROUTE_11_TRAINER_0", + [1138] = "EVENT_BEAT_ROUTE_11_TRAINER_1", + [1139] = "EVENT_BEAT_ROUTE_11_TRAINER_2", + [1140] = "EVENT_BEAT_ROUTE_11_TRAINER_3", + [1141] = "EVENT_BEAT_ROUTE_11_TRAINER_4", + [1142] = "EVENT_BEAT_ROUTE_11_TRAINER_5", + [1143] = "EVENT_BEAT_ROUTE_11_TRAINER_6", + [1144] = "EVENT_BEAT_ROUTE_11_TRAINER_7", + [1145] = "EVENT_BEAT_ROUTE_11_TRAINER_8", + [1146] = "EVENT_BEAT_ROUTE_11_TRAINER_9", + [1151] = "EVENT_GOT_ITEMFINDER", + [1152] = "EVENT_GOT_TM39", + [1154] = "EVENT_BEAT_ROUTE_12_TRAINER_0", + [1155] = "EVENT_BEAT_ROUTE_12_TRAINER_1", + [1156] = "EVENT_BEAT_ROUTE_12_TRAINER_2", + [1157] = "EVENT_BEAT_ROUTE_12_TRAINER_3", + [1158] = "EVENT_BEAT_ROUTE_12_TRAINER_4", + [1159] = "EVENT_BEAT_ROUTE_12_TRAINER_5", + [1160] = "EVENT_BEAT_ROUTE_12_TRAINER_6", + [1166] = "EVENT_FIGHT_ROUTE12_SNORLAX", + [1167] = "EVENT_BEAT_ROUTE12_SNORLAX", + [1169] = "EVENT_BEAT_ROUTE_13_TRAINER_0", + [1170] = "EVENT_BEAT_ROUTE_13_TRAINER_1", + [1171] = "EVENT_BEAT_ROUTE_13_TRAINER_2", + [1172] = "EVENT_BEAT_ROUTE_13_TRAINER_3", + [1173] = "EVENT_BEAT_ROUTE_13_TRAINER_4", + [1174] = "EVENT_BEAT_ROUTE_13_TRAINER_5", + [1175] = "EVENT_BEAT_ROUTE_13_TRAINER_6", + [1176] = "EVENT_BEAT_ROUTE_13_TRAINER_7", + [1177] = "EVENT_BEAT_ROUTE_13_TRAINER_8", + [1178] = "EVENT_BEAT_ROUTE_13_TRAINER_9", + [1185] = "EVENT_BEAT_ROUTE_14_TRAINER_0", + [1186] = "EVENT_BEAT_ROUTE_14_TRAINER_1", + [1187] = "EVENT_BEAT_ROUTE_14_TRAINER_2", + [1188] = "EVENT_BEAT_ROUTE_14_TRAINER_3", + [1189] = "EVENT_BEAT_ROUTE_14_TRAINER_4", + [1190] = "EVENT_BEAT_ROUTE_14_TRAINER_5", + [1191] = "EVENT_BEAT_ROUTE_14_TRAINER_6", + [1192] = "EVENT_BEAT_ROUTE_14_TRAINER_7", + [1193] = "EVENT_BEAT_ROUTE_14_TRAINER_8", + [1194] = "EVENT_BEAT_ROUTE_14_TRAINER_9", + [1200] = "EVENT_GOT_EXP_ALL", + [1201] = "EVENT_BEAT_ROUTE_15_TRAINER_0", + [1202] = "EVENT_BEAT_ROUTE_15_TRAINER_1", + [1203] = "EVENT_BEAT_ROUTE_15_TRAINER_2", + [1204] = "EVENT_BEAT_ROUTE_15_TRAINER_3", + [1205] = "EVENT_BEAT_ROUTE_15_TRAINER_4", + [1206] = "EVENT_BEAT_ROUTE_15_TRAINER_5", + [1207] = "EVENT_BEAT_ROUTE_15_TRAINER_6", + [1208] = "EVENT_BEAT_ROUTE_15_TRAINER_7", + [1209] = "EVENT_BEAT_ROUTE_15_TRAINER_8", + [1210] = "EVENT_BEAT_ROUTE_15_TRAINER_9", + [1217] = "EVENT_BEAT_ROUTE_16_TRAINER_0", + [1218] = "EVENT_BEAT_ROUTE_16_TRAINER_1", + [1219] = "EVENT_BEAT_ROUTE_16_TRAINER_2", + [1220] = "EVENT_BEAT_ROUTE_16_TRAINER_3", + [1221] = "EVENT_BEAT_ROUTE_16_TRAINER_4", + [1222] = "EVENT_BEAT_ROUTE_16_TRAINER_5", + [1224] = "EVENT_FIGHT_ROUTE16_SNORLAX", + [1225] = "EVENT_BEAT_ROUTE16_SNORLAX", + [1230] = "EVENT_GOT_HM02", + [1231] = "EVENT_RESCUED_MR_FUJI", + [1233] = "EVENT_BEAT_ROUTE_17_TRAINER_0", + [1234] = "EVENT_BEAT_ROUTE_17_TRAINER_1", + [1235] = "EVENT_BEAT_ROUTE_17_TRAINER_2", + [1236] = "EVENT_BEAT_ROUTE_17_TRAINER_3", + [1237] = "EVENT_BEAT_ROUTE_17_TRAINER_4", + [1238] = "EVENT_BEAT_ROUTE_17_TRAINER_5", + [1239] = "EVENT_BEAT_ROUTE_17_TRAINER_6", + [1240] = "EVENT_BEAT_ROUTE_17_TRAINER_7", + [1241] = "EVENT_BEAT_ROUTE_17_TRAINER_8", + [1242] = "EVENT_BEAT_ROUTE_17_TRAINER_9", + [1249] = "EVENT_BEAT_ROUTE_18_TRAINER_0", + [1250] = "EVENT_BEAT_ROUTE_18_TRAINER_1", + [1251] = "EVENT_BEAT_ROUTE_18_TRAINER_2", + [1265] = "EVENT_BEAT_ROUTE_19_TRAINER_0", + [1266] = "EVENT_BEAT_ROUTE_19_TRAINER_1", + [1267] = "EVENT_BEAT_ROUTE_19_TRAINER_2", + [1268] = "EVENT_BEAT_ROUTE_19_TRAINER_3", + [1269] = "EVENT_BEAT_ROUTE_19_TRAINER_4", + [1270] = "EVENT_BEAT_ROUTE_19_TRAINER_5", + [1271] = "EVENT_BEAT_ROUTE_19_TRAINER_6", + [1272] = "EVENT_BEAT_ROUTE_19_TRAINER_7", + [1273] = "EVENT_BEAT_ROUTE_19_TRAINER_8", + [1274] = "EVENT_BEAT_ROUTE_19_TRAINER_9", + [1280] = "EVENT_IN_SEAFOAM_ISLANDS", + [1281] = "EVENT_BEAT_ROUTE_20_TRAINER_0", + [1282] = "EVENT_BEAT_ROUTE_20_TRAINER_1", + [1283] = "EVENT_BEAT_ROUTE_20_TRAINER_2", + [1284] = "EVENT_BEAT_ROUTE_20_TRAINER_3", + [1285] = "EVENT_BEAT_ROUTE_20_TRAINER_4", + [1286] = "EVENT_BEAT_ROUTE_20_TRAINER_5", + [1287] = "EVENT_BEAT_ROUTE_20_TRAINER_6", + [1288] = "EVENT_BEAT_ROUTE_20_TRAINER_7", + [1289] = "EVENT_BEAT_ROUTE_20_TRAINER_8", + [1290] = "EVENT_BEAT_ROUTE_20_TRAINER_9", + [1294] = "EVENT_SEAFOAM1_BOULDER1_DOWN_HOLE", + [1295] = "EVENT_SEAFOAM1_BOULDER2_DOWN_HOLE", + [1297] = "EVENT_BEAT_ROUTE_21_TRAINER_0", + [1298] = "EVENT_BEAT_ROUTE_21_TRAINER_1", + [1299] = "EVENT_BEAT_ROUTE_21_TRAINER_2", + [1300] = "EVENT_BEAT_ROUTE_21_TRAINER_3", + [1301] = "EVENT_BEAT_ROUTE_21_TRAINER_4", + [1302] = "EVENT_BEAT_ROUTE_21_TRAINER_5", + [1303] = "EVENT_BEAT_ROUTE_21_TRAINER_6", + [1304] = "EVENT_BEAT_ROUTE_21_TRAINER_7", + [1305] = "EVENT_BEAT_ROUTE_21_TRAINER_8", + [1312] = "EVENT_1ST_ROUTE22_RIVAL_BATTLE", + [1313] = "EVENT_2ND_ROUTE22_RIVAL_BATTLE", + [1317] = "EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE", + [1318] = "EVENT_BEAT_ROUTE22_RIVAL_2ND_BATTLE", + [1319] = "EVENT_ROUTE22_RIVAL_WANTS_BATTLE", + [1328] = "EVENT_PASSED_CASCADEBADGE_CHECK", + [1329] = "EVENT_PASSED_THUNDERBADGE_CHECK", + [1330] = "EVENT_PASSED_RAINBOWBADGE_CHECK", + [1331] = "EVENT_PASSED_SOULBADGE_CHECK", + [1332] = "EVENT_PASSED_MARSHBADGE_CHECK", + [1333] = "EVENT_PASSED_VOLCANOBADGE_CHECK", + [1334] = "EVENT_PASSED_EARTHBADGE_CHECK", + [1336] = "EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH1", + [1337] = "EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0", + [1338] = "EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1", + [1339] = "EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2", + [1340] = "EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3", + [1341] = "EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4", + [1342] = "EVENT_BEAT_MOLTRES", + [1343] = "EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH2", + [1344] = "EVENT_GOT_NUGGET", + [1345] = "EVENT_BEAT_ROUTE24_ROCKET", + [1346] = "EVENT_BEAT_ROUTE_24_TRAINER_0", + [1347] = "EVENT_BEAT_ROUTE_24_TRAINER_1", + [1348] = "EVENT_BEAT_ROUTE_24_TRAINER_2", + [1349] = "EVENT_BEAT_ROUTE_24_TRAINER_3", + [1350] = "EVENT_BEAT_ROUTE_24_TRAINER_4", + [1351] = "EVENT_BEAT_ROUTE_24_TRAINER_5", + [1353] = "EVENT_NUGGET_REWARD_AVAILABLE", + [1359] = "EVENT_54F", + [1360] = "EVENT_MET_BILL", + [1361] = "EVENT_BEAT_ROUTE_25_TRAINER_0", + [1362] = "EVENT_BEAT_ROUTE_25_TRAINER_1", + [1363] = "EVENT_BEAT_ROUTE_25_TRAINER_2", + [1364] = "EVENT_BEAT_ROUTE_25_TRAINER_3", + [1365] = "EVENT_BEAT_ROUTE_25_TRAINER_4", + [1366] = "EVENT_BEAT_ROUTE_25_TRAINER_5", + [1367] = "EVENT_BEAT_ROUTE_25_TRAINER_6", + [1368] = "EVENT_BEAT_ROUTE_25_TRAINER_7", + [1369] = "EVENT_BEAT_ROUTE_25_TRAINER_8", + [1371] = "EVENT_USED_CELL_SEPARATOR_ON_BILL", + [1372] = "EVENT_GOT_SS_TICKET", + [1373] = "EVENT_MET_BILL_2", + [1374] = "EVENT_BILL_SAID_USE_CELL_SEPARATOR", + [1375] = "EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING", + [1378] = "EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_0", + [1379] = "EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_1", + [1380] = "EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_2", + [1381] = "EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_3", + [1382] = "EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_4", + [1393] = "EVENT_BEAT_MT_MOON_1_TRAINER_0", + [1394] = "EVENT_BEAT_MT_MOON_1_TRAINER_1", + [1395] = "EVENT_BEAT_MT_MOON_1_TRAINER_2", + [1396] = "EVENT_BEAT_MT_MOON_1_TRAINER_3", + [1397] = "EVENT_BEAT_MT_MOON_1_TRAINER_4", + [1398] = "EVENT_BEAT_MT_MOON_1_TRAINER_5", + [1399] = "EVENT_BEAT_MT_MOON_1_TRAINER_6", + [1400] = "EVENT_GOT_DOME_FOSSIL", + [1401] = "EVENT_BEAT_MT_MOON_EXIT_SUPER_NERD", + [1402] = "EVENT_BEAT_MT_MOON_3_JESSIE_JAMES", + [1403] = "EVENT_BEAT_MT_MOON_3_TRAINER_0", + [1404] = "EVENT_BEAT_MT_MOON_3_TRAINER_1", + [1405] = "EVENT_BEAT_MT_MOON_3_TRAINER_2", + [1406] = "EVENT_57E", + [1407] = "EVENT_GOT_HELIX_FOSSIL", + [1476] = "EVENT_BEAT_SS_ANNE_5_TRAINER_0", + [1477] = "EVENT_BEAT_SS_ANNE_5_TRAINER_1", + [1504] = "EVENT_GOT_HM01", + [1505] = "EVENT_RUBBED_CAPTAINS_BACK", + [1506] = "EVENT_SS_ANNE_LEFT", + [1507] = "EVENT_WALKED_PAST_GUARD_AFTER_SS_ANNE_LEFT", + [1508] = "EVENT_STARTED_WALKING_OUT_OF_DOCK", + [1509] = "EVENT_WALKED_OUT_OF_DOCK", + [1521] = "EVENT_BEAT_SS_ANNE_8_TRAINER_0", + [1522] = "EVENT_BEAT_SS_ANNE_8_TRAINER_1", + [1523] = "EVENT_BEAT_SS_ANNE_8_TRAINER_2", + [1524] = "EVENT_BEAT_SS_ANNE_8_TRAINER_3", + [1537] = "EVENT_BEAT_SS_ANNE_9_TRAINER_0", + [1538] = "EVENT_BEAT_SS_ANNE_9_TRAINER_1", + [1539] = "EVENT_BEAT_SS_ANNE_9_TRAINER_2", + [1540] = "EVENT_BEAT_SS_ANNE_9_TRAINER_3", + [1553] = "EVENT_BEAT_SS_ANNE_10_TRAINER_0", + [1554] = "EVENT_BEAT_SS_ANNE_10_TRAINER_1", + [1555] = "EVENT_BEAT_SS_ANNE_10_TRAINER_2", + [1556] = "EVENT_BEAT_SS_ANNE_10_TRAINER_3", + [1557] = "EVENT_BEAT_SS_ANNE_10_TRAINER_4", + [1558] = "EVENT_BEAT_SS_ANNE_10_TRAINER_5", + [1632] = "EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH1", + [1633] = "EVENT_BEAT_VICTORY_ROAD_3_TRAINER_0", + [1634] = "EVENT_BEAT_VICTORY_ROAD_3_TRAINER_1", + [1635] = "EVENT_BEAT_VICTORY_ROAD_3_TRAINER_2", + [1636] = "EVENT_BEAT_VICTORY_ROAD_3_TRAINER_3", + [1638] = "EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH2", + [1649] = "EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_0", + [1650] = "EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_1", + [1651] = "EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_2", + [1652] = "EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_3", + [1653] = "EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_4", + [1655] = "EVENT_ENTERED_ROCKET_HIDEOUT", + [1663] = "EVENT_67F", + [1665] = "EVENT_BEAT_ROCKET_HIDEOUT_2_TRAINER_0", + [1681] = "EVENT_BEAT_ROCKET_HIDEOUT_3_TRAINER_0", + [1682] = "EVENT_BEAT_ROCKET_HIDEOUT_3_TRAINER_1", + [1696] = "EVENT_6A0", + [1698] = "EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES", + [1699] = "EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT", + [1700] = "EVENT_BEAT_ROCKET_HIDEOUT_4_TRAINER_2", + [1701] = "EVENT_ROCKET_HIDEOUT_4_DOOR_UNLOCKED", + [1702] = "EVENT_ROCKET_DROPPED_LIFT_KEY", + [1703] = "EVENT_BEAT_ROCKET_HIDEOUT_GIOVANNI", + [1778] = "EVENT_BEAT_SILPH_CO_2F_TRAINER_0", + [1779] = "EVENT_BEAT_SILPH_CO_2F_TRAINER_1", + [1780] = "EVENT_BEAT_SILPH_CO_2F_TRAINER_2", + [1781] = "EVENT_BEAT_SILPH_CO_2F_TRAINER_3", + [1789] = "EVENT_SILPH_CO_2_UNLOCKED_DOOR1", + [1790] = "EVENT_SILPH_CO_2_UNLOCKED_DOOR2", + [1791] = "EVENT_GOT_TM36", + [1794] = "EVENT_BEAT_SILPH_CO_3F_TRAINER_0", + [1795] = "EVENT_BEAT_SILPH_CO_3F_TRAINER_1", + [1800] = "EVENT_SILPH_CO_3_UNLOCKED_DOOR1", + [1801] = "EVENT_SILPH_CO_3_UNLOCKED_DOOR2", + [1810] = "EVENT_BEAT_SILPH_CO_4F_TRAINER_0", + [1811] = "EVENT_BEAT_SILPH_CO_4F_TRAINER_1", + [1812] = "EVENT_BEAT_SILPH_CO_4F_TRAINER_2", + [1816] = "EVENT_SILPH_CO_4_UNLOCKED_DOOR1", + [1817] = "EVENT_SILPH_CO_4_UNLOCKED_DOOR2", + [1826] = "EVENT_BEAT_SILPH_CO_5F_TRAINER_0", + [1827] = "EVENT_BEAT_SILPH_CO_5F_TRAINER_1", + [1828] = "EVENT_BEAT_SILPH_CO_5F_TRAINER_2", + [1829] = "EVENT_BEAT_SILPH_CO_5F_TRAINER_3", + [1832] = "EVENT_SILPH_CO_5_UNLOCKED_DOOR1", + [1833] = "EVENT_SILPH_CO_5_UNLOCKED_DOOR2", + [1834] = "EVENT_SILPH_CO_5_UNLOCKED_DOOR3", + [1846] = "EVENT_BEAT_SILPH_CO_6F_TRAINER_0", + [1847] = "EVENT_BEAT_SILPH_CO_6F_TRAINER_1", + [1848] = "EVENT_BEAT_SILPH_CO_6F_TRAINER_2", + [1855] = "EVENT_SILPH_CO_6_UNLOCKED_DOOR", + [1856] = "EVENT_BEAT_SILPH_CO_RIVAL", + [1861] = "EVENT_BEAT_SILPH_CO_7F_TRAINER_0", + [1862] = "EVENT_BEAT_SILPH_CO_7F_TRAINER_1", + [1863] = "EVENT_BEAT_SILPH_CO_7F_TRAINER_2", + [1864] = "EVENT_BEAT_SILPH_CO_7F_TRAINER_3", + [1868] = "EVENT_SILPH_CO_7_UNLOCKED_DOOR1", + [1869] = "EVENT_SILPH_CO_7_UNLOCKED_DOOR2", + [1870] = "EVENT_SILPH_CO_7_UNLOCKED_DOOR3", + [1874] = "EVENT_BEAT_SILPH_CO_8F_TRAINER_0", + [1875] = "EVENT_BEAT_SILPH_CO_8F_TRAINER_1", + [1876] = "EVENT_BEAT_SILPH_CO_8F_TRAINER_2", + [1880] = "EVENT_SILPH_CO_8_UNLOCKED_DOOR", + [1890] = "EVENT_BEAT_SILPH_CO_9F_TRAINER_0", + [1891] = "EVENT_BEAT_SILPH_CO_9F_TRAINER_1", + [1892] = "EVENT_BEAT_SILPH_CO_9F_TRAINER_2", + [1896] = "EVENT_SILPH_CO_9_UNLOCKED_DOOR1", + [1897] = "EVENT_SILPH_CO_9_UNLOCKED_DOOR2", + [1898] = "EVENT_SILPH_CO_9_UNLOCKED_DOOR3", + [1899] = "EVENT_SILPH_CO_9_UNLOCKED_DOOR4", + [1905] = "EVENT_BEAT_SILPH_CO_10F_TRAINER_0", + [1906] = "EVENT_BEAT_SILPH_CO_10F_TRAINER_1", + [1912] = "EVENT_SILPH_CO_10_UNLOCKED_DOOR", + [1920] = "EVENT_780", + [1921] = "EVENT_781", + [1922] = "EVENT_782", + [1924] = "EVENT_BEAT_SILPH_CO_11F_JESSIE_JAMES", + [1925] = "EVENT_BEAT_SILPH_CO_11F_TRAINER_0", + [1928] = "EVENT_SILPH_CO_11_UNLOCKED_DOOR", + [1933] = "EVENT_GOT_MASTER_BALL", + [1935] = "EVENT_BEAT_SILPH_CO_GIOVANNI", + [2049] = "EVENT_BEAT_MANSION_2_TRAINER_0", + [2065] = "EVENT_BEAT_MANSION_3_TRAINER_0", + [2066] = "EVENT_BEAT_MANSION_3_TRAINER_1", + [2081] = "EVENT_BEAT_MANSION_4_TRAINER_0", + [2082] = "EVENT_BEAT_MANSION_4_TRAINER_1", + [2176] = "EVENT_GOT_HM03", + [2241] = "EVENT_BEAT_MEWTWO", + [2273] = "EVENT_BEAT_LORELEIS_ROOM_TRAINER_0", + [2278] = "EVENT_AUTOWALKED_INTO_LORELEIS_ROOM", + [2281] = "EVENT_BEAT_BRUNOS_ROOM_TRAINER_0", + [2286] = "EVENT_AUTOWALKED_INTO_BRUNOS_ROOM", + [2289] = "EVENT_BEAT_AGATHAS_ROOM_TRAINER_0", + [2294] = "EVENT_AUTOWALKED_INTO_AGATHAS_ROOM", + [2297] = "EVENT_BEAT_LANCES_ROOM_TRAINER_0", + [2302] = "EVENT_BEAT_LANCE", + [2303] = "EVENT_LANCES_ROOM_LOCK_DOOR", + [2305] = "EVENT_BEAT_CHAMPION_RIVAL", + [2321] = "EVENT_BEAT_VICTORY_ROAD_1_TRAINER_0", + [2322] = "EVENT_BEAT_VICTORY_ROAD_1_TRAINER_1", + [2327] = "EVENT_VICTORY_ROAD_1_BOULDER_ON_SWITCH", + [2481] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_0", + [2482] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_1", + [2483] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_2", + [2484] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_3", + [2485] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_4", + [2486] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_5", + [2487] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_6", + [2488] = "EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_7", + [2496] = "EVENT_SEAFOAM2_BOULDER1_DOWN_HOLE", + [2497] = "EVENT_SEAFOAM2_BOULDER2_DOWN_HOLE", + [2504] = "EVENT_SEAFOAM3_BOULDER1_DOWN_HOLE", + [2505] = "EVENT_SEAFOAM3_BOULDER2_DOWN_HOLE", + [2512] = "EVENT_SEAFOAM4_BOULDER1_DOWN_HOLE", + [2513] = "EVENT_SEAFOAM4_BOULDER2_DOWN_HOLE", + [2522] = "EVENT_BEAT_ARTICUNO", + }, + byName = { + EVENT_1B8 = 440, + EVENT_1BF = 447, + EVENT_1ST_LOCK_OPENED = 353, + EVENT_1ST_ROUTE22_RIVAL_BATTLE = 1312, + EVENT_2A7 = 679, + EVENT_2ND_LOCK_OPENED = 352, + EVENT_2ND_ROUTE22_RIVAL_BATTLE = 1313, + EVENT_54F = 1359, + EVENT_57E = 1406, + EVENT_67F = 1663, + EVENT_6A0 = 1696, + EVENT_780 = 1920, + EVENT_781 = 1921, + EVENT_782 = 1922, + EVENT_AUTOWALKED_INTO_AGATHAS_ROOM = 2294, + EVENT_AUTOWALKED_INTO_BRUNOS_ROOM = 2286, + EVENT_AUTOWALKED_INTO_LORELEIS_ROOM = 2278, + EVENT_BATTLED_RIVAL_IN_OAKS_LAB = 35, + EVENT_BEAT_AGATHAS_ROOM_TRAINER_0 = 2289, + EVENT_BEAT_ARTICUNO = 2522, + EVENT_BEAT_BLAINE = 665, + EVENT_BEAT_BROCK = 119, + EVENT_BEAT_BRUNOS_ROOM_TRAINER_0 = 2281, + EVENT_BEAT_CELADON_GYM_TRAINER_0 = 426, + EVENT_BEAT_CELADON_GYM_TRAINER_1 = 427, + EVENT_BEAT_CELADON_GYM_TRAINER_2 = 428, + EVENT_BEAT_CELADON_GYM_TRAINER_3 = 429, + EVENT_BEAT_CELADON_GYM_TRAINER_4 = 430, + EVENT_BEAT_CELADON_GYM_TRAINER_5 = 431, + EVENT_BEAT_CELADON_GYM_TRAINER_6 = 432, + EVENT_BEAT_CERULEAN_GYM_TRAINER_0 = 186, + EVENT_BEAT_CERULEAN_GYM_TRAINER_1 = 187, + EVENT_BEAT_CERULEAN_RIVAL = 152, + EVENT_BEAT_CERULEAN_ROCKET_THIEF = 167, + EVENT_BEAT_CHAMPION_RIVAL = 2305, + EVENT_BEAT_CINNABAR_GYM_TRAINER_0 = 666, + EVENT_BEAT_CINNABAR_GYM_TRAINER_1 = 667, + EVENT_BEAT_CINNABAR_GYM_TRAINER_2 = 668, + EVENT_BEAT_CINNABAR_GYM_TRAINER_3 = 669, + EVENT_BEAT_CINNABAR_GYM_TRAINER_4 = 670, + EVENT_BEAT_CINNABAR_GYM_TRAINER_5 = 671, + EVENT_BEAT_CINNABAR_GYM_TRAINER_6 = 672, + EVENT_BEAT_ERIKA = 425, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_0 = 850, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_1 = 851, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_2 = 852, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_3 = 853, + EVENT_BEAT_FUCHSIA_GYM_TRAINER_0 = 602, + EVENT_BEAT_FUCHSIA_GYM_TRAINER_1 = 603, + EVENT_BEAT_FUCHSIA_GYM_TRAINER_2 = 604, + EVENT_BEAT_FUCHSIA_GYM_TRAINER_3 = 605, + EVENT_BEAT_FUCHSIA_GYM_TRAINER_4 = 606, + EVENT_BEAT_FUCHSIA_GYM_TRAINER_5 = 607, + EVENT_BEAT_GHOST_MAROWAK = 271, + EVENT_BEAT_KARATE_MASTER = 849, + EVENT_BEAT_KOGA = 601, + EVENT_BEAT_LANCE = 2302, + EVENT_BEAT_LANCES_ROOM_TRAINER_0 = 2297, + EVENT_BEAT_LORELEIS_ROOM_TRAINER_0 = 2273, + EVENT_BEAT_LT_SURGE = 359, + EVENT_BEAT_MANSION_1_TRAINER_0 = 649, + EVENT_BEAT_MANSION_2_TRAINER_0 = 2049, + EVENT_BEAT_MANSION_3_TRAINER_0 = 2065, + EVENT_BEAT_MANSION_3_TRAINER_1 = 2066, + EVENT_BEAT_MANSION_4_TRAINER_0 = 2081, + EVENT_BEAT_MANSION_4_TRAINER_1 = 2082, + EVENT_BEAT_MEWTWO = 2241, + EVENT_BEAT_MISTY = 191, + EVENT_BEAT_MOLTRES = 1342, + EVENT_BEAT_MT_MOON_1_TRAINER_0 = 1393, + EVENT_BEAT_MT_MOON_1_TRAINER_1 = 1394, + EVENT_BEAT_MT_MOON_1_TRAINER_2 = 1395, + EVENT_BEAT_MT_MOON_1_TRAINER_3 = 1396, + EVENT_BEAT_MT_MOON_1_TRAINER_4 = 1397, + EVENT_BEAT_MT_MOON_1_TRAINER_5 = 1398, + EVENT_BEAT_MT_MOON_1_TRAINER_6 = 1399, + EVENT_BEAT_MT_MOON_3_JESSIE_JAMES = 1402, + EVENT_BEAT_MT_MOON_3_TRAINER_0 = 1403, + EVENT_BEAT_MT_MOON_3_TRAINER_1 = 1404, + EVENT_BEAT_MT_MOON_3_TRAINER_2 = 1405, + EVENT_BEAT_MT_MOON_EXIT_SUPER_NERD = 1401, + EVENT_BEAT_PEWTER_GYM_TRAINER_0 = 114, + EVENT_BEAT_POKEMONTOWER_3_TRAINER_0 = 241, + EVENT_BEAT_POKEMONTOWER_3_TRAINER_1 = 242, + EVENT_BEAT_POKEMONTOWER_3_TRAINER_2 = 243, + EVENT_BEAT_POKEMONTOWER_4_TRAINER_0 = 249, + EVENT_BEAT_POKEMONTOWER_4_TRAINER_1 = 250, + EVENT_BEAT_POKEMONTOWER_4_TRAINER_2 = 251, + EVENT_BEAT_POKEMONTOWER_5_TRAINER_0 = 258, + EVENT_BEAT_POKEMONTOWER_5_TRAINER_1 = 259, + EVENT_BEAT_POKEMONTOWER_5_TRAINER_2 = 260, + EVENT_BEAT_POKEMONTOWER_5_TRAINER_3 = 261, + EVENT_BEAT_POKEMONTOWER_6_TRAINER_0 = 265, + EVENT_BEAT_POKEMONTOWER_6_TRAINER_1 = 266, + EVENT_BEAT_POKEMONTOWER_6_TRAINER_2 = 267, + EVENT_BEAT_POKEMONTOWER_7_JESSIE_JAMES = 273, + EVENT_BEAT_POKEMON_TOWER_RIVAL = 239, + EVENT_BEAT_POWER_PLANT_VOLTORB_0 = 1121, + EVENT_BEAT_POWER_PLANT_VOLTORB_1 = 1122, + EVENT_BEAT_POWER_PLANT_VOLTORB_2 = 1123, + EVENT_BEAT_POWER_PLANT_VOLTORB_3 = 1124, + EVENT_BEAT_POWER_PLANT_VOLTORB_4 = 1125, + EVENT_BEAT_POWER_PLANT_VOLTORB_5 = 1126, + EVENT_BEAT_POWER_PLANT_VOLTORB_6 = 1127, + EVENT_BEAT_POWER_PLANT_VOLTORB_7 = 1128, + EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_0 = 1649, + EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_1 = 1650, + EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_2 = 1651, + EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_3 = 1652, + EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_4 = 1653, + EVENT_BEAT_ROCKET_HIDEOUT_2_TRAINER_0 = 1665, + EVENT_BEAT_ROCKET_HIDEOUT_3_TRAINER_0 = 1681, + EVENT_BEAT_ROCKET_HIDEOUT_3_TRAINER_1 = 1682, + EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES = 1698, + EVENT_BEAT_ROCKET_HIDEOUT_4_TRAINER_2 = 1700, + EVENT_BEAT_ROCKET_HIDEOUT_GIOVANNI = 1703, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_0 = 1113, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_1 = 1114, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_2 = 1115, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_3 = 1116, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_4 = 1117, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_5 = 1118, + EVENT_BEAT_ROCK_TUNNEL_1_TRAINER_6 = 1119, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_0 = 2481, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_1 = 2482, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_2 = 2483, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_3 = 2484, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_4 = 2485, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_5 = 2486, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_6 = 2487, + EVENT_BEAT_ROCK_TUNNEL_2_TRAINER_7 = 2488, + EVENT_BEAT_ROUTE12_SNORLAX = 1167, + EVENT_BEAT_ROUTE16_SNORLAX = 1225, + EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE = 1317, + EVENT_BEAT_ROUTE22_RIVAL_2ND_BATTLE = 1318, + EVENT_BEAT_ROUTE24_ROCKET = 1345, + EVENT_BEAT_ROUTE_10_TRAINER_0 = 1105, + EVENT_BEAT_ROUTE_10_TRAINER_1 = 1106, + EVENT_BEAT_ROUTE_10_TRAINER_2 = 1107, + EVENT_BEAT_ROUTE_10_TRAINER_3 = 1108, + EVENT_BEAT_ROUTE_10_TRAINER_4 = 1109, + EVENT_BEAT_ROUTE_10_TRAINER_5 = 1110, + EVENT_BEAT_ROUTE_11_TRAINER_0 = 1137, + EVENT_BEAT_ROUTE_11_TRAINER_1 = 1138, + EVENT_BEAT_ROUTE_11_TRAINER_2 = 1139, + EVENT_BEAT_ROUTE_11_TRAINER_3 = 1140, + EVENT_BEAT_ROUTE_11_TRAINER_4 = 1141, + EVENT_BEAT_ROUTE_11_TRAINER_5 = 1142, + EVENT_BEAT_ROUTE_11_TRAINER_6 = 1143, + EVENT_BEAT_ROUTE_11_TRAINER_7 = 1144, + EVENT_BEAT_ROUTE_11_TRAINER_8 = 1145, + EVENT_BEAT_ROUTE_11_TRAINER_9 = 1146, + EVENT_BEAT_ROUTE_12_TRAINER_0 = 1154, + EVENT_BEAT_ROUTE_12_TRAINER_1 = 1155, + EVENT_BEAT_ROUTE_12_TRAINER_2 = 1156, + EVENT_BEAT_ROUTE_12_TRAINER_3 = 1157, + EVENT_BEAT_ROUTE_12_TRAINER_4 = 1158, + EVENT_BEAT_ROUTE_12_TRAINER_5 = 1159, + EVENT_BEAT_ROUTE_12_TRAINER_6 = 1160, + EVENT_BEAT_ROUTE_13_TRAINER_0 = 1169, + EVENT_BEAT_ROUTE_13_TRAINER_1 = 1170, + EVENT_BEAT_ROUTE_13_TRAINER_2 = 1171, + EVENT_BEAT_ROUTE_13_TRAINER_3 = 1172, + EVENT_BEAT_ROUTE_13_TRAINER_4 = 1173, + EVENT_BEAT_ROUTE_13_TRAINER_5 = 1174, + EVENT_BEAT_ROUTE_13_TRAINER_6 = 1175, + EVENT_BEAT_ROUTE_13_TRAINER_7 = 1176, + EVENT_BEAT_ROUTE_13_TRAINER_8 = 1177, + EVENT_BEAT_ROUTE_13_TRAINER_9 = 1178, + EVENT_BEAT_ROUTE_14_TRAINER_0 = 1185, + EVENT_BEAT_ROUTE_14_TRAINER_1 = 1186, + EVENT_BEAT_ROUTE_14_TRAINER_2 = 1187, + EVENT_BEAT_ROUTE_14_TRAINER_3 = 1188, + EVENT_BEAT_ROUTE_14_TRAINER_4 = 1189, + EVENT_BEAT_ROUTE_14_TRAINER_5 = 1190, + EVENT_BEAT_ROUTE_14_TRAINER_6 = 1191, + EVENT_BEAT_ROUTE_14_TRAINER_7 = 1192, + EVENT_BEAT_ROUTE_14_TRAINER_8 = 1193, + EVENT_BEAT_ROUTE_14_TRAINER_9 = 1194, + EVENT_BEAT_ROUTE_15_TRAINER_0 = 1201, + EVENT_BEAT_ROUTE_15_TRAINER_1 = 1202, + EVENT_BEAT_ROUTE_15_TRAINER_2 = 1203, + EVENT_BEAT_ROUTE_15_TRAINER_3 = 1204, + EVENT_BEAT_ROUTE_15_TRAINER_4 = 1205, + EVENT_BEAT_ROUTE_15_TRAINER_5 = 1206, + EVENT_BEAT_ROUTE_15_TRAINER_6 = 1207, + EVENT_BEAT_ROUTE_15_TRAINER_7 = 1208, + EVENT_BEAT_ROUTE_15_TRAINER_8 = 1209, + EVENT_BEAT_ROUTE_15_TRAINER_9 = 1210, + EVENT_BEAT_ROUTE_16_TRAINER_0 = 1217, + EVENT_BEAT_ROUTE_16_TRAINER_1 = 1218, + EVENT_BEAT_ROUTE_16_TRAINER_2 = 1219, + EVENT_BEAT_ROUTE_16_TRAINER_3 = 1220, + EVENT_BEAT_ROUTE_16_TRAINER_4 = 1221, + EVENT_BEAT_ROUTE_16_TRAINER_5 = 1222, + EVENT_BEAT_ROUTE_17_TRAINER_0 = 1233, + EVENT_BEAT_ROUTE_17_TRAINER_1 = 1234, + EVENT_BEAT_ROUTE_17_TRAINER_2 = 1235, + EVENT_BEAT_ROUTE_17_TRAINER_3 = 1236, + EVENT_BEAT_ROUTE_17_TRAINER_4 = 1237, + EVENT_BEAT_ROUTE_17_TRAINER_5 = 1238, + EVENT_BEAT_ROUTE_17_TRAINER_6 = 1239, + EVENT_BEAT_ROUTE_17_TRAINER_7 = 1240, + EVENT_BEAT_ROUTE_17_TRAINER_8 = 1241, + EVENT_BEAT_ROUTE_17_TRAINER_9 = 1242, + EVENT_BEAT_ROUTE_18_TRAINER_0 = 1249, + EVENT_BEAT_ROUTE_18_TRAINER_1 = 1250, + EVENT_BEAT_ROUTE_18_TRAINER_2 = 1251, + EVENT_BEAT_ROUTE_19_TRAINER_0 = 1265, + EVENT_BEAT_ROUTE_19_TRAINER_1 = 1266, + EVENT_BEAT_ROUTE_19_TRAINER_2 = 1267, + EVENT_BEAT_ROUTE_19_TRAINER_3 = 1268, + EVENT_BEAT_ROUTE_19_TRAINER_4 = 1269, + EVENT_BEAT_ROUTE_19_TRAINER_5 = 1270, + EVENT_BEAT_ROUTE_19_TRAINER_6 = 1271, + EVENT_BEAT_ROUTE_19_TRAINER_7 = 1272, + EVENT_BEAT_ROUTE_19_TRAINER_8 = 1273, + EVENT_BEAT_ROUTE_19_TRAINER_9 = 1274, + EVENT_BEAT_ROUTE_20_TRAINER_0 = 1281, + EVENT_BEAT_ROUTE_20_TRAINER_1 = 1282, + EVENT_BEAT_ROUTE_20_TRAINER_2 = 1283, + EVENT_BEAT_ROUTE_20_TRAINER_3 = 1284, + EVENT_BEAT_ROUTE_20_TRAINER_4 = 1285, + EVENT_BEAT_ROUTE_20_TRAINER_5 = 1286, + EVENT_BEAT_ROUTE_20_TRAINER_6 = 1287, + EVENT_BEAT_ROUTE_20_TRAINER_7 = 1288, + EVENT_BEAT_ROUTE_20_TRAINER_8 = 1289, + EVENT_BEAT_ROUTE_20_TRAINER_9 = 1290, + EVENT_BEAT_ROUTE_21_TRAINER_0 = 1297, + EVENT_BEAT_ROUTE_21_TRAINER_1 = 1298, + EVENT_BEAT_ROUTE_21_TRAINER_2 = 1299, + EVENT_BEAT_ROUTE_21_TRAINER_3 = 1300, + EVENT_BEAT_ROUTE_21_TRAINER_4 = 1301, + EVENT_BEAT_ROUTE_21_TRAINER_5 = 1302, + EVENT_BEAT_ROUTE_21_TRAINER_6 = 1303, + EVENT_BEAT_ROUTE_21_TRAINER_7 = 1304, + EVENT_BEAT_ROUTE_21_TRAINER_8 = 1305, + EVENT_BEAT_ROUTE_24_TRAINER_0 = 1346, + EVENT_BEAT_ROUTE_24_TRAINER_1 = 1347, + EVENT_BEAT_ROUTE_24_TRAINER_2 = 1348, + EVENT_BEAT_ROUTE_24_TRAINER_3 = 1349, + EVENT_BEAT_ROUTE_24_TRAINER_4 = 1350, + EVENT_BEAT_ROUTE_24_TRAINER_5 = 1351, + EVENT_BEAT_ROUTE_25_TRAINER_0 = 1361, + EVENT_BEAT_ROUTE_25_TRAINER_1 = 1362, + EVENT_BEAT_ROUTE_25_TRAINER_2 = 1363, + EVENT_BEAT_ROUTE_25_TRAINER_3 = 1364, + EVENT_BEAT_ROUTE_25_TRAINER_4 = 1365, + EVENT_BEAT_ROUTE_25_TRAINER_5 = 1366, + EVENT_BEAT_ROUTE_25_TRAINER_6 = 1367, + EVENT_BEAT_ROUTE_25_TRAINER_7 = 1368, + EVENT_BEAT_ROUTE_25_TRAINER_8 = 1369, + EVENT_BEAT_ROUTE_3_TRAINER_0 = 994, + EVENT_BEAT_ROUTE_3_TRAINER_1 = 995, + EVENT_BEAT_ROUTE_3_TRAINER_2 = 996, + EVENT_BEAT_ROUTE_3_TRAINER_3 = 997, + EVENT_BEAT_ROUTE_3_TRAINER_4 = 998, + EVENT_BEAT_ROUTE_3_TRAINER_5 = 999, + EVENT_BEAT_ROUTE_3_TRAINER_6 = 1000, + EVENT_BEAT_ROUTE_3_TRAINER_7 = 1001, + EVENT_BEAT_ROUTE_4_TRAINER_0 = 1010, + EVENT_BEAT_ROUTE_6_TRAINER_0 = 1041, + EVENT_BEAT_ROUTE_6_TRAINER_1 = 1042, + EVENT_BEAT_ROUTE_6_TRAINER_2 = 1043, + EVENT_BEAT_ROUTE_6_TRAINER_3 = 1044, + EVENT_BEAT_ROUTE_6_TRAINER_4 = 1045, + EVENT_BEAT_ROUTE_6_TRAINER_5 = 1046, + EVENT_BEAT_ROUTE_8_TRAINER_0 = 1073, + EVENT_BEAT_ROUTE_8_TRAINER_1 = 1074, + EVENT_BEAT_ROUTE_8_TRAINER_2 = 1075, + EVENT_BEAT_ROUTE_8_TRAINER_3 = 1076, + EVENT_BEAT_ROUTE_8_TRAINER_4 = 1077, + EVENT_BEAT_ROUTE_8_TRAINER_5 = 1078, + EVENT_BEAT_ROUTE_8_TRAINER_6 = 1079, + EVENT_BEAT_ROUTE_8_TRAINER_7 = 1080, + EVENT_BEAT_ROUTE_8_TRAINER_8 = 1081, + EVENT_BEAT_ROUTE_9_TRAINER_0 = 1089, + EVENT_BEAT_ROUTE_9_TRAINER_1 = 1090, + EVENT_BEAT_ROUTE_9_TRAINER_2 = 1091, + EVENT_BEAT_ROUTE_9_TRAINER_3 = 1092, + EVENT_BEAT_ROUTE_9_TRAINER_4 = 1093, + EVENT_BEAT_ROUTE_9_TRAINER_5 = 1094, + EVENT_BEAT_ROUTE_9_TRAINER_6 = 1095, + EVENT_BEAT_ROUTE_9_TRAINER_7 = 1096, + EVENT_BEAT_ROUTE_9_TRAINER_8 = 1097, + EVENT_BEAT_SABRINA = 865, + EVENT_BEAT_SAFFRON_GYM_TRAINER_0 = 866, + EVENT_BEAT_SAFFRON_GYM_TRAINER_1 = 867, + EVENT_BEAT_SAFFRON_GYM_TRAINER_2 = 868, + EVENT_BEAT_SAFFRON_GYM_TRAINER_3 = 869, + EVENT_BEAT_SAFFRON_GYM_TRAINER_4 = 870, + EVENT_BEAT_SAFFRON_GYM_TRAINER_5 = 871, + EVENT_BEAT_SAFFRON_GYM_TRAINER_6 = 872, + EVENT_BEAT_SILPH_CO_10F_TRAINER_0 = 1905, + EVENT_BEAT_SILPH_CO_10F_TRAINER_1 = 1906, + EVENT_BEAT_SILPH_CO_11F_JESSIE_JAMES = 1924, + EVENT_BEAT_SILPH_CO_11F_TRAINER_0 = 1925, + EVENT_BEAT_SILPH_CO_2F_TRAINER_0 = 1778, + EVENT_BEAT_SILPH_CO_2F_TRAINER_1 = 1779, + EVENT_BEAT_SILPH_CO_2F_TRAINER_2 = 1780, + EVENT_BEAT_SILPH_CO_2F_TRAINER_3 = 1781, + EVENT_BEAT_SILPH_CO_3F_TRAINER_0 = 1794, + EVENT_BEAT_SILPH_CO_3F_TRAINER_1 = 1795, + EVENT_BEAT_SILPH_CO_4F_TRAINER_0 = 1810, + EVENT_BEAT_SILPH_CO_4F_TRAINER_1 = 1811, + EVENT_BEAT_SILPH_CO_4F_TRAINER_2 = 1812, + EVENT_BEAT_SILPH_CO_5F_TRAINER_0 = 1826, + EVENT_BEAT_SILPH_CO_5F_TRAINER_1 = 1827, + EVENT_BEAT_SILPH_CO_5F_TRAINER_2 = 1828, + EVENT_BEAT_SILPH_CO_5F_TRAINER_3 = 1829, + EVENT_BEAT_SILPH_CO_6F_TRAINER_0 = 1846, + EVENT_BEAT_SILPH_CO_6F_TRAINER_1 = 1847, + EVENT_BEAT_SILPH_CO_6F_TRAINER_2 = 1848, + EVENT_BEAT_SILPH_CO_7F_TRAINER_0 = 1861, + EVENT_BEAT_SILPH_CO_7F_TRAINER_1 = 1862, + EVENT_BEAT_SILPH_CO_7F_TRAINER_2 = 1863, + EVENT_BEAT_SILPH_CO_7F_TRAINER_3 = 1864, + EVENT_BEAT_SILPH_CO_8F_TRAINER_0 = 1874, + EVENT_BEAT_SILPH_CO_8F_TRAINER_1 = 1875, + EVENT_BEAT_SILPH_CO_8F_TRAINER_2 = 1876, + EVENT_BEAT_SILPH_CO_9F_TRAINER_0 = 1890, + EVENT_BEAT_SILPH_CO_9F_TRAINER_1 = 1891, + EVENT_BEAT_SILPH_CO_9F_TRAINER_2 = 1892, + EVENT_BEAT_SILPH_CO_GIOVANNI = 1935, + EVENT_BEAT_SILPH_CO_RIVAL = 1856, + EVENT_BEAT_SS_ANNE_10_TRAINER_0 = 1553, + EVENT_BEAT_SS_ANNE_10_TRAINER_1 = 1554, + EVENT_BEAT_SS_ANNE_10_TRAINER_2 = 1555, + EVENT_BEAT_SS_ANNE_10_TRAINER_3 = 1556, + EVENT_BEAT_SS_ANNE_10_TRAINER_4 = 1557, + EVENT_BEAT_SS_ANNE_10_TRAINER_5 = 1558, + EVENT_BEAT_SS_ANNE_5_TRAINER_0 = 1476, + EVENT_BEAT_SS_ANNE_5_TRAINER_1 = 1477, + EVENT_BEAT_SS_ANNE_8_TRAINER_0 = 1521, + EVENT_BEAT_SS_ANNE_8_TRAINER_1 = 1522, + EVENT_BEAT_SS_ANNE_8_TRAINER_2 = 1523, + EVENT_BEAT_SS_ANNE_8_TRAINER_3 = 1524, + EVENT_BEAT_SS_ANNE_9_TRAINER_0 = 1537, + EVENT_BEAT_SS_ANNE_9_TRAINER_1 = 1538, + EVENT_BEAT_SS_ANNE_9_TRAINER_2 = 1539, + EVENT_BEAT_SS_ANNE_9_TRAINER_3 = 1540, + EVENT_BEAT_VERMILION_GYM_TRAINER_0 = 354, + EVENT_BEAT_VERMILION_GYM_TRAINER_1 = 355, + EVENT_BEAT_VERMILION_GYM_TRAINER_2 = 356, + EVENT_BEAT_VICTORY_ROAD_1_TRAINER_0 = 2321, + EVENT_BEAT_VICTORY_ROAD_1_TRAINER_1 = 2322, + EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0 = 1337, + EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1 = 1338, + EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2 = 1339, + EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3 = 1340, + EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4 = 1341, + EVENT_BEAT_VICTORY_ROAD_3_TRAINER_0 = 1633, + EVENT_BEAT_VICTORY_ROAD_3_TRAINER_1 = 1634, + EVENT_BEAT_VICTORY_ROAD_3_TRAINER_2 = 1635, + EVENT_BEAT_VICTORY_ROAD_3_TRAINER_3 = 1636, + EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_0 = 1378, + EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_1 = 1379, + EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_2 = 1380, + EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_3 = 1381, + EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_4 = 1382, + EVENT_BEAT_VIRIDIAN_GYM_GIOVANNI = 81, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_0 = 82, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_1 = 83, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_2 = 84, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_3 = 85, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_4 = 86, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_5 = 87, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_6 = 88, + EVENT_BEAT_VIRIDIAN_GYM_TRAINER_7 = 89, + EVENT_BEAT_ZAPDOS = 1129, + EVENT_BILL_SAID_USE_CELL_SEPARATOR = 1374, + EVENT_BOUGHT_MAGIKARP = 1023, + EVENT_BOUGHT_MUSEUM_TICKET = 104, + EVENT_CINNABAR_GYM_GATE0_UNLOCKED = 680, + EVENT_CINNABAR_GYM_GATE1_UNLOCKED = 681, + EVENT_CINNABAR_GYM_GATE2_UNLOCKED = 682, + EVENT_CINNABAR_GYM_GATE3_UNLOCKED = 683, + EVENT_CINNABAR_GYM_GATE4_UNLOCKED = 684, + EVENT_CINNABAR_GYM_GATE5_UNLOCKED = 685, + EVENT_CINNABAR_GYM_GATE6_UNLOCKED = 686, + EVENT_COMPLETED_CATCH_TRAINING = 45, + EVENT_COMPLETED_CATCH_TRAINING_AGAIN = 46, + EVENT_DAISY_WALKING = 26, + EVENT_DEFEATED_FIGHTING_DOJO = 848, + EVENT_ENTERED_BLUES_HOUSE = 25, + EVENT_ENTERED_ROCKET_HIDEOUT = 1655, + EVENT_FIGHT_ROUTE12_SNORLAX = 1166, + EVENT_FIGHT_ROUTE16_SNORLAX = 1224, + EVENT_FOLLOWED_OAK_INTO_LAB = 0, + EVENT_FOLLOWED_OAK_INTO_LAB_2 = 32, + EVENT_FOUND_ROCKET_HIDEOUT = 441, + EVENT_GAVE_FOSSIL_TO_LAB = 736, + EVENT_GAVE_GOLD_TEETH = 569, + EVENT_GOT_10_COINS = 442, + EVENT_GOT_20_COINS = 443, + EVENT_GOT_20_COINS_2 = 444, + EVENT_GOT_BICYCLE = 192, + EVENT_GOT_BIKE_VOUCHER = 337, + EVENT_GOT_BULBASAUR_IN_CERULEAN = 168, + EVENT_GOT_COIN_CASE = 480, + EVENT_GOT_DOME_FOSSIL = 1400, + EVENT_GOT_EXP_ALL = 1200, + EVENT_GOT_HELIX_FOSSIL = 1407, + EVENT_GOT_HITMONCHAN = 855, + EVENT_GOT_HITMONLEE = 854, + EVENT_GOT_HM01 = 1504, + EVENT_GOT_HM02 = 1230, + EVENT_GOT_HM03 = 2176, + EVENT_GOT_HM04 = 568, + EVENT_GOT_HM05 = 984, + EVENT_GOT_ITEMFINDER = 1151, + EVENT_GOT_MASTER_BALL = 1933, + EVENT_GOT_NUGGET = 1344, + EVENT_GOT_OAKS_PARCEL = 57, + EVENT_GOT_OLD_AMBER = 105, + EVENT_GOT_POKEBALLS_FROM_OAK = 36, + EVENT_GOT_POKEDEX = 37, + EVENT_GOT_POKE_FLUTE = 296, + EVENT_GOT_POTION_SAMPLE = 960, + EVENT_GOT_SQUIRTLE_FROM_OFFICER_JENNY = 327, + EVENT_GOT_SS_TICKET = 1372, + EVENT_GOT_STARTER = 34, + EVENT_GOT_TM06 = 600, + EVENT_GOT_TM11 = 190, + EVENT_GOT_TM13 = 396, + EVENT_GOT_TM18 = 399, + EVENT_GOT_TM21 = 424, + EVENT_GOT_TM24 = 358, + EVENT_GOT_TM27 = 80, + EVENT_GOT_TM29 = 944, + EVENT_GOT_TM31 = 832, + EVENT_GOT_TM34 = 118, + EVENT_GOT_TM35 = 727, + EVENT_GOT_TM36 = 1791, + EVENT_GOT_TM38 = 664, + EVENT_GOT_TM39 = 1152, + EVENT_GOT_TM41 = 384, + EVENT_GOT_TM42 = 41, + EVENT_GOT_TM46 = 864, + EVENT_GOT_TM48 = 397, + EVENT_GOT_TM49 = 398, + EVENT_GOT_TOWN_MAP = 24, + EVENT_HALL_OF_FAME_DEX_RATING = 3, + EVENT_INITIAL_CATCH_TRAINING = 47, + EVENT_IN_PURIFIED_ZONE = 263, + EVENT_IN_SAFARI_ZONE = 591, + EVENT_IN_SEAFOAM_ISLANDS = 1280, + EVENT_LAB_HANDING_OVER_FOSSIL_MON = 738, + EVENT_LAB_STILL_REVIVING_FOSSIL = 737, + EVENT_LANCES_ROOM_LOCK_DOOR = 2303, + EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING = 1375, + EVENT_LEFT_FANCLUB_AFTER_BIKE_VOUCHER = 338, + EVENT_MANSION_SWITCH_ON = 632, + EVENT_MET_BILL = 1360, + EVENT_MET_BILL_2 = 1373, + EVENT_NUGGET_REWARD_AVAILABLE = 1353, + EVENT_OAK_APPEARED_IN_PALLET = 39, + EVENT_OAK_ASKED_TO_CHOOSE_MON = 33, + EVENT_OAK_GOT_PARCEL = 56, + EVENT_PALLET_AFTER_GETTING_POKEBALLS = 6, + EVENT_PALLET_AFTER_GETTING_POKEBALLS_2 = 38, + EVENT_PASSED_CASCADEBADGE_CHECK = 1328, + EVENT_PASSED_EARTHBADGE_CHECK = 1334, + EVENT_PASSED_MARSHBADGE_CHECK = 1332, + EVENT_PASSED_RAINBOWBADGE_CHECK = 1330, + EVENT_PASSED_SOULBADGE_CHECK = 1331, + EVENT_PASSED_THUNDERBADGE_CHECK = 1329, + EVENT_PASSED_VOLCANOBADGE_CHECK = 1333, + EVENT_PIKACHU_FAN_BOAST = 343, + EVENT_PLAYER_AT_RIGHT_EXIT_TO_PALLET_TOWN = 5, + EVENT_POKEMONTOWER_7_JESSIE_JAMES_ON_LEFT = 274, + EVENT_POKEMON_TOWER_RIVAL_ON_LEFT = 238, + EVENT_RESCUED_MR_FUJI = 1231, + EVENT_RESCUED_MR_FUJI_2 = 279, + EVENT_ROCKET_DROPPED_LIFT_KEY = 1702, + EVENT_ROCKET_HIDEOUT_4_DOOR_UNLOCKED = 1701, + EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT = 1699, + EVENT_ROUTE22_RIVAL_WANTS_BATTLE = 1319, + EVENT_RUBBED_CAPTAINS_BACK = 1505, + EVENT_SAFARI_GAME_OVER = 590, + EVENT_SEAFOAM1_BOULDER1_DOWN_HOLE = 1294, + EVENT_SEAFOAM1_BOULDER2_DOWN_HOLE = 1295, + EVENT_SEAFOAM2_BOULDER1_DOWN_HOLE = 2496, + EVENT_SEAFOAM2_BOULDER2_DOWN_HOLE = 2497, + EVENT_SEAFOAM3_BOULDER1_DOWN_HOLE = 2504, + EVENT_SEAFOAM3_BOULDER2_DOWN_HOLE = 2505, + EVENT_SEAFOAM4_BOULDER1_DOWN_HOLE = 2512, + EVENT_SEAFOAM4_BOULDER2_DOWN_HOLE = 2513, + EVENT_SEEL_FAN_BOAST = 342, + EVENT_SILPH_CO_10_UNLOCKED_DOOR = 1912, + EVENT_SILPH_CO_11_UNLOCKED_DOOR = 1928, + EVENT_SILPH_CO_2_UNLOCKED_DOOR1 = 1789, + EVENT_SILPH_CO_2_UNLOCKED_DOOR2 = 1790, + EVENT_SILPH_CO_3_UNLOCKED_DOOR1 = 1800, + EVENT_SILPH_CO_3_UNLOCKED_DOOR2 = 1801, + EVENT_SILPH_CO_4_UNLOCKED_DOOR1 = 1816, + EVENT_SILPH_CO_4_UNLOCKED_DOOR2 = 1817, + EVENT_SILPH_CO_5_UNLOCKED_DOOR1 = 1832, + EVENT_SILPH_CO_5_UNLOCKED_DOOR2 = 1833, + EVENT_SILPH_CO_5_UNLOCKED_DOOR3 = 1834, + EVENT_SILPH_CO_6_UNLOCKED_DOOR = 1855, + EVENT_SILPH_CO_7_UNLOCKED_DOOR1 = 1868, + EVENT_SILPH_CO_7_UNLOCKED_DOOR2 = 1869, + EVENT_SILPH_CO_7_UNLOCKED_DOOR3 = 1870, + EVENT_SILPH_CO_8_UNLOCKED_DOOR = 1880, + EVENT_SILPH_CO_9_UNLOCKED_DOOR1 = 1896, + EVENT_SILPH_CO_9_UNLOCKED_DOOR2 = 1897, + EVENT_SILPH_CO_9_UNLOCKED_DOOR3 = 1898, + EVENT_SILPH_CO_9_UNLOCKED_DOOR4 = 1899, + EVENT_SILPH_CO_RECEPTIONIST_AT_DESK = 919, + EVENT_SPAWNED_OLD_MAN_1 = 44, + EVENT_SS_ANNE_LEFT = 1506, + EVENT_STARTED_WALKING_OUT_OF_DOCK = 1508, + EVENT_USED_CELL_SEPARATOR_ON_BILL = 1371, + EVENT_VICTORY_ROAD_1_BOULDER_ON_SWITCH = 2327, + EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH1 = 1336, + EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH2 = 1343, + EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH1 = 1632, + EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH2 = 1638, + EVENT_VIRIDIAN_GYM_OPEN = 40, + EVENT_WALKED_OUT_OF_DOCK = 1509, + EVENT_WALKED_PAST_GUARD_AFTER_SS_ANNE_LEFT = 1507, + }, + count = 2560, + source = "pokeyellow constants/event_constants.asm", +} diff --git a/src/save_convert/data/toggle_objects.lua b/src/save_convert/data/toggle_objects.lua new file mode 100644 index 00000000..639b44ba --- /dev/null +++ b/src/save_convert/data/toggle_objects.lua @@ -0,0 +1,243 @@ +-- wToggleableObjectFlags bit index -> { map id, object_event name, default +-- visible } for the Gen1 save codec (src/save_convert/GenSave.lua). +-- Derived entry by entry from ../pokered/data/maps/toggleable_objects.asm +-- (ToggleableObjectStates: three bytes per entry, blocks laid out in map-id +-- order, so an entry's position in the table IS its wToggleableObjectFlags +-- bit -- constants/toggle_constants.asm numbers the same list), with the +-- default taken from each row's ON/OFF state. Bit set = hidden +-- (engine/overworld/toggleable_objects.asm IsObjectHidden). Object names +-- match data/generated/maps.lua object_event names one for one; the two +-- placeholder bits with no object_event in this port stay as comments so +-- the numbering remains auditable (#763, #857). +return { + byBit = { + [0] = { "PALLET_TOWN", "PALLETTOWN_OAK", false }, + [1] = { "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN_SLEEPY", true }, + [2] = { "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN", false }, + [3] = { "PEWTER_CITY", "PEWTERCITY_SUPER_NERD1", true }, + [4] = { "PEWTER_CITY", "PEWTERCITY_YOUNGSTER", true }, + [5] = { "CERULEAN_CITY", "CERULEANCITY_RIVAL", false }, + [6] = { "CERULEAN_CITY", "CERULEANCITY_ROCKET", true }, + [7] = { "CERULEAN_CITY", "CERULEANCITY_GUARD1", false }, + [8] = { "CERULEAN_CITY", "CERULEANCITY_SUPER_NERD3", true }, + [9] = { "CERULEAN_CITY", "CERULEANCITY_GUARD2", true }, + [10] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET1", true }, + [11] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET2", true }, + [12] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET3", true }, + [13] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET4", true }, + [14] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET5", true }, + [15] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET6", true }, + [16] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET7", true }, + [17] = { "SAFFRON_CITY", "SAFFRONCITY_SCIENTIST", false }, + [18] = { "SAFFRON_CITY", "SAFFRONCITY_SILPH_WORKER_M", false }, + [19] = { "SAFFRON_CITY", "SAFFRONCITY_SILPH_WORKER_F", false }, + [20] = { "SAFFRON_CITY", "SAFFRONCITY_GENTLEMAN", false }, + [21] = { "SAFFRON_CITY", "SAFFRONCITY_PIDGEOT", false }, + [22] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKER", false }, + [23] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET8", true }, + [24] = { "SAFFRON_CITY", "SAFFRONCITY_ROCKET9", false }, + [25] = { "ROUTE_2", "ROUTE2_MOON_STONE", true }, + [26] = { "ROUTE_2", "ROUTE2_HP_UP", true }, + [27] = { "ROUTE_4", "ROUTE4_TM_WHIRLWIND", true }, + [28] = { "ROUTE_9", "ROUTE9_TM_TELEPORT", true }, + [29] = { "ROUTE_12", "ROUTE12_SNORLAX", true }, + [30] = { "ROUTE_12", "ROUTE12_TM_PAY_DAY", true }, + [31] = { "ROUTE_12", "ROUTE12_IRON", true }, + [32] = { "ROUTE_15", "ROUTE15_TM_RAGE", true }, + [33] = { "ROUTE_16", "ROUTE16_SNORLAX", true }, + [34] = { "ROUTE_22", "ROUTE22_RIVAL1", false }, + [35] = { "ROUTE_22", "ROUTE22_RIVAL2", false }, + [36] = { "ROUTE_24", "ROUTE24_COOLTRAINER_M1", true }, + [37] = { "ROUTE_24", "ROUTE24_TM_THUNDER_WAVE", true }, + [38] = { "ROUTE_25", "ROUTE25_TM_SEISMIC_TOSS", true }, + [39] = { "BLUES_HOUSE", "BLUESHOUSE_DAISY1", true }, + [40] = { "BLUES_HOUSE", "BLUESHOUSE_DAISY2", false }, + [41] = { "BLUES_HOUSE", "BLUESHOUSE_TOWN_MAP", true }, + [42] = { "OAKS_LAB", "OAKSLAB_RIVAL", true }, + [43] = { "OAKS_LAB", "OAKSLAB_CHARMANDER_POKE_BALL", true }, + [44] = { "OAKS_LAB", "OAKSLAB_SQUIRTLE_POKE_BALL", true }, + [45] = { "OAKS_LAB", "OAKSLAB_BULBASAUR_POKE_BALL", true }, + [46] = { "OAKS_LAB", "OAKSLAB_OAK1", false }, + [47] = { "OAKS_LAB", "OAKSLAB_POKEDEX1", true }, + [48] = { "OAKS_LAB", "OAKSLAB_POKEDEX2", true }, + [49] = { "OAKS_LAB", "OAKSLAB_OAK2", false }, + [50] = { "VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI", true }, + [51] = { "VIRIDIAN_GYM", "VIRIDIANGYM_REVIVE", true }, + [52] = { "MUSEUM_1F", "MUSEUM1F_OLD_AMBER", true }, + [53] = { "CERULEAN_CAVE_1F", "CERULEANCAVE1F_FULL_RESTORE", true }, + [54] = { "CERULEAN_CAVE_1F", "CERULEANCAVE1F_MAX_ELIXER", true }, + [55] = { "CERULEAN_CAVE_1F", "CERULEANCAVE1F_NUGGET", true }, + [56] = { "POKEMON_TOWER_2F", "POKEMONTOWER2F_RIVAL", true }, + [57] = { "POKEMON_TOWER_3F", "POKEMONTOWER3F_ESCAPE_ROPE", true }, + [58] = { "POKEMON_TOWER_4F", "POKEMONTOWER4F_ELIXER", true }, + [59] = { "POKEMON_TOWER_4F", "POKEMONTOWER4F_AWAKENING", true }, + [60] = { "POKEMON_TOWER_4F", "POKEMONTOWER4F_HP_UP", true }, + [61] = { "POKEMON_TOWER_5F", "POKEMONTOWER5F_NUGGET", true }, + [62] = { "POKEMON_TOWER_6F", "POKEMONTOWER6F_RARE_CANDY", true }, + [63] = { "POKEMON_TOWER_6F", "POKEMONTOWER6F_X_ACCURACY", true }, + [64] = { "POKEMON_TOWER_7F", "POKEMONTOWER7F_ROCKET1", true }, + [65] = { "POKEMON_TOWER_7F", "POKEMONTOWER7F_ROCKET2", true }, + [66] = { "POKEMON_TOWER_7F", "POKEMONTOWER7F_ROCKET3", true }, + [67] = { "POKEMON_TOWER_7F", "POKEMONTOWER7F_MR_FUJI", true }, + [68] = { "MR_FUJIS_HOUSE", "MRFUJISHOUSE_MR_FUJI", false }, + [69] = { "CELADON_MANSION_ROOF_HOUSE", "CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL", true }, + [70] = { "GAME_CORNER", "GAMECORNER_ROCKET", true }, + [71] = { "WARDENS_HOUSE", "WARDENSHOUSE_RARE_CANDY", true }, + [72] = { "POKEMON_MANSION_1F", "POKEMONMANSION1F_ESCAPE_ROPE", true }, + [73] = { "POKEMON_MANSION_1F", "POKEMONMANSION1F_CARBOS", true }, + [74] = { "FIGHTING_DOJO", "FIGHTINGDOJO_HITMONLEE_POKE_BALL", true }, + [75] = { "FIGHTING_DOJO", "FIGHTINGDOJO_HITMONCHAN_POKE_BALL", true }, + [76] = { "SILPH_CO_1F", "SILPHCO1F_LINK_RECEPTIONIST", false }, + [77] = { "POWER_PLANT", "POWERPLANT_VOLTORB1", true }, + [78] = { "POWER_PLANT", "POWERPLANT_VOLTORB2", true }, + [79] = { "POWER_PLANT", "POWERPLANT_VOLTORB3", true }, + [80] = { "POWER_PLANT", "POWERPLANT_ELECTRODE1", true }, + [81] = { "POWER_PLANT", "POWERPLANT_VOLTORB4", true }, + [82] = { "POWER_PLANT", "POWERPLANT_VOLTORB5", true }, + [83] = { "POWER_PLANT", "POWERPLANT_ELECTRODE2", true }, + [84] = { "POWER_PLANT", "POWERPLANT_VOLTORB6", true }, + [85] = { "POWER_PLANT", "POWERPLANT_ZAPDOS", true }, + [86] = { "POWER_PLANT", "POWERPLANT_CARBOS", true }, + [87] = { "POWER_PLANT", "POWERPLANT_HP_UP", true }, + [88] = { "POWER_PLANT", "POWERPLANT_RARE_CANDY", true }, + [89] = { "POWER_PLANT", "POWERPLANT_TM_THUNDER", true }, + [90] = { "POWER_PLANT", "POWERPLANT_TM_REFLECT", true }, + [91] = { "VICTORY_ROAD_2F", "VICTORYROAD2F_MOLTRES", true }, + [92] = { "VICTORY_ROAD_2F", "VICTORYROAD2F_TM_SUBMISSION", true }, + [93] = { "VICTORY_ROAD_2F", "VICTORYROAD2F_FULL_HEAL", true }, + [94] = { "VICTORY_ROAD_2F", "VICTORYROAD2F_TM_MEGA_KICK", true }, + [95] = { "VICTORY_ROAD_2F", "VICTORYROAD2F_GUARD_SPEC", true }, + [96] = { "VICTORY_ROAD_2F", "VICTORYROAD2F_BOULDER3", true }, + [97] = { "BILLS_HOUSE", "BILLSHOUSE_BILL_POKEMON", true }, + [98] = { "BILLS_HOUSE", "BILLSHOUSE_BILL1", false }, + [99] = { "BILLS_HOUSE", "BILLSHOUSE_BILL2", false }, + [100] = { "VIRIDIAN_FOREST", "VIRIDIANFOREST_ANTIDOTE", true }, + [101] = { "VIRIDIAN_FOREST", "VIRIDIANFOREST_POTION", true }, + [102] = { "VIRIDIAN_FOREST", "VIRIDIANFOREST_POKE_BALL", true }, + [103] = { "MT_MOON_1F", "MTMOON1F_POTION1", true }, + [104] = { "MT_MOON_1F", "MTMOON1F_MOON_STONE", true }, + [105] = { "MT_MOON_1F", "MTMOON1F_RARE_CANDY", true }, + [106] = { "MT_MOON_1F", "MTMOON1F_ESCAPE_ROPE", true }, + [107] = { "MT_MOON_1F", "MTMOON1F_POTION2", true }, + [108] = { "MT_MOON_1F", "MTMOON1F_TM_WATER_GUN", true }, + [109] = { "MT_MOON_B2F", "MTMOONB2F_DOME_FOSSIL", true }, + [110] = { "MT_MOON_B2F", "MTMOONB2F_HELIX_FOSSIL", true }, + [111] = { "MT_MOON_B2F", "MTMOONB2F_HP_UP", true }, + [112] = { "MT_MOON_B2F", "MTMOONB2F_TM_MEGA_PUNCH", true }, + [113] = { "SS_ANNE_2F", "SSANNE2F_RIVAL", false }, + [114] = { "SS_ANNE_1F_ROOMS", "SSANNE1FROOMS_TM_BODY_SLAM", true }, + [115] = { "SS_ANNE_2F_ROOMS", "SSANNE2FROOMS_MAX_ETHER", true }, + [116] = { "SS_ANNE_2F_ROOMS", "SSANNE2FROOMS_RARE_CANDY", true }, + [117] = { "SS_ANNE_B1F_ROOMS", "SSANNEB1FROOMS_ETHER", true }, + [118] = { "SS_ANNE_B1F_ROOMS", "SSANNEB1FROOMS_TM_REST", true }, + [119] = { "SS_ANNE_B1F_ROOMS", "SSANNEB1FROOMS_MAX_POTION", true }, + [120] = { "VICTORY_ROAD_3F", "VICTORYROAD3F_MAX_REVIVE", true }, + [121] = { "VICTORY_ROAD_3F", "VICTORYROAD3F_TM_EXPLOSION", true }, + [122] = { "VICTORY_ROAD_3F", "VICTORYROAD3F_BOULDER4", true }, + [123] = { "ROCKET_HIDEOUT_B1F", "ROCKETHIDEOUTB1F_ESCAPE_ROPE", true }, + [124] = { "ROCKET_HIDEOUT_B1F", "ROCKETHIDEOUTB1F_HYPER_POTION", true }, + [125] = { "ROCKET_HIDEOUT_B2F", "ROCKETHIDEOUTB2F_MOON_STONE", true }, + [126] = { "ROCKET_HIDEOUT_B2F", "ROCKETHIDEOUTB2F_NUGGET", true }, + [127] = { "ROCKET_HIDEOUT_B2F", "ROCKETHIDEOUTB2F_TM_HORN_DRILL", true }, + [128] = { "ROCKET_HIDEOUT_B2F", "ROCKETHIDEOUTB2F_SUPER_POTION", true }, + [129] = { "ROCKET_HIDEOUT_B3F", "ROCKETHIDEOUTB3F_TM_DOUBLE_EDGE", true }, + [130] = { "ROCKET_HIDEOUT_B3F", "ROCKETHIDEOUTB3F_RARE_CANDY", true }, + [131] = { "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_GIOVANNI", true }, + [132] = { "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_HP_UP", true }, + [133] = { "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_TM_RAZOR_WIND", true }, + [134] = { "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_IRON", true }, + [135] = { "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_SILPH_SCOPE", false }, + [136] = { "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_LIFT_KEY", false }, + [137] = { "SILPH_CO_2F", "SILPHCO2F_SILPH_WORKER_F", true }, + [138] = { "SILPH_CO_2F", "SILPHCO2F_SCIENTIST1", true }, + [139] = { "SILPH_CO_2F", "SILPHCO2F_SCIENTIST2", true }, + [140] = { "SILPH_CO_2F", "SILPHCO2F_ROCKET1", true }, + [141] = { "SILPH_CO_2F", "SILPHCO2F_ROCKET2", true }, + [142] = { "SILPH_CO_3F", "SILPHCO3F_ROCKET", true }, + [143] = { "SILPH_CO_3F", "SILPHCO3F_SCIENTIST", true }, + [144] = { "SILPH_CO_3F", "SILPHCO3F_HYPER_POTION", true }, + [145] = { "SILPH_CO_4F", "SILPHCO4F_ROCKET1", true }, + [146] = { "SILPH_CO_4F", "SILPHCO4F_SCIENTIST", true }, + [147] = { "SILPH_CO_4F", "SILPHCO4F_ROCKET2", true }, + [148] = { "SILPH_CO_4F", "SILPHCO4F_FULL_HEAL", true }, + [149] = { "SILPH_CO_4F", "SILPHCO4F_MAX_REVIVE", true }, + [150] = { "SILPH_CO_4F", "SILPHCO4F_ESCAPE_ROPE", true }, + [151] = { "SILPH_CO_5F", "SILPHCO5F_ROCKET1", true }, + [152] = { "SILPH_CO_5F", "SILPHCO5F_SCIENTIST", true }, + [153] = { "SILPH_CO_5F", "SILPHCO5F_ROCKER", true }, + [154] = { "SILPH_CO_5F", "SILPHCO5F_ROCKET2", true }, + [155] = { "SILPH_CO_5F", "SILPHCO5F_TM_TAKE_DOWN", true }, + [156] = { "SILPH_CO_5F", "SILPHCO5F_PROTEIN", true }, + [157] = { "SILPH_CO_5F", "SILPHCO5F_CARD_KEY", true }, + [158] = { "SILPH_CO_6F", "SILPHCO6F_ROCKET1", true }, + [159] = { "SILPH_CO_6F", "SILPHCO6F_SCIENTIST", true }, + [160] = { "SILPH_CO_6F", "SILPHCO6F_ROCKET2", true }, + [161] = { "SILPH_CO_6F", "SILPHCO6F_HP_UP", true }, + [162] = { "SILPH_CO_6F", "SILPHCO6F_X_ACCURACY", true }, + [163] = { "SILPH_CO_7F", "SILPHCO7F_ROCKET1", true }, + [164] = { "SILPH_CO_7F", "SILPHCO7F_SCIENTIST", true }, + [165] = { "SILPH_CO_7F", "SILPHCO7F_ROCKET2", true }, + [166] = { "SILPH_CO_7F", "SILPHCO7F_ROCKET3", true }, + [167] = { "SILPH_CO_7F", "SILPHCO7F_RIVAL", true }, + [168] = { "SILPH_CO_7F", "SILPHCO7F_CALCIUM", true }, + [169] = { "SILPH_CO_7F", "SILPHCO7F_TM_SWORDS_DANCE", true }, + -- [170] SILPH_CO_7F (SILPHCO7F_UNUSED): placeholder entry, no object_event in this port + [171] = { "SILPH_CO_8F", "SILPHCO8F_ROCKET1", true }, + [172] = { "SILPH_CO_8F", "SILPHCO8F_SCIENTIST", true }, + [173] = { "SILPH_CO_8F", "SILPHCO8F_ROCKET2", true }, + [174] = { "SILPH_CO_9F", "SILPHCO9F_ROCKET1", true }, + [175] = { "SILPH_CO_9F", "SILPHCO9F_SCIENTIST", true }, + [176] = { "SILPH_CO_9F", "SILPHCO9F_ROCKET2", true }, + [177] = { "SILPH_CO_10F", "SILPHCO10F_ROCKET", true }, + [178] = { "SILPH_CO_10F", "SILPHCO10F_SCIENTIST", true }, + [179] = { "SILPH_CO_10F", "SILPHCO10F_SILPH_WORKER_F", true }, + [180] = { "SILPH_CO_10F", "SILPHCO10F_TM_EARTHQUAKE", true }, + [181] = { "SILPH_CO_10F", "SILPHCO10F_RARE_CANDY", true }, + [182] = { "SILPH_CO_10F", "SILPHCO10F_CARBOS", true }, + [183] = { "SILPH_CO_11F", "SILPHCO11F_GIOVANNI", true }, + [184] = { "SILPH_CO_11F", "SILPHCO11F_ROCKET1", true }, + [185] = { "SILPH_CO_11F", "SILPHCO11F_ROCKET2", true }, + -- [186] UNUSED_MAP_F4 ($02): placeholder entry, no object_event in this port + [187] = { "POKEMON_MANSION_2F", "POKEMONMANSION2F_CALCIUM", true }, + [188] = { "POKEMON_MANSION_3F", "POKEMONMANSION3F_MAX_POTION", true }, + [189] = { "POKEMON_MANSION_3F", "POKEMONMANSION3F_IRON", true }, + [190] = { "POKEMON_MANSION_B1F", "POKEMONMANSIONB1F_RARE_CANDY", true }, + [191] = { "POKEMON_MANSION_B1F", "POKEMONMANSIONB1F_FULL_RESTORE", true }, + [192] = { "POKEMON_MANSION_B1F", "POKEMONMANSIONB1F_TM_BLIZZARD", true }, + [193] = { "POKEMON_MANSION_B1F", "POKEMONMANSIONB1F_TM_SOLARBEAM", true }, + [194] = { "POKEMON_MANSION_B1F", "POKEMONMANSIONB1F_SECRET_KEY", true }, + [195] = { "SAFARI_ZONE_EAST", "SAFARIZONEEAST_FULL_RESTORE", true }, + [196] = { "SAFARI_ZONE_EAST", "SAFARIZONEEAST_MAX_RESTORE", true }, + [197] = { "SAFARI_ZONE_EAST", "SAFARIZONEEAST_CARBOS", true }, + [198] = { "SAFARI_ZONE_EAST", "SAFARIZONEEAST_TM_EGG_BOMB", true }, + [199] = { "SAFARI_ZONE_NORTH", "SAFARIZONENORTH_PROTEIN", true }, + [200] = { "SAFARI_ZONE_NORTH", "SAFARIZONENORTH_TM_SKULL_BASH", true }, + [201] = { "SAFARI_ZONE_WEST", "SAFARIZONEWEST_MAX_POTION", true }, + [202] = { "SAFARI_ZONE_WEST", "SAFARIZONEWEST_TM_DOUBLE_TEAM", true }, + [203] = { "SAFARI_ZONE_WEST", "SAFARIZONEWEST_MAX_REVIVE", true }, + [204] = { "SAFARI_ZONE_WEST", "SAFARIZONEWEST_GOLD_TEETH", true }, + [205] = { "SAFARI_ZONE_CENTER", "SAFARIZONECENTER_NUGGET", true }, + [206] = { "CERULEAN_CAVE_2F", "CERULEANCAVE2F_PP_UP", true }, + [207] = { "CERULEAN_CAVE_2F", "CERULEANCAVE2F_ULTRA_BALL", true }, + [208] = { "CERULEAN_CAVE_2F", "CERULEANCAVE2F_FULL_RESTORE", true }, + [209] = { "CERULEAN_CAVE_B1F", "CERULEANCAVEB1F_MEWTWO", true }, + [210] = { "CERULEAN_CAVE_B1F", "CERULEANCAVEB1F_ULTRA_BALL", true }, + [211] = { "CERULEAN_CAVE_B1F", "CERULEANCAVEB1F_MAX_REVIVE", true }, + [212] = { "VICTORY_ROAD_1F", "VICTORYROAD1F_TM_SKY_ATTACK", true }, + [213] = { "VICTORY_ROAD_1F", "VICTORYROAD1F_RARE_CANDY", true }, + [214] = { "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK", false }, + [215] = { "SEAFOAM_ISLANDS_1F", "SEAFOAMISLANDS1F_BOULDER1", true }, + [216] = { "SEAFOAM_ISLANDS_1F", "SEAFOAMISLANDS1F_BOULDER2", true }, + [217] = { "SEAFOAM_ISLANDS_B1F", "SEAFOAMISLANDSB1F_BOULDER1", false }, + [218] = { "SEAFOAM_ISLANDS_B1F", "SEAFOAMISLANDSB1F_BOULDER2", false }, + [219] = { "SEAFOAM_ISLANDS_B2F", "SEAFOAMISLANDSB2F_BOULDER1", false }, + [220] = { "SEAFOAM_ISLANDS_B2F", "SEAFOAMISLANDSB2F_BOULDER2", false }, + [221] = { "SEAFOAM_ISLANDS_B3F", "SEAFOAMISLANDSB3F_BOULDER2", true }, + [222] = { "SEAFOAM_ISLANDS_B3F", "SEAFOAMISLANDSB3F_BOULDER3", true }, + [223] = { "SEAFOAM_ISLANDS_B3F", "SEAFOAMISLANDSB3F_BOULDER5", false }, + [224] = { "SEAFOAM_ISLANDS_B3F", "SEAFOAMISLANDSB3F_BOULDER6", false }, + [225] = { "SEAFOAM_ISLANDS_B4F", "SEAFOAMISLANDSB4F_BOULDER1", false }, + [226] = { "SEAFOAM_ISLANDS_B4F", "SEAFOAMISLANDSB4F_BOULDER2", false }, + [227] = { "SEAFOAM_ISLANDS_B4F", "SEAFOAMISLANDSB4F_ARTICUNO", true }, + }, +} diff --git a/src/script/Commands.lua b/src/script/Commands.lua index 9d815504..dba4844b 100644 --- a/src/script/Commands.lua +++ b/src/script/Commands.lua @@ -260,6 +260,26 @@ function Commands.take_item(ctx, itemId, count) if inv[itemId] == 0 then inv[itemId] = nil end end +-- save_end_battle_text : SaveEndBattleTextPointers +-- (home/trainers.asm, called from e.g. RocketHideoutB4FScript10 just before +-- wCurOpponent is set). The armed line is the trainer's OWN loss line and +-- belongs on the battle screen: PrintEndBattleText (home/trainers.asm) runs +-- from TrainerBattleVictory (engine/battle/core.asm) after +-- TrainerDefeatedText and the pic scroll but BEFORE MoneyForWinningText, and +-- TrainerEndBattleText prints _TrainerNameText first so the line opens with +-- the "CLASS: " tag. Scripts that printed it with a plain show_text after +-- start_battle got it a box too late -- after the payout -- and untagged +-- (#866). Arms exactly one battle; start_battle consumes it. +function Commands.save_end_battle_text(ctx, textId) + local text = ctx.game.data.text[textId] + if not text and ctx.overworld then + text = ctx.game.data:resolveText(ctx.overworld.map.def.label, textId) + end + -- BattleState takes finished text, so expand {PLAYER}/{RIVAL} here the + -- way OverworldState:engageTrainer does for the sight/talk path + ctx.endBattleText = TextBox.substitute(ctx.game, text or textId) +end + -- start_battle "wild" species level | start_battle "trainer" OPP_CLASS partyIndex function Commands.start_battle(ctx, kind, a, b) local BattleState = require("src.battle.BattleState") @@ -270,6 +290,9 @@ function Commands.start_battle(ctx, kind, a, b) else battle = BattleState.newTrainer(ctx.game, a, b) end + -- one SaveEndBattleTextPointers arms one battle; leaving it set would leak + -- the line into the next scripted fight + battle.endBattleText, ctx.endBattleText = ctx.endBattleText, nil battle.onFinish = function(result) ctx.lastBattleResult = result ctx.lastCheck = result == "win" @@ -638,9 +661,8 @@ end -- AskName runs for party (AddPartyMon) and box (SendNewMonToBox) when a -- script runner is present; mods that pre-set gift.nickname skip it. -- Box deposits also print SentToBoxText (give_pokemon.asm:36-37). --- skipNickname suppresses the AskName prompt: Yellow's lab Pikachu is --- added straight through AddPartyMon (pokeyellow scripts/OaksLab.asm --- OaksLabPlayerReceivedMonText) -- the starter Pikachu keeps its name. +-- skipNickname suppresses AskName for callers that name the gift themselves; +-- no vanilla script uses it (pokeyellow scripts/OaksLab.asm, #1013) function Commands.give_pokemon(ctx, species, level, skipNickname) -- Native mods can transform a gift before the Pokémon object is created. -- This is intentionally an event rather than a special-case starter hook: @@ -1074,10 +1096,25 @@ function Commands.march_in_place(ctx, objIndex, on) ow.marchers[npc] = on and true or nil end +-- pikachu_make_way: callfar OaksLabPikachuMovementScript (pokeyellow +-- scripts/OaksLab_2.asm); a no-op without a Yellow follower (#1021) +function Commands.pikachu_make_way(ctx) + local ow = ctx.overworld + if not ow then return end + local runner = ctx.runner + local started = require("src.world.PikachuFollower") + .oaksLabMakeWay(ctx.game, ow, function() runner:resume() end) + if started then runner:yield() end +end + -- play_music [opts]: switch map music now; opts.keep marks it --- to survive the next warp (the story files' keepMusic idiom) +-- to survive the next warp (the story files' keepMusic idiom). +-- opts.tempo is the Music_*AlternateTempo override (audio/alternate_tempo.asm +-- re-points channel 1 at a stub that only changes the song's `tempo`) (#847). function Commands.play_music(ctx, songId, opts) - require("src.core.Music").play(ctx.game.data, songId) + local tempo = opts and opts.tempo + require("src.core.Music").play(ctx.game.data, songId, nil, + tempo and { tempo = tempo } or nil) if opts and opts.keep and ctx.overworld then ctx.overworld.keepMusicOnce = true end @@ -1087,6 +1124,15 @@ function Commands.stop_music(ctx) require("src.core.Music").stop() end +-- fade_music [control]: FadeOutAudio (home/fade_audio.asm) -- ramp the +-- current song to silence over 7 * control frames and stop it, the way +-- Music_Cities1AlternateTempo does before it restarts Cities1 (#847). +-- Non-blocking, like the ROM's write to wAudioFadeOutControl: pair it with +-- the `wait` that stands in for the following DelayFrames. +function Commands.fade_music(ctx, control) + require("src.core.Music").fadeOut(control or 10) +end + -- play_default_music: PlayDefaultMusic -- resume the current map's own -- theme (data.audio.mapSongs) after a cutscene override, keeping the -- bike/surf substitution rules. Headless-safe no-op without an overworld. @@ -1323,7 +1369,7 @@ for _, verb in ipairs({ "show_text", "ask", "choice", "start_battle", "warp", "old_man_demo", "static_battle", "rival_battle", "give_item", "give_pokemon", "wait", "wait_flag", "move_player", "move_npc", "move_npc_to", "walk_npc", - "emote", "fade", "pan_camera", "play_once" }) do + "emote", "fade", "pan_camera", "play_once", "pikachu_make_way" }) do local meta = Commands.meta[verb] or {} Commands.meta[verb] = meta meta.blocking = true diff --git a/src/ui/BagMenu.lua b/src/ui/BagMenu.lua index fc9539c4..1a2c59ee 100644 --- a/src/ui/BagMenu.lua +++ b/src/ui/BagMenu.lua @@ -253,19 +253,50 @@ local function useOn(game, battle, id, target, list, moveIndex, picker) return end + if result == "kept" then + if battle then + list:close() + showMessages(game, payload, function() + battle:itemUsed({}) + end) + else + showMessages(game, payload, closePicker) + end + return + end + if result == "consumed" then consume(game, id) + -- refresh counts in the list + for i, it in ipairs(list.items) do + if it.value == id then + local left = game.save.inventory[id] + if left then it.right = "x" .. left else table.remove(list.items, i) end + break + end + end + list.index = math.min(list.index, math.max(1, #list.items)) if extra and extra.evolveTo then list:close() local Evolution = require("src.pokemon.Evolution") - Evolution.evolve(game, target, extra.evolveTo) + -- item_effects.asm ItemUseEvoStone sets wForceEvolution before + -- TryEvolvingMon, so a stone evolution's B press is read and + -- discarded (EvolutionState.lua's cancelable check). via = "ITEM" + -- is what makes that non-cancelable here, same as the RARE_CANDY + -- call below; without it the stone (already consumed above) could + -- be cancelled out from under the player (#883) + Evolution.evolve(game, target, extra.evolveTo, nil, "ITEM") return end -- RARE CANDY: after the level text, the stat window, any level-up -- moves and a level evolution follow (item_effects.asm .useRareCandy -- runs PrintStatsBox, LearnMoveFromLevelUp and TryEvolvingMon) if extra and extra.leveledTo and target then - list:close() + -- ...but the bag stays open underneath it all: RARE_CANDY is in + -- pokered's UsableItems_PartyMenu (data/items/use_party.asm), and + -- .useItem_partyMenu jumps back to StartMenu_Item once UseItem + -- returns, cursor still on the candy (start_sub_menus.asm) -- so + -- mashing A burns through a stack of them (#796) showMessages(game, payload, function() local StatBox = require("src.battle.BattleState").StatBox game.stack:push(StatBox.new(game, target, function() @@ -304,15 +335,6 @@ local function useOn(game, battle, id, target, list, moveIndex, picker) end) return end - -- refresh counts in the list - for i, it in ipairs(list.items) do - if it.value == id then - local left = game.save.inventory[id] - if left then it.right = "x" .. left else table.remove(list.items, i) end - break - end - end - list.index = math.min(list.index, math.max(1, #list.items)) -- HP medicine: fill the bar in the still-open picker first, then print -- and close, the order item_effects.asm .doneHealing runs in -- (SFX_HEAL_HP -> UpdateHPBar2 -> RedrawPartyMenu prints the message). @@ -394,7 +416,7 @@ local function useItem(game, battle, id, list) showMessages(game, payload) return end - if ItemEffects.needsTarget(id, def) and not ItemEffects.isBall(id) then + if ItemEffects.needsTarget(id, def, game.data) and not ItemEffects.isBall(id) then -- TMs/HMs boot up and announce their move before the target picker -- (ItemUseTMHM: BootedUpTMText / BootedUpHMText + TeachMachineMoveText) if def and def.machine then diff --git a/src/ui/BoxMenu.lua b/src/ui/BoxMenu.lua index 332e90ef..81a6357d 100644 --- a/src/ui/BoxMenu.lua +++ b/src/ui/BoxMenu.lua @@ -67,6 +67,7 @@ local function withdraw(game) game.stack:push(ListMenu.new(game, Strings("BOX %d (WITHDRAW)", game.save.currentBox), items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_withdraw", onChoose = function(item, list) local mon = box[item.value] if not mon then return end @@ -114,6 +115,7 @@ local function deposit(game) end game.stack:push(ListMenu.new(game, "PARTY (DEPOSIT)", items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_deposit", onChoose = function(item, list) local mon = game.save.party[item.value] if not mon then return end @@ -160,6 +162,7 @@ local function release(game) game.stack:push(ListMenu.new(game, Strings("BOX %d (RELEASE)", game.save.currentBox), items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_release", onChoose = function(_, list) local mon = box[list.index] if not mon then return end @@ -193,6 +196,7 @@ local function changeBox(game) end game.stack:push(ListMenu.new(game, "CHANGE BOX", items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_change", onChoose = function(item, list) -- the original asks BEFORE switching ("When you change a #MON -- BOX, data will be saved. OK?"); declining aborts the change @@ -203,6 +207,8 @@ local function changeBox(game) if not yes then return end game.save.currentBox = item.value if game.writeSave then game:writeSave() end + -- ChangeBox (engine/menus/save.asm) rings SFX_SAVE after SaveGameData (#1044) + require("src.core.Sound").play(game.data, "Save") list:close() end, })) diff --git a/src/ui/Credits.lua b/src/ui/Credits.lua index dfb02eea..b12db5fb 100644 --- a/src/ui/Credits.lua +++ b/src/ui/Credits.lua @@ -9,8 +9,9 @@ -- CRED_TEXT_MON text appears at once, hold 110, mon wipe -- CRED_TEXT_FADE fade in, hold 120, next screen replaces the text -- CRED_TEXT text appears at once, hold 140 --- The mon wipe is DisplayCreditsMon: the middle band scrolls left 8px per --- frame for 27 frames (ScrollCreditsMonLeft x7 then x20) while the next +-- The mon wipe is DisplayCreditsMon: three CreditsCopyTileMapToVRAM copies +-- (9 frames of Delay3, text still up), then the middle band scrolls left 8px +-- per frame for 27 frames (ScrollCreditsMonLeft x7 then x20) while the next -- CreditsMons entry crosses right-to-left as a black silhouette -- (BGP %11111100), leaving the band blank; BGP is left at %11000000, which -- is why every post-wipe screen is a FADE variant. CRED_COPYRIGHT @@ -30,6 +31,7 @@ -- to just THE END. local Font = require("src.render.Font") +local GameVersion = require("src.core.GameVersion") local Music = require("src.core.Music") local Strings = require("src.core.Strings") @@ -52,14 +54,23 @@ local HOLD_FADE = 120 local HOLD_TEXT = 140 local WIPE_FRAMES = 27 -- ScrollCreditsMonLeft: 7 + 20 calls, 8px/frame +-- DisplayCreditsMon runs three CreditsCopyTileMapToVRAM calls (vBGMap0+$c, +-- vBGMap0, vBGMap1) before the first scroll, and each one ends in `jp Delay3` +-- (home/palettes.asm), so the credits text sits still for 9 more frames on +-- every mon screen. Dropping them ran the 15 mon screens 135 frames short and +-- brought THE END up 2.2s early against a credits theme whose length is fixed +-- by the ROM program (Music_Credits is 5880 frames and does not loop), which +-- is what made the song look like it overran the roll (#703). +local MON_PREP_FRAMES = 9 -- LoadCopyrightTiles (engine/movie/title.asm CopyrightTextString): tile -- sequences into the extracted title/copyright.png strip (tiles $60-$72: --- (c)'95.'96.'98 + Nintendo + Creatures inc.); the GAME FREAK inc. row is --- the title/gamefreak_inc.png strip (GameFreakLogoGraphics, tiles --- $73-$7B), with the intro's composed gamefreak_text.png as a fallback --- for pre-regeneration data. -local COPY_PREFIX = { 0, 1, 2, 1, 3, 1, 4 } -- (c)'95.'96.'98 +-- Red/Blue (c)'95.'96.'98, Yellow (c)1995-1999 + NineTile) + Nintendo + +-- Creatures inc.; the GAME FREAK inc. row is title/gamefreak_inc.png +-- (GameFreakLogoGraphics, tiles $73-$7B), with the intro's composed +-- gamefreak_text.png as a fallback for pre-regeneration data. +local COPY_PREFIX_RB = { 0, 1, 2, 1, 3, 1, 4 } -- (c)'95.'96.'98 +local COPY_PREFIX_YELLOW = { 0, 1, 2, 3, 1, 2 } -- (c)1995-199 local COPY_NINTENDO = { 5, 6, 7, 8, 9, 10 } -- Nintendo local COPY_CREATURES = { 11, 12, 13, 14, 15, 16, 17, 18 } -- Creatures inc. @@ -131,6 +142,12 @@ function Credits.new(game, onDone, onTheEnd) and title.gamefreakInc.path) or tryImage(intro and intro.gamefreakText and intro.gamefreakText.path) + self.yellowCopy = GameVersion.isYellow() + or (title and title.layout == "yellow_pikachu") + self.copyPrefix = self.yellowCopy and COPY_PREFIX_YELLOW or COPY_PREFIX_RB + self.nineImg = self.yellowCopy and tryImage( + title and title.nine and title.nine.path + or "assets/generated/title/nine.png") or nil return self end @@ -204,12 +221,17 @@ function Credits:update(dt) self.timer = self.screen.mon and HOLD_FADE_MON or HOLD_FADE elseif self.phase == "hold" then if self.screen.mon then - self.phase = "wipe" - self.timer = WIPE_FRAMES - self.monImg, self.monTint = self:monSprite(self.screen.mon) + -- the text stays up through DisplayCreditsMon's VRAM copies; the + -- silhouette only starts moving once ScrollCreditsMonLeft does + self.phase = "mon_prep" + self.timer = MON_PREP_FRAMES else self:nextScreen() end + elseif self.phase == "mon_prep" then + self.phase = "wipe" + self.timer = WIPE_FRAMES + self.monImg, self.monTint = self:monSprite(self.screen.mon) elseif self.phase == "wipe" then self.monImg = nil self:nextScreen() @@ -241,6 +263,17 @@ function Credits:drawPage(screen, xoff, shade) if screen.copyright then self:drawCopyright(xoff) end end +function Credits:drawCopyPrefix(x, y) + local img = self.copyImg + for _, t in ipairs(self.copyPrefix) do + love.graphics.draw(img, self.copyQuads[t], x, y) + x = x + 8 + end + if self.nineImg then + love.graphics.draw(self.nineImg, x, y) + end +end + function Credits:drawCopyright(xoff) local img = self.copyImg if img then @@ -253,11 +286,11 @@ function Credits:drawCopyright(xoff) end return x end - row(COPY_PREFIX, xoff + 16, 56) + self:drawCopyPrefix(xoff + 16, 56) row(COPY_NINTENDO, xoff + 80, 56) - row(COPY_PREFIX, xoff + 16, 72) + self:drawCopyPrefix(xoff + 16, 72) row(COPY_CREATURES, xoff + 80, 72) - row(COPY_PREFIX, xoff + 16, 88) + self:drawCopyPrefix(xoff + 16, 88) if self.gfImg then love.graphics.draw(self.gfImg, xoff + 80, 88) else @@ -313,7 +346,8 @@ function Credits:draw() love.graphics.rectangle("fill", 0, 0, 160, 32) love.graphics.rectangle("fill", 0, 112, 160, 32) love.graphics.setColor(1, 1, 1, 1) - if self.phase == "fade" or self.phase == "hold" then + if self.phase == "fade" or self.phase == "hold" + or self.phase == "mon_prep" then self:drawPage(self.screen, 0, self.shade) elseif self.phase == "wipe" then -- ScrollCreditsMonLeft: the middle band scrolls left 8px/frame while diff --git a/src/ui/DexEntryMenu.lua b/src/ui/DexEntryMenu.lua index 11e0d808..f5f1a09d 100644 --- a/src/ui/DexEntryMenu.lua +++ b/src/ui/DexEntryMenu.lua @@ -6,6 +6,11 @@ -- StarterDex (engine/events/starter_dex.asm), which temporarily sets the -- owned bit so Oak's lab ball previews show height/weight/description -- without permanently marking the mon owned. +-- +-- `onDone` (optional) runs right after the page pops itself, the way a +-- TextBox onDone does; map scripts use it to continue once the player +-- closes the entry (the Fighting Dojo prize balls chain their take-it +-- prompt off it). local Font = require("src.render.Font") local Strings = require("src.core.Strings") @@ -31,9 +36,10 @@ local function resolveArgs(speciesOrOpts) return speciesOrOpts, false end -function DexEntryMenu.new(game, speciesOrOpts) +function DexEntryMenu.new(game, speciesOrOpts, onDone) local species, forceOwned = resolveArgs(speciesOrOpts) - local self = setmetatable({ game = game, forceOwned = forceOwned }, DexEntryMenu) + local self = setmetatable({ game = game, forceOwned = forceOwned, + onDone = onDone }, DexEntryMenu) self.def = game.data.pokemon[species] local path, trueColor = require("src.pokemon.Sprites").path( game.data, species, "front", { kind = "dex" }) @@ -52,6 +58,7 @@ function DexEntryMenu:update(dt) local input = self.game.input if input:wasPressed("a") or input:wasPressed("b") then self.game.stack:pop() + if self.onDone then self.onDone() end end end @@ -87,7 +94,7 @@ function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor) -- same number width as the list (constants.dexDigits), so a dex past 999 -- prints the extra digit everywhere at once local digits = (game.data.constants or {}).dexDigits or 3 - Font.draw(("No.%0" .. digits .. "d"):format(def.dex or 0), 72, 32) + Font.draw(Strings("No.") .. ("%0" .. digits .. "d"):format(def.dex or 0), 72, 32) local owned = forceOwned or (game.save.pokedex and game.save.pokedex.owned[def.id]) -- height/weight print only once owned, like the description @@ -98,8 +105,8 @@ function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor) -- pokedex.asm; the tiles come from gfx/pokedex/pokedex.png via -- engine/gfx/load_pokedex_tiles.asm) if e.heightM then - Font.draw((("GR. %.1fm"):format(e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 64, 44) - Font.draw((("GEW. %.1fkg"):format(e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 64, 54) + Font.draw((Strings("GR. %.1fm", e.heightM):gsub("(%d)%.(%d)", "%1,%2")), 72, 44) + Font.draw((Strings("GEW. %.1fkg", e.weightKg or 0):gsub("(%d)%.(%d)", "%1,%2")), 72, 54) else Font.draw(Strings("HT %d′%02d″", e.heightFt, e.heightIn or 0), 72, 44) Font.draw(Strings("WT %.1flb", (e.weight or 0) / 10), 72, 54) diff --git a/src/ui/EvolutionState.lua b/src/ui/EvolutionState.lua index 5954d553..d819b03e 100644 --- a/src/ui/EvolutionState.lua +++ b/src/ui/EvolutionState.lua @@ -2,8 +2,8 @@ -- flashes back and forth with the evolved form, speeding up, then the -- new form appears with its cry and the congratulations text. -- pokered engine/movie/evolution.asm (Evolution_CheckForCancel) polls the --- joypad during the flash: holding B aborts the evolution -- the mon keeps --- its species and _StoppedEvolvingText ("Huh? MON stopped evolving!") +-- joypad during the flash: a fresh B press aborts the evolution -- the mon +-- keeps its species and _StoppedEvolvingText ("Huh? MON stopped evolving!") -- prints. Two kinds are exempt: trade evolutions, which evos_moves.asm -- routes past the poll entirely (wLinkState == LINK_STATE_TRADING, #213), -- and stone evolutions, where the B press is read but thrown away because @@ -12,6 +12,7 @@ local Font = require("src.render.Font") local Music = require("src.core.Music") local Strings = require("src.core.Strings") +local romText = require("src.core.RomText") local EvolutionState = {} EvolutionState.__index = EvolutionState @@ -46,6 +47,8 @@ function EvolutionState:sgbPalettes(game) end local FLASH_FRAMES = 220 +-- evolution.asm EvolveMon delays 80 frames before .animLoop, polling nothing (#968, #1031) +local CANCEL_GRACE_FRAMES = 80 local function frontSprite(game, species, mon) local path, trueColor = require("src.pokemon.Sprites").path( @@ -82,16 +85,16 @@ function EvolutionState:update(dt) self.t = self.t + 1 if self.done then return end local game = self.game - -- evos_moves.asm EvolveMon: each flash iteration polls hJoyHeld and, for - -- a cancelable evolution, aborts when B is held -- the mon keeps its - -- species (Evolution.apply never runs) and _StoppedEvolvingText prints. - if self.cancelable and game.input:isDown("b") then + -- evolution.asm Evolution_CheckForCancel reads hJoy5, a fresh edge rather + -- than a hold, so B held from the level-up box must not cancel (#968, #1031) + if self.cancelable and self.t > CANCEL_GRACE_FRAMES + and game.input:wasPressed("b") then self.done = true self.canceled = true local TextBox = require("src.render.TextBox") - -- mirrors data/generated/text.lua _StoppedEvolvingText game.stack:push(TextBox.new(game, - Strings("Huh? %s\nstopped evolving!", self.oldName), + romText(game.data, "_StoppedEvolvingText", + "Huh? %s\nstopped evolving!", self.oldName), function() Music.restoreMap(game.data) game.stack:pop() -- the evolution screen itself @@ -106,6 +109,8 @@ function EvolutionState:update(dt) require("src.core.Sound").playCry(game.data, self.newSpecies) local TextBox = require("src.render.TextBox") local newName = game.data.pokemon[self.newSpecies].name + -- _EvolvedText extracts truncated (it stops at a dynamic marker the + -- decoder does not follow), so the engine's wording stands here game.stack:push(TextBox.new(game, Strings("Congratulations!\nYour %s\nevolved into\n%s!", self.oldName, newName), diff --git a/src/ui/FlyMenu.lua b/src/ui/FlyMenu.lua index 3e512bb3..27d5a02b 100644 --- a/src/ui/FlyMenu.lua +++ b/src/ui/FlyMenu.lua @@ -12,12 +12,11 @@ function FlyMenu.new(game) local seen = {} for _, mapId in ipairs(game.data.field.flyOrder or {}) do -- towns only (dungeon escape spots share the table), each listed once. - -- Indigo Plateau (tileset PLATEAU) is a valid Fly destination too, so allow - -- it past the OVERWORLD-only isOutdoor gate while the CAVERN/FACILITY escape - -- spots stay excluded (LoadTownMap_Fly cycles it like any town, #203). + -- Map.isFlyTown is the BuildFlyLocationsList gate: map ids 0..10, so + -- INDIGO_PLATEAU cycles like any town (#203) while the ROUTE_4/ROUTE_10 + -- Pokemon Centers, fly warps but not towns, stay out (#788). local def = game.data.maps[mapId] - if visited[mapId] and def and not seen[mapId] - and (Map.isOutdoor(def) or def.tileset == "PLATEAU") then + if visited[mapId] and def and not seen[mapId] and Map.isFlyTown(def) then seen[mapId] = true table.insert(items, { value = mapId, diff --git a/src/ui/HallOfFame.lua b/src/ui/HallOfFame.lua index 610df29f..848fde8e 100644 --- a/src/ui/HallOfFame.lua +++ b/src/ui/HallOfFame.lua @@ -1,6 +1,7 @@ -- Hall of Fame induction (engine/movie/hall_of_fame.asm): each party --- member's front sprite scrolls onto the right side of the screen --- (HoFShowMonOrPlayer's .ScrollPic), then HoFDisplayMonInfo draws the +-- member's back sprite sweeps across the screen and its front sprite then +-- scrolls onto the right side (HoFShowMonOrPlayer's two .ScrollPic passes, +-- #847), then HoFDisplayMonInfo draws the -- left-side LEVEL/TYPE box, plays the cry, holds, and pops the bottom -- "HALL OF FAME" text box before fading to the next mon. After the -- party, the player pic scrolls in and HoFDisplayPlayerStats shows the @@ -41,6 +42,25 @@ end local SCROLL_SPEED = 4 -- px/frame @ 60fps local PIC_X, PIC_Y = 12 * 8, 5 * 8 +-- The BACK pic pass that runs in front of every front pic (#847). +-- HoFShowMonOrPlayer loads the back pic (predef LoadMonBackPic, or +-- RedPicBack through HoFLoadPlayerPics) into the same 7x7 window at +-- hlcoord 12,5, points the tilemap at base tile $31, and sweeps hSCX from +-- $c0 to $a0 at e = 4 -- in screen pixels the pic enters at x = 160 and +-- leaves at x = -64, 56 frames. Only then is the window re-pointed at the +-- front pic (base tile 0) with hSCY back to 0 and e = -4, so the front pic +-- starts at x = -64 rather than at its own width. +-- hSCY = $d0 during the back pass puts the window's top row at y = 88, so +-- its 7 tiles sit flush with the bottom of the screen. ScaleSpriteByTwo +-- (engine/battle/scale_sprites.asm) doubles the 32x32 back sprite into that +-- 7x7 = 56x56 buffer with the last 4 source rows and the last source column +-- dropped, so what shows is the sprite's top-left 28x28 at 2x. +local BACK_START_X, BACK_END_X = 160, -64 +local BACK_Y = 88 +local BACK_SCALE = 2 +local BACK_CROP = 28 +local FRONT_START_X = -64 + -- After HoFDisplayAndRecordMonInfo: 80 DelayFrames, then the bottom -- HALL OF FAME box for 180 DelayFrames, then GBFadeOutToWhite. local INFO_HOLD = 80 @@ -74,6 +94,8 @@ function HallOfFame.new(game, onDone) self.phase = "mons" self.sprites = {} -- species -> image or false self.spriteTrueColor = {} -- species -> full-color art flag (#637) + self.backs = {} -- species (or "@player") -> back image or false (#847) + self.backTrueColor = {} local playerPath, playerTrueColor = require("src.pokemon.Sprites").playerPath( game.data, "front", { kind = "hof" }) @@ -98,20 +120,14 @@ function HallOfFame:nextMon() local mon = self.game.save.party[self.index] self.showHofBanner = false self.fade = 0 - if mon then - self.phase = "mons" - self.timer = INFO_HOLD - Sound.playCry(self.game.data, mon.species) - local sprite = self:spriteFor(mon.species) - local w = sprite and sprite:getWidth() or 56 - self.scrollX = -w - else - -- HoFShowMonOrPlayer with wHoFMonOrPlayer = player - self.phase = "player" - self.timer = 0 - local w = self.playerPic and self.playerPic:getWidth() or 56 - self.scrollX = -w - end + self.backQuad = nil + -- HoFShowMonOrPlayer runs the back pic sweep for a party member and for + -- the player alike (wHoFMonOrPlayer only picks which pics get loaded), so + -- both enter through the "back" phase and the front pic follows it (#847) + self.phase = "back" + self.afterBack = mon and "mons" or "player" + self.timer = 0 + self.scrollX = BACK_START_X end function HallOfFame:spriteFor(species) @@ -126,6 +142,30 @@ function HallOfFame:spriteFor(species) return cached or nil end +-- The back pic for the pass ahead of the current front pic: the party +-- member's own back sprite (predef LoadMonBackPic), or RedPicBack once the +-- party is done (HoFLoadPlayerPics). Cached like spriteFor (#847). +function HallOfFame:backPicFor() + local mon = self.game.save.party[self.index] + local key = mon and mon.species or "@player" + local cached = self.backs[key] + if cached == nil then + local Sprites = require("src.pokemon.Sprites") + local path, trueColor + if mon then + path, trueColor = Sprites.path(self.game.data, mon.species, "back", + { kind = "hof" }) + else + path, trueColor = Sprites.playerPath(self.game.data, "back", + { kind = "hof" }) + end + cached = tryImage(path) or false + self.backs[key] = cached + self.backTrueColor[key] = cached and trueColor or false + end + return cached or nil, self.backTrueColor[key] +end + -- HoFDisplayPlayerStats' DisplayDexRating tally (also -- OverworldController:dexRating / PokedexMenu.new's seen+owned counts) function HallOfFame:dexSeenOwned() @@ -154,9 +194,28 @@ function HallOfFame:update(dt) local input = self.game.input local skip = input:wasPressed("a") + -- .ScrollPic with d = $a0, e = 4: the back pic crosses the screen right to + -- left and is gone before the front pic starts. Both scrolls are plain + -- DelayFrame loops in the ROM, so neither takes a button (#847). + if self.phase == "back" then + self.scrollX = self.scrollX - SCROLL_SPEED + if self.scrollX <= BACK_END_X then + self.phase = self.afterBack + self.timer = self.phase == "mons" and INFO_HOLD or 0 + self.scrollX = FRONT_START_X + end + return + end + if self.phase == "mons" or self.phase == "fade" then if self.phase == "mons" and self.scrollX < PIC_X then self.scrollX = math.min(PIC_X, self.scrollX + SCROLL_SPEED) + -- PlayCry is the tail of HoFDisplayMonInfo, which runs only after + -- HoFShowMonOrPlayer's two scrolls have both settled (#847) + if self.scrollX >= PIC_X then + local mon = self.game.save.party[self.index] + if mon then Sound.playCry(self.game.data, mon.species) end + end return end if self.phase == "fade" then @@ -277,6 +336,27 @@ function HallOfFame:drawPic(img, trueColor) end end +-- The back pic sweep (see the BACK_* constants): the same 7x7 window as the +-- front pic, one screen lower, cropped to the 28x28 ScaleSpriteByTwo keeps. +function HallOfFame:drawBackPic() + local img, trueColor = self:backPicFor() + if not img then return end + local w = math.min(img:getWidth(), BACK_CROP) + local h = math.min(img:getHeight(), BACK_CROP) + if not self.backQuad then + self.backQuad = love.graphics.newQuad(0, 0, w, h, img:getDimensions()) + end + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(img, self.backQuad, self.scrollX, BACK_Y, 0, + BACK_SCALE, BACK_SCALE) + -- same whole-screen palette exemption the front pic takes (#637) + if trueColor then + require("src.render.PaletteFX").markTrueColor(self.scrollX, BACK_Y, + w * BACK_SCALE, + h * BACK_SCALE) + end +end + -- HoFDisplayPlayerStats boxes + labels (player pic already on the right) function HallOfFame:drawPlayerStats() local save = self.game.save @@ -301,7 +381,9 @@ function HallOfFame:draw() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) - if self.phase == "mons" or self.phase == "fade" then + if self.phase == "back" then + self:drawBackPic() + elseif self.phase == "mons" or self.phase == "fade" then local mon = self.game.save.party[self.index] if mon then self:drawPic(self:spriteFor(mon.species), diff --git a/src/ui/IntroMovie.lua b/src/ui/IntroMovie.lua index 15f14be2..a7a0e197 100644 --- a/src/ui/IntroMovie.lua +++ b/src/ui/IntroMovie.lua @@ -1,31 +1,8 @@ --- Boot splash + attract movie, a faithful port of PlayIntro --- (engine/movie/intro.asm) and AnimateShootingStar (engine/movie/splash.asm) --- using the real extracted art (data/generated/field.lua `intro` manifest). --- --- Three frame-counted phases: --- 1. copyright card, 180 frames (intro.asm:311-312). --- 2. shooting star: 64 frames of empty letterbox (intro.asm:323-324), then --- the big star streaks down-left for 40 frames while the studio logo --- sits centered in the letterbox band (the GAME FREAK logo + letter --- row it replaces sat at (72,56)/(40,80); splash.asm:27-60, 211-228), --- the logo flashes 3x10 frames (splash.asm:72-82), 4 waves of small --- stars rain from the logo -- 6x24 frames, +1px every 3 frames, lower --- star blinking (splash.asm:97-146, 163-209) -- and a 40 frame hold --- (intro.asm:329-331). --- 3. the Gengar/Nidorino fight (PlayIntroScene, intro.asm:23-141), played --- from FIGHT_SCRIPT below: Music_IntroBattle starts, Gengar (56x56 BG --- pose from a gengar_N.tilemap, at tile 13,7 = x104,y56) scrolls left --- while Nidorino (48x48 OAM at x-8,y72) walks right, then the scripted --- hip/hop hops, Gengar's raise + slash lunge, Nidorino's dodge leap, --- retreat, crouch and final lunge, ending in a 24-frame fade to white --- (GBFadeOutToWhite, home/fade.asm:26-40). --- --- Any of A/B/START skips the whole movie (CheckForUserInterruption). --- Pops itself and calls onDone() when finished or skipped. All art loads --- through pcall and every missing graphic degrades to a text/rect --- fallback, so the movie stays headless-safe. +-- ..(engine/movie/intro.asm ln 8) +-- ..(engine/movie/splash.asm ln 27) local Font = require("src.render.Font") +local GameVersion = require("src.core.GameVersion") local Music = require("src.core.Music") local Sound = require("src.core.Sound") local Strings = require("src.core.Strings") @@ -76,15 +53,20 @@ local WAVE_FRAMES = 24 -- 8 substeps x 3 frames (splash.asm:186-209) local WAVES_END = WAVES_START + 6 * WAVE_FRAMES -- 4 waves + 2 empty local SPLASH_FRAMES = WAVES_END + 40 -- ld c, 40 (intro.asm:329-331) --- logo 16x24 at grid (10,9), letters row at grid y=12 cols 6..15 --- (GameFreakLogoOAMData, splash.asm:211-228; screen = grid*8, OAM offsets --- cancel) +-- ..(engine/movie/splash.asm ln 211) local LOGO_X, LOGO_Y = 72, 56 +local TEXT_X, TEXT_Y = 40, 80 + +-- CopyrightTextString (engine/movie/title.asm). Red/Blue years are +-- (c)'95.'96.'98; Yellow's sheet spells (c)1995-1999 and finishes the +-- last digit with NineTile (title screen). Intro originally overflows +-- into the font "A" tile for that digit; we draw NineTile so both +-- screens read 1999. +local COPY_PREFIX_RB = { 0, 1, 2, 1, 3, 1, 4 } +local COPY_PREFIX_YELLOW = { 0, 1, 2, 3, 1, 2 } +local COPY_NINTENDO = { 5, 6, 7, 8, 9, 10 } +local COPY_CREATURES = { 11, 12, 13, 14, 15, 16, 17, 18 } --- The studio logo (assets/logo/minilogo.png) stands in for both the --- GAME FREAK logo and its letter row, so it gets the whole band between --- the letterbox bars (y 32..112) down to where the star waves spawn --- (y=88): fit it inside this box, centered, aspect preserved. local STUDIO_BOX = { w = 128, h = 52, cx = 80, cy = 60 } -- the 4 waves of small stars: screen X positions, all spawning at y=88 @@ -155,10 +137,25 @@ function IntroMovie.new(game, onDone) self.studio = intro.studio or {} self.skipAll = intro.skip and true or false local function img(e) return tryImage(e and e.path) end - self.copyright = tryImage("assets/generated/title/copyright.png") - -- studio mark replaces the GAME FREAK logo + splash text entirely; the - -- extracted logo stays as the fallback if the asset is missing - self.studioLogo = tryImage(self.studio.logo or "assets/logo/minilogo.png") + local titleCfg = game.data.field and game.data.field.title or {} + self.copyright = img(titleCfg.copyright) + or tryImage("assets/generated/title/copyright.png") + self.copyQuads = {} + if self.copyright then + local iw, ih = self.copyright:getDimensions() + for t = 0, 18 do + self.copyQuads[t] = love.graphics.newQuad(t * 8, 0, 8, 8, iw, ih) + end + end + self.gfInc = img(titleCfg.gamefreakInc) + or tryImage("assets/generated/title/gamefreak_inc.png") + -- ..(pokeyellow engine/movie/title.asm CopyrightTextString / NineTile) + self.yellowCopy = GameVersion.isYellow() + or titleCfg.layout == "yellow_pikachu" + self.copyPrefix = self.yellowCopy and COPY_PREFIX_YELLOW or COPY_PREFIX_RB + self.nineImg = self.yellowCopy and ( + img(titleCfg.nine) or tryImage("assets/generated/title/nine.png")) or nil + self.studioLogo = tryImage(self.studio.logo) if self.studioLogo then self.studioLogo:setFilter("nearest", "nearest") local iw, ih = self.studioLogo:getDimensions() @@ -306,24 +303,12 @@ function IntroMovie:drawSplash() if self.studioLogo then love.graphics.draw(self.studioLogo, self.studioX, self.studioY, 0, self.studioScale, self.studioScale) - elseif self.logo then - love.graphics.draw(self.logo, LOGO_X, LOGO_Y) + else + if self.logo then love.graphics.draw(self.logo, LOGO_X, LOGO_Y) end + if self.gfText then love.graphics.draw(self.gfText, TEXT_X, TEXT_Y) end end love.graphics.setColor(1, 1, 1, 1) end - if t >= STAR_START and t < FLASH_START then - -- big star: from OAM (160,0) moving +4Y/-4X per frame - -- (GameFreakShootingStarOAMData + .bigStarLoop, splash.asm:32-60) - local n = t - STAR_START + 1 - local sx, sy = 152 - 4 * n, -16 + 4 * n - if self.bigStar then - love.graphics.draw(self.bigStar, sx, sy) - else - love.graphics.setColor(0, 0, 0, 1) - love.graphics.rectangle("fill", sx + 6, sy + 6, 4, 4) - love.graphics.setColor(1, 1, 1, 1) - end - end if t >= WAVES_START then -- small stars: wave w spawns at y=88 every 24 frames, everything falls -- +1px per 3-frame substep until the wave loop ends; the lower star in @@ -351,6 +336,18 @@ function IntroMovie:drawSplash() end end drawBars() + if t >= STAR_START and t < FLASH_START then + -- ..(engine/movie/splash.asm ln 32) + local n = t - STAR_START + 1 + local sx, sy = 152 - 4 * n, -16 + 4 * n + if self.bigStar then + love.graphics.draw(self.bigStar, sx, sy) + else + love.graphics.setColor(0, 0, 0, 1) + love.graphics.rectangle("fill", sx + 6, sy + 6, 4, 4) + love.graphics.setColor(1, 1, 1, 1) + end + end end function IntroMovie:drawFight() @@ -379,17 +376,48 @@ function IntroMovie:drawFight() end end +function IntroMovie:drawCopyPrefix(x, y) + for _, t in ipairs(self.copyPrefix) do + love.graphics.draw(self.copyright, self.copyQuads[t], x, y) + x = x + 8 + end + if self.nineImg then + love.graphics.draw(self.nineImg, x, y) + end +end + +function IntroMovie:drawCopyright() + if self.studio.card or self.studio.credit then + local card = self.studio.card or "" + local credit = self.studio.credit or "" + love.graphics.setColor(0, 0, 0, 1) + Font.draw(card, (160 - #card * 8) / 2, 64) + Font.draw(credit, (160 - #credit * 8) / 2, 80) + elseif self.copyright and self.gfInc then + local function row(seq, x, y) + for _, t in ipairs(seq) do + love.graphics.draw(self.copyright, self.copyQuads[t], x, y) + x = x + 8 + end + end + for _, y in ipairs({ 56, 72, 88 }) do self:drawCopyPrefix(16, y) end + row(COPY_NINTENDO, 80, 56) + row(COPY_CREATURES, 80, 72) + love.graphics.draw(self.gfInc, 80, 88) + else + love.graphics.setColor(0, 0, 0, 1) + Font.draw(Strings("Nintendo"), 80, 56) + Font.draw(Strings("Creatures inc."), 80, 72) + Font.draw(Strings("GAME FREAK inc."), 16, 88) + end + love.graphics.setColor(1, 1, 1, 1) +end + function IntroMovie:draw() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) if self.phase == 1 then - -- custom boot card (replaces the Nintendo / GAME FREAK copyright - -- card; no (c) glyph in the charmap, keep it ASCII-safe) - love.graphics.setColor(0, 0, 0, 1) - local credit = self.studio.credit or Strings("bois club") - Font.draw("2026", (160 - 4 * 8) / 2, 48) - Font.draw(credit, (160 - #credit * 8) / 2, 64) - Font.draw("bryanthaboi", (160 - 11 * 8) / 2, 80) + self:drawCopyright() elseif self.phase == 2 then self:drawSplash() else diff --git a/src/ui/ListMenu.lua b/src/ui/ListMenu.lua index 2f41dec5..b3856a0c 100644 --- a/src/ui/ListMenu.lua +++ b/src/ui/ListMenu.lua @@ -51,6 +51,7 @@ function ListMenu.new(game, title, items, opts) local self = setmetatable({}, ListMenu) self.game = game self.title = title + self.kind = opts.kind or title self.items = items self.index = 1 self.scroll = 0 @@ -237,8 +238,11 @@ function ListMenu:draw() if i == self.index then -- hollowIndex: a chosen row keeps the hollow '▷' left behind by -- pokered's PlaceUnfilledArrowMenuCursor (the old man demo's - -- auto A-press, home/list_menu.asm:89-91) - Font.drawCode((self.swapIndex == i or self.hollowIndex == i) + -- auto A-press, home/list_menu.asm:89-91). A swap-marked row does + -- NOT stay hollow under the cursor: PlaceMenuCursor writes '▶' + -- into the tilemap over the '▷' whenever the cursor sits there + -- (home/window.asm:184-185) and restores it on the way out (#814) + Font.drawCode(self.hollowIndex == i and Theme.cursorHollow or Theme.cursor, 8, y) end if self.swapIndex == i and i ~= self.index then diff --git a/src/ui/MoveLearnMenu.lua b/src/ui/MoveLearnMenu.lua index 222f7f1c..f32a60de 100644 --- a/src/ui/MoveLearnMenu.lua +++ b/src/ui/MoveLearnMenu.lua @@ -5,6 +5,7 @@ local Font = require("src.render.Font") local Strings = require("src.core.Strings") +local romText = require("src.core.RomText") local MoveLearnMenu = {} MoveLearnMenu.__index = MoveLearnMenu @@ -43,10 +44,13 @@ function MoveLearnMenu:enter() local mdef = game.data.moves[self.newMoveId] local name = self:monName() self.selecting = false + -- _TryingToLearnText is the whole exchange in pokered, delete prompt + -- included, so the extracted line carries all four slots at once game.stack:push(TextBox.new(game, - Strings("%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f", - name, mdef.name, name) .. - Strings("Delete an older\nmove to make room\vfor %s?", mdef.name), + romText(game.data, "_TryingToLearnText", + "%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f" + .. "Delete an older\nmove to make room\vfor %s?", + name, mdef.name, name, mdef.name), nil, { choice = function(yes) if yes then @@ -77,7 +81,8 @@ function MoveLearnMenu:update(dt) -- HMCantDeleteText, then back to the forget list local TextBox = require("src.render.TextBox") self.game.stack:push(TextBox.new(self.game, - Strings("HM techniques\ncan't be deleted!"))) + romText(self.game.data, "_HMCantDeleteText", + "HM techniques\ncan't be deleted!"))) return end local mdef = self.game.data.moves[self.newMoveId] @@ -97,7 +102,8 @@ function MoveLearnMenu:confirmAbandon() local mdef = game.data.moves[self.newMoveId] self.selecting = false game.stack:push(TextBox.new(game, - Strings("Abandon learning\n%s?", mdef.name), nil, { + romText(game.data, "_AbandonLearningText", + "Abandon learning\n%s?", mdef.name), nil, { choice = function(yes) if yes then self:finish(false) else self:enter() end end, @@ -113,12 +119,17 @@ function MoveLearnMenu:finish(learned) game.stack:pop() local msg if learned then - -- OneTwoAndText/PoofText/ForgotAndText - msg = Strings("1, 2 and... Poof!\f%s forgot\n%s!\fAnd...\f%s learned\n%s!", - name, self.forgot, name, mdef.name) + -- pokered pages this as four texts in a row; _ForgotAndText carries + -- the "And..." tail + msg = romText(game.data, "_OneTwoAndText", "1, 2 and...") + .. romText(game.data, "_PoofText", " Poof!") + .. romText(game.data, "_ForgotAndText", + "\f%s forgot\n%s!\fAnd...", name, self.forgot) + .. "\f" .. romText(game.data, "_LearnedMove1Text", + "%s learned\n%s!", name, mdef.name) else - -- DidNotLearnText - msg = Strings("%s\ndid not learn\v%s!", name, mdef.name) + msg = romText(game.data, "_DidNotLearnText", + "%s\ndid not learn\v%s!", name, mdef.name) end game.stack:push(TextBox.new(game, msg, function() if self.onDone then self.onDone(learned) end diff --git a/src/ui/NamingScreen.lua b/src/ui/NamingScreen.lua index 6073ca44..c35f4b9d 100644 --- a/src/ui/NamingScreen.lua +++ b/src/ui/NamingScreen.lua @@ -99,7 +99,20 @@ end function NamingScreen:confirm() local name = table.concat(self.glyphs) if name == "" then - name = (self.presets and self.presets[1]) or self.default or "A" + -- An empty confirm (START, or the ED cell with nothing typed) must not + -- invent a letter (#833). DisplayNamingScreen seeds wStringBuffer with + -- '@' (engine/menus/naming_screen.asm) and every caller checks that first + -- byte: AskName falls through to .declinedNickname, copying the species + -- name over the nick slot -- vanilla's "un-nicknamed", which this port + -- models as mon.nickname == nil (src/save_convert/GenSave.lua), so + -- evolution can still rename the mon. DisplayNameRaterScreen takes + -- .playerCancelled and keeps the old nick, which is why an explicit + -- opts.default still wins here. Player/rival naming + -- (oak_speech2.asm ChoosePlayerName) re-opens on '@' and never accepts an + -- empty result; the port keeps its preset fallback for that. + -- Contract for callers: "" means NO name -- BattleState:askNicknameUI and + -- Commands.give_pokemon both guard on #name > 0 before setting nickname. + name = (self.presets and self.presets[1]) or self.default or "" end Sound.play(self.game.data, "Press_AB") self.game.stack:pop() diff --git a/src/ui/OakSpeech.lua b/src/ui/OakSpeech.lua index 9803e12f..8c5c08b5 100644 --- a/src/ui/OakSpeech.lua +++ b/src/ui/OakSpeech.lua @@ -265,7 +265,8 @@ function OakSpeech.new(game, onDone) or "assets/generated/intro/shrink2.png") -- RedSprite: the walking sprite the pic shrinks into (frame 0 = -- standing, facing down) - local red = game.data.sprites and game.data.sprites.SPRITE_RED + local playerSprites = (game.data.field and game.data.field.playerSprites) or {} + local red = game.data.sprites and game.data.sprites[playerSprites.walk or "SPRITE_RED"] or game.data.sprites.SPRITE_RED self.walkSheet = tryImage(red and red.image) return self end diff --git a/src/ui/OptionsMenu.lua b/src/ui/OptionsMenu.lua index 56379f7f..cca24cd9 100644 --- a/src/ui/OptionsMenu.lua +++ b/src/ui/OptionsMenu.lua @@ -392,14 +392,35 @@ local function buildRows(game) return true end }, -- fast-forward the logic clock only; music and sfx keep their tempo - -- (src/core/GameSpeed.lua), so this is safe to leave on - { id = "speed", label = Strings("GAME SPEED"), + -- (src/core/GameSpeed.lua), so this is safe to leave on. Per-category + -- (RFC 0007): overworld walking, battle turns and menu navigation each + -- cycle their own multiplier -- GameSpeed.CATEGORIES is the single + -- source of truth for which three rows exist. + { id = "speedOverworld", label = Strings("OVERWORLD SPEED"), value = function(g) - return GameSpeed.levelLabel(g.save.options.speed) + return GameSpeed.levelLabel(g.save.options.speedOverworld) end, step = function(g, dir) local o = g.save.options - o.speed = GameSpeed.cycle(o.speed, dir) + o.speedOverworld = GameSpeed.cycle(o.speedOverworld, dir) + return true + end }, + { id = "speedBattle", label = Strings("BATTLE SPEED"), + value = function(g) + return GameSpeed.levelLabel(g.save.options.speedBattle) + end, + step = function(g, dir) + local o = g.save.options + o.speedBattle = GameSpeed.cycle(o.speedBattle, dir) + return true + end }, + { id = "speedMenu", label = Strings("MENU SPEED"), + value = function(g) + return GameSpeed.levelLabel(g.save.options.speedMenu) + end, + step = function(g, dir) + local o = g.save.options + o.speedMenu = GameSpeed.cycle(o.speedMenu, dir) return true end }, -- the manager's discoverable home (18-mod-manager-ux); inert until @@ -437,6 +458,25 @@ local function buildRows(game) require("src.core.TouchControls"):applyOptions(o) return true end }, + -- Haptic feedback for on-screen pad presses (#806): OFF / LIGHT / + -- MEDIUM / HEAVY, where the intensity is a vibration duration -- + -- love.system.vibrate takes nothing else. Hidden with TOUCH PAD below, + -- since the only thing that buzzes is a virtual button press. + { id = "haptics", label = Strings("VIBRATION"), + value = function(g) + local TC = require("src.core.TouchControls") + return Strings(TC.hapticLabel(g.save.options.haptics)) + end, + step = function(g, dir) + local o = g.save.options + local TC = require("src.core.TouchControls") + o.haptics = TC.cycleHaptics(o.haptics, dir) + TC:applyOptions(o) + -- sample the level being selected: stepping the row is the only way + -- to compare LIGHT against HEAVY without leaving the menu + TC.buzz(o.haptics) + return true + end }, } -- issue #136: hide GBC FX on Android/iOS -- the present shader soft-bricks if not GBCFX.isSupported() then @@ -454,8 +494,10 @@ local function buildRows(game) end rows = filtered end - -- TOUCH PAD only where the overlay can appear (mobile, or desktop with - -- POKEPORT_TOUCH=1). POKEPORT_TOUCH=0 forces it off everywhere. + -- TOUCH PAD and VIBRATION only where the overlay can appear (mobile, or + -- desktop with POKEPORT_TOUCH=1). POKEPORT_TOUCH=0 forces it off + -- everywhere. VIBRATION rides the same gate: nothing else in the port + -- vibrates, and love.system.vibrate is a no-op on desktop anyway. do local env = os.getenv("POKEPORT_TOUCH") local osName = love.system and love.system.getOS and love.system.getOS() @@ -464,7 +506,9 @@ local function buildRows(game) if not show then local filtered = {} for _, row in ipairs(rows) do - if row.id ~= "touchControls" then filtered[#filtered + 1] = row end + if row.id ~= "touchControls" and row.id ~= "haptics" then + filtered[#filtered + 1] = row + end end rows = filtered end @@ -562,8 +606,14 @@ function OptionsMenu:update(dt) end function OptionsMenu:draw() + -- Through Strings, like every other label on this menu. CANCEL is + -- appended AFTER the rows hook (see the header), which is what keeps a mod + -- from orphaning the exit -- but it also means a translation mod never sees + -- this string, and cannot: there is no row for it to rewrite. So the one + -- word a Spanish player could not read on a fully translated OPTIONS menu + -- was the way out of it. OptionRows.draw(self.game, self.rows, self.index, self.scroll or 0, - "CANCEL", #self.rows + 1) + Strings("CANCEL"), #self.rows + 1) end return OptionsMenu diff --git a/src/ui/PartyMenu.lua b/src/ui/PartyMenu.lua index 09a95f56..33df1d36 100644 --- a/src/ui/PartyMenu.lua +++ b/src/ui/PartyMenu.lua @@ -107,6 +107,23 @@ PartyMenu.iconFrames = { PIKACHU = { rest = 0, alt = 3 }, -- Yellow: PikachuSprite tile 0 <-> 12 } +local function gridIndex(index, count, direction) + if count < 1 then return nil end + local row, col = math.floor((index - 1) / 2), (index - 1) % 2 + if direction == "left" or direction == "right" then + local other = row * 2 + (1 - col) + 1 + return other <= count and other or index + end + local step = direction == "up" and -1 or direction == "down" and 1 + if not step then return nil end + local rows = math.ceil(count / 2) + for offset = 1, rows do + local other = ((row + step * offset) % rows) * 2 + col + 1 + if other <= count then return other end + end + return index +end + -- Which 16x16 frame of `name`'s sheet to draw; `ih` (sheet pixel -- height) only matters for the fallback, which keeps the old uniform -- behavior for icons outside the table (BALL/HELIX y-bob instead). @@ -155,7 +172,11 @@ local function obpIcon(path) return love.graphics.newImage(id) end -local function drawIcon(game, mon, x, y, selected, counter) +-- `forceAlt` picks the second animation frame outright, for callers with no +-- selection cursor of their own: Trade_AnimCircledMon +-- (engine/movie/trade.asm) cycles the party sprite's two frames the whole +-- time the mon rides the link cable (#750). +function PartyMenu.drawIcon(game, mon, x, y, selected, counter, forceAlt) local icons = game.data.icons if not icons then return end local def = game.data.pokemon[mon.species] @@ -202,7 +223,7 @@ local function drawIcon(game, mon, x, y, selected, counter) end local img = iconImages[key] if not img then return end - local alt = false + local alt = forceAlt or false if selected then local px = math.floor(mon.hp * 48 / math.max(1, mon.stats.hp)) local speed = px >= 27 and 5 or px >= 10 and 16 or 32 @@ -237,13 +258,24 @@ local function drawIcon(game, mon, x, y, selected, counter) -- whatever size the file is (unchanged path) love.graphics.draw(img, x, y) end + return true end function PartyMenu.new(game, opts) opts = opts or {} local self = setmetatable({}, PartyMenu) self.game = game - self.index = 1 + -- PartyMenuInit (home/pokemon.asm) seeds the cursor from + -- wPartyAndBillsPCSavedMenuItem rather than from zero, and + -- HandlePartyMenuInput writes wCurrentMenuItem back into it on every + -- input, so the party cursor survives closing and reopening the menu. + -- Only a battle clears it -- InitBattleVariables and end_of_battle.asm + -- both zero the byte, which BattleState mirrors. The clamp covers a + -- party that shrank (deposit / release) while the saved index was + -- pointing past the end. #768 + local count = #(opts.party or (game.save and game.save.party) or {}) + self.index = math.min(math.max(1, game.partyMenuSavedIndex or 1), + math.max(1, count)) self.onSwitch = opts.onSwitch self.onCancel = opts.onCancel self.pickOnly = opts.pickOnly @@ -292,6 +324,13 @@ function PartyMenu:close() if self.game.stack:top() == self then self.game.stack:pop() end end +function PartyMenu:gridNavigation() + if not self.battle + or not Runtime.wantsHook("ui.party.grid_navigation") then return false end + return Runtime.call("ui.party.grid_navigation", function() return false end, + self) == true +end + function PartyMenu:update(dt) -- icon animation counter; 320 = a whole cycle at every HP speed self.blink = ((self.blink or 0) + 1) % 320 @@ -516,10 +555,23 @@ function PartyMenu:update(dt) return end - if input:wasPressed("up") then + local grid + if self:gridNavigation() then + local direction = input:wasPressed("left") and "left" + or input:wasPressed("right") and "right" + or input:wasPressed("up") and "up" + or input:wasPressed("down") and "down" + grid = gridIndex(self.index, #party, direction) + end + if grid then + self.index = grid + self.game.partyMenuSavedIndex = self.index + elseif input:wasPressed("up") then self.index = self.index > 1 and self.index - 1 or math.max(1, #party) + self.game.partyMenuSavedIndex = self.index -- HandlePartyMenuInput #768 elseif input:wasPressed("down") then self.index = self.index < #party and self.index + 1 or 1 + self.game.partyMenuSavedIndex = self.index -- HandlePartyMenuInput #768 elseif input:wasPressed("b") then self.game.stack:pop() if self.onCancel then self.onCancel() end @@ -567,10 +619,17 @@ function PartyMenu:update(dt) { label = Strings("STATS"), action = "stats" }, { label = Strings("CANCEL"), action = "cancel" } } else - -- STATS/SWITCH plus this mon's field moves (start_sub_menus.asm - -- builds the same dynamic list) - items = { { label = Strings("STATS"), action = "stats" }, - { label = Strings("SWITCH"), action = "switch" } } + -- This mon's field moves FIRST, then STATS/SWITCH + -- (start_sub_menus.asm builds the same dynamic list). The order is + -- load bearing: DisplayFieldMoveMonMenu (engine/menus/text_box.asm) + -- grows the box upward one row per field move and prints the field + -- move names ABOVE PokemonMenuEntries ("STATS/SWITCH/CANCEL"), and + -- StartMenu_Pokemon .choseOutOfBattleMove indexes wFieldMoves with + -- menu items 0..n-1 while STATS/SWITCH sit at the bottom of the + -- list. GetMonFieldMoves walks wPartyMon1Moves in slot order, so + -- the field moves keep the mon's move-list order -- which the loop + -- below already does. #768 + items = {} -- Field moves (HMs/TMs) are usable out of battle even when the mon -- is fainted -- Gen 1 does not require HP for Cut/Fly/Surf/etc. -- Battle still excludes this list via `not self.battle`. Softboiled @@ -615,6 +674,10 @@ function PartyMenu:update(dt) end end end + -- PokemonMenuEntries always closes the list, under the field moves + -- (text_box.asm .donePrintingNames). #768 + items[#items + 1] = { label = Strings("STATS"), action = "stats" } + items[#items + 1] = { label = Strings("SWITCH"), action = "switch" } end local ctx = { battle = self.battle, overworld = ow } local hooked = Runtime.call("ui.party.submenu", sameItems, @@ -685,7 +748,7 @@ function PartyMenu:draw() local def = self.game.data.pokemon[mon.species] local y = PartyMenu.entryY(i) love.graphics.setColor(1, 1, 1, 1) - drawIcon(self.game, mon, 8, y, i == self.index, self.blink or 0) + PartyMenu.drawIcon(self.game, mon, 8, y, i == self.index, self.blink or 0) love.graphics.setColor(0, 0, 0, 1) Font.draw(mon.nickname or def.name, 24, y) -- level at column 13 ( tile + digits, PrintLevel) AND the @@ -748,8 +811,10 @@ function PartyMenu:draw() if i == self.index then Font.drawCode(Theme.cursor, 0, cursorY) end - if i == self.swapFrom or i == self.softboiledFrom then - Font.drawCode(Theme.cursorHollow, 0, cursorY) -- the unfilled swap arrow + -- the unfilled swap arrow; the filled cursor replaces it in the tilemap + -- when they share a row (PlaceMenuCursor, home/window.asm:184-185) (#814) + if (i == self.swapFrom or i == self.softboiledFrom) and i ~= self.index then + Font.drawCode(Theme.cursorHollow, 0, cursorY) end end if self.swapFrom then diff --git a/src/ui/PlayerPC.lua b/src/ui/PlayerPC.lua index 5e31ca39..bc951f98 100644 --- a/src/ui/PlayerPC.lua +++ b/src/ui/PlayerPC.lua @@ -44,7 +44,7 @@ local function askQuantity(game, list, count, id, cb) cb(1) return end - list.footer = "How many?" + list.footer = Strings("How many?") local QuantityBox = require("src.ui.QuantityBox") game.stack:push(QuantityBox.new(game, { max = count, @@ -72,6 +72,7 @@ end local function withdraw(game) local pc = game.save.pcItems game.stack:push(ListMenu.new(game, "WITHDRAW ITEM", buildItems(game, pc), { + kind = "pc_item_withdraw", messageBox = true, noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) @@ -110,6 +111,7 @@ local function deposit(game) if not Bag.isBadge(id) then depositable[id] = count end end game.stack:push(ListMenu.new(game, "DEPOSIT ITEM", buildItems(game, depositable), { + kind = "pc_item_deposit", messageBox = true, noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) @@ -131,6 +133,7 @@ end local function toss(game) local pc = game.save.pcItems game.stack:push(ListMenu.new(game, "TOSS ITEM", buildItems(game, pc), { + kind = "pc_item_toss", messageBox = true, noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) @@ -161,8 +164,14 @@ local function toss(game) })) end -function PlayerPC.new(game) +-- opts.direct marks the bedroom PC (OpenRedsPC), the one entry outside the main menu +function PlayerPC.new(game, opts) game.save.pcItems = game.save.pcItems or {} + -- ExitPlayerPC (players_pc.asm) rings SFX_TURN_OFF_PC only while + -- BIT_USING_GENERIC_PC is clear (#960) + local logOff = function() + if opts and opts.direct then Sound.play(game.data, "Turn_Off_PC") end + end return Menu.new(game, { -- keepOpen so B in the item lists returns here instead of dropping the -- whole PC session (players_pc.asm re-shows the PC menu); same pattern @@ -170,10 +179,10 @@ function PlayerPC.new(game) { label = Strings("WITHDRAW ITEM"), keepOpen = true, onSelect = function() withdraw(game) end }, { label = Strings("DEPOSIT ITEM"), keepOpen = true, onSelect = function() deposit(game) end }, { label = Strings("TOSS ITEM"), keepOpen = true, onSelect = function() toss(game) end }, - { label = Strings("LOG OFF") }, + { label = Strings("LOG OFF"), onSelect = logOff }, -- silent PC session (BIT_NO_MENU_BUTTON_SOUND); players_pc.asm -- PlayersPCMenu TextBoxBorder (0,0) b=8 c=14 → 16x10 - }, { tx = 0, ty = 0, tw = 16, th = 10, noSound = true }) + }, { tx = 0, ty = 0, tw = 16, th = 10, noSound = true, onCancel = logOff }) end return PlayerPC diff --git a/src/ui/StartMenu.lua b/src/ui/StartMenu.lua index 7dec6aef..b43ff715 100644 --- a/src/ui/StartMenu.lua +++ b/src/ui/StartMenu.lua @@ -66,14 +66,24 @@ function StartMenu.new(game) panel .. Strings("\fWould you like to\nSAVE the game?"), nil, { choice = function(yes) if not yes then return end - -- "Now saving..." beat before the write (save.asm - -- NowSavingString), then GameSavedText + SFX_SAVE + -- SaveMenu .save (engine/menus/save.asm:164-181): "Now saving..." + -- is a bare PlaceString held by DelayFrames 120, then GameSavedText, + -- which ends in `done` and so never reaches TX_PROMPT_BUTTON. + -- Neither page takes a button press (#765); the second waits on + -- SFX_SAVE (PlaySoundWaitForCurrent + WaitForSoundToFinish) and then + -- DelayFrames 30. The write itself is invisible either side of the + -- "Now saving..." hold, so it stays on that box's onDone. game.stack:push(TextBox.new(game, Strings("Now saving..."), function() game:writeSave() - require("src.core.Sound").play(game.data, "Save") game.stack:push(TextBox.new(game, - Strings("%s saved\nthe game!", game.save.player.name or "RED"))) - end)) + Strings("%s saved\nthe game!", game.save.player.name or "RED"), + nil, { auto = { + sound = function() + return require("src.core.Sound").play(game.data, "Save") + end, + delay = 30, + } })) + end, { auto = { delay = 120 } })) end, })) end }) diff --git a/src/ui/SurfingMinigame.lua b/src/ui/SurfingMinigame.lua index 96659568..6d0711da 100644 --- a/src/ui/SurfingMinigame.lua +++ b/src/ui/SurfingMinigame.lua @@ -3,14 +3,21 @@ -- spin in the air and land flat for points; a crooked landing wipes out -- and ends the run. The scene is built from the real ROM sheets -- (gfx/surfing_pikachu.asm, ripped at import to --- assets/generated/minigame/surf_1a/1b.png): the scalloped water tiles, --- the beach with the palm and the doll hut, the "HP:" score strip with --- the sheet digits, the cloud, and the OAM Pikachu poses -- the air --- tricks quantize to the sheet's rotation frames like the original's --- sprite anims, instead of free-rotating one pose. The original drew --- the big wave with per-scanline scroll tricks (wLYOverrides); here the --- crest profile is a curve filled with the sheet's foam/shade tiles. --- Score model keeps the original's shape (ride ticks + airtime + full +-- assets/generated/minigame/surf_1a/1b.png). +-- +-- #726: the background is the original's own metatile scroller, not a +-- procedural stand-in. SurfingPikachu1Graphics1 is copied to vChars2 +-- with LCDC's BG char base unset, so BG tile id N is simply tile N of +-- surf_1a (5 tiles per row). SurfingMinigame_ScrollAndGenerateBGMap +-- walks a jumptable of wave states, each of which hands back one +-- 8-metatile column (2x2 tiles each, so 16px wide by the 128px the BG +-- shows above the HP window) plus the two Pikachu ride heights for that +-- column. Porting those tables verbatim is what makes the water read as +-- water: the earlier stand-in tiled the wave-face tiles ($02/$07) over +-- the whole sea and drew the swell as a LOVE ellipse, which is the +-- "messed up graphics" in the report. +-- +-- Score model keeps the port's shape (ride ticks + airtime + full -- rotations); high score persists in save.surfingHighScore for the -- beach-house printer. @@ -23,10 +30,13 @@ local SurfingMinigame = {} SurfingMinigame.__index = SurfingMinigame SurfingMinigame.isOpaque = true -local PIKA_X = 44 -- fixed screen x while riding -local RUN_DISTANCE = 3200 -- scroll px from paddle-out to the beach +-- SURFING_MINIGAME_CENTER_X/FLAT_WATER_Y (surfing_pikachu.asm:1-2) are OAM +-- coordinates; screen x/y are those minus OAM_X_OFS/OAM_Y_OFS. +local FLAT_WATER_Y = 116 +local PIKA_X = 68 -- fixed screen x while riding (center 80, 24px pose) +local RUN_DISTANCE = 3072 -- 24 sections of 8 metatile columns local GRAVITY = 0.14 -local HORIZON = 24 -- sea starts under the sky strip +local BG_HEIGHT = 128 -- rows the BG shows; the HP window covers the rest -- surf_1b quads: {x, y, w, h} in sheet pixels (pose pitch is 24x24) local B = { @@ -50,18 +60,158 @@ local POSES = { [315] = { 0, 0, 24, 24 }, -- tail down } --- surf_1a quads (BG tiles) -local A = { - scallop = { 16, 0, 8, 8 }, -- open-water pattern, row A - scallop2 = { 16, 8, 8, 8 }, -- row B variant - shade = { 8, 16, 8, 8 }, -- gray dither, wave belly - lip = { 24, 0, 8, 8 }, -- foam curl for the crest edge - palm = { 8, 32, 8, 8 }, -- palm fronds - beach = { 24, 32, 16, 8 }, -- black shore silhouette - hut = { 8, 40, 16, 8 }, -- the Pikachu doll hut on the sand - hp = { 20, 40, 20, 8 }, -- "HP:" score label +-- surf_1a is 5 tiles wide, so BG tile id N lives at (N%5*8, N/5*8). The +-- only quad the scene needs by hand is the window's "HP:" label, which +-- straddles a tile boundary in the sheet. +local HP_LABEL = { 20, 40, 20, 8 } + +-- SurfingMinigame_BGMetatileTable (surfing_pikachu.asm): 2x2 tiles each, +-- stored top-left, top-right, bottom-left, bottom-right. +local BG_METATILES = { + [0x00] = { 0x00, 0x00, 0x00, 0x00 }, -- sky block (blank) + [0x01] = { 0x0b, 0x0b, 0x0b, 0x0b }, -- open water + [0x02] = { 0x0b, 0x02, 0x02, 0x06 }, + [0x03] = { 0x03, 0x0b, 0x07, 0x03 }, + [0x04] = { 0x06, 0x06, 0x06, 0x06 }, + [0x05] = { 0x07, 0x07, 0x07, 0x07 }, + [0x06] = { 0x06, 0x04, 0x04, 0x08 }, + [0x07] = { 0x05, 0x07, 0x08, 0x05 }, + [0x08] = { 0x0b, 0x0b, 0x11, 0x12 }, + [0x09] = { 0x0b, 0x0b, 0x13, 0x03 }, + [0x0a] = { 0x14, 0x12, 0x04, 0x08 }, + [0x0b] = { 0x13, 0x07, 0x08, 0x05 }, + [0x0c] = { 0x06, 0x14, 0x06, 0x14 }, -- unused, identical to 11 + [0x0d] = { 0x13, 0x07, 0x13, 0x07 }, + [0x0e] = { 0x08, 0x08, 0x08, 0x08 }, -- solid blue + [0x0f] = { 0x14, 0x12, 0x14, 0x12 }, + [0x10] = { 0x0b, 0x11, 0x02, 0x14 }, + [0x11] = { 0x06, 0x14, 0x06, 0x14 }, + [0x12] = { 0x0c, 0x0c, 0x0d, 0x0d }, -- beach top block + [0x13] = { 0x0d, 0x0d, 0x0d, 0x0d }, -- beach sand block + [0x14] = { 0x0e, 0x0f, 0x10, 0x0b }, -- beach shore block + [0x15] = { 0x12, 0x13, 0x12, 0x13 }, } +-- SurfingMinigameWavePattern00..1C plus SurfingMinigameBeachPattern: one +-- column of 8 metatiles, top to bottom. +local WAVE_PATTERNS = { + [0x00] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01 }, + [0x01] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x06 }, + [0x02] = { 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x06, 0x0e }, + [0x03] = { 0x00, 0x00, 0x00, 0x10, 0x11, 0x06, 0x0e, 0x0e }, + [0x04] = { 0x00, 0x00, 0x00, 0x15, 0x15, 0x0e, 0x0e, 0x0e }, + [0x05] = { 0x00, 0x00, 0x00, 0x03, 0x05, 0x07, 0x0e, 0x0e }, + [0x06] = { 0x00, 0x00, 0x00, 0x01, 0x03, 0x05, 0x07, 0x0e }, + [0x07] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x05, 0x07 }, + [0x08] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x06 }, + [0x09] = { 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x06, 0x0e }, + [0x0a] = { 0x00, 0x00, 0x00, 0x08, 0x0f, 0x0a, 0x0e, 0x0e }, + [0x0b] = { 0x00, 0x00, 0x00, 0x09, 0x0d, 0x0b, 0x0e, 0x0e }, + [0x0c] = { 0x00, 0x00, 0x00, 0x01, 0x03, 0x05, 0x07, 0x0e }, + [0x0d] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x05, 0x07 }, + [0x0e] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x06 }, + [0x0f] = { 0x00, 0x00, 0x00, 0x01, 0x10, 0x11, 0x06, 0x0e }, + [0x10] = { 0x00, 0x00, 0x00, 0x01, 0x15, 0x15, 0x0e, 0x0e }, + [0x11] = { 0x00, 0x00, 0x00, 0x01, 0x03, 0x05, 0x07, 0x0e }, + [0x12] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x05, 0x07 }, + [0x13] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x06 }, + [0x14] = { 0x00, 0x00, 0x00, 0x01, 0x08, 0x0f, 0x0a, 0x0e }, + [0x15] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0x0d, 0x0b, 0x0e }, + [0x16] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x05, 0x07 }, + [0x17] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x10, 0x11, 0x06 }, + [0x18] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x15, 0x15, 0x0e }, + [0x19] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x05, 0x07 }, + [0x1a] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0f, 0x0a }, + [0x1b] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x09, 0x0d, 0x0b }, + [0x1c] = { 0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, + beach = { 0x00, 0x00, 0x00, 0x12, 0x13, 0x13, 0x13, 0x13 }, +} + +-- RunSurfingMinigameRoutine's .WaveFunctions jumptable, flattened: +-- { pattern, left ride height, right ride height, what to do next }. +-- next: 0 = advance one state, 1 = reset to the chooser, 2 = stay put. +-- State 0 is SurfingMinigame_ChooseNextWaveSequence and is handled in +-- code because it rolls Random and forces the Big Kahuna near the goal. +local ADV, RESET, STAY = 0, 1, 2 +local WAVE_STEPS = { + [0x01] = { 0x13, 116, 108, ADV }, [0x02] = { 0x14, 100, 92, ADV }, + [0x03] = { 0x15, 92, 92, ADV }, [0x04] = { 0x16, 100, 108, ADV }, + [0x05] = { 0x00, 116, 116, ADV }, [0x06] = { 0x17, 116, 108, ADV }, + [0x07] = { 0x18, 100, 100, ADV }, [0x08] = { 0x19, 100, 108, ADV }, + [0x09] = { 0x00, 116, 116, ADV }, [0x0a] = { 0x00, 116, 116, ADV }, + [0x0b] = { 0x00, 116, 116, ADV }, [0x0c] = { 0x00, 116, 116, ADV }, + [0x0d] = { 0x00, 116, 116, RESET }, + [0x0e] = { 0x08, 116, 108, ADV }, [0x0f] = { 0x09, 100, 92, ADV }, + [0x10] = { 0x0a, 84, 76, ADV }, [0x11] = { 0x0b, 76, 76, ADV }, + [0x12] = { 0x0c, 84, 92, ADV }, [0x13] = { 0x0d, 100, 108, ADV }, + [0x14] = { 0x00, 116, 116, ADV }, [0x15] = { 0x00, 116, 116, ADV }, + [0x16] = { 0x00, 116, 116, ADV }, [0x17] = { 0x00, 116, 116, ADV }, + [0x18] = { 0x00, 116, 116, ADV }, [0x19] = { 0x00, 116, 116, RESET }, + [0x1a] = { 0x0e, 116, 108, ADV }, [0x1b] = { 0x0f, 100, 92, ADV }, + [0x1c] = { 0x10, 84, 84, ADV }, [0x1d] = { 0x11, 84, 92, ADV }, + [0x1e] = { 0x12, 100, 108, ADV }, [0x1f] = { 0x0e, 116, 108, ADV }, + [0x20] = { 0x0f, 100, 92, ADV }, [0x21] = { 0x10, 84, 84, ADV }, + [0x22] = { 0x11, 84, 92, ADV }, [0x23] = { 0x12, 100, 108, ADV }, + [0x24] = { 0x00, 116, 116, ADV }, [0x25] = { 0x00, 116, 116, ADV }, + [0x26] = { 0x00, 116, 116, ADV }, [0x27] = { 0x00, 116, 116, ADV }, + [0x28] = { 0x00, 116, 116, RESET }, + [0x29] = { 0x13, 116, 108, ADV }, [0x2a] = { 0x14, 100, 92, ADV }, + [0x2b] = { 0x15, 92, 92, ADV }, [0x2c] = { 0x16, 100, 108, ADV }, + [0x2d] = { 0x00, 116, 116, ADV }, [0x2e] = { 0x00, 116, 116, ADV }, + [0x2f] = { 0x00, 116, 116, ADV }, [0x30] = { 0x00, 116, 116, ADV }, + [0x31] = { 0x00, 116, 116, RESET }, + [0x32] = { 0x17, 116, 108, ADV }, [0x33] = { 0x18, 100, 100, ADV }, + [0x34] = { 0x19, 100, 108, ADV }, [0x35] = { 0x17, 116, 108, ADV }, + [0x36] = { 0x18, 100, 100, ADV }, [0x37] = { 0x19, 100, 108, ADV }, + [0x38] = { 0x17, 116, 108, ADV }, [0x39] = { 0x18, 100, 100, ADV }, + [0x3a] = { 0x19, 100, 108, ADV }, [0x3b] = { 0x00, 116, 116, ADV }, + [0x3c] = { 0x00, 116, 116, ADV }, [0x3d] = { 0x00, 116, 116, ADV }, + [0x3e] = { 0x00, 116, 116, ADV }, [0x3f] = { 0x00, 116, 116, RESET }, + [0x40] = { 0x1a, 116, 108, ADV }, [0x41] = { 0x1b, 108, 108, ADV }, + [0x42] = { 0x0e, 116, 108, ADV }, [0x43] = { 0x0f, 100, 92, ADV }, + [0x44] = { 0x10, 84, 84, ADV }, [0x45] = { 0x11, 84, 92, ADV }, + [0x46] = { 0x12, 100, 108, ADV }, [0x47] = { 0x1a, 116, 108, ADV }, + [0x48] = { 0x1b, 108, 108, ADV }, [0x49] = { 0x00, 116, 116, ADV }, + [0x4a] = { 0x00, 116, 116, ADV }, [0x4b] = { 0x00, 116, 116, ADV }, + [0x4c] = { 0x00, 116, 116, RESET }, + [0x4d] = { 0x08, 116, 108, ADV }, [0x4e] = { 0x09, 100, 92, ADV }, + [0x4f] = { 0x0a, 84, 76, ADV }, [0x50] = { 0x0b, 76, 76, ADV }, + [0x51] = { 0x0c, 84, 92, ADV }, [0x52] = { 0x0d, 100, 108, ADV }, + [0x53] = { 0x00, 116, 116, ADV }, [0x54] = { 0x1a, 116, 108, ADV }, + [0x55] = { 0x1b, 108, 108, ADV }, [0x56] = { 0x1a, 116, 108, ADV }, + [0x57] = { 0x1b, 108, 108, ADV }, [0x58] = { 0x00, 116, 116, ADV }, + [0x59] = { 0x00, 116, 116, ADV }, [0x5a] = { 0x00, 116, 116, ADV }, + [0x5b] = { 0x00, 116, 116, RESET }, + [0x5c] = { 0x0e, 116, 108, ADV }, [0x5d] = { 0x0f, 100, 92, ADV }, + [0x5e] = { 0x10, 84, 84, ADV }, [0x5f] = { 0x11, 84, 92, ADV }, + [0x60] = { 0x12, 100, 108, ADV }, [0x61] = { 0x13, 116, 108, ADV }, + [0x62] = { 0x14, 100, 92, ADV }, [0x63] = { 0x15, 92, 92, ADV }, + [0x64] = { 0x16, 100, 108, ADV }, [0x65] = { 0x00, 116, 116, ADV }, + [0x66] = { 0x00, 116, 116, ADV }, [0x67] = { 0x00, 116, 116, ADV }, + [0x68] = { 0x00, 116, 116, ADV }, [0x69] = { 0x00, 116, 116, RESET }, + -- 6a..71: the forced "Big Kahuna" finale; 71 holds flat water (its + -- loader just rets, so the state never advances on its own). + [0x6a] = { 0x01, 116, 108, ADV }, [0x6b] = { 0x02, 100, 92, ADV }, + [0x6c] = { 0x03, 84, 76, ADV }, [0x6d] = { 0x04, 68, 68, ADV }, + [0x6e] = { 0x05, 68, 76, ADV }, [0x6f] = { 0x06, 84, 92, ADV }, + [0x70] = { 0x07, 100, 108, ADV }, [0x71] = { 0x00, 116, 116, STAY }, + -- 72..7b: the run-out to the beach, entered by hand at the goal + -- (SurfingMinigame_WaitToShowResults writes $72). + [0x72] = { 0x00, 116, 116, ADV }, [0x73] = { 0x1c, 116, 116, ADV }, + [0x74] = { "beach", 116, 116, ADV }, [0x75] = { "beach", 116, 116, ADV }, + [0x76] = { "beach", 116, 116, ADV }, [0x77] = { "beach", 116, 116, ADV }, + [0x78] = { "beach", 116, 116, ADV }, [0x79] = { "beach", 116, 116, ADV }, + [0x7a] = { "beach", 116, 116, ADV }, [0x7b] = { "beach", 116, 116, RESET }, +} +-- SurfingMinigame_WaveSequenceStarts +local SEQ_STARTS = { 0x01, 0x0e, 0x1a, 0x29, 0x32, 0x40, 0x4d, 0x5c } + +-- the #726 table-integrity check in tests/drivers reads these; nothing +-- else should +SurfingMinigame.BG_METATILES = BG_METATILES +SurfingMinigame.WAVE_PATTERNS = WAVE_PATTERNS +SurfingMinigame.WAVE_STEPS = WAVE_STEPS + -- SGB-style zones: one sea palette over the frame plus a yellow -- OBJ-flavored palette tracking Pikachu's tiles (rectangular attribute -- blocks are all the SGB could do, bleed and all) @@ -91,6 +241,17 @@ function SurfingMinigame.new(game, onDone) self.resultShown = 0 self.banner = nil -- {quad, frames}: GOOD!/YEAH-/Oh no.. + -- SurfingPikachuMinigame_LoadGFXAndLayout prefills the BG with flat + -- water and only starts generating $a0 pixels (ten metatile columns) + -- ahead of the viewport, so the run opens on calm sea. + self.waveFn = 0 + self.cols = {} + for c = 0, 10 do + self.cols[c] = { pat = WAVE_PATTERNS[0x00], + hl = FLAT_WATER_Y, hr = FLAT_WATER_Y } + end + self.colTail = 10 + local function sheet(path) local ok, img = pcall(love.graphics.newImage, path) return ok and img or nil @@ -98,8 +259,12 @@ function SurfingMinigame.new(game, onDone) self.bg = sheet("assets/generated/minigame/surf_1a.png") self.ob = sheet("assets/generated/minigame/surf_1b.png") if self.bg then - self.aq = {} - for k, spec in pairs(A) do self.aq[k] = newQuad(spec, self.bg) end + self.tq = {} + for n = 0, 64 do + self.tq[n] = love.graphics.newQuad((n % 5) * 8, math.floor(n / 5) * 8, + 8, 8, self.bg:getDimensions()) + end + self.hpq = newQuad(HP_LABEL, self.bg) end if self.ob then self.bq = {} @@ -122,11 +287,53 @@ function SurfingMinigame.new(game, onDone) return self end --- crest height at screen x for the current scroll (two sines so the --- wave rolls instead of looping visibly) +-- SurfingMinigame_ChooseNextWaveSequence: past section $16 the finale is +-- forced, otherwise a nonzero Random picks one of eight sequence starts. +-- Either way this column itself is flat water. +function SurfingMinigame:chooseSequence() + if math.floor(self.distance / 128) >= 0x16 then + self.waveFn = 0x6a + else + local r = math.random(0, 255) + if r ~= 0 then self.waveFn = SEQ_STARTS[((r - 1) % 8) + 1] end + end + return WAVE_PATTERNS[0x00], FLAT_WATER_Y, FLAT_WATER_Y +end + +-- one 16px metatile column, appended on the right as the sea scrolls +function SurfingMinigame:pushColumn() + local pat, hl, hr + if self.waveFn == 0 then + pat, hl, hr = self:chooseSequence() + else + local step = WAVE_STEPS[self.waveFn] + if not step then + self.waveFn = 0 + pat, hl, hr = WAVE_PATTERNS[0x00], FLAT_WATER_Y, FLAT_WATER_Y + else + pat, hl, hr = WAVE_PATTERNS[step[1]], step[2], step[3] + if step[4] == ADV then self.waveFn = self.waveFn + 1 + elseif step[4] == RESET then self.waveFn = 0 end + end + end + self.colTail = self.colTail + 1 + self.cols[self.colTail] = { pat = pat, hl = hl, hr = hr } + self.cols[self.colTail - 24] = nil -- columns behind the viewport +end + +-- keep the generated columns covering the viewport plus the lookahead +function SurfingMinigame:generateAhead() + while self.colTail * 16 < self.distance + 176 do self:pushColumn() end +end + +-- Pikachu's screen y for a screen x, from the per-tile-column ride +-- heights the wave states hand back (SurfingMinigame_SetPikachuHeight +-- samples the same array either side of the scroll's low bit). function SurfingMinigame:seaY(x) - local s = self.distance + x - return 92 - 14 * math.sin(s / 26) - 6 * math.sin(s / 9.5) + local tile = math.floor((self.distance + x) / 8) + local col = self.cols[math.floor(tile / 2)] + if not col then return FLAT_WATER_Y - 16 end + return (tile % 2 == 0 and col.hl or col.hr) - 16 end function SurfingMinigame:finishRun() @@ -146,6 +353,13 @@ function SurfingMinigame:update() if self.banner.frames <= 0 then self.banner = nil end end if self.phase == "results" then + -- the sea keeps sliding under the card for a beat so the beach + -- run-out (states $72..$7b) actually crosses the screen, like + -- SurfingMinigame_WaitToShowResults scrolling to the sand + if self.resultShown < 150 then + self.distance = self.distance + 1.2 + self:generateAhead() + end self.resultShown = self.resultShown + 1 if self.resultShown > 30 and (input:wasPressed("a") or input:wasPressed("b")) then @@ -162,9 +376,13 @@ function SurfingMinigame:update() -- the wave scrolls by the current speed; the beach ends the run self.distance = self.distance + 0.8 + self.speed * 0.35 + self:generateAhead() if self.distance >= RUN_DISTANCE then -- rode it all the way in: distance bonus like the original's goal self.score = self.score + 500 + -- SurfingMinigame_WaitToShowResults hands the generator state $72 so + -- the sand runs out under the coast-in + self.waveFn = 0x72 self:finishRun() return end @@ -218,16 +436,11 @@ function SurfingMinigame:update() end end --- draw one 8x8 sheet tile quad at x, y -function SurfingMinigame:tile(q, x, y) - love.graphics.draw(self.bg, self.aq[q], x, y) -end - function SurfingMinigame:sgbPalettes() local P = require("src.render.PaletteFX") local zones = { P.whole(SEA_PAL) } if self.phase ~= "wipeout" and self.phase ~= "results" then - local tx = math.floor((PIKA_X - 12) / 8) + local tx = math.floor(PIKA_X / 8) local ty = math.floor(math.max(0, self.pikaScreenY or 60) / 8) zones[#zones + 1] = P.zone(PIKA_PAL, tx, ty, tx + 3, ty + 3) end @@ -242,87 +455,52 @@ function SurfingMinigame:drawScore(x, y, n) end end +-- the BG map: metatile columns from the wave generator, scrolled by +-- distance (SurfingMinigame_ScrollAndGenerateBGMap) +function SurfingMinigame:drawBackground() + local scx = math.floor(self.distance) + local first = math.floor(scx / 16) + for c = first, first + 10 do + local col = self.cols[c] + if col then + local x = c * 16 - scx + for i = 1, 8 do + local mt = BG_METATILES[col.pat[i]] + if mt then + local y = (i - 1) * 16 + love.graphics.draw(self.bg, self.tq[mt[1]], x, y) + love.graphics.draw(self.bg, self.tq[mt[2]], x + 8, y) + love.graphics.draw(self.bg, self.tq[mt[3]], x, y + 8) + love.graphics.draw(self.bg, self.tq[mt[4]], x + 8, y + 8) + end + end + end + end +end + function SurfingMinigame:draw() local haveSheets = self.bg and self.ob - -- sky love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) if not haveSheets then -- cache predates the surf sheets: plain shapes keep it playable love.graphics.setColor(0, 0, 0, 1) Font.draw(Strings("SCORE %d", self.score), 4, 4) - love.graphics.rectangle("fill", PIKA_X - 8, - self:seaY(PIKA_X) - 16 - self.y, 16, 16) + love.graphics.rectangle("fill", PIKA_X, self:seaY(PIKA_X) - self.y, 16, 16) love.graphics.setColor(1, 1, 1, 1) return end + self:drawBackground() + -- cloud in the sky strip love.graphics.draw(self.ob, self.bq.cloud, 112, 8) - -- open water: the scalloped pattern tiles the whole sea, phase-locked - -- to the scroll so the surface slides - local shift = math.floor(self.distance) % 8 - for ty = HORIZON, 136, 8 do - local alt = (ty / 8) % 2 == 0 - for tx = -8, 160, 8 do - self:tile(alt and "scallop" or "scallop2", tx - shift, ty) - end - end - - -- the wave face: a white patch hugging the ride line (the original - -- carved it with per-scanline scroll; the ellipse stands in), with a - -- few scallops floating inside and the foam lip along its upper edge - local faceY = self:seaY(56) + 10 - love.graphics.setColor(1, 1, 1, 1) - love.graphics.ellipse("fill", 56, faceY, 46, 30) - love.graphics.ellipse("fill", 100, faceY + 16, 40, 22) - for _, spot in ipairs({ { 30, 8 }, { 70, 16 }, { 48, 22 } }) do - self:tile("scallop", 56 - 46 + spot[1] - shift, faceY - 24 + spot[2]) - end - local pikaY = self:seaY(PIKA_X) - 20 - self.y - for a = 205, 335, 18 do - local r = math.rad(a) - local lx = 56 + math.cos(r) * 44 - 4 - local ly = faceY + math.sin(r) * 28 - 4 - -- foam that would land inside Pikachu's SGB zone comes out orange; - -- leave that patch to the spray ellipse instead - if math.abs(lx - PIKA_X) > 28 or math.abs(ly - (pikaY + 12)) > 26 then - self:tile("lip", lx, ly) - end - end - self:tile("shade", 92 - shift, faceY + 20) - self:tile("shade", 116 - shift, faceY + 24) - - -- beach slides through at the start and again before the goal - local beachX - if self.distance < 160 then - beachX = -self.distance - elseif self.distance > RUN_DISTANCE - 200 then - beachX = 160 - (self.distance - (RUN_DISTANCE - 200)) - end - if beachX then - for tx = 0, 32, 8 do - self:tile("beach", beachX + tx, 128) - self:tile("beach", beachX + tx, 136) - end - love.graphics.setColor(0, 0, 0, 1) - love.graphics.rectangle("fill", beachX + 9, 118, 2, 10) - love.graphics.setColor(1, 1, 1, 1) - self:tile("palm", beachX + 6, 112) - self:tile("hut", beachX + 20, 118) - end - - -- Pikachu. The white spray patch under him doubles as the yellow SGB - -- zone's backdrop: shade 0 maps to white in both palettes, so the - -- attribute-block bleed never shows on the water pattern. - love.graphics.setColor(1, 1, 1, 1) - local py = self:seaY(PIKA_X) - 20 - self.y + -- Pikachu rides at the height his tile column reports + local py = self:seaY(PIKA_X) - self.y self.pikaScreenY = py -- the yellow SGB zone tracks this - love.graphics.ellipse("fill", PIKA_X, py + 12, 25, 21) if self.phase == "wipeout" then - love.graphics.draw(self.ob, self.bq.splash, PIKA_X - 16, - self:seaY(PIKA_X) - 16) + love.graphics.draw(self.ob, self.bq.splash, PIKA_X - 4, py) else local quad if self.phase == "ride" and self.speed <= 2 @@ -332,7 +510,7 @@ function SurfingMinigame:draw() local bucket = math.floor(((self.rot % 360) + 22.5) / 45) % 8 * 45 quad = self.bq.poses[bucket] or self.bq.poses[0] end - love.graphics.draw(self.ob, quad, PIKA_X - 12, py) + love.graphics.draw(self.ob, quad, PIKA_X, py) end -- banner beats: GOOD! / YEAH- / Oh no.. @@ -340,11 +518,12 @@ function SurfingMinigame:draw() love.graphics.draw(self.ob, self.bq[self.banner.quad], 60, 40) end - -- score strip, bottom right: HP: + sheet digits + -- the HP window sits under the BG rows ($7e into hWY puts it at y 126; + -- tile-aligned here): "HP:" plus the sheet digits over plain white love.graphics.setColor(1, 1, 1, 1) - love.graphics.rectangle("fill", 100, 134, 60, 10) - love.graphics.draw(self.bg, self.aq.hp, 102, 135) - self:drawScore(126, 135, self.score) + love.graphics.rectangle("fill", 0, BG_HEIGHT, 160, 144 - BG_HEIGHT) + love.graphics.draw(self.bg, self.hpq, 8, BG_HEIGHT + 4) + self:drawScore(32, BG_HEIGHT + 4, self.score) if self.phase == "results" then love.graphics.setColor(1, 1, 1, 1) diff --git a/src/ui/TitleState.lua b/src/ui/TitleState.lua index 9f293f42..f628c1db 100644 --- a/src/ui/TitleState.lua +++ b/src/ui/TitleState.lua @@ -68,7 +68,14 @@ function TitleState:sgbPalettes(game) local top = game.stack and game.stack:top() local box = top and top.titleUiBox if box then - z[#z + 1] = P.trueColorZone(box[1], box[2], box[3], box[4]) + -- A DMG-grays zone, not the trueColor opt-out: through the shade-remap + -- shader GRAYS is the identity for the box's four shades, so SGB / + -- ADVANCED / OG modes keep #133's white paper and black ink exactly, + -- while effectiveColors still substitutes the mono and inverted display + -- modes -- a trueColor rect skipped the shader entirely, leaving the + -- main menu and CONTINUE info box a raw white hole over a CLASSIC + -- pea-green title instead of matching it like the START menu does (#870). + z[#z + 1] = P.zone(P.GRAYS, box[1], box[2], box[3], box[4]) end return z[3] and z or nil end @@ -96,11 +103,40 @@ local YELLOW_CYCLE_SPECIES = { "JIGGLYPUFF", "MEOWTH", "PSYDUCK", "VULPIX", "ABRA", "GROWLITHE", "CUBONE", "GASTLY", "HITMONLEE", "SNORLAX", "DRAGONITE", } -local CYCLE_FRAMES = 240 -- the original waits ~4s between picks +-- ..(engine/movie/title.asm ln 227) +local HOLD_FRAMES = 200 +local STARTERS = { CHARMANDER = true, SQUIRTLE = true, BULBASAUR = true } + +-- ..(engine/movie/title2.asm ln 13) +local function scrollFrames(steps, offset) + local frames = {} + for _, step in ipairs(steps) do + for _ = 1, step[2] do + frames[#frames + 1] = offset + offset = offset - step[1] + end + end + return frames +end +local OUT_FRAMES = scrollFrames( + { { 1, 2 }, { 2, 2 }, { 3, 2 }, { 4, 2 }, { 5, 2 }, { 6, 2 }, + { 8, 3 }, { 9, 3 } }, 0) +local IN_FRAMES = scrollFrames( + { { 10, 2 }, { 9, 4 }, { 8, 4 }, { 6, 3 }, { 5, 2 }, { 3, 1 }, + { 1, 1 } }, 120) + +-- ..(engine/movie/title2.asm ln 85) +local BALL_FRAMES = { 97, 95, 94, 93, 92, 93, 94, 95, 97, 100 } +local BALL_REST = 100 local function tryImage(path) if not path then return nil end - local ok, img = pcall(love.graphics.newImage, path) + -- resolve through Assets so a mod's derived art (save/mod-derived/) + -- wins here the way it does for every other generated sheet -- but + -- load uncached, because on NX the per-version overlay redirects the + -- open itself and a cached image would leak across Yellow/Blue boots + local ok, img = pcall(love.graphics.newImage, + require("src.render.Assets").resolve(path)) return ok and img or nil end @@ -144,16 +180,57 @@ function TitleState.new(game, opts) -- branding comes from field.title with the shipped art as fallback, so -- a total conversion rebrands the title without replacing the screen local title = (game.data.field and game.data.field.title) or {} + -- field.title itself is extraction data the field schema never exposes; + -- boot.title is the mod-reachable half of the same seam, so its keys + -- override here (a localized ribbon, a rebranded logo) + local boot = game.data.field and game.data.field.boot + if boot and type(boot.title) == "table" then + local merged = {} + for key, value in pairs(title) do merged[key] = value end + for key, value in pairs(boot.title) do merged[key] = value end + title = merged + end self.title = title self.logo = tryImage(imagePath(title.logo) or "assets/logo/pokemon_logo.png") - -- versionRibbon is the file-12 key; version is the importer's + -- versionRibbon is the file-12 key; version is the importer's. The + -- vanilla sheet is two fragments the draw pass repositions, so an + -- explicit ribbon (a conversion's or a translation's continuous art) + -- draws whole instead. + self.versionFull = imagePath(title.versionRibbon) ~= nil self.version = tryImage(imagePath(title.versionRibbon or title.version) or "assets/generated/title/red_version.png") - self.player = tryImage("assets/generated/title/player.png") + self.player = tryImage(imagePath(title.player) + or "assets/generated/title/player.png") + -- ..(engine/movie/title2.asm ln 85) + if self.player then + local pw, ph = self.player:getDimensions() + self.ballQuad = love.graphics.newQuad(0, 16, 8, 8, pw, ph) + self.playerQuads = { + { love.graphics.newQuad(0, 0, pw, 16, pw, ph), 0, 0 }, + { love.graphics.newQuad(8, 16, pw - 8, 8, pw, ph), 8, 16 }, + { love.graphics.newQuad(0, 24, pw, ph - 24, pw, ph), 0, 24 }, + } + end + self.copyImg = tryImage(imagePath(title.copyright) + or "assets/generated/title/copyright.png") + self.copyQuads = {} + if self.copyImg then + local iw, ih = self.copyImg:getDimensions() + for t = 0, 18 do + self.copyQuads[t] = love.graphics.newQuad(t * 8, 0, 8, 8, iw, ih) + end + end + self.gfInc = tryImage(imagePath(title.gamefreakInc) + or "assets/generated/title/gamefreak_inc.png") self.blue = GameVersion.isBlue() self.yellow = GameVersion.isYellow() or title.layout == "yellow_pikachu" + -- ..(pokeyellow engine/movie/title.asm .tileScreenCopyrightTiles / NineTile) + self.nineImg = self.yellow and tryImage(imagePath(title.nine) + or "assets/generated/title/nine.png") or nil + self.copyPrefix = self.yellow + and { 0, 1, 2, 3, 1, 2 } or { 0, 1, 2, 1, 3, 1, 4 } -- Yellow title is a fixed Pikachu composition (title_yellow.asm), not -- TitleMons cycling. Prefer composed pikachu.png from the Yellow import. self.yellowPikachu = self.yellow and tryImage(imagePath(title.pikachu) @@ -176,7 +253,10 @@ function TitleState.new(game, opts) self.blinkTimer = 0 self.blinkAt = nil else - self.phase = "loop" + -- ..(engine/movie/title.asm ln 28) + self.scy = 0x40 + self.phase = "drop" + self.dropStep, self.dropLeft = 1, nil self.showBubble = true end local defaultCycle = self.yellowLayout and { "PIKACHU" } @@ -189,14 +269,15 @@ function TitleState.new(game, opts) self.cycleIndex = 1 self.timer = 0 self.blink = 0 + self.scrollPhase = "hold" + self.scrollFrame = 1 + self.monOffset = 0 + self.ballY = BALL_REST return self end function TitleState:enter() - -- Yellow defers the title theme until after the logo drop and - -- Pikachu's cry (title.asm plays MUSIC_TITLE_SCREEN only after - -- WaitForSoundToFinish on PikachuCry1) - if self.yellowLayout then return end + if self.phase ~= "loop" then return end self:startMusic() end @@ -213,6 +294,11 @@ end local DROP_STEPS = { { -4, 16 }, { 3, 4 }, { -3, 4 }, { 2, 2 }, { -2, 2 }, { 1, 2 }, { -1, 2 }, } +local SETTLE_FRAMES = 36 + +-- ..(engine/movie/title.asm ln 201) +local RIBBON_FRAMES = {} +for offset = 112, 4, -4 do RIBBON_FRAMES[#RIBBON_FRAMES + 1] = offset end -- the boot cinematic up to the interactive loop; one call per frame function TitleState:updateSequence() @@ -236,12 +322,23 @@ function TitleState:updateSequence() self.dropLeft = nil end elseif self.phase == "settle" then - -- ld c, 36 / DelayFrames, then the whoosh and the bubble self.timer = self.timer + 1 - if self.timer >= 36 then + if self.timer >= SETTLE_FRAMES then Sound.play(data, "Intro_Whoosh") self.showBubble = true - self.phase = "bubble" + self.phase = self.yellowLayout and "bubble" or "ribbon" + self.ribbonOffset = RIBBON_FRAMES[1] + self.timer = 0 + end + elseif self.phase == "ribbon" then + self.timer = self.timer + 1 + local offset = RIBBON_FRAMES[self.timer + 1] + if offset then + self.ribbonOffset = offset + else + self.ribbonOffset = nil + self:startMusic() + self.phase = "loop" self.timer = 0 end elseif self.phase == "bubble" then @@ -340,8 +437,12 @@ function ContinueInfo:draw() -- box at (4,7), 8x14 content; labels double-spaced from (5,9) Font.drawBox(4, 7, 16, 10) love.graphics.setColor(0, 0, 0, 1) - Font.draw(Strings("PLAYER"), 40, 72) - Font.draw((save.player and save.player.name) or "RED", 96, 72) + -- the name follows the label's real width (one space after it), so a + -- localized label longer than PLAYER's six glyphs cannot run into it + local playerLabel = Strings("PLAYER") + Font.draw(playerLabel, 40, 72) + Font.draw((save.player and save.player.name) or "RED", + math.max(96, 40 + (#Font.split(playerLabel) + 1) * 8), 72) local badges = require("src.inventory.Badges").count(self.game.data, save) Font.draw(Strings("BADGES"), 40, 88) Font.draw(("%2d"):format(badges), 128, 88) @@ -394,17 +495,70 @@ function TitleState:openMenu() end local th = #items * 2 + 2 local menu = Menu.new(game, items, { tx = 0, ty = 0, tw = 13, th = th }) - -- full-width title LOGO zones would recolor this box; see sgbPalettes - menu.titleUiBox = { 0, 0, 12, th - 1 } + -- full-width title LOGO zones would recolor this box; see sgbPalettes. + -- Menu.new may have grown tw for longer (e.g. localized) labels, so the + -- recolor zone follows the box's real width instead of the vanilla 13. + menu.titleUiBox = { 0, 0, menu.tw - 1, th - 1 } game.stack:push(menu) end +-- ..(engine/movie/title.asm ln 271) +function TitleState:pickNewMon() + if #self.cycleSpecies < 2 then return end + local pick = self.cycleIndex + while pick == self.cycleIndex do + pick = love.math.random(1, #self.cycleSpecies) + end + self.cycleIndex = pick +end + +function TitleState:setCyclePhase(phase) + self.scrollPhase = phase + self.scrollFrame = 1 + self.timer = 0 + if phase == "in" then + self:pickNewMon() + self.monOffset = IN_FRAMES[1] + elseif phase == "out" then + self.monOffset = OUT_FRAMES[1] + elseif phase == "ball" then + self.ballY = BALL_FRAMES[1] + else + self.monOffset = 0 + end +end + +function TitleState:updateCycle() + local phase = self.scrollPhase + if phase == "hold" then + if self.timer >= HOLD_FRAMES then self:setCyclePhase("out") end + return + end + local frames = phase == "out" and OUT_FRAMES + or phase == "ball" and BALL_FRAMES or IN_FRAMES + self.scrollFrame = self.scrollFrame + 1 + local value = frames[self.scrollFrame] + if value then + if phase == "ball" then self.ballY = value else self.monOffset = value end + return + end + if phase == "out" then + -- ..(engine/movie/title.asm ln 235) + self:setCyclePhase( + STARTERS[self.cycleSpecies[self.cycleIndex]] and "ball" or "in") + elseif phase == "ball" then + self:setCyclePhase("in") + else + self:setCyclePhase("hold") + end +end + function TitleState:update(dt) + if self.phase ~= "loop" then + self:updateSequence() + return + end if self.yellowLayout then - if self.phase ~= "loop" then - self:updateSequence() - return -- input is ignored until the cinematic lands (title.asm) - end self:updateBlink() local input = self.game.input if input:wasPressed("start") or input:wasPressed("a") then @@ -419,21 +573,7 @@ function TitleState:update(dt) end self.timer = self.timer + 1 self.blink = (self.blink + 1) % 60 - if not self.yellowLayout and self.timer >= CYCLE_FRAMES then - self.timer = 0 - -- random pick that never repeats the current one - if #self.cycleSpecies > 1 then - local pick = self.cycleIndex - while pick == self.cycleIndex do - pick = love.math.random(1, #self.cycleSpecies) - end - self.cycleIndex = pick - end - self.slideIn = 20 -- TitleScreenScrollInMon slides the pic in - end - if self.slideIn and self.slideIn > 0 then - self.slideIn = self.slideIn - 1 - end + self:updateCycle() local input = self.game.input if input:wasPressed("start") or input:wasPressed("a") then -- the title mon cries when you leave the title (.finishedWaiting); @@ -445,15 +585,14 @@ function TitleState:update(dt) end end --- The original tilemap (engine/movie/title.asm): logo at tile (2,1), --- the version ribbon at (7,8), Red's title art as OAM at px (82,80), --- the title mon in the 7x7 box at tile (5,10), copyright on row 17. --- Yellow (title_yellow.asm): logo (2,1), speech bubble (6,4), Pikachu --- (4,8) 12x9 -- no version ribbon, no cycling mon, no Red OAM. +-- ..(engine/movie/title.asm ln 28) function TitleState:draw() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) - local scrollY = self.yellowLayout and -(self.scy or 0) or 0 + local scrollY = -(self.scy or 0) + -- ..(engine/movie/title.asm ln 28) + local preRibbon = not self.yellowLayout + and (self.phase == "drop" or self.phase == "settle") if self.logo then love.graphics.draw(self.logo, 16, 8 + scrollY) else @@ -481,24 +620,29 @@ function TitleState:draw() -- Yellow's Version_GFX slot holds a leftover "Blue Version" ribbon -- (pokeyellow gfx/title/blue_version.png, unreferenced by title code); -- the Yellow fallback layout draws no ribbon at all. - if self.version and not self.yellow then + if self.version and not self.yellow and not preRibbon then local iw, ih = self.version:getDimensions() - if self.blue then + local rx = self.ribbonOffset or 0 + if self.versionFull then + -- a continuous ribbon (versionRibbon) centers as one piece + love.graphics.draw(self.version, math.floor((160 - iw) / 2) + rx, 64) + elseif self.blue then love.graphics.draw(self.version, - love.graphics.newQuad(0, 0, 64, 8, iw, ih), 56, 64) + love.graphics.newQuad(0, 0, 64, 8, iw, ih), 56 + rx, 64) else love.graphics.draw(self.version, - love.graphics.newQuad(0, 0, 16, 8, iw, ih), 56, 64) + love.graphics.newQuad(0, 0, 16, 8, iw, ih), 56 + rx, 64) love.graphics.draw(self.version, - love.graphics.newQuad(40, 0, 40, 8, iw, ih), 80, 64) + love.graphics.newQuad(40, 0, 40, 8, iw, ih), 80 + rx, 64) end end - local sprite, spriteTrueColor = self:currentSprite() + local sprite, spriteTrueColor + if self.scrollPhase ~= "ball" then + sprite, spriteTrueColor = self:currentSprite() + end if sprite then local w, h = sprite:getDimensions() - local slide = (self.slideIn or 0) * 8 -- scroll in from the right - -- bottom-aligned and centered in the (5,10)-(11,16) tile box - local x = 40 + math.floor((56 - w) / 2) + slide + local x = 40 + math.floor((56 - w) / 2) + self.monOffset local y = 136 - h love.graphics.draw(sprite, x, y) -- a full-color mon keeps its own palette through the SGB pass, minus @@ -514,13 +658,34 @@ function TitleState:draw() end end -- Red is OAM in the original: he draws over the mon's box edge - if self.player then + if self.playerQuads then + for _, part in ipairs(self.playerQuads) do + love.graphics.draw(self.player, part[1], 82 + part[2], 80 + part[3]) + end + love.graphics.draw(self.player, self.ballQuad, 82, self.ballY) + elseif self.player then love.graphics.draw(self.player, 82, 80) end end + self:drawCopyright(136 + (preRibbon and 0 or scrollY)) +end + +function TitleState:drawCopyright(y) + if not self.title.copyrightText and self.copyImg and self.gfInc then + local x = 16 + for _, t in ipairs(self.copyPrefix) do + love.graphics.draw(self.copyImg, self.copyQuads[t], x, y) + x = x + 8 + end + if self.nineImg then + love.graphics.draw(self.nineImg, x, y) + x = x + 8 + end + love.graphics.draw(self.gfInc, x, y) + return + end love.graphics.setColor(0, 0, 0, 1) - Font.draw(self.title.copyrightText or Strings("2026 bois club games"), - 1, 136 + scrollY) + Font.draw(self.title.copyrightText or Strings("GAME FREAK inc."), 16, y) love.graphics.setColor(1, 1, 1, 1) end diff --git a/src/ui/TownMap.lua b/src/ui/TownMap.lua index 59048c56..ec6f305c 100644 --- a/src/ui/TownMap.lua +++ b/src/ui/TownMap.lua @@ -147,13 +147,14 @@ local function buildFlyList(game, byMap) for _, mapId in ipairs(field.flyOrder or {}) do local def = game.data.maps and game.data.maps[mapId] -- INDIGO_PLATEAU is a normal Fly spot (engine/menus/town_map.asm - -- LoadTownMap_Fly cycles it like any town), but its map uses tileset - -- "PLATEAU" not OVERWORLD, so Map.isOutdoor() alone dropped it from the - -- cursor even though it is visited and has a fly warp. Allow PLATEAU here - -- while the CAVERN/FACILITY dungeon escape spots that share flyOrder still - -- fail the gate and stay out (#203). + -- LoadTownMap_Fly cycles it like any town): its map id sits inside + -- BuildFlyLocationsList's 0..NUM_CITY_MAPS-1 walk, which is what + -- Map.isFlyTown checks, so it passes even though its tileset is + -- "PLATEAU" not OVERWORLD (#203). The ROUTE_4/ROUTE_10 Pokemon Centers + -- carry fly warps but are not towns, so they stay out (#788), as do the + -- CAVERN/FACILITY dungeon escape spots that share flyOrder. if not seen[mapId] and visited[mapId] and flyWarps[mapId] - and def and (Map.isOutdoor(def) or def.tileset == "PLATEAU") then + and def and Map.isFlyTown(def) then seen[mapId] = true local loc = byMap[mapId] or { name = mapId:gsub("_", " ") } table.insert(flyLocs, loc) @@ -220,8 +221,13 @@ function TownMap.new(game, opts) local mapId = game.overworld and game.overworld.map and game.overworld.map.id self.playerLoc = mapId and self.byMap[mapId] or nil self.sel = 1 - for i, loc in ipairs(self.locs) do - if loc == self.playerLoc then self.sel = i break end + -- LoadTownMap_Fly always opens with hl on wFlyLocationsList[0], the FIRST + -- fly destination (PALLET_TOWN), never the player's current town (#795). + -- Only the plain viewer snaps the cursor to where the player stands. + if not self.fly then + for i, loc in ipairs(self.locs) do + if loc == self.playerLoc then self.sel = i break end + end end self.blink = 0 return self @@ -266,14 +272,17 @@ function TownMap:update(dt) if self.fly then -- LoadTownMap_Fly: Up/Down cycle the visited destinations, A flies there, -- B cancels (handled above). moveList walks self.locs, now the fly list. + -- Up steps FORWARD through the towns (.pressedUp does inc hl: PALLET -> + -- VIRIDIAN -> PEWTER -> ...), Down steps back and wraps to the last + -- visited town from the top; the port had the two swapped (#795). if input:wasPressed("a") then Sound.play(self.game.data, "Press_AB") local mapId = self.flyMapIds[self.sel] self.game.stack:pop() if mapId and self.onFly then self.onFly(mapId) end return - elseif input:wasPressed("up") then self:moveList(-1) - elseif input:wasPressed("down") then self:moveList(1) + elseif input:wasPressed("up") then self:moveList(1) + elseif input:wasPressed("down") then self:moveList(-1) end elseif self.nestSpecies then if input:wasPressed("a") then diff --git a/src/ui/TradeAnim.lua b/src/ui/TradeAnim.lua index 423c2d0f..791f7ebe 100644 --- a/src/ui/TradeAnim.lua +++ b/src/ui/TradeAnim.lua @@ -10,8 +10,16 @@ local TradeAnim = {} TradeAnim.__index = TradeAnim TradeAnim.isOpaque = true +-- Trade_LoadMonSprite runs SET_PAL_POKEMON_WHOLE_SCREEN for the mon it puts +-- on screen; every other step of the sequence runs SET_PAL_GENERIC, which is +-- PAL_MEWMON (data/sgb/sgb_packets.asm PalPacket_Generic). #750 function TradeAnim:sgbPalettes(game) - return require("src.render.PaletteFX").wholeNamed(game.data, "MEWMON") + local P = require("src.render.PaletteFX") + local mon = (self.phase == "show_player" and self.sent) + or (self.phase == "show_enemy" and self.received) + local colors = mon and P.monPal(game.data, mon.species) + if colors then return { P.whole(colors) } end + return P.wholeNamed(game.data, "MEWMON") end local DEFAULT_ART = { @@ -350,17 +358,17 @@ function TradeAnim:drawMonInfo(mon, ot, otId, boxTy) love.graphics.setColor(1, 1, 1, 1) end -function TradeAnim:drawIconInBubble(sprite, x, y) - local spr = sprite - if spr then - local sw, sh = spr:getDimensions() - local s = 16 / math.max(sw, sh) - love.graphics.draw(spr, x, y, 0, s, s) - else - love.graphics.setColor(0, 0, 0, 1) - love.graphics.rectangle("fill", x + 4, y + 4, 8, 8) - love.graphics.setColor(1, 1, 1, 1) - end +-- Trade_WriteCircledMonOAM: the mon crosses the cable as its party-menu +-- sprite (wMonPartySpriteSpecies -> WriteMonPartySpriteOAMBySpecies), not as +-- its battle pic, and Trade_AnimCircledMon flips both it and the ring to +-- their second frame every step. The ring is four OAM blocks -- +-- Trade_CircleOAMBlocks .OAMBlock0-3 at (8,8) (24,8) (8,24) (24,24) with the +-- X/Y flips -- so the 16x32 bubble sheet holds one quadrant per frame and the +-- circle it makes is 32x32 around the 16x16 icon. The icon rides OAM +-- block 0 and the circle blocks 1-4 (Trade_WriteCircleOAMBlock counts a up +-- from 1), and the lower OAM index wins overlap on DMG, so the icon draws +-- on top of the circle's filled interior. #750 +function TradeAnim:drawIconInBubble(mon, x, y) if self.img.bubble then if not self.bubbleQuad then local iw, ih = self.img.bubble:getDimensions() @@ -370,7 +378,19 @@ function TradeAnim:drawIconInBubble(sprite, x, y) or self.bubbleQuad end local q = self.cableFlash and self.bubbleQuadAlt or self.bubbleQuad - love.graphics.draw(self.img.bubble, q, x - 8, y - 8) + local left, top = x - 8, y - 8 + local right, bottom = left + 32, top + 32 + love.graphics.draw(self.img.bubble, q, left, top) + love.graphics.draw(self.img.bubble, q, right, top, 0, -1, 1) + love.graphics.draw(self.img.bubble, q, left, bottom, 0, 1, -1) + love.graphics.draw(self.img.bubble, q, right, bottom, 0, -1, -1) + end + local drawn = mon and require("src.ui.PartyMenu").drawIcon( + self.game, mon, x, y, false, 0, self.cableFlash) + if not drawn then + love.graphics.setColor(0, 0, 0, 1) + love.graphics.rectangle("fill", x + 4, y + 4, 8, 8) + love.graphics.setColor(1, 1, 1, 1) end end @@ -471,13 +491,8 @@ function TradeAnim:draw() love.graphics.translate(160, 0) self:drawRightGB() love.graphics.pop() - local sprite - if p == "transfer_lr" then - sprite = self.sentSprite - else - sprite = self.recvSprite - end - self:drawIconInBubble(sprite, self.monX, self.monY) + local mon = p == "transfer_lr" and self.sent or self.received + self:drawIconInBubble(mon, self.monX, self.monY) if self.cableFlash then love.graphics.setColor(1, 1, 1, 0.15) love.graphics.rectangle("fill", 0, 32, 160, 8) diff --git a/src/ui/TrainerCard.lua b/src/ui/TrainerCard.lua index 3ea51ae8..43f0fdba 100644 --- a/src/ui/TrainerCard.lua +++ b/src/ui/TrainerCard.lua @@ -67,8 +67,15 @@ function TrainerCard.new(game, opts) end end self.circle = tryImage("assets/generated/trainer_card/circle_tile.png") - self.pic = tryImage(require("src.pokemon.Sprites").playerPath( - game.data, "front", { kind = "trainer_card" })) + + -- Capture both return values from playerPath: path and trueColor flag. + -- The trueColor flag is set by the player.sprite hook when a mod injects + -- a custom portrait that should bypass the MEWMON palette pipeline. + local picPath, picTrueColor = require("src.pokemon.Sprites").playerPath( + game.data, "front", { kind = "trainer_card" }) + self.pic = tryImage(picPath) + self.picTrueColor = self.pic and picTrueColor or false + return self end @@ -117,7 +124,18 @@ function TrainerCard:draw() -- top card (rows 0-7): NAME / MONEY / TIME, pic upper-right self:frameBox(0, 0, 20, 8) if self.pic then + love.graphics.setColor(1, 1, 1, 1) love.graphics.draw(self.pic, 104, 4) + -- True-colour portraits (e.g. mod-injected custom characters) carry their + -- own colours and must not be re-mapped by the MEWMON zone shader. + -- markTrueColor appends a colors=false zone that the Renderer splices at + -- the end of the zone list, causing it to re-blit just this rect without + -- the palette shader on top of the already-colourised frame. + -- This matches the pattern used by OakSpeech, HallOfFame and SummaryMenu. + if self.picTrueColor then + local w, h = self.pic:getDimensions() + require("src.render.PaletteFX").markTrueColor(104, 4, w, h) + end end love.graphics.setColor(0, 0, 0, 1) Font.draw(Strings("NAME/%s", save.player.name or "RED"), 16, 16) diff --git a/src/ui/kit/Kit.lua b/src/ui/kit/Kit.lua new file mode 100644 index 00000000..8c423e2e --- /dev/null +++ b/src/ui/kit/Kit.lua @@ -0,0 +1,938 @@ +-- Immediate-mode widget kit shared by the launcher and the save editor. +-- +-- This is the replacement for the vendored FlexLove tree that the launcher +-- used to rebuild every frame. The contract that mattered there is kept -- +-- the UI is rebuilt from owner state each frame, so it can never drift -- +-- but without a retained element tree, per-element id hashing, or a +-- property snapshot pass. Measured effect on the launcher's build+draw: +-- ~9.2ms/frame down to well under 1ms (see POKEPORT_LAUNCHER_PROF). +-- +-- Usage, once per love.draw(): +-- Kit.layout(w, h) -- fonts + scale, only on resize +-- Kit.beginFrame(mx, my, clicked, wheel) +-- ... widgets ... +-- Kit.endFrame() +-- +-- WHY IT IS FAST (the rules any new widget must follow): +-- 1. No allocation in the steady state. Widgets take and return scalars; +-- the per-frame tables that do exist (nav list, audit) are reused and +-- truncated, never rebuilt. LuaJIT's GC is the difference between a +-- 6ms frame and a 0.6ms one when a list has 200 rows. +-- 2. Text is cached as love.graphics.Text objects keyed by font+string +-- (Kit.text). G.print re-shapes the string every call; a Text object +-- shapes once and then costs one batched draw. Colour is applied at +-- draw time, which does NOT break the batch -- switching FONTS does, +-- which is the other reason the cache pays. +-- 3. Measurement (font:getWidth, ellipsize) is memoised per font+string. +-- Ellipsising is O(glyphs) with a getWidth per step and list rows do it +-- for every visible cell, every frame, on strings that never change. +-- 4. Lists PAGINATE. Row count is bounded by the page size, so a 500-mod +-- index costs exactly what a 10-mod one does. There is no virtualised +-- scroller and no momentum integrator to run. +-- 5. Draw flat. No stencil, no mesh, no canvas, no shader, no blend-mode +-- change -- every one is a pipeline flush. Rounded corners, the emboss +-- and a card's drop shadow are allowed because they only add VERTICES at +-- the same pipeline state (see Theme.lua's header for the full rule). +-- +-- ACCESSIBILITY / INPUT: every control is reachable four ways -- mouse, +-- touch (>= 30px targets), keyboard (spatial focus ring, arrows + Enter), +-- and gamepad (the same ring, driven by the d-pad, plus a virtual cursor). +-- Hit testing is a plain rect with no z-order, so overlapping layers must be +-- drawn in dispatch order and a modal raises Kit.blockClicks over what it +-- covers. + +local Theme = require("src.ui.kit.Theme") +local PAL = Theme.PAL + +local Kit = {} +Kit.Theme = Theme +Kit.PAL = PAL + +Kit.mouseX, Kit.mouseY = 0, 0 +Kit.mouseClicked = false -- left button pressed this frame +Kit.mouseDown = false -- held, polled (drag / press-and-hold) +Kit.wheelY = 0 -- wheel notches queued since the last frame +Kit.focus = nil -- id of the text field receiving keystrokes +Kit.focusId = nil -- id of the keyboard/gamepad focus ring target +Kit.time = 0 +Kit.fonts = {} +Kit.scale = 1 +Kit.blockClicks = false +Kit.audit = nil + +local G = love and love.graphics or nil +local edits = {} -- queued textinput / backspace since the last frame +local kbField = nil -- id of the field the soft keyboard is raised for + +local function has(name) + return Theme.probe(name) +end + +-- ------------------------------------------------------------ soft keyboard +-- Mobile LOVE only delivers love.textinput while setTextInput(true) is +-- active, and that call is what raises the Android/iOS soft keyboard; the +-- rect keeps the focused field visible above it. setTextInput is global SDL +-- state, not per-widget, so desktop text input is never turned off (#529). +local function mobile() + local osName = love and love.system and love.system.getOS + and love.system.getOS() + return osName == "Android" or osName == "iOS" +end +Kit.isMobile = mobile + +local function syncSoftKeyboard(id, x, y, w, h) + if not (love and love.keyboard and love.keyboard.setTextInput) then return end + if id then + if kbField ~= id then + kbField = id + love.keyboard.setTextInput(true, math.floor(x), math.floor(y), + math.ceil(w), math.ceil(h)) + end + elseif kbField then + kbField = nil + if mobile() then love.keyboard.setTextInput(false) end + end +end + +-- ------------------------------------------------------------- text caching +-- Two caches, both keyed by font name + string, both cleared wholesale when +-- the font set is rebuilt (a resize). A wholesale clear is correct and +-- cheap: an LRU would cost more bookkeeping per lookup than it saves, and +-- the working set of a UI is small and stable between resizes. +local textCache, textCacheN = {}, 0 +local widthCache = {} +local ellipsisCache = {} +local CACHE_MAX = 1024 + +local wrapCacheRef -- forward declaration; the table is defined below +local function clearCaches() + textCache, textCacheN = {}, 0 + widthCache = {} + ellipsisCache = {} + if wrapCacheRef then + for k in pairs(wrapCacheRef) do wrapCacheRef[k] = nil end + end +end +Kit.clearCaches = clearCaches + +local function font(name) + return Kit.fonts[name] or Kit.fonts.small +end +Kit.font = font + +-- Rebuild the font set when the window size changes. The scale never dips +-- below 0.9 so text and the 30px tap targets stay readable on a phone; a +-- narrow window is answered by REFLOW (see Layout.lua), never by shrinking. +-- Global size multiplier. Everything in the UI derives from Kit.scale, so +-- one factor here moves text, tap targets, padding and row heights together +-- and nothing drifts out of proportion. 1.3 because the launcher is read at +-- couch distance as often as at desk distance, and the old sizing was tuned +-- for the latter only. +local UI_SCALE = 1.3 + +function Kit.layout(width, height) + local s = Theme.clamp(math.min(width / 640, height / 768), 0.9, 1.6) * UI_SCALE + local key = ("%dx%d"):format(math.floor(width), math.floor(height)) + if Kit._fontKey ~= key then + Kit._fontKey = key + Kit.fonts = Theme.fonts(s) + clearCaches() -- every cached Text/width belongs to the old font set + end + Kit.scale = s + Kit.width, Kit.height = width, height + return s +end + +function Kit.textWidth(name, str) + str = tostring(str) + local key = name .. "\0" .. str + local w = widthCache[key] + if w then return w end + local f = font(name) + -- Never let a malformed string (a mod name from a third-party index) throw + -- out of a measurement: an unmeasurable string is treated as zero-width and + -- the ellipsis logic clips it away. + if f then + local ok, got = pcall(f.getWidth, f, str) + w = ok and got or 0 + else + w = 0 + end + widthCache[key] = w + return w +end + +function Kit.textHeight(name) + local f = font(name) + return f and f:getHeight() or 12 +end + +function Kit.ellipsize(name, str, maxW) + str = tostring(str or "") + local key = name .. "\0" .. math.floor(maxW) .. "\0" .. str + local c = ellipsisCache[key] + if c then return c end + c = Theme.ellipsize(font(name), str, maxW) + ellipsisCache[key] = c + return c +end + +function Kit.ellipsizeLeft(name, str, maxW) + str = tostring(str or "") + local key = name .. "\1" .. math.floor(maxW) .. "\0" .. str + local c = ellipsisCache[key] + if c then return c end + c = Theme.ellipsizeLeft(font(name), str, maxW) + ellipsisCache[key] = c + return c +end + +-- A cached, pre-shaped Text object. Falls back to G.print under a stub or +-- when the cache is saturated. +local function textObject(name, str) + if not (G and has("newText")) then return nil end + local key = name .. "\0" .. str + local t = textCache[key] + if t then return t end + if textCacheN >= CACHE_MAX then clearCaches() end + local f = font(name) + if not f then return nil end + local ok, obj = pcall(G.newText, f, str) + if not ok then return nil end + textCache[key] = obj + textCacheN = textCacheN + 1 + return obj +end + +-- Bold text: the same cached run drawn twice, one pixel apart. The UI face +-- has a single weight, so this is the only way to get emphasis without +-- shipping a second font -- and it keeps the measurement identical, which +-- matters because every layout here is measured, not flowed. +function Kit.textBold(name, str, x, y, c, a) + local w = Kit.text(name, str, x, y, c, a) + Kit.text(name, str, x + Theme.BOLD_OFFSET, y, c, a) + return w +end + +function Kit.textCenterBold(name, str, x, y, w, c, a) + local tw = Kit.textWidth(name, tostring(str)) + return Kit.textBold(name, str, x + (w - tw) / 2, y, c, a) +end + +-- Draw a string. Returns its width, so callers can lay out inline runs +-- without a second measurement. +function Kit.text(name, str, x, y, c, a) + if not G then return 0 end + str = tostring(str) + Theme.col(c or PAL.text, a or 1) + local obj = textObject(name, str) + if obj then + G.draw(obj, Theme.snap(x), Theme.snap(y)) + else + local f = font(name) + if not f then return 0 end + G.setFont(f) + -- Same guard as the measurement path: a string LOVE cannot shape must + -- not take the whole frame down with it. + pcall(G.print, str, Theme.snap(x), Theme.snap(y)) + end + return Kit.textWidth(name, str) +end + +function Kit.textRight(name, str, x2, y, c, a) + return Kit.text(name, str, x2 - Kit.textWidth(name, tostring(str)), y, c, a) +end + +function Kit.textCenter(name, str, x, y, w, c, a) + return Kit.text(name, str, x + (w - Kit.textWidth(name, tostring(str))) / 2, + y, c, a) +end + +-- Word-wrapped text. Font:getWrap re-shapes the whole string every call and +-- list rows ask for the same (font, width, string) every frame, so the line +-- split is memoised alongside the other measurement caches. `maxLines` +-- truncates with an ellipsis rather than overflowing the box the caller +-- reserved -- an immediate-mode layout has no way to grow after the fact. +local wrapCache = {} +wrapCacheRef = wrapCache + +function Kit.wrapLines(name, str, w) + str = tostring(str or "") + if str == "" or w <= 0 then return nil end + local key = name .. "\0" .. math.floor(w) .. "\0" .. str + local lines = wrapCache[key] + if lines then return lines end + local f = font(name) + if not f then return nil end + local ok, _, wrapped = pcall(f.getWrap, f, str, w) + lines = (ok and wrapped) or { str } + wrapCache[key] = lines + return lines +end + +-- Returns the height consumed. +function Kit.textWrapped(name, str, x, y, w, c, maxLines, a) + local lines = Kit.wrapLines(name, str, w) + if not lines then return 0 end + local lh = Kit.textHeight(name) + local n = #lines + if maxLines and n > maxLines then n = maxLines end + for i = 1, n do + local line = lines[i] + if maxLines and i == maxLines and #lines > maxLines then + line = Kit.ellipsize(name, line .. "...", w) + end + Kit.text(name, line, x, y + (i - 1) * lh, c, a) + end + return n * lh +end + +-- Height a wrapped run will need, without drawing it. Panels call this to +-- reserve space before laying the block out. +function Kit.wrapHeight(name, str, w, maxLines) + local lines = Kit.wrapLines(name, str, w) + if not lines then return 0 end + local n = #lines + if maxLines and n > maxLines then n = maxLines end + return n * Kit.textHeight(name) +end + +-- 12px / 2px-tracked uppercase section caption -- the design's one and only +-- section header. Returns its height so callers can stack below. +function Kit.caption(x, y, str, c) + if not G then return Kit.textHeight("caption") end + local f = font("caption") + if not f then return 12 end + G.setFont(f) + Theme.col(c or PAL.caption, 1) + Theme.spaced(f, str, Theme.snap(x), Theme.snap(y), 2 * Kit.scale) + return f:getHeight() +end + +function Kit.captionWidth(str) + return Theme.spacedWidth(font("caption"), str, 2 * Kit.scale) +end + +-- ------------------------------------------------------------- frame cycle +function Kit.beginFrame(mx, my, clicked, wheel) + Kit.mouseX, Kit.mouseY = mx or 0, my or 0 + Kit.mouseClicked = clicked and true or false + Kit.wheelY = wheel or 0 + local down = false + if love and love.mouse and love.mouse.isDown then + down = love.mouse.isDown(1) and true or false + end + Kit.mouseDown = down + if not down then Kit._drag = nil end + Kit.resetClip() + Kit.blockClicks = false + if love and love.timer and love.timer.getTime then + Kit.time = love.timer.getTime() + end + -- Resolve any queued focus-ring movement against LAST frame's geometry. + -- Immediate mode has no geometry until the frame is built, and the ring + -- must move before widgets test themselves against it. + Kit._resolveNav() + -- Start collecting this frame's focusables. + Kit._navN = 0 +end + +-- Retire this frame's keystrokes, wheel notches and one-shot activations. +-- Anything typed while no field had focus is dropped here rather than +-- replayed into the next field that gets clicked. +function Kit.endFrame() + for i = #edits, 1, -1 do edits[i] = nil end + Kit.wheelY = 0 + Kit._activateId = nil + -- This frame's focusables become next frame's navigation graph. + local n = Kit._navN or 0 + Kit._navPrevN = n + -- If the focused id vanished (panel switch, list repaged), park the ring + -- on the first focusable so the keyboard is never stranded. + if Kit.focusId and not Kit._navSeen[Kit.focusId] and n > 0 then + Kit.focusId = Kit._nav[1] and Kit._nav[1].id or nil + end + for k in pairs(Kit._navSeen) do Kit._navSeen[k] = nil end +end + +-- ------------------------------------------------------------ focus ring +-- Spatial navigation. Every focusable control registers its rect as it +-- draws; a queued direction picks the nearest candidate in that direction +-- from the previous frame's set. Spatial rather than index-order because +-- the launcher is a multi-column layout: tab-order would zigzag between +-- columns, while "press right, go right" is what both a keyboard and a +-- d-pad user expects. +Kit._nav = {} +Kit._navN = 0 +Kit._navPrevN = 0 +Kit._navSeen = {} +Kit._navQueue = nil +Kit._activateId = nil + +-- Register a focusable. Returns true when it currently holds the ring. +-- Shielded widgets do not register: while a modal owns the frame the ring +-- must not wander through (or Enter-activate) the controls underneath it. +function Kit.focusable(id, x, y, w, h) + if Kit.blockClicks then return false end + local n = (Kit._navN or 0) + 1 + Kit._navN = n + local slot = Kit._nav[n] + if not slot then slot = {}; Kit._nav[n] = slot end + slot.id, slot.x, slot.y, slot.w, slot.h = id, x, y, w, h + Kit._navSeen[id] = true + -- First focusable ever drawn adopts the ring, so keyboard users start + -- somewhere rather than nowhere. + if Kit.focusId == nil then Kit.focusId = id end + return Kit.focusId == id +end + +function Kit.navigate(dir) + Kit._navQueue = dir +end + +function Kit.activateFocused() + if Kit.focusId then Kit._activateId = Kit.focusId end +end + +function Kit.setFocus(id) + Kit.focusId = id +end + +-- Pick the nearest focusable in `dir` from the current one. Candidates must +-- lie in the half-plane of the direction; the score prefers a small step +-- along the axis of travel and penalises drift across it, which keeps a +-- column walk inside its column. +function Kit._resolveNav() + local dir = Kit._navQueue + Kit._navQueue = nil + local n = Kit._navPrevN or 0 + if not dir or n == 0 then return end + local cur + for i = 1, n do + if Kit._nav[i].id == Kit.focusId then cur = Kit._nav[i] break end + end + if not cur then + Kit.focusId = Kit._nav[1].id + return + end + local cx, cy = cur.x + cur.w / 2, cur.y + cur.h / 2 + local best, bestScore + for i = 1, n do + local c = Kit._nav[i] + if c.id ~= cur.id then + local dx = (c.x + c.w / 2) - cx + local dy = (c.y + c.h / 2) - cy + local along, across + if dir == "left" then along, across = -dx, math.abs(dy) + elseif dir == "right" then along, across = dx, math.abs(dy) + elseif dir == "up" then along, across = -dy, math.abs(dx) + else along, across = dy, math.abs(dx) end + -- A control merely overlapping on the travel axis is not "in that + -- direction"; require real separation so a tall row's neighbours do + -- not all qualify. + if along > 1 then + local score = along + across * 2 + if not bestScore or score < bestScore then best, bestScore = c, score end + end + end + end + if best then Kit.focusId = best.id end +end + +-- ------------------------------------------------------------ input plumbing +function Kit.textinput(text) + if not Kit.focus then return false end + edits[#edits + 1] = text + return true +end + +-- Returns true when the key was consumed, so the host can leave its own +-- shortcuts alone while the user is typing or driving the ring. +function Kit.keypressed(key) + if Kit.focus then + if key == "backspace" then edits[#edits + 1] = "\b" return true + elseif key == "return" or key == "kpenter" or key == "escape" then + edits[#edits + 1] = "\r" return true + end + -- printable keys arrive through textinput; everything else falls through + return false + end + if key == "up" or key == "down" or key == "left" or key == "right" then + Kit.navigate(key) + return true + elseif key == "return" or key == "kpenter" or key == "space" then + Kit.activateFocused() + return true + end + return false +end + +-- Gamepad d-pad / stick, routed by the host's pad handling. +function Kit.gamepadpressed(button) + if button == "dpup" then Kit.navigate("up") return true + elseif button == "dpdown" then Kit.navigate("down") return true + elseif button == "dpleft" then Kit.navigate("left") return true + elseif button == "dpright" then Kit.navigate("right") return true + elseif button == "a" then Kit.activateFocused() return true end + return false +end + +function Kit.blur() + Kit.focus = nil + syncSoftKeyboard(nil) +end + +-- -------------------------------------------------------------- hit testing +-- A widget inside a clip region can sit at coordinates outside the visible +-- rect, so the active clip bounds the hit: what the user cannot see cannot +-- take the tap. +function Kit.hit(x, y, w, h) + local c = Kit._clipRect + if c and not (Kit.mouseX >= c.x and Kit.mouseX <= c.x + c.w + and Kit.mouseY >= c.y and Kit.mouseY <= c.y + c.h) then + return false + end + return Kit.mouseX >= x and Kit.mouseX <= x + w + and Kit.mouseY >= y and Kit.mouseY <= y + h +end + +function Kit.hover(x, y, w, h) + -- Shielded widgets (drawn while a modal owns the frame) must not glow + -- either: a hover highlight under the scrim reads as "still clickable". + if Kit.blockClicks then return false end + return Kit.hit(x, y, w, h) +end + +function Kit.press(x, y, w, h) + if Kit.blockClicks then return false end + return Kit.mouseClicked and Kit.hit(x, y, w, h) +end + +-- Layout audit: when a test sets Kit.audit to a table, every control that +-- could take a click this frame appends its rect (plus the clip that bounds +-- it), so a window-size sweep can assert no two controls overlap and none +-- escapes the window. Shielded widgets are skipped: under a modal they +-- cannot take the tap, and the modal legitimately covers them. +local function audit(class, x, y, w, h, label) + local a = Kit.audit + if not a or Kit.blockClicks then return end + local c = Kit._clipRect + a[#a + 1] = { class = class, x = x, y = y, w = w, h = h, + label = tostring(label or ""), + clip = c and { x = c.x, y = c.y, w = c.w, h = c.h } or nil } +end +Kit._audit = audit + +-- ------------------------------------------------------------------ metrics +-- Minimum tap target. 30px at scale 1 (up from the editor's 26) because the +-- launcher is the first thing a phone user touches and these are the only +-- controls that matter. +function Kit.tapMin() return math.floor(30 * Kit.scale) end + +-- ---------------------------------------------------------------- surfaces +function Kit.card(x, y, w, h, emphasis) + Theme.card(x, y, w, h, emphasis) +end + +-- A list row. `id` opts it into the focus ring; pass nil for decorative +-- rows. Returns (clicked, inkColor) -- a selected row fills white, so the +-- caller must print with the returned ink or it will draw white on white. +function Kit.row(x, y, w, h, selected, id) + audit("row", x, y, w, h, id or "row") + local focused = id and Kit.focusable(id, x, y, w, h) or false + local hot = Kit.hover(x, y, w, h) + local state = selected and "selected" or (hot and "hover" or nil) + local ink = Theme.row(x, y, w, h, state) + -- The focus ring is a second inset outline, so it reads on both a black + -- row and a white selected one. + if focused then + Theme.strokeRounded(x + 2, y + 2, w - 4, h - 4, + selected and PAL.inverse or PAL.lineStrong, Theme.A.focus, 1, + Theme.radius()) + end + local clicked = Kit.press(x, y, w, h) + or (id ~= nil and Kit._activateId == id) + return clicked, ink +end + +-- Empty-state box: hairline outline and a centred hint. (The old dashed +-- border sampled a rounded path into a polyline every frame; a solid +-- hairline says the same thing for one rect.) +function Kit.emptyBox(x, y, w, h, message) + if not G then return end + Theme.strokeRounded(x, y, w, h, PAL.line, 0.22, 1, Theme.radius()) + Kit.textCenter("button", Kit.ellipsize("button", message, w - 24 * Kit.scale), + x, y + (h - Kit.textHeight("button")) / 2, w, PAL.muted) +end + +-- ----------------------------------------------------------------- buttons +-- Button kinds. In a black/white theme the semantics live in the OUTLINE +-- and INK colour; the fill is black until the control is hot or focused, at +-- which point it inverts to a solid fill with dark ink. That inversion is +-- the single strongest contrast signal available and costs one rect. +-- `solid` means the control is filled even at rest: reserved for the single +-- most important action on a screen (Play), which should not have to be +-- hovered before it looks like the answer. +-- Buttons are COLOUR-CODED by what they do, so a control's job is readable +-- before its label is. The button IS the colour: a solid fill with black +-- ink, not an outline with coloured text. Against a black field a filled +-- chip is the strongest, fastest-to-scan signal available, and every accent +-- in this palette is high-luminance, so black ink on it clears contrast +-- requirements comfortably. +-- primary green -- the commit action (Play, Save, Install) +-- good green -- safe helpers +-- accent blue -- navigation / information (Details, Edit, Import) +-- warn yellow -- attention (an update is waiting) +-- danger red -- destructive, always two-press +-- ghost white -- neutral verbs with no better colour +-- disabled grey -- never hidden, always still readable +-- Hover/focus is a white ring around the fill (plus a slight lift), which +-- reads on every colour without needing a second shade of each. +local KINDS = { + primary = { fill = PAL.green, ink = PAL.inverse }, + good = { fill = PAL.green, ink = PAL.inverse }, + accent = { fill = PAL.blue, ink = PAL.inverse }, + warn = { fill = PAL.yellow, ink = PAL.inverse }, + danger = { fill = PAL.red, ink = PAL.inverse }, + ghost = { fill = PAL.ink, ink = PAL.inverse }, + disabled = { fill = PAL.steel, ink = PAL.inverse, flat = true }, +} +Kit.KINDS = KINDS + +-- opts: { kind, font, enabled, align, id, glow, fill, ink } +-- id -- opts into the focus ring (give every real control one) +-- glow -- a pulsing outline for "something is waiting for you" (the +-- update button). No blend-mode change: the alpha of the +-- existing outline is animated instead. +-- fill/ink -- override the kind's colours. The ONE caller is the +-- launcher's Play button, which wears its cartridge colour +-- (red/blue/gold) rather than a semantic one: on that screen +-- "which game am I launching" outranks "what kind of verb is +-- this", and the colour is already the tab's identity. +-- Returns true when activated, by click OR by the focus ring's Enter/A. +function Kit.button(x, y, w, h, label, opts) + opts = opts or {} + local enabled = opts.enabled ~= false + -- Disabled buttons audit too: they stay visible, so they still must not + -- paint over a neighbour. + audit("control", x, y, w, h, label) + local focused = enabled and opts.id + and Kit.focusable(opts.id, x, y, w, h) or false + local kind = KINDS[enabled and (opts.kind or "ghost") or "disabled"] + if enabled and opts.fill then + kind = { fill = opts.fill, ink = opts.ink or PAL.inverse } + end + local hot = enabled and Kit.hover(x, y, w, h) + + if G then + -- The fill IS the control: a rounded, embossed, colour-coded key. A + -- disabled button keeps its shape in a dead grey rather than + -- disappearing, so a layout never reflows on state. + Theme.fillRounded(x, y, w, h, kind.fill, enabled and 1 or 0.45) + Theme.emboss(x, y, w, h, enabled and (hot and 1.3 or 1) or 0.4) + if hot or focused then + -- White ring outside the fill: legible on green, blue, yellow, red and + -- white alike, which one darker/lighter shade per colour would not be. + Theme.strokeRounded(x - 2, y - 2, w + 4, h + 4, PAL.lineStrong, + Theme.A.focus, 2, Theme.radius() + 2) + elseif opts.glow and enabled then + -- "Something is waiting for you" (the update button): a pulsing ring. + -- Pure alpha on one existing stroke -- no extra draw calls, no blend + -- mode change. + local a = 0.25 + 0.75 * (0.5 + 0.5 * math.sin(Kit.time * 3)) + Theme.strokeRounded(x - 2, y - 2, w + 4, h + 4, PAL.lineStrong, a, 2, + Theme.radius() + 2) + end + local fname = opts.font or "button" + local ink = enabled and kind.ink or PAL.inverse + local ty = y + (h - Kit.textHeight(fname)) / 2 + local shown = Kit.ellipsize(fname, label, w - 16 * Kit.scale) + -- Button labels are bold: they are the shortest, most-scanned text on + -- screen and sit on a saturated fill. + if opts.align == "left" then + Kit.textBold(fname, shown, x + 10 * Kit.scale, ty, ink) + else + Kit.textCenterBold(fname, shown, x, ty, w, ink) + end + end + if not enabled then return false end + return Kit.press(x, y, w, h) + or (not Kit.blockClicks and opts.id ~= nil + and Kit._activateId == opts.id) +end + +-- A small square control: +/- steppers, arrow cyclers, the row X. +function Kit.stepper(x, y, w, h, glyph, opts) + opts = opts or {} + opts.kind = opts.kind or "ghost" + opts.font = opts.font or "small" + return Kit.button(x, y, w, h, glyph, opts) +end + +-- A pill toggle (badges, dex SEEN/OWN, sub-tabs). `on` inverts it. +function Kit.chip(x, y, w, h, label, on, color, id) + audit("control", x, y, w, h, label) + local focused = id and Kit.focusable(id, x, y, w, h) or false + local c = color or PAL.line + if G then + local hot = focused or Kit.hover(x, y, w, h) + if on then + Theme.fillRounded(x, y, w, h, c, 1) + Theme.emboss(x, y, w, h, 1) + Kit.textCenterBold("micro", label, x, + y + (h - Kit.textHeight("micro")) / 2, w, PAL.inverse) + else + Theme.fillRounded(x, y, w, h, PAL.bg, 1) + Theme.strokeRounded(x, y, w, h, c, + hot and Theme.A.focus or Theme.A.hover, 1) + Kit.textCenterBold("micro", label, x, + y + (h - Kit.textHeight("micro")) / 2, w, c) + end + if hot then + Theme.strokeRounded(x - 2, y - 2, w + 4, h + 4, PAL.lineStrong, + Theme.A.focus, 2, Theme.radius() + 2) + end + end + return Kit.press(x, y, w, h) or (id ~= nil and Kit._activateId == id) +end + +-- A status label with no interaction: outlined text, the "INSTALLED"/"UPDATE" +-- markers on mod rows. +function Kit.tag(x, y, w, h, label, color) + if not G then return end + Theme.strokeRounded(x, y, w, h, color or PAL.line, 0.7, 1) + Kit.textCenter("micro", label, x, y + (h - Kit.textHeight("micro")) / 2, w, + color or PAL.muted) +end + +-- Checkbox row. Returns (newChecked, changed). +function Kit.checkbox(x, y, w, h, checked, label, id, labelColor) + local clicked, ink = Kit.row(x, y, w, h, false, id) + local box = 20 * Kit.scale + local bx, by = x + 12 * Kit.scale, y + (h - box) / 2 + if G then + local br = math.min(Theme.radius(), box / 3) + if checked then + Theme.fillRounded(bx, by, box, box, PAL.ink, 1, br) + Kit.textCenter("small", "X", bx, + by + (box - Kit.textHeight("small")) / 2, box, PAL.inverse) + else + Theme.strokeRounded(bx, by, box, box, PAL.line, Theme.A.hover, 1, br) + end + local lx = bx + box + 12 * Kit.scale + Kit.text("mono", Kit.ellipsize("mono", label, x + w - lx - 10 * Kit.scale), + lx, y + (h - Kit.textHeight("mono")) / 2, + labelColor or ink or PAL.text) + end + if clicked then return not checked, true end + return checked, false +end + +-- A two-state switch, for the settings ladders. +function Kit.toggle(x, y, w, h, on, id) + audit("control", x, y, w, h, "toggle") + local focused = id and Kit.focusable(id, x, y, w, h) or false + if G then + -- Track, then a knob inset inside it, so the control reads as a switch + -- rather than as a white square with a word next to it. The label sits + -- in the empty half, which is the half that says what pressing does. + local r = math.min(Theme.radius(), h / 2) + Theme.fillRounded(x, y, w, h, PAL.rowBg, 1, r) + Theme.strokeRounded(x, y, w, h, PAL.line, + (focused or Kit.hover(x, y, w, h)) and Theme.A.focus or Theme.A.hover, 1, r) + local inset = 3 + local knob = w / 2 - inset + Theme.fillRounded(on and (x + w / 2) or (x + inset), y + inset, knob, + h - 2 * inset, PAL.ink, 1, math.min(r, (h - 2 * inset) / 2)) + Kit.textCenter("micro", on and "ON" or "OFF", + on and x or (x + w / 2), y + (h - Kit.textHeight("micro")) / 2, w / 2, + PAL.text) + end + local hitTaken = Kit.press(x, y, w, h) or (id ~= nil and Kit._activateId == id) + if hitTaken then return not on, true end + return on, false +end + +-- A determinate progress bar with an optional caption. +function Kit.progress(x, y, w, h, frac, label) + Theme.meter(x, y, w, h, (frac or 0) * 100, PAL.ink) + if label then + Kit.text("micro", label, x, y + h + 4 * Kit.scale, PAL.muted) + end +end + +-- --------------------------------------------------------------- text field +function Kit.textfield(id, x, y, w, h, value, placeholder) + audit("control", x, y, w, h, id) + local focusRing = Kit.focusable(id, x, y, w, h) + value = tostring(value or "") + if Kit.press(x, y, w, h) or (Kit._activateId == id) then Kit.focus = id end + local focused = (Kit.focus == id) + if focused then + syncSoftKeyboard(id, x, y, w, h) + for _, e in ipairs(edits) do + if e == "\b" then + value = value:sub(1, -2) + elseif e == "\r" then + Kit.blur() + focused = false + else + value = value .. e + end + end + end + if G then + Theme.fillRounded(x, y, w, h, PAL.bg, 1) + Theme.strokeRounded(x, y, w, h, PAL.line, + (focused or focusRing) and Theme.A.focus or Theme.A.hairline, + focused and 2 or 1) + local pad = 10 * Kit.scale + local ty = y + (h - Kit.textHeight("mono")) / 2 + if value == "" and not focused then + Kit.text("mono", placeholder or "", x + pad, ty, PAL.faint) + else + local shown = Kit.ellipsizeLeft("mono", value, w - 2 * pad) + local tw = Kit.text("mono", shown, x + pad, ty, PAL.heading) + if focused and (Kit.time % 1) < 0.55 then + Theme.fill(x + pad + tw + 2, ty, math.max(1, Kit.scale), + Kit.textHeight("mono"), PAL.ink, 1) + end + end + end + return value +end + +-- -------------------------------------------------------------------- pager +-- Prev / Next / "1-12 of 151". Drawn even for a single page, so a list is +-- never silently truncated. This is the ONLY way the launcher moves through +-- a long list: no scrollbars, no momentum, bounded row count per frame. +-- Returns the new page (1-based) and the row height consumed. +function Kit.pager(x, y, w, page, total, perPage, idPrefix) + local h = math.max(Kit.tapMin(), 30 * Kit.scale) + local bw = 74 * Kit.scale + local pages = math.max(1, math.ceil(total / math.max(1, perPage))) + page = math.floor(Theme.clamp(page or 1, 1, pages)) + local gap = 8 * Kit.scale + idPrefix = idPrefix or "pager" + + if Kit.button(x, y, bw, h, "< Prev", { kind = "ghost", font = "small", + enabled = page > 1, id = idPrefix .. ":prev" }) then + page = math.max(1, page - 1) + end + if Kit.button(x + bw + gap, y, bw, h, "Next >", { kind = "ghost", + font = "small", enabled = page < pages, id = idPrefix .. ":next" }) then + page = math.min(pages, page + 1) + end + + local first = total > 0 and ((page - 1) * perPage + 1) or 0 + local last = math.min(total, page * perPage) + local label = ("%d-%d of %d (page %d/%d)"):format(first, last, total, page, pages) + local labelX = x + 2 * bw + 2 * gap + gap + Kit.text("mono", Kit.ellipsize("mono", label, math.max(0, x + w - labelX)), + labelX, y + (h - Kit.textHeight("mono")) / 2, PAL.caption) + return page, h +end + +-- Slice helper so callers never hand-roll page arithmetic (and never draw a +-- row that is off the page -- the entire performance claim rests on this). +function Kit.pageBounds(page, total, perPage) + local pages = math.max(1, math.ceil(total / math.max(1, perPage))) + page = math.floor(Theme.clamp(page or 1, 1, pages)) + local first = (page - 1) * perPage + 1 + local last = math.min(total, page * perPage) + return first, last, page, pages +end + +-- How many rows of `rowH` (plus `gap`) fit in `h` pixels. Panels call this +-- to derive perPage from the real viewport instead of a magic number, so a +-- tall window shows more rows and a phone shows fewer -- with no scrolling +-- either way. +function Kit.rowsThatFit(h, rowH, gap, minRows, maxRows) + local per = math.floor((h + (gap or 0)) / math.max(1, rowH + (gap or 0))) + return math.max(minRows or 1, math.min(maxRows or 99, per)) +end + +-- Mouse wheel over a paginated list turns PAGES. The wheel still has to do +-- something (users expect it), but it moves a bounded page index rather than +-- driving a pixel offset, so there is no scroll state and no interpolation. +function Kit.wheelPage(x, y, w, h, page, total, perPage) + if Kit.blockClicks or (Kit.wheelY or 0) == 0 then return page end + if not Kit.hit(x, y, w, h) then return page end + local pages = math.max(1, math.ceil(total / math.max(1, perPage))) + local moved = Theme.clamp((page or 1) + (Kit.wheelY > 0 and -1 or 1), 1, pages) + Kit.wheelY = 0 + return math.floor(moved) +end + +-- ------------------------------------------------------------------ spinner +-- The one animated element in the UI: a rotating arc of ticks. Drawn as N +-- short lines at descending alpha, which needs no shader, no canvas and no +-- blend-mode change. `t` defaults to the frame clock so every spinner on +-- screen stays in phase. +function Kit.spinner(cx, cy, r, t) + if not G or not has("line") then return end + t = t or Kit.time + local ticks = 12 + local step = (math.pi * 2) / ticks + local head = math.floor((t * 10) % ticks) + if has("setLineWidth") then G.setLineWidth(math.max(2, 2 * Kit.scale)) end + for i = 0, ticks - 1 do + local a = ((ticks - ((i - head) % ticks)) / ticks) + local ang = i * step - math.pi / 2 + local c, s = math.cos(ang), math.sin(ang) + Theme.col(PAL.ink, a * a) + G.line(cx + c * r * 0.55, cy + s * r * 0.55, cx + c * r, cy + s * r) + end + if has("setLineWidth") then G.setLineWidth(1) end +end + +-- ------------------------------------------------------------------- clip +-- Clip drawing to a rect. A stack: pushes intersect with the rect above and +-- a pop restores that rect rather than clearing the scissor, so a nested +-- region can never unclip its parent. The tracked rect also bounds Kit.hit, +-- so a widget clipped out of view is inert instead of taking taps aimed at +-- whatever is drawn where it left. +local clipStack = {} + +local function applyClip(rect) + Kit._clipRect = rect + if not (G and G.setScissor) then return end + if not rect then + G.setScissor() + elseif rect.w <= 0 or rect.h <= 0 then + -- LOVE rejects negative scissor dimensions; an exhausted clip region is + -- empty, not invalid. + G.setScissor(0, 0, 0, 0) + else + G.setScissor(math.floor(rect.x), math.floor(rect.y), + math.ceil(rect.w), math.ceil(rect.h)) + end +end + +function Kit.pushClip(x, y, w, h) + local prev = clipStack[#clipStack] + local x2, y2 = x + math.max(0, w), y + math.max(0, h) + if prev then + x, y = math.max(x, prev.x), math.max(y, prev.y) + x2 = math.min(x2, prev.x + prev.w) + y2 = math.min(y2, prev.y + prev.h) + end + local rect = { x = x, y = y, w = math.max(0, x2 - x), h = math.max(0, y2 - y) } + clipStack[#clipStack + 1] = rect + applyClip(rect) +end + +function Kit.popClip() + clipStack[#clipStack] = nil + applyClip(clipStack[#clipStack]) +end + +-- A pcall-ed draw that raised mid-clip must not leak the stack into later +-- frames (every hit test would stay fenced to the dead rect), so the frame +-- boundary clears it. +function Kit.resetClip() + for i = #clipStack, 1, -1 do clipStack[i] = nil end + applyClip(nil) +end + +return Kit diff --git a/src/ui/kit/Layout.lua b/src/ui/kit/Layout.lua new file mode 100644 index 00000000..53abb929 --- /dev/null +++ b/src/ui/kit/Layout.lua @@ -0,0 +1,115 @@ +-- Shared layout metrics for the launcher and the save editor. +-- +-- Both windows derive one `m` table per frame from the real window size and +-- the platform safe area, and every panel lays itself out in explicit pixels +-- off that table. Explicit pixels are the point: the old view expressed +-- widths as "100%" and leaned on a layout engine to resolve them, which is +-- where the launcher's layout bugs lived (percentages resolving against a +-- border box instead of a content box, auto-sized children measuring zero +-- height inside an auto-sized parent, flex-shrink compressing text until it +-- overlapped). None of those failure modes exist when a column is simply +-- `math.floor((contentW - gap) / 2)`. +-- +-- REFLOW, not shrink: a narrow window drops to fewer columns rather than +-- scaling the desktop layout down. Scale has a floor (Kit.layout clamps to +-- 0.9) so tap targets and text stay legible on a phone. + +local Kit = require("src.ui.kit.Kit") +local Theme = require("src.ui.kit.Theme") +local SafeArea = require("src.core.SafeArea") + +local Layout = {} + +-- Breakpoints, in safe-area pixels. Named so panels read intent rather than +-- magic numbers. +Layout.BP = { + twoCol = 640, -- side-by-side columns become possible + threeCol = 1100, -- wide desktop: mod list + detail + chrome +} + +-- Build the frame's metrics. `maxAppW` caps the content column on an +-- ultrawide monitor so the UI stays a readable measure instead of stretching. +function Layout.metrics(maxAppW) + local W, H = 0, 0 + if love and love.graphics and love.graphics.getDimensions then + W, H = love.graphics.getDimensions() + end + local ox, oy, sw, sh = SafeArea.rect() + local s = Kit.layout(sw, sh) + + local appW = math.min(sw, (maxAppW or 1200) * s) + local m = { + W = W, H = H, s = s, + x = math.floor(ox + (sw - appW) / 2), + top = math.floor(oy), + w = math.floor(appW), + h = math.floor(sh), + pad = math.floor(Theme.clamp(appW * 0.03, 10, 24)), + gap = math.floor(12 * s), + colGap = math.floor(16 * s), + rowH = math.max(Kit.tapMin(), math.floor(44 * s)), + btnH = math.max(Kit.tapMin(), math.floor(38 * s)), + chip = math.max(Kit.tapMin(), math.floor(40 * s)), + railH = math.max(3, math.floor(4 * s)), + logoH = math.floor(Theme.clamp(sh * 0.10, 36, 84)), + } + m.cols = (appW >= Layout.BP.threeCol * s and 3) + or (appW >= Layout.BP.twoCol * s and 2) + or 1 + m.twoCol = m.cols >= 2 + m.contentW = m.w - 2 * m.pad + m.colW = m.twoCol + and math.floor((m.contentW - m.colGap) / 2) + or m.contentW + m.contentX = m.x + m.pad + return m +end + +-- A vertical cursor for stacking blocks down a column. Immediate mode has +-- no layout pass, so panels advance a y by hand; this makes that explicit +-- and keeps the arithmetic in one place. +local Cursor = {} +Cursor.__index = Cursor + +function Layout.cursor(x, y, w) + return setmetatable({ x = x, y = y, w = w, y0 = y }, Cursor) +end + +-- Reserve `h` pixels and return the rect that was reserved. +function Cursor:take(h, gapAfter) + local x, y = self.x, self.y + self.y = self.y + h + (gapAfter or 0) + return x, y, self.w, h +end + +function Cursor:skip(h) + self.y = self.y + h +end + +function Cursor:height() + return self.y - self.y0 +end + +-- Split the cursor's width into `n` equal columns with `gap` between them, +-- returning a function that yields the i-th column's x and width. +function Layout.columns(x, w, n, gap) + n = math.max(1, n) + local cw = math.floor((w - gap * (n - 1)) / n) + return function(i) + return x + (i - 1) * (cw + gap), cw + end +end + +-- Lay a row of buttons out right-aligned within [x, x+w], returning a +-- function that yields each button's x as it is consumed right to left. +function Layout.rightCluster(x, w, gap) + local cursor = x + w + return function(bw) + cursor = cursor - bw + local bx = cursor + cursor = cursor - gap + return bx + end +end + +return Layout diff --git a/src/ui/kit/Loader.lua b/src/ui/kit/Loader.lua new file mode 100644 index 00000000..5f4169ae --- /dev/null +++ b/src/ui/kit/Loader.lua @@ -0,0 +1,133 @@ +-- Non-dismissable loading overlays. +-- +-- The rule this module enforces: ANY operation that can make the UI wait -- +-- a network fetch, a ROM extraction, a mod install, an update check -- puts +-- something obvious on screen for its whole duration. The old launcher +-- failed this twice over: slow work ran synchronously on the main thread, so +-- the window simply stopped responding (the Find Mods tab could hang for +-- minutes with no indication it was doing anything at all), and the few +-- operations that did report progress did so as a small line of text. +-- +-- Two presentations: +-- Loader.overlay(...) a modal scrim + panel, for work the user must wait +-- on before doing anything else. It BLOCKS input +-- (Kit.blockClicks) and offers no dismiss control -- +-- that is deliberate, so a half-finished install can +-- never be clicked around. Cancellable work passes +-- an onCancel and gets exactly one Cancel button. +-- Loader.inline(...) a spinner + label sized to a control, for work that +-- only blocks part of the UI (a row's update check). +-- +-- Callers drive both from a state table; nothing here owns state or time, so +-- the same overlay renders identically in a screenshot test. + +local Kit = require("src.ui.kit.Kit") +local Theme = require("src.ui.kit.Theme") +local PAL = Theme.PAL + +local Loader = {} + +-- Scrim alpha. Not opaque: the user keeps the context of what they were +-- doing, which is most of why a modal beats a blank screen. +local SCRIM_A = 0.82 + +-- spec = { +-- title = "Fetching mod index", -- required, the verb in progress +-- detail = "index.json from ...", -- optional second line +-- progress = 0..1 or nil, -- nil = indeterminate (spinner) +-- count = "3 of 12", -- optional right-aligned counter +-- onCancel = function() end, -- optional; adds a Cancel button +-- cancelLabel = "Cancel", +-- } +-- Returns true when the cancel button was activated this frame. +function Loader.overlay(m, spec) + if not spec then return false end + local G = love and love.graphics + local W, H = m.W, m.H + + -- The scrim covers the whole window, not just the app column: a modal that + -- leaves the letterboxed margins live is a modal you can click around. + if G then + Theme.fill(0, 0, W, H, PAL.bg, SCRIM_A) + end + -- Everything drawn BEFORE this call is now shielded; the panel below + -- lowers the shield for its own controls. + Kit.blockClicks = true + + local pw = math.floor(math.min(m.w - 2 * m.pad, 460 * m.s)) + local ph = math.floor((spec.onCancel and 210 or 160) * m.s) + local px = math.floor((W - pw) / 2) + local py = math.floor((H - ph) / 2) + + Kit.card(px, py, pw, ph, true) + + local pad = math.floor(18 * m.s) + local cx = px + pw / 2 + + -- Spinner (indeterminate) or a progress bar (determinate). Never both. + local y = py + pad + if spec.progress then + Kit.textCenter("button", spec.title, px + pad, y, pw - 2 * pad, PAL.heading) + y = y + Kit.textHeight("button") + math.floor(14 * m.s) + Kit.progress(px + pad, y, pw - 2 * pad, math.floor(10 * m.s), spec.progress) + y = y + math.floor(10 * m.s) + math.floor(10 * m.s) + local pct = ("%d%%"):format(math.floor(spec.progress * 100 + 0.5)) + Kit.textCenter("small", pct, px + pad, y, pw - 2 * pad, PAL.detail) + y = y + Kit.textHeight("small") + math.floor(6 * m.s) + else + local r = math.floor(16 * m.s) + Kit.spinner(cx, y + r, r) + y = y + 2 * r + math.floor(14 * m.s) + Kit.textCenter("button", spec.title, px + pad, y, pw - 2 * pad, PAL.heading) + y = y + Kit.textHeight("button") + math.floor(6 * m.s) + end + + if spec.detail and spec.detail ~= "" then + Kit.textCenter("small", + Kit.ellipsize("small", spec.detail, pw - 2 * pad), + px + pad, y, pw - 2 * pad, PAL.muted) + y = y + Kit.textHeight("small") + math.floor(4 * m.s) + end + if spec.count and spec.count ~= "" then + Kit.textCenter("micro", spec.count, px + pad, y, pw - 2 * pad, PAL.faint) + end + + local cancelled = false + if spec.onCancel then + -- The one control a blocking overlay may have. It lives inside the + -- panel, so it is the only thing on screen that can take a click. + Kit.blockClicks = false + local bw = math.floor(math.min(pw - 2 * pad, 160 * m.s)) + local bh = m.btnH + if Kit.button(px + (pw - bw) / 2, py + ph - pad - bh, bw, bh, + spec.cancelLabel or "Cancel", + { kind = "ghost", id = "loader:cancel" }) then + cancelled = true + end + Kit.blockClicks = true + end + return cancelled +end + +-- A spinner plus label occupying a control-sized rect. Used in place of the +-- button that started the work, so the row does not reflow while it runs. +function Loader.inline(x, y, w, h, label) + local r = math.floor(math.min(h, 20 * Kit.scale) / 2) + local cx = x + r + 4 + Kit.spinner(cx, y + h / 2, r) + if label then + local lx = cx + r + 8 + Kit.text("small", Kit.ellipsize("small", label, math.max(0, x + w - lx)), + lx, y + (h - Kit.textHeight("small")) / 2, PAL.muted) + end +end + +-- A tiny spinner sized to sit inside a text run (a mod row checking for +-- updates). Returns the width it consumed. +function Loader.dot(x, y, size) + local r = size / 2 + Kit.spinner(x + r, y + r, r) + return size +end + +return Loader diff --git a/src/ui/kit/Theme.lua b/src/ui/kit/Theme.lua new file mode 100644 index 00000000..426f16da --- /dev/null +++ b/src/ui/kit/Theme.lua @@ -0,0 +1,431 @@ +-- High-contrast theme for the launcher (src/import/LauncherView.lua). The +-- save editor keeps its own tools/save-editor/Theme.lua, whose primitives +-- take a radius where these take a colour -- do not cross-wire them. This +-- replaces the old navy gradient look wholesale: a near-black field with the +-- faintest red cast, cards a few values above it, white hairline outlines, +-- flat fills, no gradients and no glows anywhere. +-- +-- That is not only a visual choice. Every effect this theme drops was a GPU +-- pipeline flush in the old renderer: +-- * gradients needed a stencil pass + a dynamic mesh per card +-- (G.stencil / setStencilTest / draw(mesh) = 3 state changes per card), +-- * glows set blend mode "add", drew 7 stacked rects, then set it back. +-- Flat fills with a 1px outline all share one pipeline state, so LOVE batches +-- an entire panel into a couple of draw calls. What this theme DOES pay for +-- is extra vertices at the same pipeline state: rounded corners, the +-- two-rect emboss on a control, and the three stacked rounded rects that make +-- a card's drop shadow (Theme.shadow). Vertices are the tier of expense this +-- theme is willing to pay; the tier above it -- stencils, meshes, blend-mode +-- changes, canvases, shaders -- is the one it will not, and a shadow drawn as +-- a blurred canvas would land squarely in it. +-- +-- Emphasis is carried by INVERSION, not by colour weight: a selected or +-- focused control fills white and prints black. That keeps contrast at +-- maximum for accessibility and costs exactly one extra rect. +-- +-- Every colour below is 0-255 RGB; alpha is passed per draw call to col(). +-- Everything degrades under the headless love_stub used by tests/ (no fonts, +-- no line, no mesh): each primitive probes for what it needs. + +local Theme = {} + +local PAL = { + -- field + surfaces. The field carries a FAINT red cast (a few points of + -- red over an otherwise neutral near-black) and cards sit a few steps above + -- it in the same hue, so a card reads as a raised object rather than as an + -- outline drawn on the page. These are still flat fills -- the depth comes + -- from the value step plus Theme.shadow, not from a gradient. + field = { 16, 8, 10 }, -- the page BEHIND the cards + bg = { 0, 0, 0 }, -- true black: button rests, field interiors + surface = { 28, 21, 24 }, -- card interiors + rowBg = { 20, 14, 17 }, -- rows inside a card, one step below it + raised = { 44, 34, 38 }, -- hover feedback + ink = { 255, 255, 255 }, -- the selected/focused fill + -- outlines. Two weights only: a hairline for structure, solid for focus. + line = { 255, 255, 255 }, -- hairline, drawn at alpha 0.35 + lineStrong = { 255, 255, 255 }, -- focus / selection, drawn at alpha 1 + -- text + heading = { 255, 255, 255 }, + text = { 255, 255, 255 }, + detail = { 200, 200, 200 }, + muted = { 150, 150, 150 }, + caption = { 170, 170, 170 }, -- letterspaced section captions + faint = { 110, 110, 110 }, -- slot indices, hints + inverse = { 0, 0, 0 }, -- ink on a white (selected/focused) fill + -- semantics. Used for TEXT and OUTLINES only, never as a large fill, so + -- the black/white contrast story is never diluted. + green = { 0, 255, 140 }, -- safe / confirmed / installed + yellow = { 255, 214, 0 }, -- attention / update available + red = { 255, 80, 90 }, -- destructive + blue = { 90, 190, 255 }, -- links, in-panel navigation + steel = { 120, 120, 120 }, -- disabled + -- the tri-colour version rail is the one piece of brand colour that stays + railRed = { 255, 60, 72 }, + railBlue = { 70, 150, 255 }, + railGold = { 255, 203, 5 }, +} +-- Semantic aliases kept so ported call sites read the same as before. +PAL.cardBorder = PAL.line +PAL.greenInk = PAL.inverse +PAL.blueInk = PAL.blue +PAL.redSoft = PAL.red +PAL.greenDark = PAL.green +Theme.PAL = PAL + +-- Standard alphas, so "hairline" means one thing everywhere. +Theme.A = { + hairline = 0.35, + hover = 0.65, + focus = 1.0, + fillHover= 1.0, + disabled = 0.30, +} + +local G = love and love.graphics or nil + +local has = {} +local function probe(name) + if has[name] == nil then has[name] = (G and type(G[name]) == "function") or false end + return has[name] +end +Theme.probe = probe + +function Theme.col(c, a) + if not G then return end + G.setColor(c[1] / 255, c[2] / 255, c[3] / 255, a or 1) +end +local col = Theme.col + +function Theme.clamp(n, lo, hi) + if n < lo then return lo end + if n > hi then return hi end + return n +end +local clamp = Theme.clamp + +-- --------------------------------------------------------------- primitives +-- Square, flat, snapped to whole pixels. Snapping matters at 1px line width: +-- a rect on a half pixel renders as a 2px grey smear instead of a crisp white +-- hairline, which is the whole look. +local function snap(v) return math.floor(v + 0.5) end +Theme.snap = snap + +function Theme.fill(x, y, w, h, c, a) + if not G or w <= 0 or h <= 0 then return end + col(c or PAL.bg, a or 1) + G.rectangle("fill", snap(x), snap(y), snap(w), snap(h)) +end + +-- Corner radius for controls. Fixed rather than scaled: LOVE tessellates a +-- rounded rect by radius, so a scale-driven radius would change the vertex +-- count with the window size, and these are the two tiers the design needs. +-- Controls get the smaller one, containers the larger, so a button never +-- looks like a card and a card never looks like a button. +function Theme.radius() + return 8 +end + +function Theme.cardRadius() + return 14 +end + +-- DROP SHADOW. Three stacked rounded rects at low alpha, each one step wider +-- and one step lower than the last -- a cheap falloff that needs no blur, no +-- canvas and no blend-mode change, so it stays inside the pipeline budget the +-- rest of this file is written to. Drawn BEFORE the surface it belongs to, +-- and never for a control (only containers cast one, or the whole screen +-- reads as floating debris). +function Theme.shadow(x, y, w, h, r) + if not G or w <= 0 or h <= 0 then return end + r = r or Theme.cardRadius() + for i = 1, 3 do + local spread = i * 2 + col(PAL.bg, 0.13) + G.rectangle("fill", snap(x - spread), snap(y - spread + i * 3), + snap(w + 2 * spread), snap(h + 2 * spread), r + spread, r + spread) + end +end + +function Theme.fillRounded(x, y, w, h, c, a, r) + if not G or w <= 0 or h <= 0 then return end + r = r or Theme.radius() + col(c or PAL.bg, a or 1) + G.rectangle("fill", snap(x), snap(y), snap(w), snap(h), r, r) +end + +function Theme.strokeRounded(x, y, w, h, c, a, lw, r) + if not G or w <= 0 or h <= 0 then return end + lw = lw or 1 + r = r or Theme.radius() + if probe("setLineWidth") then G.setLineWidth(lw) end + col(c or PAL.line, a or Theme.A.hairline) + G.rectangle("line", snap(x) + lw / 2, snap(y) + lw / 2, + snap(w) - lw, snap(h) - lw, r, r) + if probe("setLineWidth") then G.setLineWidth(1) end +end + +-- EMBOSS. A lit top edge and a shaded bottom edge inside the control, which +-- is what makes a flat fill read as a raised key. Two thin rects on top of +-- the fill -- no gradient mesh, no stencil, no blend-mode change, so it costs +-- the same pipeline state as everything around it. +function Theme.emboss(x, y, w, h, strength) + if not G or w <= 2 or h <= 2 then return end + strength = strength or 1 + local t = math.max(1, math.floor(h * 0.10)) + -- The inset must clear the corner arc, but a narrow control (a stepper, a + -- row chip) is thinner than two radii -- clamp or the highlight rect goes + -- negative-width and vanishes. + local r = math.min(Theme.radius(), math.floor(w / 3)) + -- highlight along the top + col(PAL.ink, 0.28 * strength) + G.rectangle("fill", snap(x) + r, snap(y) + 1, snap(w) - 2 * r, t) + -- shadow along the bottom + col(PAL.bg, 0.30 * strength) + G.rectangle("fill", snap(x) + r, snap(y + h) - t - 1, snap(w) - 2 * r, t) +end + +-- Faux bold: the UI face ships in one weight, so a bold run is the same text +-- drawn a second time one pixel across. Callers do this only for button +-- labels, where the extra draw is bounded by the number of controls on +-- screen and the text is already a cached Text object. +Theme.BOLD_OFFSET = 1 + +-- A 1px outline drawn INSIDE the rect, so a bordered control never bleeds +-- into its neighbour's pixel and adjacent outlines never double up to 2px. +function Theme.stroke(x, y, w, h, c, a, lw) + if not G or w <= 0 or h <= 0 then return end + lw = lw or 1 + if probe("setLineWidth") then G.setLineWidth(lw) end + col(c or PAL.line, a or Theme.A.hairline) + G.rectangle("line", snap(x) + lw / 2, snap(y) + lw / 2, + snap(w) - lw, snap(h) - lw) + if probe("setLineWidth") then G.setLineWidth(1) end +end + +-- The design's only container: a rounded surface a few values above the +-- field, its own drop shadow, and a white hairline. `emphasis` raises the +-- outline to full white (used for the focused/active card). +function Theme.card(x, y, w, h, emphasis) + local r = Theme.cardRadius() + Theme.shadow(x, y, w, h, r) + Theme.fillRounded(x, y, w, h, PAL.surface, 1, r) + Theme.strokeRounded(x, y, w, h, PAL.line, + emphasis and Theme.A.focus or Theme.A.hairline, 1, r) +end + +-- A list row. Three states, each one rect plus one outline: +-- normal one value below the card it sits in, hairline +-- hover lifted fill, brighter hairline +-- selected WHITE fill (callers print ink = PAL.inverse over it) +function Theme.row(x, y, w, h, state) + local r = Theme.radius() + if state == "selected" then + Theme.fillRounded(x, y, w, h, PAL.ink, 1, r) + return PAL.inverse + end + Theme.fillRounded(x, y, w, h, + state == "hover" and PAL.raised or PAL.rowBg, 1, r) + Theme.strokeRounded(x, y, w, h, PAL.line, + state == "hover" and Theme.A.hover or Theme.A.hairline, 1, r) + return PAL.text +end + +-- A percentage meter (HP, box fill, dex completion, import progress). +-- pct is 0-100. Outline + solid fill, rounded to the track's own half-height +-- so a thin bar reads as a capsule instead of a clipped rectangle. +function Theme.meter(x, y, w, h, pct, c) + if not G then return end + local r = math.min(Theme.radius(), h / 2) + Theme.strokeRounded(x, y, w, h, PAL.line, Theme.A.hairline, 1, r) + local fill = (w - 2) * clamp((pct or 0) / 100, 0, 1) + if fill > 0 then + Theme.fillRounded(x + 1, y + 1, fill, h - 2, c or PAL.ink, 1, + math.min(r, fill / 2)) + end +end + +-- The 4px tri-colour rail across the top of both windows: the only brand +-- colour on screen, and the one thing that says "this is the Gen 1 launcher". +function Theme.versionRail(x, y, w, h) + if not G then return end + local bars = { PAL.railRed, PAL.railBlue, PAL.railGold } + local seg = w / 3 + for i, c in ipairs(bars) do + Theme.fill(x + (i - 1) * seg, y, seg, h, c, 1) + end +end + +-- ------------------------------------------------------------------- text +-- Letterspaced caption text. The UI font has no tracking control, so this +-- advances glyph by glyph; captions are short by construction. +-- Measuring never throws. Third-party strings (mod names from an index, +-- translated captions) reach these primitives unvalidated. +local function safeWidthOrZero(font, s) + local ok, w = pcall(font.getWidth, font, s) + return ok and w or 0 +end + +-- Steps CODEPOINTS, not bytes: a translated caption (the JP strings) is +-- multi-byte, and printing half a sequence is a "UTF-8 decoding error" that +-- takes the frame down. +local function eachChar(text, fn) + local i = 1 + local n = #text + while i <= n do + local j = i + 1 + while j <= n do + local b = text:byte(j) + if b < 0x80 or b >= 0xC0 then break end + j = j + 1 + end + fn(text:sub(i, j - 1)) + i = j + end +end + +function Theme.spaced(font, text, x, y, spacing) + if not G or not font then return 0 end + local cx = x + eachChar(tostring(text), function(ch) + pcall(G.print, ch, cx, y) + cx = cx + safeWidthOrZero(font, ch) + spacing + end) + return math.max(0, cx - x - spacing) +end + +function Theme.spacedWidth(font, text, spacing) + if not font then return 0 end + local w = 0 + eachChar(tostring(text), function(ch) + w = w + safeWidthOrZero(font, ch) + spacing + end) + return math.max(0, w - spacing) +end + +-- UTF-8 stepping. Truncation MUST move whole codepoints: LOVE's Font:getWidth +-- raises "UTF-8 decoding error" on a string cut through a multi-byte sequence, +-- and a launcher listing mods with non-ASCII names (the JP index) hits that on +-- the first frame. A continuation byte is 10xxxxxx (0x80..0xBF). +local function prevCharStart(s, i) + -- largest j < i where s:byte(j) starts a codepoint + local j = i - 1 + while j > 1 do + local b = s:byte(j) + if b < 0x80 or b >= 0xC0 then break end + j = j - 1 + end + return j +end + +local function nextCharStart(s, i) + local j = i + 1 + while j <= #s do + local b = s:byte(j) + if b < 0x80 or b >= 0xC0 then break end + j = j + 1 + end + return j +end + +-- Width that never throws on malformed input: a mod name can carry anything. +local function safeWidth(font, s) + local ok, w = pcall(font.getWidth, font, s) + return ok and w or math.huge +end +Theme.safeWidth = safeWidth + +-- Clip text to a pixel width with a trailing ellipsis. Results are memoised +-- per (font, text, width) in Kit's measurement cache -- this function is the +-- single hottest string operation in a list-heavy frame, and it is O(n) in +-- glyphs with a getWidth call per step. +function Theme.ellipsize(font, text, maxW) + text = tostring(text or "") + if not font then return text end + -- A non-positive budget means "nothing fits", not "everything fits". + if maxW <= 0 then return "" end + if safeWidth(font, text) <= maxW then return text end + local ell = "..." + local ew = safeWidth(font, ell) + local last = #text + 1 -- one past the end of the kept prefix + while last > 1 do + last = prevCharStart(text, last) + local head = text:sub(1, last - 1) + if safeWidth(font, head) + ew <= maxW then return head .. ell end + end + return ell +end + +-- Save paths truncate from the LEFT so the filename survives. +function Theme.ellipsizeLeft(font, text, maxW) + text = tostring(text or "") + if not font then return text end + if maxW <= 0 then return "" end + if safeWidth(font, text) <= maxW then return text end + local ell = "..." + local ew = safeWidth(font, ell) + local i = 1 + while i <= #text do + i = nextCharStart(text, i) + local tail = text:sub(i) + if safeWidth(font, tail) + ew <= maxW then return ell .. tail end + end + return ell +end + +-- The background: one flat clear to the faintly red-cast field colour. One +-- call, no mesh, no fan, no allocation -- the old radial field built a +-- 66-vertex mesh EVERY frame. The tint is deliberately small (a handful of +-- points of red at near-black): enough that the cards read as sitting ON +-- something, not enough to compete with the tri-colour rail for brand duty. +function Theme.field() + if not G then return end + G.clear(PAL.field[1] / 255, PAL.field[2] / 255, PAL.field[3] / 255, 1) +end + +-- ------------------------------------------------------------------- fonts +-- Font set, rebuilt only when the scale changes. Sizes are integers by +-- construction: fractional sizes measure and render at different widths, +-- which is what made ported launcher text overrun its measured box. +function Theme.fonts(s) + if not probe("newFont") then return {} end + -- Every face goes through UiFont.attach, which hangs a kana/CJK fallback + -- off it. Without that a translated build renders the entire launcher as + -- tofu boxes -- LOVE's default face is Latin-only. + local UiFont + local okUi, mod = pcall(require, "src.render.UiFont") + if okUi then UiFont = mod end + local cache = {} + local function f(px) + local n = math.max(8, math.floor(px + 0.5)) + if not cache[n] then + local face = G.newFont(n) + if UiFont and UiFont.attach then + local ok, attached = pcall(UiFont.attach, face, n) + if ok and attached then face = attached end + end + cache[n] = face + end + return cache[n] + end + return { + scale = s, + wordmark = f(14 * s), + brand = f(11 * s), + chip = f(11 * s), + tile = f(13 * s), + tab = f(13 * s), + button = f(14 * s), + small = f(12 * s), + tiny = f(11 * s), + micro = f(10 * s), + caption = f(12 * s), + mono = f(12 * s), + monoRow = f(13 * s), + monoBig = f(18 * s), + title = f(24 * s), + headline = f(26 * s), + stat = f(19 * s), + } +end + +return Theme diff --git a/src/update/Check.lua b/src/update/Check.lua index b06b1f61..c485336c 100644 --- a/src/update/Check.lua +++ b/src/update/Check.lua @@ -17,6 +17,7 @@ -- worker can reuse the exact same code path via love.filesystem.load. local Check = {} +local Platform = require("src.core.Platform") Check.REPO = "bryanthaboi/gen1recomp" @@ -51,8 +52,13 @@ end -- falls back to require. function Check.parseRelease(jsonText, Json) Json = Json or require("src.link.Json") - local doc = Json.decode(jsonText) - if type(doc) ~= "table" or not doc.tag_name then + local notJson = Json.describeUnexpected(jsonText) + if notJson then return nil, notJson end + local doc, decodeErr = Json.decode(jsonText) + if type(doc) ~= "table" then + return nil, decodeErr or "no tag_name in release json" + end + if not doc.tag_name then return nil, "no tag_name in release json" end local version = stripV(doc.tag_name) @@ -99,6 +105,10 @@ local cache = { status = "idle" } -- newest snapshot from the worker local function ensureWorker() if workerReady ~= nil then return workerReady end + if not Platform.networkValidated() then + workerReady = false + return false + end if not (love and love.thread and love.thread.newThread) then workerReady = false return false diff --git a/src/update/SwitchOta.lua b/src/update/SwitchOta.lua new file mode 100644 index 00000000..ca914bb2 --- /dev/null +++ b/src/update/SwitchOta.lua @@ -0,0 +1,229 @@ +-- Switch OTA wire format (host-testable, no love.*). +-- The native DEVKITPRO launcher (libnx + switch-curl) must implement the +-- same decisions. LÖVE on NX never runs this path — Platform.networkValidated +-- stays false and src/update/Check.lua remains gated off on NX. +-- +-- Asset: gen1recomp--switch.zip (same SD zip used for install). + +local SwitchOta = {} + +SwitchOta.RELEASES_API = + "https://api.github.com/repos/bryanthaboi/gen1recomp/releases/latest" +SwitchOta.OTA_ASSET_PATTERN = "^gen1recomp%-(%d+%.%d+%.%d+)%-switch%.zip$" +SwitchOta.CHECK_TIMEOUT_SEC = 6 +SwitchOta.GAME_NRO_NAME = "gen1recomp-game.nro" +SwitchOta.LAUNCHER_NRO_NAME = "gen1recomp.nro" +SwitchOta.SAVE_DIR_NAME = "pokemon-love2d" +SwitchOta.INSTALL_DIR = "switch/gen1recomp" + +local function parseSemver(s) + if type(s) ~= "string" then return nil end + local body = s:match("^v?(.+)$") + if not body then return nil end + local maj, min, pat = body:match("^(%d+)%.(%d+)%.(%d+)$") + if not maj then return nil end + return { major = tonumber(maj), minor = tonumber(min), patch = tonumber(pat) } +end + +function SwitchOta.compareSemver(a, b) + local pa, pb = parseSemver(a), parseSemver(b) + if not pa and not pb then return 0 end + if not pa then return -1 end + if not pb then return 1 end + for _, field in ipairs({ "major", "minor", "patch" }) do + if pa[field] < pb[field] then return -1 end + if pa[field] > pb[field] then return 1 end + end + return 0 +end + +function SwitchOta.isOtaAssetName(name) + if type(name) ~= "string" then return false end + return name:match(SwitchOta.OTA_ASSET_PATTERN) ~= nil +end + +function SwitchOta.versionFromOtaAsset(name) + if type(name) ~= "string" then return nil end + return name:match(SwitchOta.OTA_ASSET_PATTERN) +end + +local function findJsonObjectStart(jsonText, pos) + if type(jsonText) ~= "string" or not pos or pos < 1 then return nil end + local depth = 0 + local p = pos + while p >= 1 do + local c = jsonText:sub(p, p) + if c == "}" then + depth = depth + 1 + elseif c == "{" then + if depth == 0 then return p end + depth = depth - 1 + end + p = p - 1 + end + return nil +end + +local function findJsonObjectEnd(jsonText, objectStart) + if type(jsonText) ~= "string" or not objectStart then return nil end + if jsonText:sub(objectStart, objectStart) ~= "{" then return nil end + local depth = 1 + local p = objectStart + 1 + local len = #jsonText + while p <= len do + local c = jsonText:sub(p, p) + if c == "{" then + depth = depth + 1 + elseif c == "}" then + depth = depth - 1 + if depth == 0 then return p + 1 end + end + p = p + 1 + end + return nil +end + +-- Parse GitHub releases/latest JSON (tag_name + assets[].name/browser_download_url). +-- Returns { tag, version, assetName, downloadUrl } or nil + reason. +function SwitchOta.parseRelease(jsonText) + if type(jsonText) ~= "string" or jsonText == "" then + return nil, "empty_json" + end + local tag = jsonText:match('"tag_name"%s*:%s*"(.-)"') + if not tag then return nil, "missing_tag" end + local version = tag:match("^v?(%d+%.%d+%.%d+)$") + if not version then return nil, "bad_tag" end + + local cursor = 1 + while true do + local nameKeyPos = jsonText:find('"name"', cursor, true) + if not nameKeyPos then break end + local tail = jsonText:sub(nameKeyPos) + local name = tail:match('"name"%s*:%s*"(.-)"') + if name and SwitchOta.isOtaAssetName(name) then + local assetStart = findJsonObjectStart(jsonText, nameKeyPos) + local assetEnd = assetStart and findJsonObjectEnd(jsonText, assetStart) + if assetStart and assetEnd and assetEnd > nameKeyPos then + local assetBlock = jsonText:sub(assetStart, assetEnd - 1) + local downloadUrl = assetBlock:match('"browser_download_url"%s*:%s*"(.-)"') + if downloadUrl and downloadUrl ~= "" then + return { + tag = tag, + version = version, + assetName = name, + downloadUrl = downloadUrl, + } + end + end + end + cursor = nameKeyPos + 6 + end + return nil, "missing_ota_asset" +end + +-- Decide check outcome given installed version and parsed release. +-- Returns status: uptodate | available | error +function SwitchOta.decideUpdate(installedVersion, release) + if type(installedVersion) ~= "string" or not parseSemver(installedVersion) then + return { status = "error", reason = "bad_installed_version" } + end + if type(release) ~= "table" or not release.version then + return { status = "error", reason = "bad_release" } + end + local cmp = SwitchOta.compareSemver(release.version, installedVersion) + if cmp <= 0 then + return { status = "uptodate", version = installedVersion } + end + return { + status = "available", + version = release.version, + assetName = release.assetName, + downloadUrl = release.downloadUrl, + } +end + +-- Parse sha256sums.txt lines: " " or " *" +function SwitchOta.parseSums(text) + local sums = {} + if type(text) ~= "string" then return sums end + for line in (text .. "\n"):gmatch("(.-)\n") do + local hex, name = line:match("^(%x+)%s+%*?%./?(.-)%s*$") + if hex and name and name ~= "" then + sums[name] = hex:lower() + end + end + return sums +end + +-- Require a matching sha256. Missing sum is ALWAYS reject (no silent accept). +function SwitchOta.verifySha256(assetName, actualHex, sums) + if type(assetName) ~= "string" or assetName == "" then + return false, "bad_asset_name" + end + if type(sums) ~= "table" then + return false, "missing_sums" + end + local expected = sums[assetName] + if type(expected) ~= "string" or expected == "" then + return false, "sum_not_found" + end + if type(actualHex) ~= "string" or actualHex == "" then + return false, "missing_actual_hash" + end + if actualHex:lower() ~= expected:lower() then + return false, "hash_mismatch" + end + return true, nil +end + +-- Atomic apply plan: never writes live NROs directly; never touches saves. +-- Replaces game + launcher so NACP versions stay in sync (hbmenu / Sphaira). +function SwitchOta.planAtomicApply(installDir, verifiedTempPath) + installDir = installDir or SwitchOta.INSTALL_DIR + local gameNro = installDir .. "/" .. SwitchOta.GAME_NRO_NAME + local launcherNro = installDir .. "/" .. SwitchOta.LAUNCHER_NRO_NAME + local partPath = gameNro .. ".part" + local launcherPart = launcherNro .. ".part" + return { + steps = { + { op = "copy_to_part", from = verifiedTempPath, to = partPath }, + { op = "rename", from = partPath, to = gameNro }, + { op = "copy_to_part", from = "launcher", to = launcherPart }, + { op = "rename", from = launcherPart, to = launcherNro }, + { op = "env_set_next_load", target = gameNro }, + }, + preserve = { installDir .. "/" .. SwitchOta.SAVE_DIR_NAME }, + forbidden = { + "delete:" .. installDir .. "/" .. SwitchOta.SAVE_DIR_NAME, + "write_direct:" .. gameNro, + }, + } +end + +-- Offline / skip / timeout policy. Never blocks forever. +-- elapsedSec: time spent on the network check so far +-- events: { networkOk=bool, userSkip=bool, apiError=bool } +function SwitchOta.offlinePolicy(elapsedSec, events) + events = events or {} + if events.userSkip then + return { action = "play_installed", reason = "user_skip", message = "update skipped" } + end + if events.apiError or events.networkOk == false then + return { + action = "play_installed", + reason = "offline_or_error", + message = "offline or update check failed — play installed version", + } + end + local timeout = SwitchOta.CHECK_TIMEOUT_SEC + if type(elapsedSec) == "number" and elapsedSec >= timeout then + return { + action = "play_installed", + reason = "timeout", + message = "update check timed out after " .. tostring(timeout) .. "s", + } + end + return { action = "keep_checking", reason = "in_flight" } +end + +return SwitchOta diff --git a/src/update/check_worker.lua b/src/update/check_worker.lua index 24ed03a7..7a0dd1ba 100644 --- a/src/update/check_worker.lua +++ b/src/update/check_worker.lua @@ -80,7 +80,10 @@ local function curlCapture(url) local pipe = HostShell.popen(cmd) if not pipe then return nil end local out = pipe:read("*a") - pipe:close() + -- HostShell.pclose, not pipe:close(): a close outside the spawn lock can + -- free a FILE while another thread's popen walks the stream list, which + -- deadlocks that thread permanently (see HostShell's popen notes). + HostShell.pclose(pipe) if not out or out == "" then return nil end return out end @@ -89,7 +92,7 @@ local function haveCurl() local pipe = HostShell.popen("curl --version") if not pipe then return false end local out = pipe:read("*a") - pipe:close() + HostShell.pclose(pipe) return out ~= nil and out:find("curl", 1, true) ~= nil end @@ -234,6 +237,11 @@ local function launchDownload(url, partAbs, doneAbs) local batRel = "updates/dl.bat" love.filesystem.write(batRel, "@echo off\r\n" + -- start /b hands the child our cwd, the install folder, and the + -- detached cmd.exe held that folder un-movable for the rest of the + -- transfer after the game exited (#727). Every path below is + -- absolute, so park the child in its own directory (the save dir). + .. "cd /d \"%~dp0\"\r\n" .. "curl -fsSL --connect-timeout 15 --max-time 900 -o \"" .. partAbs .. "\" \"" .. url .. "\"\r\n" .. "type nul > \"" .. doneAbs .. "\"\r\n") @@ -271,6 +279,14 @@ local function doDownload() -- stalled or run-away transfer breaks out and lets verification fail cleanly local waited, lastSize, lastChange = 0, -1, 0 while true do + -- A queued quit means the window already closed. Bail so the join in + -- Check.shutdown does not hold the dead window's process (and, on + -- Windows, its folder) open for up to the whole transfer (#727). The + -- quit stays on the channel for the command loop; the detached curl + -- times out on its own and the next launch's doCheck verifies and + -- re-offers whatever landed. + local peeked = cmdCh:peek() + if type(peeked) == "table" and peeked.cmd == "quit" then return end if love.filesystem.getInfo(doneRel) then break end local pinfo = love.filesystem.getInfo(partRel) local cur = (pinfo and pinfo.size) or 0 diff --git a/src/world/Map.lua b/src/world/Map.lua index 14b25b2c..c28ba8c8 100644 --- a/src/world/Map.lua +++ b/src/world/Map.lua @@ -24,6 +24,13 @@ local NO_SHORE_TILESETS = { SHIP_PORT = true } -- what counts as "outside" for the wLastMap memory (CheckIfInOutsideMap) local OUTSIDE_TILESETS = { "OVERWORLD", "PLATEAU" } +-- pokered's fly destination gate: BuildFlyLocationsList +-- (engine/items/town_map.asm) walks map ids 0..NUM_CITY_MAPS-1, the eleven +-- towns PALLET_TOWN..SAFFRON_CITY, so routes never appear even though +-- ROUTE_4/ROUTE_10 carry fly-warp landing spots (those exist for the +-- dungeon-escape/heal tables, special_warps.asm FlyWarpDataPtr) +local NUM_CITY_MAPS = 11 + -- warp pads and fall-through holes (data/tilesets/warp_pad_hole_tile_ids -- .asm WarpPadAndHoleData); a tileset record carrying warpPadTiles -- ({ [tileId] = "pad"|"hole" }) wins over these vanilla rows @@ -153,6 +160,17 @@ function Map.isOutside(def, tilesets) return false end +-- FLY destination (LoadTownMap_Fly / BuildFlyLocationsList): the eleven +-- towns, map indices 0..NUM_CITY_MAPS-1. ROUTE_4 and ROUTE_10 are outdoor +-- and have fly warps but are not towns, so the outdoor test alone offered +-- their Pokemon Centers as fly targets (#788). Maps without a vanilla +-- index (mod-authored) keep the old outdoor/PLATEAU surface test, which is +-- how a mod adds its own fly town. +function Map.isFlyTown(def) + if def.index ~= nil then return def.index < NUM_CITY_MAPS end + return Map.isOutdoor(def) or def.tileset == "PLATEAU" +end + -- region groups maps a rule applies to without naming them; the id prefix -- is the fallback for caches that predate the property function Map.inRegion(def, region, prefix) diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index 086140fd..a345ba6c 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -37,6 +37,10 @@ local mapScripts -- registry of hand-ported map scripts local COMPASS = { up = "north", down = "south", left = "west", right = "east" } local DIRVEC = { up = { 0, -1 }, down = { 0, 1 }, left = { -1, 0 }, right = { 1, 0 } } +-- pokered's wNumberOfNoRandomBattleStepsLeft: three completed steps +-- after a wild battle before another random battle can start. +local WILD_ENCOUNTER_GRACE_STEPS = 3 + -- Fly animation coord paths (engine/overworld/player_animations.asm): -- y/x pairs in GB screen pixels, one pair every 3 frames (DoFlyAnimation's -- Delay3). The port anchors a path on the player's own position instead @@ -81,8 +85,10 @@ local HEAL_FLASH_MAP = { [0] = 0, [1] = 2, [2] = 1, [3] = 3 } -- above (screen = tile*8 + pixel - 8/16), measured against the player -- sprite's fixed screen spot: ResetPlayerSpriteData parks it at $3c/$40 -- (home/reset_player_sprite.asm), i.e. screen (64,60). So what ports over --- is the delta from the sprite's top-left, which SpriteRenderer:draw puts at --- (px, py - 4). `tile` indexes the three stacked 8x8 tiles of +-- is the delta from the sprite's top-left, which the vanilla +-- SpriteRenderer:draw puts at (px, py - 4); custom frame anchors move that +-- origin while keeping these offsets frame-relative. `tile` indexes the +-- three stacked 8x8 tiles of -- assets/generated/fx/fishing_rod.png: FishingRodOAM only ever draws $fd -- (row 0, up/down) and $fe (row 1, left/right), and RIGHT is the LEFT tile -- x-flipped. Blitting the whole 8x24 sheet is what drew the rod as a @@ -206,7 +212,7 @@ function OverworldState.computeNeighbors(maps, rootId, hops, reachW, reachH) return out end -function OverworldState:enter(mapId, x, y, facing) +function OverworldState:enter(mapId, x, y, facing, opts) Game = require("src.core.Game") Game.overworld = self Collision.load(Game.data) -- tile-pair (elevation) collisions @@ -224,10 +230,12 @@ function OverworldState:enter(mapId, x, y, facing) -- a fresh entry, or a stale flag can freeze player input forever self.engaging = false self.emote = nil + -- volatile WRAM state in pokered; never serialize across save/load + self.wildEncounterGraceSteps = 0 -- survives save/load: a loaded game may start inside a building whose -- exit mat is a LAST_MAP warp self.lastOutdoor = Game.save.lastOutdoor - self:setMap(mapId, x, y, facing, { via = "boot" }) + self:setMap(mapId, x, y, facing, opts or { via = "boot" }) -- boot/load: derive the flag from the tile the save left us standing on, -- like MapEntryAfterBattle's IsPlayerStandingOnWarp, so a game saved on a -- door mat can still walk straight back out (issue #378) @@ -282,7 +290,7 @@ end function OverworldState:setMap(mapId, x, y, facing, opts) local fromMapId = self.map and self.map.id - if fromMapId then + if fromMapId and not (opts and opts.checkpoint) then Runtime.emit("map.exited", { mapId = fromMapId, toMapId = mapId }) end -- ambient choreography is per-map: parallel runners die here, and the @@ -364,7 +372,12 @@ function OverworldState:setMap(mapId, x, y, facing, opts) Game.save.flashLit = nil self:setDark(false) end - if Game.data.field.flyWarps[mapId] then + -- MarkTownVisitedAndLoadToggleableObjects marks towns only (cp + -- FIRST_ROUTE_MAP): the fly-warp table also carries the ROUTE_4/ROUTE_10 + -- Pokemon Centers and the dungeon escape spots, and entering those never + -- sets a wTownVisitedFlag bit, so it must not set save.visited either (#788) + local mapDef = Game.data.maps[mapId] + if Game.data.field.flyWarps[mapId] and mapDef and Map.isFlyTown(mapDef) then Game.save.visited = Game.save.visited or {} Game.save.visited[mapId] = true end @@ -435,8 +448,10 @@ function OverworldState:setMap(mapId, x, y, facing, opts) self.entities = { self.player } for _, n in ipairs(self.npcs) do table.insert(self.entities, n) end -- Yellow's companion Pikachu trails the player (never in - -- self.entities: it does not block movement, pikachu_follow.asm) - require("src.world.PikachuFollower").onMapEntered(Game, self, opts) + -- self.entities: it does not block movement, pikachu_follow.asm). + -- true = fresh map entry: the follower spawns under the player and + -- walks out of the warp, not beside him (#863) + require("src.world.PikachuFollower").onMapEntered(Game, self, opts, true) -- opts.keepMusic: the Oak-escort warp keeps MUSIC_MEET_PROF_OAK -- playing into the lab (BIT_NO_MAP_MUSIC in wStatusFlags7); @@ -444,8 +459,10 @@ function OverworldState:setMap(mapId, x, y, facing, opts) local keepMusic = (opts and opts.keepMusic) or self.keepMusicOnce self.keepMusicOnce = nil if not keepMusic then - require("src.core.Music").playMap(Game.data, mapId, Game.save.onBike, - self.player.surfing) + -- ..(home/overworld.asm ln 2346) + local Music = require("src.core.Music") + Music.playMap(Game.data, mapId, Game.save.onBike, self.player.surfing, + Music.MAP_FADE) end -- forced bike/surf tiles fire the moment the player is placed on the @@ -453,13 +470,13 @@ function OverworldState:setMap(mapId, x, y, facing, opts) -- (home/overworld.asm) -- a warp can land directly on one (the Route -- 16/18 gate exits), and the scripted door-mat walkout that follows -- suppresses onStepComplete, so waiting for a plain step never mounts - self:checkForcedMovement() + if not (opts and opts.checkpoint) then self:checkForcedMovement() end -- Seafoam B4F's map script pushes off the B3F stair warps every frame -- while the upper plugs are out (SeafoamIslandsB4FDefaultScript); the -- B3F/B4F force-surf mouths also arm their MOVE_OBJECT current scripts -- from CheckForceBikeOrSurf. Re-check here so a warp-in does not sit -- idle on those cells waiting for a player step. - self:checkSeafoamCurrent() + if not (opts and opts.checkpoint) then self:checkSeafoamCurrent() end -- snap the camera immediately: the overworld doesn't update while a -- Transition is on top, so a stale camera would show the new map at @@ -469,27 +486,31 @@ function OverworldState:setMap(mapId, x, y, facing, opts) -- fires before the onEnter chain so a listener sees the map in the same -- state the map script does - Runtime.emit("map.entered", { - mapId = mapId, map = self.map, fromMapId = fromMapId, - via = (opts and opts.via) - or (opts and opts.seamless and "connection") - or (fromMapId and "warp" or "boot"), - }) + if not (opts and opts.checkpoint) then + Runtime.emit("map.entered", { + mapId = mapId, map = self.map, fromMapId = fromMapId, + via = (opts and opts.via) + or (opts and opts.seamless and "connection") + or (fromMapId and "warp" or "boot"), + }) + end -- map-enter hooks (hand-ported map scripts, e.g. Victory Road barriers). -- fromMapId lets elevators seed a valid walk-out floor when the ROM -- car warps still point at a missing map (Silph's UNUSED_MAP_ED) and -- the player B-cancels the floor menu without .UpdateWarp. - local hooks = mapScripts.get(mapId) - if hooks and hooks.onEnter then - hooks.onEnter(Game, self, fromMapId) + if not (opts and opts.checkpoint) then + local hooks = mapScripts.get(mapId) + if hooks and hooks.onEnter then + hooks.onEnter(Game, self, fromMapId) + end end self:rebuildNeighbors() Logger.info("map: %s at (%d,%d)", mapId, x, y) -- Route22Gate_Script rewrites wLastMap from the player's Y on entry -- too (not only on step), so a save/load mid-gate keeps exits correct - self:syncLastMapRewrite() + if not (opts and opts.checkpoint) then self:syncLastMapRewrite() end end -- Neighbor maps drawn at the composed connection offsets: at least the @@ -753,8 +774,8 @@ function OverworldState:pushBattle(battle) local enemyLevel = battle.enemy and battle.enemy.mon and battle.enemy.mon.level or 0 -- the battle theme starts with the wipe, not after it -- (audio/play_battle_music.asm runs before the transition) - if battle.computeMusicKind then - require("src.core.Music").playBattle(Game.data, battle:computeMusicKind()) + if battle.playBattleTheme then + battle:playBattleTheme() end -- The fade back in from white on the way out is BattleState:finish()'s @@ -962,8 +983,14 @@ function OverworldState:update(dt) -- the bird carries the player in on landing, with its own -- SFX_FLY (EnterMapAnim .flyAnimation) self.arriveWarp = "fly" + -- keep the sprite hidden through the warp fade-out (#916): flyAnim + -- just went nil but flyArrive is not armed until startWarpTo's + -- midpoint, and the overworld keeps drawing beneath the veil, so + -- without this the trainer pops back in at the old cell for 32 frames + self.playerHidden = true self:startWarpTo(d.map, d.x, d.y, "down", nil, { via = "fly" }) else + self.playerHidden = false self.player.inputLocked = false end return @@ -995,6 +1022,10 @@ function OverworldState:update(dt) self.player.spinFrames = nil self.player.spinRise = nil self.player.inputLocked = false + -- keep the sprite hidden through the warp fade-out (#916): the spin is + -- over but the arrival spin-drop is not armed until startWarpTo's + -- midpoint, so without this the standing trainer shows under the veil + self.playerHidden = true self:warpToHealPoint(onDone, { arrive = "teleport" }) return end @@ -1072,8 +1103,10 @@ function OverworldState:update(dt) local mapId = self.pendingSeamMusic self.pendingSeamMusic = nil if mapId == self.map.id then - require("src.core.Music").playMap(Game.data, mapId, Game.save.onBike, - self.player.surfing) + -- ..(home/overworld.asm ln 677) + local Music = require("src.core.Music") + Music.playMap(Game.data, mapId, Game.save.onBike, self.player.surfing, + Music.MAP_FADE) end end if stepped and not scripted then @@ -1197,7 +1230,7 @@ function OverworldState:handleInput() if self:checkLedgeHop(dir) then return end if self:checkBoulderPush(dir) then return end end - local result, why = self.player:tryMove(dir, self.map, self.entities) + local result = self.player:tryMove(dir, self.map, self.entities) -- a collision while standing on a warp square fires the warp when the -- extra check passes (CheckWarpsCollision: route-gate doorways, dock -- entrances, ...), and only while BIT_STANDING_ON_WARP is set (issue @@ -1210,7 +1243,9 @@ function OverworldState:handleInput() return result end end - if result == "blocked" and why ~= "entity" then + -- CollisionCheckOnLand (home/overworld.asm): a sprite takes the same + -- .collision branch as an impassable tile (#960) + if result == "blocked" then if (self.bumpCooldown or 0) <= 0 then require("src.core.Sound").play(Game.data, "Collision") self.bumpCooldown = 16 @@ -1254,8 +1289,14 @@ end function OverworldState:checkBoulderPush(dir) local p = self.player local fx, fy = Collision.target(p.cellX, p.cellY, dir) - local npc = self:npcAtCell(fx, fy) - if not npc or not Map.isPushable(npc.def) or npc.moving then + -- IsSpriteInFrontOfPlayer (home/overworld.asm) hands TryPushingBoulder + -- the LOWEST sprite index standing on the faced cell, so in the original a + -- second sprite parked on the boulder's cell hides the boulder from the + -- push path for the rest of the map visit. Pick the pushable sprite out of + -- the cell instead: a scripted walk-up that lands a trainer on the boulder + -- must not brick it permanently (#809). + local npc = self:pushableAtCell(fx, fy) + if not npc or npc.moving then self.boulderTried = nil -- pokered resets when no boulder is in front return false end @@ -1715,6 +1756,22 @@ function OverworldState:npcAtCell(cx, cy) return nil end +-- The Strength boulder on a cell, ignoring anything else standing there. +-- npcAtCell returns whichever object the map listed first, which is only +-- well defined while at most one sprite occupies a cell; scripted walks +-- (TrainerWalkUpToPlayer) can break that, and the push path must still find +-- the boulder underneath (#809). +function OverworldState:pushableAtCell(cx, cy) + for _, npc in ipairs(self.npcs) do + if ((npc.cellX == cx and npc.cellY == cy) or + (npc.targetX == cx and npc.targetY == cy)) + and Map.isPushable(npc.def) then + return npc + end + end + return nil +end + -- what the A press resolved to, for world.interacted's listeners local function interacted(self, fx, fy, kind, target) Runtime.emit("world.interacted", { mapId = self.map.id, x = fx, y = fy, @@ -1898,7 +1955,14 @@ function OverworldState:tryHiddenObject(fx, fy) save.hiddenTaken = save.hiddenTaken or {} if save.hiddenTaken[key] then return false end if not require("src.inventory.Bag").add(save, h.item, 1, Game.data) then - Game.stack:push(TextBox.new(Game, romText(Game.data, "_CantCarryMoreText", "You can't carry\nany more items!"))) + -- hidden_items.asm FoundHiddenItemText: the find is announced first, + -- then GiveItem's .bagFull branch prints _HiddenItemBagFullText and + -- leaves the spot unfound; _CantCarryMoreText is the Toss line (#872) + local name = Game.data.items[h.item] and Game.data.items[h.item].name or h.item + Game.stack:push(TextBox.new(Game, + Strings("%s found\n%s!", save.player.name, name) .. "\f" + .. romText(Game.data, "_HiddenItemBagFullText", + "But, {PLAYER} has\nno more room for\vother items!"))) return true end save.hiddenTaken[key] = true @@ -1977,7 +2041,8 @@ function OverworldState:tryHiddenObject(fx, fy) -- SOMEONE'S/BILL'S PC main menu (DisplayPCMainMenu). Every other -- pcTile is a Pokémon Center-style PC that shows the multi-PC menu. (#228) require("src.core.Sound").play(Game.data, "Turn_On_PC") - Screens.push(Game, "PlayerPC") + -- direct access: ExitPlayerPC rings SFX_TURN_OFF_PC (players_pc.asm, #960) + Screens.push(Game, "PlayerPC", { direct = true }) else self:openPC() end @@ -2357,8 +2422,21 @@ function OverworldState:trySurf(fx, fy, onClose) Game.stack:push(TextBox.new(Game, text, function() if onClose then onClose() end p.surfing = true + -- walking / biking / surfing is ONE state byte in the original: + -- ItemUseSurfboard (engine/items/item_effects.asm) writes 2 over + -- whatever wWalkBikeSurfState held, so mounting a surf ends the bike + -- outright -- no bike step cadence in Player:tryMove and no bike + -- theme on the water (#846). Music.playMap re-picks the override + -- with BOTH flags, which setSurfing alone cannot do: effectiveMapSong + -- (src/core/Music.lua) prefers state.onBike over state.surfing. + Game.save.onBike = false self:syncSurfingPikachu() - require("src.core.Music").setSurfing(Game.data, true) + local Music = require("src.core.Music") + if self.map then + Music.playMap(Game.data, self.map.id, false, true) + else + Music.setSurfing(Game.data, true) -- headless harness with no map loaded + end Game.stack:push(require("src.render.Transition").whiteFlash(Game, nil, function() self:stepForwardOrCrossEdge(p.facing) end)) end)) @@ -2530,7 +2608,17 @@ function OverworldState:talkTo(npc) -- the string "0" as truthy, so screen it out and fall through to text. if d.item and d.item ~= "0" and d.item ~= 0 then if not require("src.inventory.Bag").add(Game.save, d.item, 1, Game.data) then - Game.stack:push(TextBox.new(Game, romText(Game.data, "_CantCarryMoreText", "You can't carry\nany more items!"))) + -- pick_up_item.asm .BagFull prints _NoMoreRoomForItemText, not the + -- Toss-screen _CantCarryMoreText; Yellow announces the find first, + -- then the refusal (#872) + local noRoom = romText(Game.data, "_NoMoreRoomForItemText", + "No more room for\nitems!") + if GameVersion.isYellow() then + local name = Game.data.items[d.item] and Game.data.items[d.item].name or d.item + noRoom = Strings("%s found\n%s!", Game.save.player.name, name) + .. "\f" .. noRoom + end + Game.stack:push(TextBox.new(Game, noRoom)) return end Game.save.itemsTaken = Game.save.itemsTaken or {} @@ -2660,6 +2748,8 @@ function OverworldState:openPC(onDone) label = (Game.save.player.name or "RED") .. "'s PC", keepOpen = true, onSelect = function() + -- pc.asm .playersPC plays SFX_ENTER_PC before the farcall (#960) + require("src.core.Sound").play(Game.data, "Enter_PC") Screens.push(Game, "PlayerPC") done() end, @@ -2671,6 +2761,8 @@ function OverworldState:openPC(onDone) label = Strings("PROF.OAK's PC"), keepOpen = true, onSelect = function() + -- pc.asm OaksPC plays SFX_ENTER_PC before the farcall (#960) + require("src.core.Sound").play(Game.data, "Enter_PC") self:openOaksPC(done) end, }) @@ -2854,17 +2946,29 @@ function OverworldState:nurseHeal(onDone, npc) -- line: it comes back on the counter facing the player Follower.setVisible(self, true) if npc then npc:facePlayer(self.player) end - self:finishNurseHeal(bye, onDone) + self:finishNurseHeal(bye, onDone, npc) end end)) end) end })) end -function OverworldState:finishNurseHeal(bye, onDone) +-- pokecenter.asm bows the nurse between the two PrintText calls (#995) +function OverworldState:finishNurseHeal(bye, onDone, npc) local t = Game.data.text local fit = t._PokemonFightingFitText or Strings("Your POKéMON are\nfighting fit!") - Game.stack:push(TextBox.new(Game, fit .. "\f" .. bye, onDone)) + Game.stack:push(TextBox.new(Game, fit, function() + local function farewell() + Game.stack:push(TextBox.new(Game, bye, function() + if npc then npc:facePlayer(self.player) end + if onDone then onDone() end + end)) + end + if not npc then farewell() return end + npc.facing = "up" + -- bubble = false is the silent world hold, this port's DelayFrames + self.emote = { npc = npc, frames = 20, bubble = false, onDone = farewell } + end)) end -- The Cable Club link receptionist (TX_SCRIPT_CABLE_CLUB_RECEPTIONIST -> @@ -2915,8 +3019,41 @@ function OverworldState:trainerDefeated(npc) return false end +-- data/trainers/encounter_types.asm +local FEMALE_TRAINERS = { + OPP_LASS = true, OPP_JR_TRAINER_F = true, OPP_BEAUTY = true, + OPP_COOLTRAINER_F = true, +} +local EVIL_TRAINERS = { + OPP_UNUSED_JUGGLER = true, OPP_GAMBLER = true, OPP_ROCKER = true, + OPP_JUGGLER = true, OPP_CHIEF = true, OPP_SCIENTIST = true, + OPP_GIOVANNI = true, OPP_ROCKET = true, +} + +-- PlayTrainerMusic (home/trainers.asm:399) picks the encounter sting from +-- the engaged class: evil list, then female list, then male by default. +-- The rivals `ret z` out of it and keep the MUSIC_MEET_RIVAL their own +-- scripts start (data/scripts/oaks_lab.lua, story5.lua). Its other gate, +-- wGymLeaderNo, is not a leader test: that byte aliases wLoneAttackNo +-- (ram/wram.asm:1264), is cleared on every map entry +-- (engine/overworld/clear_variables.asm:8), and each gym script writes it +-- only AFTER its own `call EngageMapTrainer` (scripts/PewterGym.asm:122), +-- so leaders do get the sting and nothing on a map can be suppressed by it +-- before the leader is beaten. Returns nil when the class gets no sting. +local function meetTrainerTheme(cls) + if not cls or cls:find("RIVAL") then return nil end + return EVIL_TRAINERS[cls] and "Music_MeetEvilTrainer" + or FEMALE_TRAINERS[cls] and "Music_MeetFemaleTrainer" + or "Music_MeetMaleTrainer" +end + -- Run the pre-battle text -> battle -> won text -> flags sequence. -function OverworldState:engageTrainer(npc, onDone) +-- skipBattleText is for map scripts shaped like SilphCo11FDefaultScript +-- (scripts/SilphCo11F.asm), which DisplayTextID the challenge line BEFORE +-- the approach walk and then EngageMapTrainer with no further text: the +-- caller already showed the box, so the battle starts without a second +-- one (#869). +function OverworldState:engageTrainer(npc, onDone, endBattleText, skipBattleText) local d = npc.def Runtime.emit("world.trainer_engaged", { npc = npc, trainerClass = d.trainerClass, partyIndex = d.trainerParty }) @@ -2926,11 +3063,41 @@ function OverworldState:engageTrainer(npc, onDone) battleText = select(1, Game.data:resolveText(self.map.def.label, d.text)) or Strings("I like shorts!\nThey're comfy and\neasy to wear!") end - local wonText = header and header.won and Game.data.text[header.won] + -- `endBattleText` is a caller-supplied stand-in for header.won: the + -- text_asm trainers that hand their loss line to the battle through + -- SaveEndBattleTextPointers (scripts/GameCorner.asm GameCornerRocketText + -- passes _GameCornerRocketBattleEndText, "Dang!") have no def_trainers + -- header for the extractor to read, so their script passes the finished + -- line here and it still lands where PrintEndBattleText puts it -- between + -- TrainerDefeatedText and MoneyForWinningText, on the battle screen (#862). + local wonText = endBattleText + or (header and header.won and Game.data.text[header.won]) local BattleState = require("src.battle.BattleState") - Game.stack:push(TextBox.new(Game, battleText, function() + local function startBattle() + -- TalkToTrainer (home/trainers.asm:88) prints the before-battle text + -- FIRST and only then runs `call EngageMapTrainer` / `jp + -- StartTrainerBattle`, so a trainer challenged on foot gets the sting + -- over the battle transition rather than under the dialogue. Its + -- `bit BIT_SEEN_BY_TRAINER, [hl] / ret nz` guard is self.engaging + -- here: TrainerEngage (engine/overworld/trainer_sight.asm:224) already + -- started the sting before the "!" bubble on the sight path, so it + -- must not restart. Script-driven challenges (gyms.lua leaders, + -- scripts/SilphCo11F.asm:269 Giovanni, scripts/FightingDojo.asm:122) + -- all `call EngageMapTrainer` too, and reach this same path (#764). + if not self.engaging then + local theme = meetTrainerTheme(d.trainerClass) + if theme then require("src.core.Music").play(Game.data, theme) end + end local battle = BattleState.newTrainer(Game, d.trainerClass, d.trainerParty) + battle.checkpointOrigin = { + kind = "trainer_encounter", + map = self.map.id, + npcId = npc.id, + trainerClass = d.trainerClass, + partyIndex = d.trainerParty or 1, + event = header and header.event or nil, + } -- PrintEndBattleText (home/trainers.asm:341) is called from -- TrainerBattleVictory (engine/battle/core.asm:942), i.e. ON the battle -- screen once ScrollTrainerPicAfterBattle has brought the beaten trainer @@ -2958,7 +3125,28 @@ function OverworldState:engageTrainer(npc, onDone) end end self:pushBattle(battle) - end)) + end + if skipBattleText then + startBattle() + else + Game.stack:push(TextBox.new(Game, battleText, startBattle)) + end +end + +-- Shared GiveItem step for the victory rewards (pokered home/give.asm): +-- the item goes through the bag's capacity check, and only a successful +-- add sets the reward's gotFlag (EVENT_GOT_TM*) and copies the item name +-- into wStringBuffer for the "{RAM:wStringBuffer}" received texts. +local function giveVictoryItem(reward) + if not require("src.inventory.Bag").add(Game.save, reward.item, 1, Game.data) then + return false + end + if reward.gotFlag then + Game.save.flags[reward.gotFlag] = true + end + local idef = Game.data.items[reward.item] + Game.stringBuffer = idef and idef.name or reward.item + return true end -- Badges/items awarded after specific battles (data/scripts/victories.lua). @@ -2989,12 +3177,13 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex) if reward.badge then Game.save.inventory[reward.badge] = 1 end + local tmGiven = false if reward.item then - local inv = Game.save.inventory - inv[reward.item] = (inv[reward.item] or 0) + 1 - local idef = Game.data.items[reward.item] - -- GiveItem -> CopyToStringBuffer for "{RAM:wStringBuffer}" received texts - Game.stringBuffer = idef and idef.name or reward.item + -- pokered GiveItem (home/give.asm): AddItemToInventory first, and a + -- full bag (jr nc, .BagFull) skips the received lines for the "make + -- room" text, leaving EVENT_GOT_TM* unset so the leader's talk script + -- retries the hand-over later (offerGymTm via gyms.lua) + tmGiven = giveVictoryItem(reward) end local lines = {} if reward.dialogue then @@ -3004,13 +3193,29 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex) table.insert(lines, text[label]) end end + if reward.item then + for _, label in ipairs(reward.tmPre or {}) do + if text[label] and text[label] ~= "" then + table.insert(lines, text[label]) + end + end + if tmGiven then + for _, label in ipairs(reward.tmDialogue or {}) do + if text[label] and text[label] ~= "" then + table.insert(lines, text[label]) + end + end + elseif reward.noRoom and text[reward.noRoom] and text[reward.noRoom] ~= "" then + table.insert(lines, text[reward.noRoom]) + end + end elseif reward.badge or reward.item then if reward.badge then local name = Game.data.items[reward.badge] and Game.data.items[reward.badge].name or reward.badge table.insert(lines, Strings("%s received\nthe %s!", Game.save.player.name, name)) end - if reward.item then + if tmGiven then local name = Game.stringBuffer or reward.item table.insert(lines, Strings("%s received\n%s!", Game.save.player.name, name)) end @@ -3021,6 +3226,33 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex) self:runVictoryHook() end +-- A beaten leader re-running their ReceiveTM script when the bag was full +-- at the victory (pokered's middle branch, e.g. PewterGymBrockText +-- CheckEventReuseA EVENT_GOT_TM34 -> call PewterGymScriptReceiveTM34). +-- The script's lead-in lines (tmPre: badge info / "Wait! Take this!") +-- show again, then the same GiveItem check decides between the received +-- lines and the "make room" text. +function OverworldState:offerGymTm(reward, done) + local text = Game.data.text or {} + local lines = {} + local function addLine(label) + if label and text[label] and text[label] ~= "" then + table.insert(lines, text[label]) + end + end + for _, label in ipairs(reward.tmPre or {}) do addLine(label) end + if giveVictoryItem(reward) then + for _, label in ipairs(reward.tmDialogue or {}) do addLine(label) end + else + addLine(reward.noRoom) + end + if #lines > 0 then + Game.stack:push(TextBox.new(Game, table.concat(lines, "\f"), done)) + elseif done then + done() + end +end + -- pokered reloads the map after every battle, re-running the map -- script (e.g. LoreleiShowOrHideExitBlock); this hook is the port's -- equivalent so seals/toggles refresh without leaving the map @@ -3101,29 +3333,15 @@ function OverworldState:checkTrainerSight() end end --- data/trainers/encounter_types.asm -local FEMALE_TRAINERS = { - OPP_LASS = true, OPP_JR_TRAINER_F = true, OPP_BEAUTY = true, - OPP_COOLTRAINER_F = true, -} -local EVIL_TRAINERS = { - OPP_UNUSED_JUGGLER = true, OPP_GAMBLER = true, OPP_ROCKER = true, - OPP_JUGGLER = true, OPP_CHIEF = true, OPP_SCIENTIST = true, - OPP_GIOVANNI = true, OPP_ROCKET = true, -} - function OverworldState:startTrainerApproach(npc, dist) self.engaging = true npc.frozen = true - -- the encounter sting (PlayTrainerMusic): evil / female / male by - -- class; rivals and gym leaders keep their own music - local cls = npc.def.trainerClass - if cls and not cls:find("RIVAL") then - local theme = EVIL_TRAINERS[cls] and "Music_MeetEvilTrainer" - or FEMALE_TRAINERS[cls] and "Music_MeetFemaleTrainer" - or "Music_MeetMaleTrainer" - require("src.core.Music").play(Game.data, theme) - end + -- TrainerEngage (engine/overworld/trainer_sight.asm:224) sets + -- BIT_SEEN_BY_TRAINER and calls EngageMapTrainer before the "!" bubble, + -- so the sighting sting starts ahead of the walk-up; engageTrainer sees + -- self.engaging and does not restart it (#764) + local theme = meetTrainerTheme(npc.def.trainerClass) + if theme then require("src.core.Music").play(Game.data, theme) end local function fight() self:engageTrainer(npc, function() npc.frozen = false @@ -3135,8 +3353,26 @@ function OverworldState:startTrainerApproach(npc, dist) self.emote = { npc = npc, frames = 60, onDone = function() - if dist > 1 then - self:scriptMove(npc, npc.facing, dist - 1, fight) + -- TrainerWalkUpToPlayer (engine/overworld/trainer_sight.asm) writes + -- dist-1 NPC_MOVEMENT_* bytes and hands them to MoveSprite, and every + -- scripted step skips collision entirely (CanWalkOntoTile, + -- engine/overworld/movement.asm: "always allow walking if the + -- movement is scripted"), so the original marches the trainer straight + -- through a Strength boulder sitting on the sight line. Stop one cell + -- short of the boulder instead: two sprites on one cell is a state the + -- push path cannot represent, and the walk-up is the one scripted move + -- the player can steer a boulder into (#809). + local steps = dist - 1 + local cx, cy = npc.cellX, npc.cellY + for i = 1, steps do + cx, cy = Collision.target(cx, cy, npc.facing) + if self:pushableAtCell(cx, cy) then + steps = i - 1 + break + end + end + if steps > 0 then + self:scriptMove(npc, npc.facing, steps, fight) else fight() end @@ -3267,6 +3503,10 @@ end function OverworldState:onStepComplete() local p = self.player + local suppressWildEncounter = self.wildEncounterGraceSteps > 0 + if suppressWildEncounter then + self.wildEncounterGraceSteps = self.wildEncounterGraceSteps - 1 + end self.todSteps = (self.todSteps or 0) + 1 -- UpdatePikachuHappinessAndMood rides the step counter (poison.asm) require("src.world.PikachuFollower").onStep(Game.save) @@ -3379,6 +3619,9 @@ function OverworldState:onStepComplete() -- wild encounters in grass, on water while surfing, or -- on indoor -- maps whose tileset is not FOREST -- on EVERY tile -- (wild_encounters.asm: caves, towers, the Mansion, Power Plant) + -- The cooldown is checked after all other step processing so repel and + -- movement systems continue to advance during the protected steps. + if suppressWildEncounter then return end local encDef = Game.data.encounters[self.map.id] local enc local indoor = Game.data.field.indoorEncounters @@ -3399,6 +3642,10 @@ function OverworldState:onStepComplete() end local BattleState = require("src.battle.BattleState") local battle = BattleState.newWild(Game, enc.species, enc.level) + battle.checkpointOrigin = { + kind = "wild_encounter", + map = self.map.id, + } -- map.ghostBattles: unidentifiable without the named item (the -- Pokemon Tower's Silph Scope) local ghost = Map.ghostBattles(self.map.def) @@ -3587,9 +3834,13 @@ function OverworldState:checkForcedMovement() return true end elseif tile.mode == "surf" then + -- scripts/SeafoamIslandsB4F.asm writes wWalkBikeSurfState = 2 and + -- jp ForceBikeOrSurf, so a forced surf clears the bike state the + -- same way the party-menu mount does (#846) p.surfing = true + Game.save.onBike = false self:syncSurfingPikachu() - require("src.core.Music").setSurfing(Game.data, true) + require("src.core.Music").playMap(Game.data, self.map.id, false, true) end return false end @@ -3757,6 +4008,9 @@ end -- battle is optional; when given, Oak's Lab OPP_RIVAL1 losses skip the -- blackout (pret HandlePlayerBlackOut) so the map script can HealParty. function OverworldState:afterBattle(result, battle) + if battle and battle.kind == "wild" then + self.wildEncounterGraceSteps = WILD_ENCOUNTER_GRACE_STEPS + end local lead = Game.save.party[1] Logger.info("battle over: %s (lead %s %d/%d)", tostring(result), lead and lead.species or "-", lead and lead.hp or 0, @@ -3799,6 +4053,39 @@ function OverworldState:afterBattle(result, battle) end end +-- Rebind the data-only continuation attached to a supported battle checkpoint. +-- The overworld was reconstructed first, so transient input/NPC freezes from +-- the original encounter are intentionally not resumed. +function OverworldState:restoreBattleContinuation(battle, origin) + local game = battle and battle.game + if not game or type(origin) ~= "table" or not self.map + or origin.map ~= self.map.id then + return false + end + if origin.kind == "wild_encounter" and battle.kind == "wild" then + battle.onFinish = function(result) self:afterBattle(result, battle) end + return true + end + if origin.kind ~= "trainer_encounter" or battle.kind ~= "trainer" + or origin.trainerClass ~= battle.oppClass + or origin.partyIndex ~= (battle.partyIndex or 1) + or type(origin.npcId) ~= "string" then + return false + end + battle.onFinish = function(result) + if result == "win" then + game.save.defeatedTrainers[origin.npcId] = true + if origin.event then game.save.flags[origin.event] = true end + self:checkVictoryRewards(battle.oppClass, battle.partyIndex) + end + self:afterBattle(result, battle) + self.engaging = false + local npc = self.npcPool and self.npcPool[origin.npcId] + if npc then npc.frozen = false end + end + return true +end + -- ------------------------------------------------------------------------- -- warps -- ------------------------------------------------------------------------- @@ -3880,25 +4167,47 @@ function OverworldState:warpToHealPoint(onDone, opts) -- Dig/Teleport/Escape Rope land OUTSIDE at the last Pokemon Center TOWN -- door, like Fly (#196) -- NOT the interior heal cell a blackout returns -- to. pret routes escape-warp and blackout both through wLastBlackoutMap - -- (both appear inside in front of the nurse), but this port has decided - -- the escape-warp destination is the town PC door. Prefer the canonical - -- Fly landing (field.flyWarps, one tile south of the PC door warp), else - -- the remembered outdoor door cell; fall back to the interior heal cell - -- only for an old save with no recorded outdoor. - local out = heal.outdoor - if out then - local fw = (Game.data.field.flyWarps or {})[out.id] - map = out.id - x = fw and fw.x or out.x - y = fw and fw.y or out.y + -- (LoadSpecialWarpData .usedFlyWarp, engine/overworld/special_warps.asm), + -- and that map is ALWAYS an outdoor one: SetLastBlackoutMap copies + -- wLastMap (engine/events/set_blackout_map.asm) and WarpFound2 only + -- writes wLastMap on outside maps (home/overworld.asm), with the landing + -- cell read from FlyWarpDataPtr. Prefer the canonical Fly landing + -- (field.flyWarps, one tile south of the PC door warp), else the + -- remembered outdoor door cell. + -- + -- A heal record naming no outdoor town, or naming a map that is not + -- outdoors at all, is never a legal escape-warp destination: a .sav + -- import stamps lastHeal from wherever the cartridge was saved + -- (SaveConvert mergeDefaults), so ESCAPE ROPE was dropping the player + -- into the dungeon that save sat in, whose LAST_MAP exits then still + -- pointed at the door they had walked in through (#805). Vanilla's + -- zero-filled wLastBlackoutMap is map 0, so an unusable record falls + -- back to the boot heal town exactly as a never-healed game does. + local out = heal.outdoor or { id = heal.map, x = heal.x, y = heal.y } + local fw = (Game.data.field.flyWarps or {})[out.id] + local outX = fw and fw.x or out.x + local outY = fw and fw.y or out.y + local outDef = Game.data.maps[out.id] + if not (outDef and outX and outY + and Map.isOutside(outDef, + FieldDefaults.field(Game.data, "outsideTilesets"))) then + local zeroFill = require("src.core.SaveData") + .defaultHeal(Game.data.field.boot) + out, outX, outY = { id = zeroFill.map }, zeroFill.x, zeroFill.y end + map, x, y = out.id, outX, outY end self:startWarpTo(map, x, y, "down", onDone) -- Blackouts land at the interior heal cell, so re-point LAST_MAP exits at - -- the remembered town door. The teleport branch already lands ON that - -- outdoor map, so startWarpTo remembers it on the next exit; re-pointing - -- here would wrongly steer exits away from where the player now stands. - if heal.outdoor and not teleport then + -- the remembered town door. The teleport branch re-points at the town it + -- just landed on: PrepareForSpecialWarp (engine/overworld/special_warps.asm) + -- writes the special-warp destination straight back into wLastMap for every + -- fly/escape warp that is not a dungeon warp, so the next LAST_MAP exit + -- resolves against that town instead of the dungeon door the player walked + -- in through before using the rope (#805). + if teleport then + self:rememberOutdoor(map, x, y) + elseif heal.outdoor then self:rememberOutdoor(heal.outdoor.id, heal.outdoor.x, heal.outdoor.y) end end @@ -3925,8 +4234,21 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts) self.doorWarp = nil local arriveWarp = self.arriveWarp self.arriveWarp = nil + -- PlayMapChangeSound (home/overworld.asm) plays before the tail-called + -- GBFadeOutToBlack, so the SFX starts with the fade (#961) + if doorWarp then + local dest = Game.data.maps[mapId] + local outdoor = dest and Map.isOutdoor(dest) + require("src.core.Sound").play(Game.data, + outdoor and "Go_Outside" or "Go_Inside") + end Game.stack:push(Transition.new(Game, function() self:setMap(mapId, x, y, facing or "down", opts) + -- the departure-side hide from flyAnim/teleportOut ends here, on the new + -- map; the arrival arms its own cover (flyArrive / spinDrop) a few lines + -- down, so the player is never drawable mid-fade nor standing bare on the + -- landing frame (#916) + self.playerHidden = false -- The warp we land ON stays inert for the completed-step check until we -- physically step off it, so a warp whose destination cell is itself a -- warp cannot bounce us straight back (elevator cars, stacked stair/door @@ -3956,9 +4278,6 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts) self.player.spinDrop = true end if doorWarp then - local outdoor = Map.isOutdoor(self.map.def) - require("src.core.Sound").play(Game.data, - outdoor and "Go_Outside" or "Go_Inside") -- PlayerStepOutFromDoor (engine/overworld/auto_movement.asm): any -- warp that lands on a door tile auto-steps south once, indoor or -- outdoor. Auto-walk leaves the mat, so the arrival disable @@ -3981,7 +4300,7 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts) end, function() self.transitioning = false if onDone then onDone() end - end)) + end, true)) -- warp shape: no fade back in (LoadGBPal restores in one write) end -- Re-read a map record after its data changed (WorldAPI:invalidateMap, @@ -4228,7 +4547,19 @@ function OverworldState:drawWorld() -- Renderer:beginFrame cleared it, so a battle or a full-screen menu -- which -- draws with no map beneath it -- stays lit exactly like -- init_battle_variables.asm's `ld [wMapPalOffset], a` leaves the original. - PaletteFX.setShadeMap(self.dark and PaletteFX.DARK_BGP or nil) + -- + -- BATTLE BG "world" is the one case where a map DOES draw in a battle's + -- frame (Game.drawBaseInStack), and the shift armed here reached the + -- battle's own colorize pass, so an un-flashed Rock Tunnel battle came out + -- with FadePal2 over its pics, HUD and text (#773). The battle zeroes + -- wMapPalOffset for its whole run and restores it on the way out + -- (engine/battle/core.asm InitBattleCommon push/pop), so the map behind it + -- goes lit too for as long as the battle is up -- which is what the + -- original's saved offset means. + local battleOverWorld = Game and Game.stack + and Game.worldBgBattleInStack(Game.stack) + PaletteFX.setShadeMap((self.dark and not battleOverWorld) + and PaletteFX.DARK_BGP or nil) -- advance the water/flower tile animation (runs under dialogs too). -- TileRenderer.tick uses wall-clock 60Hz steps so display refresh rate -- does not speed or slow the cycle (issue #4). @@ -4278,7 +4609,13 @@ function OverworldState:drawWorld() -- ghost NPCs on neighbor maps, y-sorted among themselves table.sort(self.ghosts, function(a, b) return a.npc.py + a.oy < b.npc.py + b.oy end) - table.sort(self.entities, function(a, b) return a.py < b.py end) + table.sort(self.entities, function(a, b) + if a.py ~= b.py then return a.py < b.py end + -- a fresh warp spawn parks the follower on the player's own cell + -- until it trails out; the tie must draw it under him, never on + -- top (#863) + return a.pikachuFollower == true and b.pikachuFollower ~= true + end) -- === shared FX draw bodies ========================================== -- Each draws at flat world-canvas offsets; the tilt path wraps the @@ -4508,9 +4845,15 @@ function OverworldState:drawWorld() end end local quad = self.rodQuads[oam.tile] - -- the sprite's top-left is 4px above its cell (SpriteRenderer:draw) - local rx = p.px - cam.x + oam.dx - local ry = p.py - cam.y - 4 + oam.dy + -- Place the rod against the active sprite's anchored top-left. The + -- vanilla result is still (px-cam, py-cam-4), while custom larger + -- sheets keep the rod attached to their feet. + -- Fishing always uses the on-foot player sheet; read its fields + -- directly so this FX pass does not advance pose-side animation. + local sprite, px, py = p.sprite, p.px, p.py + local sx, sy = sprite:getScreenOrigin(px, py, cam.x, cam.y) + local rx = sx + oam.dx + local ry = sy + oam.dy love.graphics.setColor(1, 1, 1, 1) if quad and oam.flip then love.graphics.draw(self.rodImg, quad, rx + 8, ry, 0, -1, 1) @@ -4633,7 +4976,8 @@ function OverworldState:drawWorld() g.npc:draw(cam.x - g.ox, cam.y - g.oy) end for _, e in ipairs(self.entities) do - if not ((self.flyAnim or self.flyArrive) and e == self.player) then + if not ((self.flyAnim or self.flyArrive or self.playerHidden) + and e == self.player) then e:draw(cam.x, cam.y) -- tall grass overdraws the sprite's feet (GB sprite priority); -- the overdraw is BG tiles, so it rides the shake offset too @@ -4684,7 +5028,8 @@ function OverworldState:drawWorld() items[#items + 1] = { y = g.npc.py + g.oy + 16, kind = "ghost", g = g } end for _, e in ipairs(self.entities) do - if not ((self.flyAnim or self.flyArrive) and e == self.player) then + if not ((self.flyAnim or self.flyArrive or self.playerHidden) + and e == self.player) then items[#items + 1] = { y = e.py + 16, kind = "entity", e = e } end end diff --git a/src/world/PikachuFollower.lua b/src/world/PikachuFollower.lua index 708f386b..9e59d45a 100644 --- a/src/world/PikachuFollower.lua +++ b/src/world/PikachuFollower.lua @@ -122,6 +122,13 @@ local function shouldSpawn(game, ow) if not GameVersion.isYellow() then return false end local save = game.save if not (save.flags and save.flags.EVENT_GOT_STARTER) then return false end + -- save.pikachuInBall mirrors DisablePikachuOverworldSpriteDrawing (pokeyellow + -- scripts/OaksLab.asm); nil falls back to the rival-fight flag (#1009) + if save.pikachuInBall == nil then + if not save.flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB then return false end + elseif save.pikachuInBall then + return false + end if save.onBike or (ow.player and ow.player.surfing) then return false end if not (game.data.sprites and game.data.sprites.SPRITE_PIKACHU) then return false @@ -188,7 +195,7 @@ function PikachuFollower.current(ow) return npc end -function PikachuFollower.onMapEntered(game, ow, opts) +function PikachuFollower.onMapEntered(game, ow, opts, viaMapLoad) -- Bill's House owns a short scripted scene that deliberately keeps -- Pikachu off the normal trailing loop. A new map instance ends it. ow.pikachuBillsScene = nil @@ -200,7 +207,8 @@ function PikachuFollower.onMapEntered(game, ow, opts) -- takes .normal_spawn_state -- map coords rebased, sprite data and -- follow command buffer left alone. Re-list the same instance and let -- rebase() shift its cell; a warp arrives without it and respawns - -- behind the player, the full spawn path of that same routine. + -- under the player, the full spawn path of that same routine (the + -- viaMapLoad spawn below, #863). local keep = opts and opts.keepPikachu if keep then table.insert(ow.npcs, keep) @@ -208,6 +216,12 @@ function PikachuFollower.onMapEntered(game, ow, opts) return end local x, y = spawnCell(ow) + -- a fresh map entry (warp, boot) parks the follower ON the player's + -- cell instead: it stays hidden under him (the draw-sort tie-break in + -- OverworldController) and walks out of the warp behind him as the + -- trail opens up. Mid-map respawns (bike dismount, revive) keep the + -- behind-the-facing cell (#863) + if viaMapLoad then x, y = ow.player.cellX, ow.player.cellY end local npc = makeFollower(game, ow, x, y, ow.player.facing) table.insert(ow.npcs, npc) -- entities is the draw list; passable keeps it out of collision @@ -878,6 +892,28 @@ function PikachuFollower.onBillExitedMachine(game, ow) billsHouseEmotion(game, ow, npc, "EXCLAMATION_BUBBLE") end +-- OaksLabPikachuMovementScript (pokeyellow scripts/OaksLab_2.asm): the +-- companion clears the cell the rival stops on (#1021) +function PikachuFollower.oaksLabMakeWay(game, ow, done) + if not GameVersion.isYellow() then return false end + local npc = findFollower(ow) + if not npc or not ow.player then return false end + local p = ow.player + local steps, facing + if p.cellY == 3 then -- .movement2, b = SPRITE_FACING_LEFT + if not (npc.cellY == p.cellY and npc.cellX < p.cellX) then return false end + steps, facing = { { "down", 1 }, { "right", 1 } }, "up" + else -- OaksLabPikachuMovementData1, b = SPRITE_FACING_DOWN + if npc.cellY <= p.cellY then return false end + steps, facing = { { "left", 1 }, { "up", 1 } }, "right" + end + movePikachu(ow, npc, steps, function() + npc.facing = facing -- PIKAMOVEMENT_LOOK_UP / _LOOK_RIGHT ends each table + if done then done() end + end) + return true +end + -- --------------------------------------------------------------------- -- PikachuWalksToNurseJoy (engine/pikachu/pikachu_emotions.asm, run by -- engine/events/pokecenter.asm once the heal is accepted): the companion diff --git a/src/world/Player.lua b/src/world/Player.lua index 5fb257e8..65406b85 100644 --- a/src/world/Player.lua +++ b/src/world/Player.lua @@ -335,8 +335,13 @@ function Player:draw(camX, camY) local fishTile = self.fishing and self.fishTiles and self.fishTiles[facing] if fishTile then sprite:draw(px, py, camX, camY, facing, 0, false, true) - sprite:drawTile(fishTile, math.floor(px - camX), - math.floor(py - camY) - 4 + 8, facing == "right") + -- The fishing pose replaces the bottom 8-pixel tile. Use the sprite's + -- actual anchored frame origin so larger/custom sheets keep the pose at + -- their feet instead of falling back to the vanilla 16x16 top-left. + local sx, sy = sprite:getScreenOrigin(px, py, camX, camY) + sprite:drawTile(fishTile, sx, + sy + math.max(0, sprite.frameHeight - 8), + facing == "right") return end sprite:draw(px, py, camX, camY, facing, phase, flip) diff --git a/src/world/WorldAPI.lua b/src/world/WorldAPI.lua index 6adadc51..566afc9d 100644 --- a/src/world/WorldAPI.lua +++ b/src/world/WorldAPI.lua @@ -6,13 +6,68 @@ -- stays unsupported; anything a mod legitimately needs belongs here. local Logger = require("src.core.Logger") +local Assets = require("src.render.Assets") local MapLoader = require("src.world.MapLoader") +local Party = require("src.pokemon.Party") local Runtime = require("src.mods.Runtime") local WorldAPI = {} WorldAPI.__index = WorldAPI local NO_OVERWORLD = "no overworld" +local overviewShades = {} + +Assets.register(function() overviewShades = {} end) + +local function shadeDigit(sum, pixelCount) + return tostring(math.max(0, math.min(3, + math.floor((1 - sum / pixelCount) * 3 + 0.5)))) +end + +local function mapTileRows(map) + local tileset = map.tileset + if not (tileset and tileset.image and tileset.tilesPerRow) then return nil end + local cached = overviewShades[tileset.image] + if not cached then + local ok, pixels = pcall(Assets.imageData, tileset.image) + if not ok then return nil end + cached = { pixels = pixels, shades = {} } + overviewShades[tileset.image] = cached + end + local rows, detailRows, perRow = {}, {}, tileset.tilesPerRow + for ty = 0, map.heightCells * 2 - 1 do + local row, detailTop, detailBottom = {}, {}, {} + for tx = 0, map.widthCells * 2 - 1 do + local tile = map:tileAt(tx, ty) + local shades = cached.shades[tile] + if shades == nil then + local sums = { 0, 0, 0, 0 } + local ox, oy = (tile % perRow) * 8, math.floor(tile / perRow) * 8 + for py = 0, 7 do + for px = 0, 7 do + local r, g, b = cached.pixels:getPixel(ox + px, oy + py) + local quadrant = math.floor(py / 4) * 2 + math.floor(px / 4) + 1 + sums[quadrant] = sums[quadrant] + + r * 0.2126 + g * 0.7152 + b * 0.0722 + end + end + shades = { + shadeDigit(sums[1] + sums[2] + sums[3] + sums[4], 64), + shadeDigit(sums[1], 16), shadeDigit(sums[2], 16), + shadeDigit(sums[3], 16), shadeDigit(sums[4], 16), + } + cached.shades[tile] = shades + end + row[#row + 1] = shades[1] + detailTop[#detailTop + 1] = shades[2] .. shades[3] + detailBottom[#detailBottom + 1] = shades[4] .. shades[5] + end + rows[#rows + 1] = table.concat(row) + detailRows[#detailRows + 1] = table.concat(detailTop) + detailRows[#detailRows + 1] = table.concat(detailBottom) + end + return rows, detailRows +end function WorldAPI.new(game, modId) return setmetatable({ game = game, modId = modId }, WorldAPI) @@ -43,6 +98,53 @@ function WorldAPI:current() facing = p and p.facing } end +-- A compact, read-only view of the active map for minimaps and companion UIs. +-- `rows` describes collision terrain; optional `tileRows` reduces each real +-- 8x8 map tile to its average Game Boy shade ("0" lightest, "3" darkest). +-- `tileDetailRows` preserves one shade per 4x4 quadrant. Markers identify +-- exits and item spots that are still active without exposing world internals. +function WorldAPI:mapOverview() + local ow = self:overworld() + if not ow or not ow.map then return nil, NO_OVERWORLD end + local map, rows, markers = ow.map, {}, {} + for y = 0, map.heightCells - 1 do + local row = {} + for x = 0, map.widthCells - 1 do + row[#row + 1] = map:isWarpTileCell(x, y) and "+" + or map:isWaterCell(x, y) and "~" + or map:isWalkableCell(x, y) and "." or " " + end + rows[#rows + 1] = table.concat(row) + end + local def = map.def or {} + for _, warp in ipairs(def.warps or {}) do + markers[#markers + 1] = { kind = "warp", x = warp.x, y = warp.y } + end + local game, save = self.game, self.game.save or {} + for _, obj in ipairs(def.objects or {}) do + if obj.item and obj.item ~= "0" and obj.item ~= 0 + and ow.objectVisible(save, map.id, obj) then + markers[#markers + 1] = { kind = "item", x = obj.x, y = obj.y } + end + end + local hidden = game.data and game.data.field and game.data.field.hiddenItems + for _, item in ipairs(hidden and hidden[map.id] or {}) do + local key = map.id .. "_" .. item.x .. "_" .. item.y + if not (save.hiddenTaken and save.hiddenTaken[key]) then + markers[#markers + 1] = { kind = "hidden", x = item.x, y = item.y } + end + end + local tileRows, tileDetailRows = mapTileRows(map) + return { mapId = map.id, width = map.widthCells, + height = map.heightCells, rows = rows, markers = markers, + tileRows = tileRows, + tileWidth = tileRows and map.widthCells * 2, + tileHeight = tileRows and map.heightCells * 2, + tileDetailRows = tileDetailRows, + tileDetailWidth = tileDetailRows and map.widthCells * 4, + tileDetailHeight = tileDetailRows and map.heightCells * 4 } +end + -- opts.arrive = "fly" | "teleport" picks the arrival FX; anything else -- lands the player without one, like a scripted warp. function WorldAPI:warpTo(mapId, x, y, facing, opts) @@ -164,6 +266,49 @@ function WorldAPI:queueScript(rows, extra) return true end +-- The supported way to start a wild encounter. Hand-rolling this -- build a +-- BattleState, push it -- silently costs evolutions and blackout-on-loss +-- (both hang off onFinish -> afterBattle) plus the entry wipe and battle +-- theme (both owned by pushBattle). Nothing raises when they are missing. +function WorldAPI:startWildBattle(species, level) + local ow = self:overworld() + if not ow then return nil, NO_OVERWORLD end + if not self.game.data.pokemon[species] then + return nil, "unknown species: " .. tostring(species) + end + -- Pokemon.new writes the level through verbatim -- into level, the stat + -- calc and the exp curve -- so a fraction has to be refused here rather + -- than round somewhere downstream. The % test also catches NaN, which + -- passes both range comparisons. + level = tonumber(level) + if not level or level % 1 ~= 0 or level < 1 or level > 100 then + return nil, "level must be a whole number 1..100" + end + -- overworld() resolves the world from UNDER whatever sits on top of it, + -- so from a battle hook this would otherwise stack a second battle over + -- the live one -- and on a loss its afterBattle blacks out and warps + -- with the outer battle still on the stack. + local BattleTransition = require("src.render.BattleTransition") + for _, state in ipairs(self.game.stack and self.game.stack.states or {}) do + if state.awardExp or getmetatable(state) == BattleTransition then + return nil, "a battle is already running" + end + end + if ow.transitioning then return nil, "the world is mid-warp" end + -- BattleState.newWild marks the species SEEN before it reports an empty + -- party, so the party check comes first: a refused call must not leave a + -- Pokedex entry behind. + local save = self.game.save + if not (save and Party.firstHealthy(save.party or {})) then + return nil, "no healthy party" + end + local battle = require("src.battle.BattleState") + .newWild(self.game, species, level) + battle.onFinish = function(result) ow:afterBattle(result, battle) end + ow:pushBattle(battle) + return true +end + -- drop a map's cached instance so the next load re-reads its record; when -- it is the active map the world reloads around the player in place function WorldAPI:invalidateMap(mapId) diff --git a/test/switch-nro-ota.spec.test.js b/test/switch-nro-ota.spec.test.js new file mode 100644 index 00000000..7d64934b --- /dev/null +++ b/test/switch-nro-ota.spec.test.js @@ -0,0 +1,606 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { createHash } from 'node:crypto'; +import { fileURLToPath } from 'node:url'; +import { spawnSync } from 'node:child_process'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); + +function read(rel) { + return fs.readFileSync(path.join(root, rel), 'utf8'); +} + +function sha256Hex(buf) { + return createHash('sha256').update(buf).digest('hex'); +} + +/** JS mirror of src/update/SwitchOta.lua — contract under test. */ +function createSwitchOta() { + const OTA_RE = /^gen1recomp-(\d+\.\d+\.\d+)-switch\.zip$/; + const CHECK_TIMEOUT_SEC = 6; + const GAME_NRO_NAME = 'gen1recomp-game.nro'; + const LAUNCHER_NRO_NAME = 'gen1recomp.nro'; + const SAVE_DIR_NAME = 'pokemon-love2d'; + const INSTALL_DIR = 'switch/gen1recomp'; + + function parseSemver(s) { + if (typeof s !== 'string') return null; + const m = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/); + if (!m) return null; + return { major: +m[1], minor: +m[2], patch: +m[3] }; + } + + function compareSemver(a, b) { + const pa = parseSemver(a); + const pb = parseSemver(b); + if (!pa && !pb) return 0; + if (!pa) return -1; + if (!pb) return 1; + for (const f of ['major', 'minor', 'patch']) { + if (pa[f] < pb[f]) return -1; + if (pa[f] > pb[f]) return 1; + } + return 0; + } + + function isOtaAssetName(name) { + return typeof name === 'string' && OTA_RE.test(name); + } + + function findJsonObjectStart(jsonText, pos) { + if (!jsonText || pos < 1) return null; + let depth = 0; + for (let p = pos; p >= 1; p--) { + const c = jsonText[p - 1]; + if (c === '}') depth += 1; + else if (c === '{') { + if (depth === 0) return p; + depth -= 1; + } + } + return null; + } + + function findJsonObjectEnd(jsonText, objectStart) { + if (!jsonText || objectStart < 1) return null; + if (jsonText[objectStart - 1] !== '{') return null; + let depth = 1; + for (let p = objectStart + 1; p <= jsonText.length; p++) { + const c = jsonText[p - 1]; + if (c === '{') depth += 1; + else if (c === '}') { + depth -= 1; + if (depth === 0) return p + 1; + } + } + return null; + } + + function parseRelease(jsonText) { + if (!jsonText) return { ok: false, reason: 'empty_json' }; + const tagM = jsonText.match(/"tag_name"\s*:\s*"([^"]+)"/); + if (!tagM) return { ok: false, reason: 'missing_tag' }; + const tag = tagM[1]; + const versionM = tag.match(/^v?(\d+\.\d+\.\d+)$/); + if (!versionM) return { ok: false, reason: 'bad_tag' }; + const version = versionM[1]; + + let cursor = 0; + while (true) { + const nameKeyPos = jsonText.indexOf('"name"', cursor); + if (nameKeyPos === -1) break; + const tail = jsonText.slice(nameKeyPos); + const nameM = tail.match(/"name"\s*:\s*"([^"]+)"/); + const name = nameM?.[1]; + if (name && isOtaAssetName(name)) { + const assetStart = findJsonObjectStart(jsonText, nameKeyPos + 1); + const assetEnd = assetStart ? findJsonObjectEnd(jsonText, assetStart) : null; + if (assetStart && assetEnd && assetEnd > nameKeyPos + 1) { + const assetBlock = jsonText.slice(assetStart - 1, assetEnd - 1); + const urlM = assetBlock.match(/"browser_download_url"\s*:\s*"([^"]+)"/); + const downloadUrl = urlM?.[1]; + if (downloadUrl) { + return { + ok: true, + tag, + version, + assetName: name, + downloadUrl, + }; + } + } + } + cursor = nameKeyPos + 6; + } + return { ok: false, reason: 'missing_ota_asset' }; + } + + function decideUpdate(installed, release) { + if (!parseSemver(installed)) return { status: 'error', reason: 'bad_installed_version' }; + if (!release?.ok || !release.version) return { status: 'error', reason: 'bad_release' }; + if (compareSemver(release.version, installed) <= 0) { + return { status: 'uptodate', version: installed }; + } + return { + status: 'available', + version: release.version, + assetName: release.assetName, + downloadUrl: release.downloadUrl, + }; + } + + function parseSums(text) { + const sums = {}; + if (typeof text !== 'string') return sums; + for (const line of text.split(/\r?\n/)) { + const m = line.match(/^([0-9a-fA-F]+)\s+\*?\.?\/?(.*?)\s*$/); + if (m) sums[m[2]] = m[1].toLowerCase(); + } + return sums; + } + + function verifySha256(assetName, actualHex, sums) { + if (!assetName) return { ok: false, reason: 'bad_asset_name' }; + if (!sums || typeof sums !== 'object') return { ok: false, reason: 'missing_sums' }; + const expected = sums[assetName]; + if (!expected) return { ok: false, reason: 'sum_not_found' }; + if (!actualHex) return { ok: false, reason: 'missing_actual_hash' }; + if (actualHex.toLowerCase() !== expected.toLowerCase()) { + return { ok: false, reason: 'hash_mismatch' }; + } + return { ok: true }; + } + + function planAtomicApply(installDir, verifiedTempPath) { + installDir = installDir || INSTALL_DIR; + const gameNro = `${installDir}/${GAME_NRO_NAME}`; + const launcherNro = `${installDir}/${LAUNCHER_NRO_NAME}`; + const partPath = `${gameNro}.part`; + const launcherPart = `${launcherNro}.part`; + return { + steps: [ + { op: 'copy_to_part', from: verifiedTempPath, to: partPath }, + { op: 'rename', from: partPath, to: gameNro }, + { op: 'copy_to_part', from: 'launcher', to: launcherPart }, + { op: 'rename', from: launcherPart, to: launcherNro }, + { op: 'env_set_next_load', target: gameNro }, + ], + preserve: [`${installDir}/${SAVE_DIR_NAME}`], + forbidden: [`delete:${installDir}/${SAVE_DIR_NAME}`, `write_direct:${gameNro}`], + }; + } + + function offlinePolicy(elapsedSec, events = {}) { + if (events.userSkip) { + return { action: 'play_installed', reason: 'user_skip', message: 'update skipped' }; + } + if (events.apiError || events.networkOk === false) { + return { + action: 'play_installed', + reason: 'offline_or_error', + message: 'offline or update check failed — play installed version', + }; + } + if (typeof elapsedSec === 'number' && elapsedSec >= CHECK_TIMEOUT_SEC) { + return { + action: 'play_installed', + reason: 'timeout', + message: `update check timed out after ${CHECK_TIMEOUT_SEC}s`, + }; + } + return { action: 'keep_checking', reason: 'in_flight' }; + } + + return { + CHECK_TIMEOUT_SEC, + GAME_NRO_NAME, + LAUNCHER_NRO_NAME, + SAVE_DIR_NAME, + compareSemver, + isOtaAssetName, + parseRelease, + decideUpdate, + parseSums, + verifySha256, + planAtomicApply, + offlinePolicy, + }; +} + +const M = createSwitchOta(); + +const GITHUB_UPLOADER = + '"login":"github-actions[bot]",' + + '"id":41898282,' + + '"node_id":"MDM6Qm90NDE4OTgyODI=",' + + '"avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4",' + + '"gravatar_id":"",' + + '"url":"https://api.github.com/users/github-actions%5Bbot%5D",' + + '"html_url":"https://github.com/apps/github-actions",' + + '"followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers",' + + '"following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",' + + '"gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",' + + '"starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",' + + '"subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",' + + '"organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs",' + + '"repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos",' + + '"events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",' + + '"received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events",' + + '"type":"Bot",' + + '"user_view_type":"public",' + + '"site_admin":false'; + +function buildGithubReleaseJson() { + return ( + '{' + + '"tag_name":"v0.1.70",' + + '"name":"0.1.70",' + + '"assets":[' + + '{' + + '"url":"https://api.github.com/repos/bryanthaboi/gen1recomp/releases/assets/502823880",' + + '"id":502823880,' + + '"name":"gen1recomp-0.1.70-switch.zip",' + + '"label":"",' + + '"uploader":{' + + GITHUB_UPLOADER + + '},' + + '"content_type":"application/zip",' + + '"state":"uploaded",' + + '"size":9000573,' + + '"browser_download_url":"https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.70/gen1recomp-0.1.70-switch.zip"' + + '}' + + ']' + + '}' + ); +} + +const sampleRelease = M.parseRelease( + JSON.stringify({ + tag_name: 'v1.5.0', + assets: [ + { + name: 'gen1recomp-1.5.0-switch.zip', + browser_download_url: 'https://example/switch.zip', + }, + { name: 'sha256sums.txt', browser_download_url: 'https://example/sums' }, + ], + }) +); + +test('AC-001: Launcher nativo verifica release no GitHub @spec:AC-001', () => { + assert.equal(M.compareSemver('1.2.0', '1.1.0'), 1); + assert.equal(M.compareSemver('1.1.0', '1.1.0'), 0); + assert.equal(M.compareSemver('1.0.0', '1.1.0'), -1); + + assert.equal(sampleRelease.ok, true); + assert.equal(sampleRelease.version, '1.5.0'); + assert.equal(sampleRelease.assetName, 'gen1recomp-1.5.0-switch.zip'); + + assert.equal(M.decideUpdate('1.4.0', sampleRelease).status, 'available'); + assert.equal(M.decideUpdate('1.5.0', sampleRelease).status, 'uptodate'); + assert.equal(M.decideUpdate('1.6.0', sampleRelease).status, 'uptodate'); + + const missing = M.parseRelease(JSON.stringify({ tag_name: 'v1.5.0', assets: [] })); + assert.equal(missing.ok, false); + assert.equal(missing.reason, 'missing_ota_asset'); + + const githubRelease = M.parseRelease(buildGithubReleaseJson()); + assert.equal(githubRelease.ok, true); + assert.equal(githubRelease.version, '0.1.70'); + assert.equal(githubRelease.assetName, 'gen1recomp-0.1.70-switch.zip'); + assert.equal( + githubRelease.downloadUrl, + 'https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.70/gen1recomp-0.1.70-switch.zip' + ); + assert.equal(M.decideUpdate('0.1.69', githubRelease).status, 'available'); +}); + +test('AC-002: Download com verificação SHA-256 @spec:AC-002', () => { + const name = 'gen1recomp-1.5.0-switch.zip'; + const payload = Buffer.from('switch-sd-payload'); + const hex = sha256Hex(payload); + const sums = M.parseSums(`${hex} ${name}\n`); + + assert.equal(M.verifySha256(name, hex, sums).ok, true); + assert.equal(M.verifySha256(name, 'deadbeef', sums).reason, 'hash_mismatch'); + // Invisible-error guard: download without a sum MUST NOT verify. + assert.equal(M.verifySha256(name, hex, {}).ok, false); + assert.equal(M.verifySha256(name, hex, {}).reason, 'sum_not_found'); + assert.equal(M.verifySha256(name, '', sums).reason, 'missing_actual_hash'); +}); + +test('AC-003: Aplicação atômica e handoff para o jogo @spec:AC-003', () => { + const plan = M.planAtomicApply('switch/gen1recomp', '/tmp/verified.zip'); + assert.ok(plan.steps.some((s) => s.op === 'copy_to_part')); + assert.ok(plan.steps.some((s) => s.op === 'rename')); + assert.ok(plan.steps.some((s) => s.op === 'env_set_next_load')); + assert.ok(plan.steps.filter((s) => s.op === 'rename').length >= 2, 'renames game + launcher'); + assert.ok(plan.preserve.some((p) => p.endsWith('pokemon-love2d'))); + assert.ok(plan.forbidden.some((f) => f.startsWith('delete:') && f.includes('pokemon-love2d'))); + assert.ok(plan.forbidden.some((f) => f.startsWith('write_direct:'))); + const renameGame = plan.steps.find((s) => s.op === 'rename' && s.to.endsWith('gen1recomp-game.nro')); + assert.ok(renameGame); + assert.ok(renameGame.from.endsWith('.part')); + const renameLauncher = plan.steps.find((s) => s.op === 'rename' && s.to.endsWith('gen1recomp.nro')); + assert.ok(renameLauncher, 'launcher NRO replaced so NACP version stays in sync'); +}); + +test('AC-004: Offline ou falha de rede não trava o jogo @spec:AC-004', () => { + assert.equal(M.CHECK_TIMEOUT_SEC, 6); + assert.equal(M.offlinePolicy(1, { userSkip: true }).action, 'play_installed'); + assert.equal(M.offlinePolicy(1, { networkOk: false }).action, 'play_installed'); + assert.equal(M.offlinePolicy(6, { networkOk: true }).action, 'play_installed'); + assert.equal(M.offlinePolicy(6, { networkOk: true }).reason, 'timeout'); + assert.equal(M.offlinePolicy(2, { networkOk: true }).action, 'keep_checking'); +}); + +test('AC-005: Documentação Switch descreve o launcher OTA @spec:AC-005', () => { + const install = read('docs/switch-install.md'); + const build = read('docs/switch-build.md'); + const updater = read('docs/updater.md'); + + for (const [name, text] of [ + ['switch-install', install], + ['switch-build', build], + ['updater', updater], + ]) { + assert.match(text, /native OTA launcher|launcher nativo/i, `${name} mentions native OTA launcher`); + } + + assert.match(install, /manual|zip|microSD/i, 'manual zip fallback still documented'); + assert.match(install, /gen1recomp-\*-switch\.zip|switch\.zip/i, 'unified Switch zip documented'); + assert.doesNotMatch(install, /switch-ota\.zip/, 'legacy separate OTA zip must not be documented'); + assert.match(install, /Sphaira|forwarder/i, 'Sphaira shortcut version note documented'); + assert.doesNotMatch( + install, + /Switch OTA uses `src\/update\/Check\.lua`/i, + 'must not claim Switch OTA uses LÖVE Check.lua' + ); + assert.match(updater, /Nintendo Switch/i); + assert.match(updater, /networkValidated|disabled|desligado|off/i); +}); + +test('AC-006: LOVE no NX continua sem rede de updater @spec:AC-006', () => { + const platform = read('src/core/Platform.lua'); + assert.match( + platform, + /networkValidated\s*=\s*not nx/, + 'Platform.networkValidated must stay false on NX' + ); + assert.doesNotMatch(platform, /networkValidated\s*=\s*true/); + + const boot = read('src/update/Boot.lua'); + assert.match(boot, /networkValidated/, 'Boot still gates on networkValidated'); + + assert.ok(read('src/update/Check.lua').length > 0); + assert.ok(read('src/update/check_worker.lua').length > 0); + assert.ok(read('src/core/HostShell.lua').length > 0); + assert.match(read('src/import/RomImporter.lua'), /updaterAllowed|networkValidated|Check/); + assert.ok(read('src/import/LauncherView.lua').length > 0); + + const switchOta = read('src/update/SwitchOta.lua'); + assert.doesNotMatch(switchOta, /networkValidated\s*=\s*true/); + assert.match(switchOta, /never runs this path|remains gated off on NX/i); +}); + +test('AC-007: Funções puras do protocolo OTA têm testes host-side @spec:AC-007', () => { + assert.ok(M.isOtaAssetName('gen1recomp-1.2.3-switch.zip')); + assert.equal(M.isOtaAssetName('gen1recomp-1.2.3-switch-ota.zip'), false); + assert.equal(M.isOtaAssetName('gen1recomp-1.2.3.love'), false); + assert.equal(M.compareSemver('2.0.0', '1.9.9'), 1); + assert.equal(M.compareSemver('1.0.0', '1.0.0'), 0); + assert.equal(M.compareSemver('0.9.0', '1.0.0'), -1); + + const rel = M.parseRelease( + JSON.stringify({ + tag_name: 'v9.9.9', + assets: [ + { + name: 'gen1recomp-9.9.9-switch.zip', + browser_download_url: 'https://example/x', + }, + ], + }) + ); + assert.equal(rel.version, '9.9.9'); + + const sums = M.parseSums('deadbeef unexpected-name.zip\n'); + assert.equal( + M.verifySha256('gen1recomp-9.9.9-switch.zip', 'deadbeef', sums).ok, + false, + 'unexpected filename in sums must not verify OTA asset' + ); + assert.equal(M.verifySha256('gen1recomp-9.9.9-switch.zip', 'anything', {}).ok, false); + + const lua = read('src/update/SwitchOta.lua'); + assert.ok(fs.existsSync(path.join(root, 'src/update/SwitchOta.lua'))); + for (const fn of [ + 'compareSemver', + 'parseRelease', + 'decideUpdate', + 'parseSums', + 'verifySha256', + 'planAtomicApply', + 'offlinePolicy', + ]) { + assert.match(lua, new RegExp(`function SwitchOta\\.${fn}`), `Lua exports ${fn}`); + } + assert.match(lua, /CHECK_TIMEOUT_SEC\s*=\s*6/); + assert.match(lua, /switch%.zip|%-switch%.zip/); + assert.doesNotMatch(lua, /switch%-ota%.zip|switch-ota\.zip/); +}); + +test('AC-008: Gates de regressão anti-“erro invisível” @spec:AC-008', () => { + const platform = read('src/core/Platform.lua'); + assert.match(platform, /networkValidated\s*=\s*not nx\s+and\s+not uwp/); + + for (const rel of ['docs/switch-install.md', 'docs/switch-build.md', 'docs/updater.md']) { + const text = read(rel); + assert.match(text, /native OTA launcher|launcher nativo/i, rel); + assert.match( + text, + /LÖVE self-updater|LOVE self-updater|self-updater LÖVE|updater LÖVE|Check\.lua/i, + `${rel} contrasts with LÖVE updater` + ); + } + + const manifest = read('scripts/switch/ota_launcher.manifest'); + assert.match(manifest, /^OTA_ENABLED=1$/m); + assert.match(manifest, /ENTRY_NRO=switch\/gen1recomp\/gen1recomp\.nro/); + assert.match(manifest, /GAME_NRO=switch\/gen1recomp\/gen1recomp-game\.nro/); + assert.match(manifest, /OTA_ASSET_GLOB=gen1recomp-\*-switch\.zip/); + assert.doesNotMatch(manifest, /switch-ota\.zip/); + assert.match(manifest, /REQUIRE_SHA256SUMS=1/); +}); + +test('AC-009: Protocolo C host-testável espelha SwitchOta.lua @spec:AC-009', () => { + const proto = read('ports/switch/ota-launcher/src/ota_protocol.c'); + const header = read('ports/switch/ota-launcher/include/ota_protocol.h'); + assert.match(header, /OTA_CHECK_TIMEOUT_SEC\s+6/); + assert.match(header, /ota_compare_semver/); + assert.match(header, /ota_parse_release/); + assert.match(header, /ota_verify_sha256/); + assert.match(header, /ota_plan_atomic_apply/); + assert.match(header, /ota_offline_policy/); + assert.match(proto, /sum_not_found/); + assert.match(proto, /env_set_next_load/); + assert.match(header, /pokemon-love2d/); + assert.match(proto, /OTA_SAVE_DIR_NAME/); + + const lua = read('src/update/SwitchOta.lua'); + assert.match(lua, /CHECK_TIMEOUT_SEC\s*=\s*6/); + + // Compile + run C host tests (gcc) + const r = spawnSync( + 'make', + ['-C', path.join(root, 'ports/switch/ota-launcher'), 'host-test'], + { encoding: 'utf8' } + ); + assert.equal(r.status, 0, `host-test failed:\n${r.stdout}\n${r.stderr}`); + assert.match(r.stdout, /all ota_protocol host tests passed/); +}); + +test('AC-010: Fonte do launcher e Makefile DEVKITPRO existem @spec:AC-010', () => { + for (const rel of [ + 'ports/switch/ota-launcher/Makefile', + 'ports/switch/ota-launcher/README.md', + 'ports/switch/ota-launcher/src/main.c', + 'ports/switch/ota-launcher/src/ota_net.c', + 'ports/switch/ota-launcher/src/ota_fs.c', + 'scripts/switch/build_ota_launcher.sh', + 'docs/switch-build.md', + ]) { + assert.ok(fs.existsSync(path.join(root, rel)), `missing ${rel}`); + } + const main = read('ports/switch/ota-launcher/src/main.c'); + assert.match(main, /envSetNextLoad|ota_fs_handoff_to_game/); + assert.match(main, /gen1recomp-game\.nro|OTA_GAME_NRO_NAME/); + assert.match(main, /Quiet by default|stays quiet|LÖVE self-updater stays off|self-updater stays off/i); + assert.match(main, /ota_ui_prompt_update|ota_ui_show_progress/); + assert.match(main, /ota_ui_alert_error/); + assert.match(main, /Step 1\/3: Downloading/); + assert.doesNotMatch(main, /\u2026/, 'OTA UI strings must be ASCII (no Unicode ellipsis)'); + assert.doesNotMatch(main, /ota_ui_alert\([^)]*Install failed/, 'generic install alert removed'); + assert.doesNotMatch(main, /consoleInit/, 'no terminal console UI'); + assert.ok(fs.existsSync(path.join(root, 'ports/switch/ota-launcher/src/ota_ui.c'))); + const otaUi = read('ports/switch/ota-launcher/src/ota_ui.c'); + assert.match(otaUi, /COL_RAIL_R|rail|FFD600|255,\s*214/); + assert.match(otaUi, /logo\.rgba/); + assert.doesNotMatch(otaUi, /stb_image/); + assert.ok( + fs.existsSync(path.join(root, 'ports/switch/assets/logo.rgba')), + 'pre-baked OTA logo asset' + ); + assert.ok( + fs.existsSync(path.join(root, 'scripts/switch/bake_ota_logo.py')), + 'logo bake script for regenerating logo.rgba' + ); + assert.match(otaUi, /ota_ui_sanitize_ascii/); + assert.match(otaUi, /draw_text_wrapped_centered/); + assert.match(otaUi, /ota_ui_alert_error/); + assert.match(read('ports/switch/ota-launcher/src/ota_net.c'), /ota_net_init/); + assert.match(read('ports/switch/ota-launcher/src/main.c'), /ota_net_init/); + assert.match(read('ports/switch/ota-launcher/src/ota_net.c'), /CURLOPT_SSL_VERIFYPEER,\s*1L/); + assert.match(read('ports/switch/ota-launcher/src/ota_net.c'), /romfs:\/cacert\.pem/); + assert.doesNotMatch(read('ports/switch/ota-launcher/src/ota_net.c'), /CURLOPT_SSL_VERIFYPEER,\s*0L/); + assert.match(read('ports/switch/ota-launcher/Makefile'), /^ROMFS\s*:=/m); + assert.match(read('ports/switch/ota-launcher/Makefile'), /cacert\.pem/); + assert.match(read('ports/switch/ota-launcher/Makefile'), /logo\.rgba/); + + const mk = read('ports/switch/ota-launcher/Makefile'); + assert.match(mk, /libnx\/switch_rules|DEVKITPRO/); + assert.match(mk, /-lcurl/); + assert.match(mk, /-lzzip/); + assert.match(mk, /ports\/switch\/assets\/icon\.jpg|assets\/icon\.jpg/, 'uses project Switch icon'); + + assert.ok(fs.existsSync(path.join(root, 'ports/switch/ota-launcher/src/ota_unzip.c'))); + assert.match(read('ports/switch/ota-launcher/src/ota_unzip.c'), /zzip\/zzip\.h/); + assert.match(read('ports/switch/ota-launcher/src/main.c'), /ota_unzip_extract_file/); + assert.match(read('ports/switch/ota-launcher/src/main.c'), /GAME_MEMBER_IN_ZIP|switch\/gen1recomp\//); + assert.match(read('ports/switch/ota-launcher/src/main.c'), /LAUNCHER_MEMBER_IN_ZIP|ota_fs_stage_launcher_bootstrap/); + assert.match(read('ports/switch/ota-launcher/src/main.c'), /ota_fs_stage_launcher_bootstrap\([\s\S]*\) != 0/); + assert.match(read('ports/switch/ota-launcher/src/main.c'), /return 2.*bootstrap|bootstrap.*return 2/i); + assert.match(read('ports/switch/ota-launcher/src/ota_fs.c'), /ota_fs_atomic_replace_nro/); + assert.match(read('ports/switch/ota-launcher/src/ota_fs.c'), /remove\(dest\)/); + assert.doesNotMatch(read('ports/switch/ota-launcher/src/ota_fs.c'), /#ifdef _WIN32[\s\S]*remove\(dest\)/); + assert.doesNotMatch(read('scripts/switch/install_devkitpro_deps.sh'), /switch-minizip/); + assert.match(read('scripts/switch/install_devkitpro_deps.sh'), /switch-zziplib/); + assert.match(read('scripts/switch/install_devkitpro_deps.sh'), /switch-dev/); + + const buildDoc = read('docs/switch-build.md'); + assert.match(buildDoc, /ports\/switch\/ota-launcher|build_ota_launcher/); + assert.match(buildDoc, /native packages.*or.*Docker|native or Docker/i); + assert.match(buildDoc, /--fused|install_devkitpro_deps/); + assert.match(buildDoc, /Requires DEVKITPRO|DEVKITPRO is[\s\S]*required/i); + assert.doesNotMatch(buildDoc, /switch-ota\.zip/); + assert.doesNotMatch(buildDoc, /--ota\b/); + + const readme = read('ports/switch/ota-launcher/README.md'); + assert.match(readme, /DEVKITPRO|devkitPro/i); + assert.match(readme, /Docker|devkita64/i); + assert.match(readme, /Quiet|quiet|silen/i); +}); + +test('AC-011: Empacotamento dual-NRO e selftest @spec:AC-011', () => { + const pack = read('scripts/switch/pack_sd_zip.sh'); + assert.match(pack, /LAUNCHER_NRO/); + assert.match(pack, /gen1recomp-game\.nro/); + assert.match(pack, /version\.txt/); + + assert.equal( + fs.existsSync(path.join(root, 'scripts/switch/pack_ota_zip.sh')), + false, + 'separate pack_ota_zip.sh removed — unified *-switch.zip' + ); + + const buildSwitch = read('scripts/build_switch.sh'); + assert.doesNotMatch(buildSwitch, /--ota\b/); + assert.doesNotMatch(buildSwitch, /pack_ota_zip\.sh/); + assert.match(buildSwitch, /build_ota_launcher\.sh/); + assert.match(buildSwitch, /dual-NRO SD zip|OTA download asset/i); + + const buildOtaLauncher = read('scripts/switch/build_ota_launcher.sh'); + assert.match(buildOtaLauncher, /ota_launcher_deps_ready/); + assert.match(buildOtaLauncher, /fail_missing_devkitpro/); + assert.match(buildSwitch, /preflight_fused_build/); + + const selftest = read('scripts/switch/selftest_build_switch.sh'); + assert.match(selftest, /dual-NRO|gen1recomp-game\.nro/); + assert.match(selftest, /ota_launcher\.manifest/); + assert.match(selftest, /OTA uses the same SD zip|same SD zip|legacy OTA-only/i); + assert.match(selftest, /ota_ui|framebuffer|no prompt when up to date/i); + + const manifest = read('scripts/switch/ota_launcher.manifest'); + assert.match(manifest, /^OTA_ENABLED=1$/m); + assert.match(manifest, /OTA_ASSET_GLOB=gen1recomp-\*-switch\.zip/); + + // Run the dual-NRO portion via full offline selftest (includes legacy + OTA) + const r = spawnSync('bash', [path.join(root, 'scripts/switch/selftest_build_switch.sh')], { + encoding: 'utf8', + }); + assert.equal(r.status, 0, `selftest failed:\n${r.stdout}\n${r.stderr}`); + assert.match(r.stdout, /dual-NRO OTA layout/); +}); diff --git a/tests/build_rom_data_cli_test.py b/tests/build_rom_data_cli_test.py new file mode 100644 index 00000000..e8800c2a --- /dev/null +++ b/tests/build_rom_data_cli_test.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""ROM-free regression tests for source-build version detection and routing.""" + +from contextlib import redirect_stderr, redirect_stdout +from io import StringIO +from pathlib import Path +from types import SimpleNamespace +from unittest import TestCase, main, mock + +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools")) +import build_rom_data # noqa: E402 + + +class BuildRomDataCliTest(TestCase): + def run_builder(self, sha1, *extra): + manifest = {"romSha1": sha1, "symbols": {}} + rom = SimpleNamespace(sha1=sha1) + with mock.patch.object(build_rom_data, "RomImage", return_value=rom), \ + mock.patch.object(build_rom_data, "load_manifest", return_value=manifest), \ + mock.patch.object(build_rom_data, "build") as build, \ + mock.patch.object(build_rom_data.os, "makedirs"), \ + redirect_stdout(StringIO()), redirect_stderr(StringIO()): + result = build_rom_data.main([ + "--rom", "fixture.gb", "--only", "constants", *extra]) + return result, build + + def test_blue_rom_selects_blue_manifest_and_cache_paths(self): + result, build = self.run_builder( + build_rom_data.CANONICAL_BLUE_SHA1) + + self.assertEqual(result, 0) + args = build.call_args.args + self.assertEqual(args[3], "blue/data/generated") + self.assertEqual(args[4], "blue/assets/generated") + + def test_red_rom_keeps_historical_root_paths(self): + result, build = self.run_builder(build_rom_data.CANONICAL_RED_SHA1) + + self.assertEqual(result, 0) + args = build.call_args.args + self.assertEqual(args[3], "data/generated") + self.assertEqual(args[4], "assets/generated") + + def test_explicit_output_paths_are_preserved(self): + result, build = self.run_builder( + build_rom_data.CANONICAL_YELLOW_SHA1, + "--out", "/tmp/custom-data", "--assets", "/tmp/custom-assets") + + self.assertEqual(result, 0) + args = build.call_args.args + self.assertEqual(args[3], "/tmp/custom-data") + self.assertEqual(args[4], "/tmp/custom-assets") + + def test_unknown_rom_is_rejected_before_build(self): + unknown = "0" * 40 + result, build = self.run_builder(unknown) + + self.assertEqual(result, 1) + build.assert_not_called() + + +if __name__ == "__main__": + main() diff --git a/tests/drivers/bag_full_pickup_bug872_test.lua b/tests/drivers/bag_full_pickup_bug872_test.lua new file mode 100644 index 00000000..2d04abbd --- /dev/null +++ b/tests/drivers/bag_full_pickup_bug872_test.lua @@ -0,0 +1,222 @@ +-- Manual check of both bag-full pickup refusals (#872): an item ball must +-- refuse with _NoMoreRoomForItemText (pokered scripts/pick_up_item.asm +-- .BagFull), never the Toss-screen _CantCarryMoreText, and a hidden item +-- announces the find first, then _HiddenItemBagFullText (hidden_items.asm). +-- Run without POKEPORT_SPEED -- the box paging under test is timing-honest. +-- POKEPORT_DRIVER=tests/drivers/bag_full_pickup_bug872_test.lua POKEPORT_IDENTITY=bug872 POKEPORT_TOUCH=0 love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local TextBox = require("src.render.TextBox") + local Bag = require("src.inventory.Bag") + local GameVersion = require("src.core.GameVersion") + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local MAP = "VIRIDIAN_FOREST" + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- neither refusal plays a jingle, but the human will want the normal + -- pickup sound as a contrast, so warn when it would be muted + local opts = game.save.options or {} + if (opts.sfxVol or 7) == 0 then + U.log("note: sfxVol is 0, so a successful pickup afterwards will be", + "silent; the refusal boxes themselves are unaffected") + end + + -- a real fresh game, so the save has a player name and clean flags + U.newGame(game) + local save = game.save + + -- Empty the bag, then refill to exactly capacity with ids that are NOT + -- POTION: Bag.add succeeds by quantity for an id already in a slot + -- (src/inventory/Bag.lua), which would mask the bug, and both test + -- targets below hand out POTION. Badges share save.inventory but are + -- not bag slots, so they are left alone. + for _, id in ipairs({ unpack(Bag.order(save)) }) do + Bag.remove(save, id, save.inventory[id] or 1) + end + local ids = {} + for id in pairs(game.data.items) do + if not Bag.isBadge(id) and id ~= "POTION" then ids[#ids + 1] = id end + end + table.sort(ids) + for _, id in ipairs(ids) do + if Bag.slots(save) >= Bag.capacity(game.data) then break end + Bag.add(save, id, 1, game.data) + end + check(("bag is full (%d/%d slots) and holds no POTION") + :format(Bag.slots(save), Bag.capacity(game.data)), + Bag.slots(save) >= Bag.capacity(game.data) + and not save.inventory.POTION) + + -- first target: the Potion item ball. pokered + -- data/maps/objects/ViridianForest.asm:36 puts it at walk cell (12, 29) + -- (object_event 12, 29, SPRITE_POKE_BALL ... POTION), free floor below. + local BALL = { x = 12, y = 29 } + U.teleport(game, MAP, BALL.x, BALL.y + 1, "left") + U.wait(10) + + local function isBall(n) + return n and n.def and n.def.item and n.def.item ~= "0" and n.def.item ~= 0 + end + local ow = game.overworld + local ball = ow:npcAtCell(BALL.x, BALL.y) + if not isBall(ball) then + -- a map edit or mod moved the object: take any item ball on the map + -- and stand on a free walkable neighbour instead + ball = nil + for _, n in ipairs(ow.npcs or {}) do + if isBall(n) then ball = n break end + end + if ball then + local sides = { + { 0, 1, "up" }, { 0, -1, "down" }, { 1, 0, "left" }, { -1, 0, "right" }, + } + for _, s in ipairs(sides) do + local cx, cy = ball.cellX + s[1], ball.cellY + s[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + U.log(("ball not at (%d, %d); using the one at (%d, %d)") + :format(BALL.x, BALL.y, ball.cellX, ball.cellY)) + -- teleport facing away, the tap below still does the turn + U.teleport(game, MAP, cx, cy, s[3] == "left" and "right" or "left") + U.wait(10) + BALL.x, BALL.y = ball.cellX, ball.cellY + break + end + end + end + end + check("an item ball is loaded on " .. MAP, ball ~= nil) + local ballItem = ball and ball.def.item or "POTION" + + -- turn toward the ball ourselves (a one-frame press only turns when the + -- player faces elsewhere, src/world/Player.lua), then trigger the talk + ow = game.overworld + local dx, dy = BALL.x - ow.player.cellX, BALL.y - ow.player.cellY + local dir = (dy < 0 and "up") or (dy > 0 and "down") + or (dx < 0 and "left") or "right" + U.tap(game, dir) + U.wait(10) + local fx, fy = game.overworld.player:facingCell() + check("player turned to face the ball", + game.overworld:npcAtCell(fx, fy) == ball) + U.tap(game, "a") + U.wait(30) + + local function readPages() + local top = game.stack:top() + if getmetatable(top) ~= TextBox then return nil end + local pages = {} + for _, page in ipairs(top.pages or {}) do + pages[#pages + 1] = table.concat(page, " / ") + end + return pages + end + local function closeBox() + for _ = 1, 8 do + if getmetatable(game.stack:top()) ~= TextBox then break end + U.tap(game, "a") + U.wait(25) + end + end + + local pages = readPages() + check("A on the full-bag ball opened a text box", pages ~= nil) + if pages then + local all = table.concat(pages, " || ") + U.log("ball refusal reads:", all) + check("it is the pickup refusal, not the Toss line", + all:find("No more room", 1, true) ~= nil + and all:find("can't carry", 1, true) == nil) + if GameVersion.isYellow() then + check("Yellow announces the find, then refuses on page 2", + #pages == 2 + and pages[1]:find(ballItem, 1, true) ~= nil + and pages[2]:find("No more room", 1, true) ~= nil) + else + check("Red/Blue refuse in one page with no found line", + #pages == 1 and pages[1]:find("found", 1, true) == nil) + end + U.shot(game, SHOT_DIR .. "/bug872_ball_refusal.png") + end + closeBox() + + -- the refusal must leave the world untouched so the pickup can be + -- retried after tossing something + check("the ball is still standing there", + game.overworld:npcAtCell(BALL.x, BALL.y) == ball) + check("itemsTaken was not marked", + not (save.itemsTaken and ball and save.itemsTaken[ball.id])) + check("the item stayed out of the bag", not save.inventory[ballItem]) + + -- second target: the hidden POTION. pokered + -- data/events/hidden_item_coords.asm:8 puts it at (x=1, y=18) on + -- VIRIDIAN_FOREST; Game.data.field.hiddenItems carries the same spot. + local hidden + local list = (game.data.field.hiddenItems or {})[MAP] or {} + for _, h in ipairs(list) do + if h.x == 1 and h.y == 18 then hidden = h break end + end + hidden = hidden or list[1] + check("a hidden item exists on " .. MAP, hidden ~= nil) + + local stood = false + if hidden then + -- {dx, dy, facing} from the hidden cell to a stand cell that looks + -- back at it; the spot itself is usually an unwalkable tree tile + local sides = { + { 0, 1, "up" }, { 0, -1, "down" }, { 1, 0, "left" }, { -1, 0, "right" }, + } + for _, s in ipairs(sides) do + local cx, cy = hidden.x + s[1], hidden.y + s[2] + local m = game.overworld.map + if m:isWalkableCell(cx, cy) and not game.overworld:npcAtCell(cx, cy) then + U.teleport(game, MAP, cx, cy, s[3] == "left" and "right" or "left") + U.wait(10) + U.tap(game, s[3]) + U.wait(10) + local hfx, hfy = game.overworld.player:facingCell() + if hfx == hidden.x and hfy == hidden.y then stood = true break end + end + end + end + check("standing against the hidden spot", stood) + + U.tap(game, "a") + U.wait(30) + pages = readPages() + check("A on the full-bag hidden spot opened a text box", pages ~= nil) + if pages then + local all = table.concat(pages, " || ") + U.log("hidden refusal reads:", all) + check("the found line comes first", + #pages == 2 and pages[1]:find("found", 1, true) ~= nil + and (not hidden or pages[1]:find( + (game.data.items[hidden.item] or {}).name or hidden.item, + 1, true) ~= nil)) + check("then the hidden-item bag-full line, not the Toss line", + #pages == 2 + and pages[2]:find("no more room", 1, true) ~= nil + and pages[2]:find("other items", 1, true) ~= nil + and all:find("can't carry", 1, true) == nil) + U.shot(game, SHOT_DIR .. "/bug872_hidden_refusal.png") + end + closeBox() + + local key = hidden and (MAP .. "_" .. hidden.x .. "_" .. hidden.y) + check("the hidden spot can still be prompted again", + not (key and save.hiddenTaken and save.hiddenTaken[key])) + check("the hidden item stayed out of the bag", + not (hidden and save.inventory[hidden.item])) + + U.log("You are still facing the hidden spot with a full bag; pressing A") + U.log("should say the item was found, then that there is no more room for") + U.log("other items, and never the bag screen's \"can't carry\" wording.") + U.log("Toss something and both spots should hand their item over normally.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/battle_bg_world_dim_bug777_test.lua b/tests/drivers/battle_bg_world_dim_bug777_test.lua new file mode 100644 index 00000000..d1cd14ee --- /dev/null +++ b/tests/drivers/battle_bg_world_dim_bug777_test.lua @@ -0,0 +1,184 @@ +-- Eye check: BATTLE BG = WORLD dims the surround only, never the battle's own +-- 160x144 field (#777, dup #772). Renderer:endFrame used to fill the WHOLE +-- window with the 55% veil; the classic battle hid that under its opaque paper +-- field, but a pipeline that stages the fight on the map and keys the field +-- out (the Dramatic Shape Voxel Mod) got the veil straight onto its sprites +-- and HP boxes. On hardware there is nothing behind the battle to dim at all: +-- _InitBattleCommon calls ClearScreen (pokered home/copy2.asm) over the whole +-- tilemap before the battle draws. +-- POKEPORT_DRIVER=tests/drivers/battle_bg_world_dim_bug777_test.lua POKEPORT_IDENTITY=bug777 POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love . +-- POKEPORT_TOUCH=0 matters: the on-screen controls draw after endFrame and +-- would land in the void samples. No POKEPORT_SPEED around the shots. +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Game = require("src.core.Game") + local Renderer = require("src.render.Renderer") + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + + -- pokered data/maps/objects/Route1.asm puts both youngsters at (5,24) and + -- (15,13) and the sign at (9,27), so the top of the road is empty; the + -- battle is pushed straight in, the cell is only somewhere to stand. + local MAP = "ROUTE_1" + local STAND = { x = 5, y = 6, facing = "down" } + local PARTY = { { "BULBASAUR", 12 }, { "PIDGEOTTO", 18 } } + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- Load a captured PNG back as ImageData. love.image cannot read absolute + -- paths, so go through io.open + newFileData. + local function loadShot(path) + local f = io.open(path, "rb") + if not f then return nil end + local bytes = f:read("*a") + f:close() + local ok, img = pcall(function() + return love.image.newImageData( + love.filesystem.newFileData(bytes, "shot.png")) + end) + return ok and img or nil + end + + -- Mean brightness of a pixel rect. Whole-region averages, so NPC steps and + -- flower/water animation between two shots wash out instead of flipping a + -- single-pixel compare. + local function regionMean(img, x, y, w, h) + local sum, n = 0, 0 + local x2 = math.min(x + w, img:getWidth()) - 1 + local y2 = math.min(y + h, img:getHeight()) - 1 + for yy = math.max(y, 0), y2 do + for xx = math.max(x, 0), x2 do + local r, g, b = img:getPixel(xx, yy) + sum = sum + (r + g + b) / 3 + n = n + 1 + end + end + return n > 0 and sum / n or 0, n + end + + -- The UI letterbox in framebuffer pixels, the same math endFrame uses for + -- uox/uoy/uvpw/uvph (screenshots are framebuffer-sized, so no dpi divide). + local function uiBox(img) + local pw, ph = img:getWidth(), img:getHeight() + local uiw, uih = Renderer:uiSize() + local Up = Renderer:uiScale() + local bw, bh = math.floor(uiw * Up + 0.5), math.floor(uih * Up + 0.5) + local bx = math.floor((pw - bw) / 2) + local by = math.floor((ph - bh) / 2) + return bx, by, bw, bh, pw, ph + end + + game.save.options = game.save.options or {} + game.save.options.battleFit = nil -- classic letterbox, the geometry under test + + game.save.party = {} + for _, slot in ipairs(PARTY) do + table.insert(game.save.party, Pokemon.new(game.data, slot[1], slot[2])) + end + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(30) + local ow = game.overworld + check("the overworld is up on " .. MAP, ow ~= nil and ow.map.id == MAP) + + -- ---- machine half: veil geometry, no battle needed ----------------------- + -- In the overworld the UI canvas is transparent over the map, so the veil + -- is not hidden by an opaque field the way the classic battle hides it. + -- Arm the dim through the real per-frame wiring (Game:draw reads + -- Game.worldBgBattleDim every frame) and compare against an unarmed shot: + -- the surround must darken, the letterbox interior must not. + local baseShot = DIR .. "/bug777_0_overworld.png" + local veilShot = DIR .. "/bug777_0_overworld_veiled.png" + U.shot(game, baseShot) + local realDim = Game.worldBgBattleDim + Game.worldBgBattleDim = function() return BattleState.BG_WORLD_DIM end + U.wait(2) + U.shot(game, veilShot) + Game.worldBgBattleDim = realDim + U.wait(2) + + local base, veil = loadShot(baseShot), loadShot(veilShot) + if check("both overworld shots decoded", base ~= nil and veil ~= nil) then + local bx, by, bw, bh, pw = uiBox(base) + local inBase = regionMean(base, bx, by, bw, bh) + local inVeil = regionMean(veil, bx, by, bw, bh) + U.log((" letterbox %dx%d at (%d, %d), interior mean %.3f -> %.3f") + :format(bw, bh, bx, by, inBase, inVeil)) + -- 0.75 sits between "unchanged" and the 0.55 veil's 0.45x; whole-box + -- means make a frame of tile animation worth far less than that gap. + check("the veil leaves the battle box alone (#777)", + inVeil >= inBase * 0.75) + if bx >= 8 then + local outBase = regionMean(base, 0, 0, bx, base:getHeight()) + local outVeil = regionMean(veil, 0, 0, bx, veil:getHeight()) + U.log((" left void strip mean %.3f -> %.3f"):format(outBase, outVeil)) + check("the veil still dims the surround", outVeil <= outBase * 0.75) + else + U.log(" window has no side voids at this scale, surround check skipped") + end + end + + -- ---- the real battle, all three BATTLE BG modes -------------------------- + local battle = BattleState.newWild(game, "RATTATA", 5) + battle.onFinish = function() end + ow:pushBattle(battle) + for _ = 1, 400 do + if game.stack:top() == battle and (battle.introSlide or 0) == 0 then break end + U.wait(1) + end + check("the battle reached the screen", game.stack:top() == battle) + for _ = 1, 120 do + if battle.phase == "menu" then break end + U.tap(game, "a") + U.wait(6) + end + check("the battle reached its FIGHT/PKMN/ITEM/RUN menu", + battle.phase == "menu") + + -- bgMode reads save.options.battleBg per frame, so one battle covers all + -- three modes; the menu is idle between shots. + local shots = {} + for _, mode in ipairs({ "white", "black", "world" }) do + game.save.options.battleBg = mode + U.wait(5) + local path = DIR .. "/bug777_" .. mode .. ".png" + U.shot(game, path) + shots[mode] = loadShot(path) + end + + if check("all three battle shots decoded", + shots.white ~= nil and shots.black ~= nil and shots.world ~= nil) then + local bx, by, bw, bh = uiBox(shots.white) + local inWhite = regionMean(shots.white, bx, by, bw, bh) + local inWorld = regionMean(shots.world, bx, by, bw, bh) + U.log((" battle box mean, WHITE %.3f vs WORLD %.3f"):format(inWhite, inWorld)) + check("WORLD leaves the battle's own screen at WHITE's brightness", + math.abs(inWhite - inWorld) < 0.02) + if bx >= 8 then + local outWhite = regionMean(shots.white, 0, 0, bx, shots.white:getHeight()) + local outWorld = regionMean(shots.world, 0, 0, bx, shots.world:getHeight()) + U.log((" void strip mean, WHITE %.3f vs WORLD %.3f"):format(outWhite, outWorld)) + check("WORLD's surround is the dimmed map, not paper", + outWorld < outWhite - 0.05) + end + end + + -- ---- over to you --------------------------------------------------------- + U.log("You are at the battle menu with BATTLE BG = WORLD: the frozen Route 1") + U.log("map sits around the battle at 55% brightness. Put bug777_white.png and") + U.log("bug777_world.png side by side: inside the battle box they must match") + U.log("exactly, same paper, same pics, same HP bars; only the frame around it") + U.log("changes. #777 dimmed the whole window instead, which with a mod that") + U.log("stages the fight on the map dropped the veil straight over the sprites") + U.log("and health boxes. The near miss to look for is the opposite failure:") + U.log("a surround that is NOT dimmed at all, which means the veil got lost") + U.log("rather than scoped.") + U.log("Shots: " .. DIR .. "/bug777_*.png") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/battle_choice_paper_bug822_test.lua b/tests/drivers/battle_choice_paper_bug822_test.lua new file mode 100644 index 00000000..77c6897a --- /dev/null +++ b/tests/drivers/battle_choice_paper_bug822_test.lua @@ -0,0 +1,216 @@ +-- Eye check: a YES/NO box over a classic battle wears the same paper as the +-- field behind it (#822). pokered data/sgb/sgb_packets.asm BlkPacket_Battle +-- attributes all 18 rows, and home/yes_no.asm InitYesNoTextBoxParameters puts +-- the box at hlcoord 14,7 -- inside the player-HP-bar region, pal 0. +-- POKEPORT_DRIVER=tests/drivers/battle_choice_paper_bug822_test.lua POKEPORT_IDENTITY=bug822 POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love . +-- No POKEPORT_SPEED: it scales the logic clock only, and these frames are judged as drawn. +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local PaletteFX = require("src.render.PaletteFX") + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + local ChoiceBox = require("src.ui.ChoiceBox") + local Theme = require("src.ui.Theme") + + -- pokered data/maps/objects/Route1.asm puts the two youngsters at (5,24) and + -- (15,13) and the sign at (9,27), so the top of the road is empty grass; the + -- battle is pushed rather than walked into, the cell is only somewhere to stand. + local MAP = "ROUTE_1" + local STAND = { x = 5, y = 6, facing = "down" } + local PARTY = { { "BULBASAUR", 12 }, { "PIDGEOTTO", 18 } } + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- ---- machine-checkable preconditions ------------------------------------ + local opts = game.save.options or {} + local sfxVol = opts.sfxVol or 7 + if sfxVol == 0 then + U.log("FAIL SFX volume is 0, so the box's A/B click is gone and there is no") + U.log(" way to tell a dead box from a live one you cannot hear. Set SFX to 7.") + end + check(("SFX volume %d, so the YES/NO box clicks when you answer it"):format(sfxVol), + sfxVol > 0) + check("the shade-remap shader compiled (no shader, no colorization at all)", + PaletteFX.shader() ~= nil) + check("PaletteFX.sendShades exists -- the raw sender the #822 fix needs", + type(PaletteFX.sendShades) == "function") + check("all 7 COLORS modes are on the ladder (" + .. table.concat(PaletteFX.MODES, ", ") .. ")", #PaletteFX.MODES == 7) + local cl = PaletteFX.CLASSIC + check(("CLASSIC paper is the light pea green %d,%d,%d and its second shade" + .. " the darker %d,%d,%d -- the pair #822 confused") + :format(cl[1][1], cl[1][2], cl[1][3], cl[2][1], cl[2][2], cl[2][3]), + cl[1][1] == 155 and cl[1][2] == 188 and cl[2][1] == 139) + check("OG's GRAYS start at pure white, so OG is the shader identity", + PaletteFX.GRAYS[1][1] == 255) + local box = Theme.choiceBox + check(("the YES/NO box sits at tile %d,%d (%dx%d) -- InitYesNoTextBoxParameters'" + .. " hlcoord 14,7"):format(box.tx, box.ty, box.tw, box.th), + box.tx == 14 and box.ty == 7) + + -- ---- get into a battle with the box up ---------------------------------- + game.save.party = {} + for _, slot in ipairs(PARTY) do + table.insert(game.save.party, Pokemon.new(game.data, slot[1], slot[2])) + end + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(20) + local ow = game.overworld + if ow and not ow.map:isWalkableCell(STAND.x, STAND.y) then + -- a map edit moved the road: any free neighbour will do, nothing here + -- depends on the cell beyond having somewhere legal to stand + for _, d in ipairs({ { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } }) do + local cx, cy = STAND.x + d[1], STAND.y + d[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + U.log(("(%d, %d) is blocked, standing on"):format(STAND.x, STAND.y), cx, cy) + U.teleport(game, MAP, cx, cy, STAND.facing) + U.wait(20) + ow = game.overworld + break + end + end + end + check("the overworld is up on " .. MAP, ow ~= nil and ow.map.id == MAP) + + local battle = BattleState.newWild(game, "RATTATA", 5) + battle.onFinish = function() end + ow:pushBattle(battle) + for _ = 1, 400 do + if game.stack:top() == battle and (battle.introSlide or 0) == 0 then break end + U.wait(1) + end + for _ = 1, 120 do + if battle.phase == "menu" then break end + U.tap(game, "a") + U.wait(6) + end + check("the battle reached its FIGHT/PKMN/ITEM/RUN menu", battle.phase == "menu") + + -- The same bare box BattleState pushes for the switch offer (BattleState.lua + -- :1291): no anchor, so it sits over the battle rather than riding a dialogue + -- box. Pushed directly because the reporter's screen is the box on top of a + -- live battle, and how it got there changes nothing about the colorization. + local choice = ChoiceBox.new(game, function() end) + game.stack:push(choice) + U.wait(10) + check("a YES/NO box is on top of the battle", game.stack:top() == choice) + + -- ---- measure both papers, mode by mode ---------------------------------- + -- Replays what Game:draw does for a classic battle with an overlay above it: + -- every state paints onto the one 160x144 UI canvas, then the topmost state + -- with sgbPalettes owns the screen. BattleState:sgbPalettes returns nil for + -- the classic layout, so PaletteFX.ensureZones decides whether a whole-screen + -- shade pass runs at all -- it does in OG / OG INV / CLASSIC, and does not in + -- the colorized modes. Offscreen so the sample boxes stay in clean 160x144 + -- space whatever the window is doing; the U.shot next to each reading is the + -- same frame as presented. + local g = love.graphics + local shader = PaletteFX.shader() + + -- The empty pocket the SGB attribute map leaves at tiles 9,4 - 10,6: below + -- the enemy HP bar (rows 0-3), right of the player mon (cols 0-8), left of + -- the enemy mon (col 11 on). Nothing is ever drawn here, so it is the field + -- paper and nothing else. + local FIELD = { 74, 36, 85, 52 } + -- Inside the YES/NO box, clear of its border tiles. The glyphs and cursor + -- live in here too, which is why the reading is the most common color rather + -- than one pixel. + local BOXI = { 120, 64, 151, 87 } + + local function modal(id, r) + local counts, best, bestN = {}, nil, -1 + for y = r[2], r[4] do + for x = r[1], r[3] do + local pr, pg, pb = id:getPixel(x, y) + local key = math.floor(pr * 255 + 0.5) .. "," .. math.floor(pg * 255 + 0.5) + .. "," .. math.floor(pb * 255 + 0.5) + counts[key] = (counts[key] or 0) + 1 + if counts[key] > bestN then best, bestN = key, counts[key] end + end + end + return best + end + + local function sample() + local prev = g.getCanvas() + local a = g.newCanvas(160, 144) + local b = g.newCanvas(160, 144) + g.setCanvas(a) + g.clear(1, 1, 1, 1) -- the battle letterbox is white (letterboxWhite) + g.setColor(1, 1, 1, 1) + battle:draw() + choice:draw() + g.setCanvas(b) + g.clear(0, 0, 0, 1) + local zones = PaletteFX.ensureZones(nil) + if zones and zones[1] then + g.setShader(shader) + PaletteFX.sendColors(shader, PaletteFX.GRAYS) + end + g.setColor(1, 1, 1, 1) + g.draw(a, 0, 0) + g.setShader() + g.setCanvas(prev) + local id = b:newImageData() + return modal(id, FIELD), modal(id, BOXI), zones ~= nil and zones[1] ~= nil + end + + local mismatched = {} + for _, m in ipairs(PaletteFX.MODES) do + -- set the SAVED option too: Game:applyOptions re-reads save.options.colors, + -- so a bare setMode gets reverted underneath the next frame + game.save.options = game.save.options or {} + game.save.options.colors = m + PaletteFX.setMode(m) + U.wait(20) + local field, boxp, framePass = sample() + local label = PaletteFX.modeLabel(m) + local same = field == boxp + local known = (m == "gbc" or m == "gbc_inv") + U.log(("%-9s field %-13s box %-13s %s"):format( + label, field, boxp, + framePass and "whole-screen pass" or "no whole-screen pass")) + if m == "classic" then + check("CLASSIC field is the light pea green 155,188,15, not the darker" + .. " 139,172,15 one bucket down", field == "155,188,15") + end + if known then + -- the other half of #822, left open on purpose: with no frame-level pass + -- the overlay paints raw DMG white onto a canvas the battle has already + -- colorized, and nothing local to drawZonePass can reach it + U.log((" %s draws its overlays raw, so a mismatch here is the known" + .. " open half, not this fix failing"):format(label)) + else + if not same then mismatched[#mismatched + 1] = label end + check(label .. " box paper matches the field behind it", same) + end + U.shot(game, DIR .. "/bug822_" .. m .. ".png") + end + check("no forced-mono or ADVANCED/OG RED mode left the box a different color" + .. " from the field", #mismatched == 0) + if #mismatched > 0 then + U.log(" mismatched on:", table.concat(mismatched, ", ")) + end + + -- ---- over to you -------------------------------------------------------- + PaletteFX.setMode("classic") + game.save.options.colors = "classic" + U.wait(20) + U.log("You are looking at a YES/NO box sitting on a wild RATTATA battle in") + U.log("CLASSIC. The paper inside the box and the empty field around the mons") + U.log("should be the one same pea green, with no seam where the box begins;") + U.log("press 2 through the ladder and OG INV should go black-on-black the same") + U.log("way, while OG, OG RED and ADVANCED look exactly as they always did.") + U.log("The near miss is a box that is only slightly lighter than the field --") + U.log("that is the old one-bucket slip, not a border. SGB and SGB INV still") + U.log("show a white box over a tinted field; that half of #822 is open.") + U.log("Shots: " .. DIR .. "/bug822_*.png. A or B answers the box and it goes.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/battle_menu_default_bug770_test.lua b/tests/drivers/battle_menu_default_bug770_test.lua new file mode 100644 index 00000000..349cb06a --- /dev/null +++ b/tests/drivers/battle_menu_default_bug770_test.lua @@ -0,0 +1,101 @@ +-- Driver: the FIGHT/PKMN/ITEM/RUN cursor after a voluntary switch (#770). +-- SendOutMon (pokered engine/battle/core.asm:1733-1735) zeroes the saved +-- battle-menu byte AND the move-list byte behind it on every player +-- send-out, so the reopened menu starts on FIGHT; the #737 sendOutMonCursors +-- reset already covers this and the driver is the proof. Judge WITHOUT +-- POKEPORT_SPEED: fast-forward makes the reopened menu impossible to read. +-- POKEPORT_DRIVER=tests/drivers/battle_menu_default_bug770_test.lua POKEPORT_IDENTITY=bug770 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + local PartyMenu = require("src.ui.PartyMenu") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- :L60 pair so the foe's free move after the switch cannot faint anyone + -- and force the ChooseNextMon path instead of the voluntary one + check("CHARIZARD resolves in the species table", + game.data.pokemon.CHARIZARD ~= nil) + check("SNORLAX resolves in the species table", + game.data.pokemon.SNORLAX ~= nil) + game.save.player.name = "bryan" + game.save.party = { + Pokemon.new(game.data, "CHARIZARD", 60), + Pokemon.new(game.data, "SNORLAX", 60), + } + check("party has two healthy mons", + #game.save.party == 2 + and game.save.party[1].hp > 0 and game.save.party[2].hp > 0) + + U.teleport(game, "PALLET_TOWN", 10, 8, "down") + U.wait(20) + check("overworld is up to push the battle from", game.overworld ~= nil) + + local battle = BattleState.newWild(game, "PIDGEY", 5) + battle.onFinish = function() end + game.overworld:pushBattle(battle) + + local function mashUntil(cond, max) + for _ = 1, max or 120 do + if cond() then return true end + U.tap(game, "a") + U.wait(4) + end + return cond() + end + + U.wait(220) -- the send-out intro plays before the menu is reachable + check("the wild battle reached its action menu", + mashUntil(function() return battle.phase == "menu" end)) + check("cursor starts on FIGHT", battle.menuIndex == 1) + + -- park the cursor on PKMN first: with it off FIGHT, "still 1 afterwards" + -- proves a reset happened rather than nothing ever moving + U.tap(game, "right") + U.wait(4) + check("cursor moved to PKMN", battle.menuIndex == 2) + U.tap(game, "a") + U.wait(10) + local menu = game.stack:top() + check("A on PKMN opened the party menu", getmetatable(menu) == PartyMenu) + + -- steer onto slot 2; #768 seeds the cursor from partyMenuSavedIndex so + -- the start slot is not fixed, but down wraps and must land on 2 + for _ = 1, 6 do + if getmetatable(menu) ~= PartyMenu or menu.index == 2 then break end + U.tap(game, "down") + U.wait(4) + end + check("party cursor sits on slot 2", + getmetatable(menu) == PartyMenu and menu.index == 2) + U.tap(game, "a") -- SWITCH / STATS / CANCEL, SWITCH preselected + U.wait(6) + check("the SWITCH submenu is open", + getmetatable(menu) == PartyMenu and menu.submenu ~= nil + and menu.subIndex == 1) + U.tap(game, "a") -- SWITCH -> resolveSwitch; the foe takes a free move + U.wait(10) + + check("the turn resolved back to the action menu", + mashUntil(function() return battle.phase == "menu" end, 300)) + check("the second mon is the one out now", + battle.player.mon == game.save.party[2]) + check("menuIndex reset to FIGHT after the send-out (#770)", + battle.menuIndex == 1) + check("moveIndex reset to the first slot too", battle.moveIndex == 1) + U.shot(game, DIR .. "/bug770_menu_after_switch.png") + U.log("captured", DIR .. "/bug770_menu_after_switch.png") + + U.log("The menu on screen just reopened after a PKMN switch; the cursor") + U.log("should be back on FIGHT. Switch again yourself: it must land on") + U.log("FIGHT every time -- reopening on PKMN is the old #770 bug.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/battle_music_bug782_test.lua b/tests/drivers/battle_music_bug782_test.lua new file mode 100644 index 00000000..b4ef78e0 --- /dev/null +++ b/tests/drivers/battle_music_bug782_test.lua @@ -0,0 +1,111 @@ +-- Manual check that Silph Co Giovanni gets the ordinary trainer theme (#782). +-- PlayBattleMusic (audio/play_battle_music.asm) only picks +-- MUSIC_GYM_LEADER_BATTLE when wGymLeaderNo is set, and scripts/SilphCo11F.asm +-- never writes it -- only the eight gym scripts do. The port keyed the boss +-- check on the trainer class alone, so this fight (OPP_GIOVANNI#2) borrowed +-- the Viridian Gym roster's theme, victory jingle, and Pikachu happiness bump. +-- The data half is asserted in tests/parity_battle_music_bug782.lua. +-- POKEPORT_DRIVER=tests/drivers/battle_music_bug782_test.lua POKEPORT_IDENTITY=bug782 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local Music = require("src.core.Music") + local BattleState = require("src.battle.BattleState") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + local vol = game.save.options and game.save.options.musicVol + if vol == 0 then + U.log("music volume is 0 in options; raise it or nothing will be audible") + end + + -- record what the engine asks the music system for; the real playback + -- still happens underneath, so the listening half is unaffected + local played = {} + local realPlayBattle = Music.playBattle + Music.playBattle = function(data, kind, trainerId) + played[#played + 1] = { call = "battle", kind = kind } + return realPlayBattle(data, kind, trainerId) + end + local realPlayVictory = Music.playVictory + Music.playVictory = function(data, kind, trainerId) + played[#played + 1] = { call = "victory", kind = kind } + return realPlayVictory(data, kind, trainerId) + end + + -- a party that can win this quickly, so the victory jingle is reachable + game.save.party = { + Pokemon.new(game.data, "MEWTWO", 90), + Pokemon.new(game.data, "CHARIZARD", 80), + } + game.save.player.name = "RED" + + -- Giovanni's fight is a coordinate trigger, not a talk: + -- SilphCo11FDefaultScript (pokered scripts/SilphCo11F.asm + -- .PlayerCoordsArray) fires on (6,13) or (7,12), walks him three tiles + -- down from his object_event spot at (6,9) + -- (pokered data/maps/objects/SilphCo11F.asm), shows his speech and starts + -- the battle. The (6,13) pad sits behind the 11F card key door, which this + -- teleported-in save has not opened (no CARD KEY, so tryCardKeyDoor never + -- swaps the door block), so take the pad on the open side: stand on (6,12) + -- and step east onto (7,12). + U.teleport(game, "SILPH_CO_11F", 6, 12, "right") + U.wait(10) + U.hold(game, "right", 40) + U.wait(10) + + -- Giovanni's approach, then his pre-battle text box: mash A until the + -- battle state is on top of the stack + local battle + for _ = 1, 400 do + local top = game.stack:top() + if getmetatable(top) == BattleState and top.kind == "trainer" then + battle = top + break + end + U.tap(game, "a") + U.wait(3) + end + + check("the coordinate trigger engaged a trainer battle", battle ~= nil) + if battle then + check("the opponent is Giovanni (OPP_GIOVANNI#2)", + battle.oppClass == "OPP_GIOVANNI" and battle.partyIndex == 2) + check("musicKind is \"trainer\", not \"gym\"", + battle.musicKind == "trainer") + check("isGymLeader is unset (no Pikachu GYMLEADER happiness bump)", + not battle.isGymLeader) + local battleCall + for _, p in ipairs(played) do + if p.call == "battle" then battleCall = p.kind end + end + check("Music.playBattle was asked for the trainer theme", + battleCall == "trainer") + end + + U.log("You are in the Silph Co Giovanni fight. The theme playing now") + U.log("should be the ordinary Vs. Trainer battle music, not the gym-leader") + U.log("theme this fight used to borrow. Win it (MEWTWO 90 vs his level") + U.log("~40 party) and the jingle at \"defeated GIOVANNI\" should be the") + U.log("plain trainer victory fanfare, again not the gym-leader one.") + U.log("For the correct-by-contrast case, the Viridian Gym rematch") + U.log("(OPP_GIOVANNI#3) still keeps the gym-leader theme.") + + -- report the victory request when the win lands, then keep idling + local reported = false + while true do + if not reported then + for _, p in ipairs(played) do + if p.call == "victory" then + check("Music.playVictory was asked for the trainer jingle", + p.kind == "trainer") + reported = true + end + end + end + coroutine.yield() + end +end diff --git a/tests/drivers/boulder_trainer_bug809_test.lua b/tests/drivers/boulder_trainer_bug809_test.lua new file mode 100644 index 00000000..52229aee --- /dev/null +++ b/tests/drivers/boulder_trainer_bug809_test.lua @@ -0,0 +1,222 @@ +-- A trainer's walk-up must stop short of a Strength boulder, and the boulder +-- must still be pushable afterwards (#809). TrainerWalkUpToPlayer (pokered +-- engine/overworld/trainer_sight.asm) writes dist-1 movement bytes that skip +-- collision, so the trainer used to park ON the boulder, and after that +-- IsSpriteInFrontOfPlayer (home/overworld.asm) handed TryPushingBoulder the +-- trainer instead of the rock. POKEPORT_DRIVER=tests/drivers/boulder_trainer_bug809_test.lua POKEPORT_IDENTITY=bug809 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . + +-- No POKEPORT_SPEED: the sighting, the "!" bubble and the walk-up all run at +-- the normal 60 Hz logic clock so the stop-short frame is the one a player +-- would see. The setup pushes are slow for the same reason; the run takes +-- about half a minute of real time before it hands the pad over. +return function(game) + local U = dofile("tests/drivers/util.lua") + + -- pokered data/maps/objects/VictoryRoad3F.asm: + -- object_event 13, 3, SPRITE_COOLTRAINER_F, STAY, RIGHT, ..., OPP_COOLTRAINER_F, 3 + -- object_event 22, 3, SPRITE_BOULDER, STAY, BOULDER_MOVEMENT_BYTE_2, ... + -- Her header range is 4 (data/generated/trainer_headers.lua VictoryRoad3F[4]), + -- so she spots the player anywhere on row 3 within four cells to her east and + -- then walks dist-1 cells toward him. Row 3 is walled at x=19, so BOULDER1 + -- cannot simply be shoved west into her sight line: it has to go down column + -- 22 to row 6, west along row 6, and back up column 17 onto row 3. + local MAP = "VICTORY_ROAD_3F" + local MAP_LABEL = "VictoryRoad3F" + local BOULDER = "VICTORYROAD3F_BOULDER1" + local TRAINER = "VICTORYROAD3F_COOLTRAINER_F2" + local START = { x = 22, y = 2, facing = "down" } + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local pass = true + local function check(label, ok) + if not ok then pass = false end + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + local function findNpc(ow, name) + for _, n in ipairs(ow.npcs or {}) do + if n.def and n.def.name == name then return n end + end + return nil + end + + -- Hold `btn` until `cond` goes true or the budget runs out, then release and + -- let any half-finished step land. Boulder pushes need a held direction: + -- handleInput only reaches checkBoulderPush while the player already faces + -- that way, and TryPushingBoulder arms on one poll and moves on the next + -- (BIT_TRIED_PUSH_BOULDER), so a single tap can never shift a rock. + local function holdUntil(btn, cond, budget) + local first = true + for _ = 1, budget or 600 do + if cond() then break end + if first then table.insert(game.input.pressQueue, btn); first = false end + game.input.state[btn] = true + coroutine.yield() + end + game.input.state[btn] = false + for _ = 1, 40 do + if not game.overworld.player.moving and #game.overworld.scriptMoves == 0 then + break + end + coroutine.yield() + end + U.wait(4) -- an input-free poll re-arms turning in place (wCheckFor180DegreeTurn) + return cond() + end + + U.teleport(game, MAP, START.x, START.y, START.facing) + U.wait(10) + local ow = game.overworld + local rock = findNpc(ow, BOULDER) + local trainer = findNpc(ow, TRAINER) + + check("BOULDER1 loaded on " .. MAP, rock ~= nil) + check("COOLTRAINER_F2 loaded on " .. MAP, trainer ~= nil) + if not (rock and trainer) then + U.log("map objects missing; nothing to drive") + while true do coroutine.yield() end + end + check("BOULDER1 starts at the asm cell (22,3)", + rock.cellX == 22 and rock.cellY == 3) + check("COOLTRAINER_F2 starts at the asm cell (13,3) facing right", + trainer.cellX == 13 and trainer.cellY == 3 and trainer.facing == "right") + check("checkBoulderPush resolves through pushableAtCell", + type(ow.pushableAtCell) == "function") + + local header = game.data:trainerHeader(MAP_LABEL, trainer.def.index) + local range = header and header.range or 0 + check("her sight range is 4 cells", range == 4) + + -- The whole route, so a map or tileset edit shows up here instead of as a + -- driver that quietly wanders off. If a cell is not walkable the boulder + -- cannot be pushed onto it (CheckForCollisionWhenPushingBoulder reuses the + -- player's passability check) and the run is not worth continuing. + local ROUTE = { + { 22, 4 }, { 22, 5 }, { 22, 6 }, { 23, 5 }, { 23, 6 }, + { 21, 6 }, { 20, 6 }, { 19, 6 }, { 18, 6 }, { 17, 6 }, + { 18, 7 }, { 17, 7 }, { 17, 5 }, { 17, 4 }, { 17, 3 }, + { 18, 4 }, { 18, 3 }, { 16, 3 }, { 16, 4 }, { 16, 2 }, + } + local routeOk = true + for _, c in ipairs(ROUTE) do + if not ow.map:isWalkableCell(c[1], c[2]) then + routeOk = false + U.log("route cell not walkable:", c[1], c[2]) + end + end + check("the push route is walkable end to end", routeOk) + + -- STRENGTH is live for the map visit. BIT_STRENGTH_ACTIVE is what + -- TryPushingBoulder gates on -- it never re-reads badges or party moves -- + -- so setting the field-move state is the whole grant (see the comment in + -- OverworldState:checkBoulderPush). + ow.strengthActive = true + + -- Victory Road rolls a wild encounter on every completed step, not just in + -- grass (wild_encounters.asm counts caves as indoor), and this run walks + -- twenty-odd cells with an empty party. Drop the map's table: a wild + -- battle mid-route interrupts the push with a screen transition and has + -- nothing to do with what is being checked. + game.data.encounters[MAP] = nil + + if not pass then + U.log("setup checks already failed; not driving the push") + while true do coroutine.yield() end + end + + local function boulderAt(x, y) + return function() return rock.cellX == x and rock.cellY == y end + end + local function playerAt(x, y) + local p = ow.player + return function() return p.cellX == x and p.cellY == y end + end + + -- down column 22 to row 6 + holdUntil("down", boulderAt(22, 6), 400) + check("boulder pushed down column 22 to (22,6)", rock.cellX == 22 and rock.cellY == 6) + -- around to its east side + holdUntil("right", playerAt(23, 5), 120) + holdUntil("down", playerAt(23, 6), 120) + -- west along row 6 to the column that reaches row 3 + holdUntil("left", boulderAt(17, 6), 700) + check("boulder pushed west along row 6 to (17,6)", rock.cellX == 17 and rock.cellY == 6) + -- around to its south side + holdUntil("down", playerAt(18, 7), 120) + holdUntil("left", playerAt(17, 7), 120) + -- up column 17 onto her row + holdUntil("up", boulderAt(17, 3), 400) + check("boulder pushed up column 17 onto row 3 at (17,3)", + rock.cellX == 17 and rock.cellY == 3) + if not pass then + U.log("the boulder never reached her row; the race below cannot happen") + while true do coroutine.yield() end + end + + -- Step onto row 3 one cell out of range (18 - 13 = 5 > 4) so the sighting + -- happens on the push itself and not a moment earlier. + holdUntil("right", playerAt(18, 4), 120) + holdUntil("up", playerAt(18, 3), 120) + check("player waiting at (18,3), one cell outside her range", + ow.player.cellX == 18 and ow.player.cellY == 3 and not ow.engaging) + + -- The engage lands on a battle we are not going to fight: stand in for it, + -- record where the walk-up stopped, and mark her beaten the way winning + -- would. Everything the walk-up does has already happened by this point. + local stopped + local realEngage = ow.engageTrainer + ow.engageTrainer = function(self, npc, onDone) + stopped = { npc = npc, x = npc.cellX, y = npc.cellY } + game.save.defeatedTrainers[npc.id] = true + if onDone then onDone() end + end + + -- One push west: the boulder lands on (16,3) and the player follows onto + -- (17,3), four cells from her, which is the frame she spots him on. + holdUntil("left", function() return stopped ~= nil end, 400) + + check("she spotted the player and finished her walk-up", stopped ~= nil) + check("the boulder moved one cell west to (16,3)", + rock.cellX == 16 and rock.cellY == 3) + if stopped then + U.log("she stopped at", stopped.x, stopped.y, "boulder at", rock.cellX, rock.cellY) + check("she is not standing on the boulder cell", + not (stopped.x == rock.cellX and stopped.y == rock.cellY)) + check("she stopped one cell short of it, at (15,3)", + stopped.x == 15 and stopped.y == 3) + check("the push path still finds the boulder under that cell", + ow:pushableAtCell(rock.cellX, rock.cellY) == rock) + check("nothing else shares the boulder's cell", + ow:npcAtCell(rock.cellX, rock.cellY) == rock) + end + U.shot(game, SHOT_DIR .. "/bug809_walkup_stop.png") + + -- ...and the rock still moves. Push it north, the one free direction left: + -- west is her, east is the player, south is where he came from. + holdUntil("down", playerAt(17, 4), 120) + holdUntil("left", playerAt(16, 4), 120) + holdUntil("up", boulderAt(16, 2), 400) + if not check("the boulder is still pushable after the engage", + rock.cellX == 16 and rock.cellY == 2) then + U.log("boulder ended at", rock.cellX, rock.cellY, "player at", + ow.player.cellX, ow.player.cellY) + end + holdUntil("down", playerAt(16, 4), 120) + U.shot(game, SHOT_DIR .. "/bug809_still_pushable.png") + + ow.engageTrainer = realEngage + U.log(pass and "ALL CHECKS PASSED" or "SOME CHECKS FAILED") + + U.log("On screen: the COOLTRAINER stands at (15,3) with a one-cell gap") + U.log("between her and the rock, which now sits at (16,2), one row up from") + U.log("where she stopped. The near miss to watch for is her sprite ending") + U.log("the walk-up on top of the rock, or standing clear of it but leaving") + U.log("it inert: walk into the rock from any side and it should still shift") + U.log("a cell. Her battle was stubbed out and she is flagged as beaten;") + U.log("re-run the driver to watch the race again from the start.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/champion_alt_tempo_bug847_test.lua b/tests/drivers/champion_alt_tempo_bug847_test.lua new file mode 100644 index 00000000..bf22c76b --- /dev/null +++ b/tests/drivers/champion_alt_tempo_bug847_test.lua @@ -0,0 +1,286 @@ +-- Driver for #847: slowed Cities1 (scripts/ChampionsRoom.asm:112 farcall +-- Music_Cities1AlternateTempo, audio/alternate_tempo.asm), the scripted walk +-- over the rival (home/overworld.asm CollisionCheckOnLand) and the back-pic +-- sweep (engine/movie/hall_of_fame.asm HoFShowMonOrPlayer). Never add +-- POKEPORT_SPEED here: it scales the logic clock only and audio is the test. +-- POKEPORT_DRIVER=tests/drivers/champion_alt_tempo_bug847_test.lua POKEPORT_IDENTITY=hof847 POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local Sprites = require("src.pokemon.Sprites") + local Music = require("src.core.Music") + local ChipSynth = require("src.core.ChipSynth") + local Commands = require("src.script.Commands") + local HallOfFame = require("src.ui.HallOfFame") + + local failures = 0 + local function check(label, ok) + if not ok then failures = failures + 1 end + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- audio/music/cities1.asm: Music_Cities1_Ch1 opens `tempo 144`, the + -- Music_Cities1_Ch1_AlternateTempo stub `tempo 232` (macros/scripts/audio.asm + -- emits db HIGH(x), LOW(x), so these are the literal engine values). + local NORMAL_TEMPO, ALT_TEMPO = 144, 232 + local SONG = "Music_Cities1" + + -- --------------------------------------------------------------- + -- party + options + -- --------------------------------------------------------------- + local SPECIES = { "CHARIZARD", "SNORLAX", "PIKACHU" } + game.save.party = {} + for i, name in ipairs(SPECIES) do + game.save.party[i] = Pokemon.new(game.data, name, 100 - (i - 1) * 27) + end + game.save.player.name = "BRYAN" + local options = game.save.options + check("sfxVol is non-zero, so the cries are audible", (options.sfxVol or 0) > 0) + check("musicVol is non-zero, so the tempo swap is audible", + (options.musicVol or 0) > 0) + + -- --------------------------------------------------------------- + -- machine checks: the parts an ear cannot separate from a bad build + -- --------------------------------------------------------------- + -- read the live registry, so a mod's map_scripts contribution is inspected + local mapScripts = require("data.scripts.init") + local champ = mapScripts.get("CHAMPIONS_ROOM") + local rows = champ and champ.talk and champ.talk.TEXT_CHAMPIONSROOM_RIVAL + check("CHAMPIONS_ROOM keeps its rival script", type(rows) == "table") + rows = rows or {} + + local iFade, iWait, iCue, iWalk, iWarp, cueOpts + for i, row in ipairs(rows) do + if row[1] == "fade_music" and not iFade then + iFade = i + elseif row[1] == "wait" and iFade and not iWait then + iWait = i + elseif row[1] == "play_music" and row[2] == SONG and not iCue then + iCue, cueOpts = i, row[3] + elseif row[1] == "move_player" and row[2] == "up" and not iWarp then + iWalk = i + elseif row[1] == "warp" and row[2] == "HALL_OF_FAME" then + iWarp = iWarp or i + end + end + check("the script fades the battle theme out before Cities1 (#847)", + iFade ~= nil and iCue ~= nil and iFade < iCue) + check("it waits out the fade, like the ld c, 100 / call DelayFrames", + iWait ~= nil and iWait > iFade and iWait < iCue + and (rows[iWait or 1][2] or 0) >= 100) + check("the Cities1 cue carries the alternate tempo " .. ALT_TEMPO, + type(cueOpts) == "table" and cueOpts.tempo == ALT_TEMPO) + check("it still walks the player out before the warp (#704)", + iWalk ~= nil and iWarp ~= nil and iWalk < iWarp) + check("Commands.fade_music exists for that first row", + type(Commands.fade_music) == "function") + + -- the tempo has to survive the song's own `tempo` command: without the + -- override the body's 144 wins and Cities1 plays as the ordinary town theme + local def = game.data.audio and game.data.audio.songs + and game.data.audio.songs[SONG] + check(SONG .. " is in the extracted song table", type(def) == "table") + if type(def) == "table" then + local slowed = {} + for k, v in pairs(def) do slowed[k] = v end + slowed.tempo = ALT_TEMPO + local okAlt, alt = pcall(ChipSynth.newEngine, game.data, slowed, + { allowLoops = true }) + local okPlain, plain = pcall(ChipSynth.newEngine, game.data, def, + { allowLoops = true }) + check("an overridden song header starts locked at " .. ALT_TEMPO, + okAlt and alt and alt.tempo == ALT_TEMPO and alt.tempoLocked == true) + check("a plain header is left unlocked, free to take its own tempo " + .. NORMAL_TEMPO, + okPlain and plain and not plain.tempoLocked) + end + + -- HoFShowMonOrPlayer loads a back pic for every party member and for the + -- player; a missing key would silently draw nothing during the sweep + for _, name in ipairs(SPECIES) do + local path = Sprites.path(game.data, name, "back", { kind = "hof" }) + check(name .. " resolves a back pic for the induction", + type(path) == "string" and path ~= "") + end + local playerBack = Sprites.playerPath(game.data, "back", { kind = "hof" }) + check("the player resolves RedPicBack for the closing sweep", + type(playerBack) == "string" and playerBack ~= "") + + -- --------------------------------------------------------------- + -- stand where ChampionsRoomPlayerEntersScript leaves the player + -- --------------------------------------------------------------- + -- pokered data/maps/objects/ChampionsRoom.asm: CHAMPIONSROOM_RIVAL at (4,2), + -- both HALL_OF_FAME warps on row 0, and RivalEntrance_RLEMovement (up 1, + -- right 1, up 3) from warp 1 lands the player at (4,3), facing the rival. + local STAND = { x = 4, y = 3 } + U.teleport(game, "CHAMPIONS_ROOM", STAND.x, STAND.y, "up") + U.wait(20) + local ow = game.overworld + local rival + for _, npc in ipairs(ow and ow.npcs or {}) do + if npc.def and npc.def.name == "CHAMPIONSROOM_RIVAL" then rival = npc end + end + check("the rival object is on the map", rival ~= nil) + if rival and (ow.player.cellX ~= rival.cellX + or ow.player.cellY ~= rival.cellY + 1) then + -- a map edit or a mod moved the object: stand on any free walkable + -- neighbour instead of facing a wall. {dx, dy, facing} is the offset from + -- the rival to the stand cell plus the direction that looks back at him. + local sides = { + { 0, 1, "up" }, { 0, -1, "down" }, { 1, 0, "left" }, { -1, 0, "right" }, + } + for _, s in ipairs(sides) do + local cx, cy = rival.cellX + s[1], rival.cellY + s[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + U.log(("(%d, %d) is not free; standing on"):format(STAND.x, STAND.y), + cx, cy, "facing", s[3]) + U.teleport(game, "CHAMPIONS_ROOM", cx, cy, s[3]) + U.wait(10) + ow = game.overworld + for _, npc in ipairs(ow.npcs or {}) do + if npc.def and npc.def.name == "CHAMPIONSROOM_RIVAL" then + rival = npc + end + end + break + end + end + end + + -- run the tail of the script, from after the rival battle, so nobody has to + -- win OPP_RIVAL3 to hear this. Only rows before that point carry jump + -- targets, so the slice needs no reindexing -- assert that. + local from + for i, row in ipairs(rows) do + if row[1] == "show_text" + and row[2] == "_ChampionsRoomRivalAfterBattleText" then + from = i + break + end + end + check("found the post-battle row to start from", from ~= nil) + local slice, jumpy = {}, false + for i = from or 1, #rows do + local row = rows[i] + if row[1] == "jump" or row[1] == "jump_if_true" + or row[1] == "jump_if_false" then + jumpy = true + end + slice[#slice + 1] = row + end + check("the tail of the script has no jump targets to reindex", not jumpy) + + if failures > 0 then + U.log("stopping before the cutscene:", failures, + "check(s) failed above, so what you would hear means nothing") + while true do coroutine.yield() end + end + + -- watch the cues the script actually issues: on speakers a dedupe that ate + -- the restart and a fade that never fired sound like the same nothing + local realPlay, realFade = Music.play, Music.fadeOut + local cues, faded = {}, false + Music.play = function(data, song, loop, ctx) + cues[#cues + 1] = { song = song, tempo = ctx and ctx.tempo } + return realPlay(data, song, loop, ctx) + end + Music.fadeOut = function(control) + faded = true + return realFade(control) + end + + ow:queueScript(slice, { npc = rival }) + + -- --------------------------------------------------------------- + -- the walk out, over the rival's cell + -- --------------------------------------------------------------- + local startY = ow.player.cellY + local minY, sharedCell, walkShot = startY, false, false + local hof + for i = 1, 6000 do + local top = game.stack:top() + if getmetatable(top) == HallOfFame or (top and top.drawMonInfo) then + hof = top + break + end + local w = game.overworld + if w and w.map and w.map.id == "CHAMPIONS_ROOM" and rival then + local px, py = w.player.cellX, w.player.cellY + if py < minY then minY = py end + if px == rival.cellX and py == rival.cellY then + sharedCell = true + if not walkShot then + walkShot = U.shot(game, DIR .. "/bug847_over_rival.png") + end + end + end + if i % 6 == 0 then U.tap(game, "a") else U.wait(1) end + end + Music.play, Music.fadeOut = realPlay, realFade + + check("the battle theme was faded, not cut", faded) + local altCue + for _, c in ipairs(cues) do + if c.song == SONG and c.tempo == ALT_TEMPO then altCue = c end + end + check("Cities1 was restarted at the alternate tempo, not deduped away", + altCue ~= nil) + check("the player walked out of the room before the warp (#704)", + minY < startY) + -- CollisionCheckOnLand skips its checks while wSimulatedJoypadStatesIndex is + -- non-zero, so passing through (4,2) is the original behavior, not a clip + check("the scripted walk passed through the rival's cell (#847, not a bug)", + sharedCell) + check("walk-over screenshot", walkShot) + check("the induction started", hof ~= nil) + if not hof then + while true do coroutine.yield() end + end + + -- --------------------------------------------------------------- + -- the back-pic sweep ahead of the first front pic + -- --------------------------------------------------------------- + check("the induction opens on the back pic sweep, at the right edge", + hof.phase == "back" and (hof.scrollX or 0) > 96) + local sweepShot = false + for _ = 1, 400 do + if hof.phase ~= "back" then break end + if not sweepShot and (hof.scrollX or 0) <= 56 then + sweepShot = U.shot(game, DIR .. "/bug847_back_sweep.png") + end + U.wait(1) + end + check("back sweep screenshot", sweepShot) + check("the front pic phase follows the sweep, entering from the left", + hof.phase == "mons" and (hof.scrollX or 0) < 0) + for _ = 1, 200 do + if (hof.scrollX or 96) > 8 then break end + U.wait(1) + end + check("front scroll screenshot", U.shot(game, DIR .. "/bug847_front.png")) + for _ = 1, 400 do + if hof.phase == "mons" and (hof.scrollX or 0) >= 96 then break end + U.wait(1) + end + check("the front pic settles at hlcoord (12,5)", (hof.scrollX or 0) == 96) + + U.log(failures == 0 and "all checks passed" or ("FAILURES: " .. failures)) + U.log("input is yours now; the rest of the party and the player's own page") + U.log("follow on their own, so just watch and listen.") + U.log("after the rival's last line the battle music should fade out over") + U.log("about a second, go quiet for another second and a half, and then") + U.log("Pewter City comes back noticeably slower and heavier than it sounds") + U.log("in town -- and it stays that slow across the warp until the hall of") + U.log("fame theme takes over. For each mon a back sprite sweeps right to") + U.log("left low on the screen, then the front sprite slides in from the") + U.log("left and only cries once it stops.") + U.log("the near miss to listen for: Cities1 at its ordinary town tempo, or") + U.log("cutting in with no gap -- that is the old behavior, not the fix.") + U.log("the other near miss: a cry that fires while a sprite is still moving.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/credits_overhang_bug703_test.lua b/tests/drivers/credits_overhang_bug703_test.lua new file mode 100644 index 00000000..c2644dae --- /dev/null +++ b/tests/drivers/credits_overhang_bug703_test.lua @@ -0,0 +1,95 @@ +-- Real-time check of the credits roll vs Music_Credits (#703). +-- The song is a fixed 5880-frame program (audio/music/credits.asm: tempo 140, +-- no loop), so on hardware it outlasts THE END by ~12s. Our roll ran 135 +-- frames short of pokered because Credits:update skipped DisplayCreditsMon's +-- three CreditsCopyTileMapToVRAM calls (each `jp Delay3`, 9 frames per mon +-- screen, 15 mon screens); that stretched the overhang to ~14.3s and made the +-- music look too fast. This driver plays the whole roll in real time (~98s, +-- do NOT set POKEPORT_SPEED: the ear half needs the real clock), counts the +-- fixed frames itself, and leaves the screen on THE END with the theme still +-- going so a listener can judge the tail. +-- SHOT_DIR=/tmp/shots POKEPORT_DRIVER=tests/drivers/credits_overhang_bug703_test.lua POKEPORT_IDENTITY=bug703 POKEPORT_TOUCH=0 love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + if os.getenv("POKEPORT_SPEED") then + U.log("warning: POKEPORT_SPEED is set; the listening half of this run", + "is meaningless at any speed but 1") + end + local opts = game.save and game.save.options + if opts and opts.musicVolume == 0 then + U.log("warning: options music volume is 0, nothing will be audible") + end + + -- the roll ends in an autosave; put the user's save back afterwards + local prevSave = love.filesystem.read("save.lua") + + U.teleport(game, "HALL_OF_FAME", 4, 2, "right") + game.save.party = { { species = "PIKACHU", level = 81 } } + game.overworld.runner:run({ { "record_hall_of_fame" } }) + U.wait(2) + + -- A through the induction until the credits state is on top; the full + -- hall-of-fame walk takes well over 60 taps, so give it real room + local Credits = require("src.ui.Credits") + local credits + for _ = 1, 2000 do + local top = game.stack:top() + if getmetatable(top) == Credits then credits = top break end + U.tap(game, "a") + U.wait(2) + end + if not check("credits state reached", credits ~= nil) then + while true do coroutine.yield() end + end + + -- Frame accounting, one fixed step per sample. From the frame Music_Credits + -- starts (phase leaves "white") pokered reaches the end of THE END's fade at + -- 128 + 35 screens + 16 + 20 = 5154 frames; the 15 mon screens each spend + -- 9 frames in mon_prep (the Delay3 x3) before their 27-frame wipe. + -- no screenshots inside this loop: U.shot yields extra fixed steps of its + -- own and would silently skew the count + local musicStart, theEndAt, prepFrames = nil, nil, 0 + for f = 1, 7000 do + U.wait(1) + local phase = credits.phase + if not musicStart and phase ~= "white" then musicStart = f end + if phase == "mon_prep" then prepFrames = prepFrames + 1 end + if phase == "end_hold" then theEndAt = f break end + end + + check("mon_prep ran 9 frames on each of the 15 mon screens (135 total)", + prepFrames == 135) + check("THE END finishes fading 5154 frames after the music starts", + musicStart ~= nil and theEndAt ~= nil + and theEndAt - musicStart == 5154) + U.log("music started at driver frame", musicStart, + "THE END done at", theEndAt, "mon_prep frames", prepFrames) + U.shot(game, DIR .. "/bug703_the_end.png") + + -- Music_Credits is 5880 frames long, so from here the theme has + -- 5880 - 5154 = 726 frames (~12.1s) left. That overhang is authentic: + -- the original does the same on hardware, and this fix only removed the + -- extra 2.2s our shortened roll had added on top of it. + U.log("listen: the theme should keep playing about 12 seconds past this") + U.wait(726) + U.log("the song should be ending right about now; silence after this", + "point is correct, the program has no loop") + U.wait(120) + + if prevSave then + love.filesystem.write("save.lua", prevSave) + else + love.filesystem.remove("save.lua") + end + U.log("done; screen stays on THE END (A or B would soft-reset)") + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/dig_bug196_test.lua b/tests/drivers/dig_bug196_test.lua index 8bd6cf60..5bad0d6e 100644 --- a/tests/drivers/dig_bug196_test.lua +++ b/tests/drivers/dig_bug196_test.lua @@ -68,14 +68,10 @@ return function(game) U.wait(4) -- open the party menu and pick DIG on slot 1 - -- (submenu order for a DIG-only mon: STATS / SWITCH / DIG) + -- (submenu order for a DIG-only mon: DIG / STATS / SWITCH -- #768) Screens.push(game, "PartyMenu") U.wait(5) - U.tap(game, "a") -- open the per-mon submenu - U.wait(2) - U.tap(game, "down") -- STATS -> SWITCH - U.wait(2) - U.tap(game, "down") -- SWITCH -> DIG + U.tap(game, "a") -- open the per-mon submenu, cursor on DIG U.wait(2) U.tap(game, "a") -- choose DIG U.wait(2) diff --git a/tests/drivers/dojo_balls_bug853_test.lua b/tests/drivers/dojo_balls_bug853_test.lua new file mode 100644 index 00000000..211e1256 --- /dev/null +++ b/tests/drivers/dojo_balls_bug853_test.lua @@ -0,0 +1,209 @@ +-- Driver: Fighting Dojo prize balls, #853 (dex page first) and #854 (the +-- question stays on screen under YES/NO). pokered scripts/FightingDojo.asm +-- runs `ld a, HITMONLEE / call DisplayPokedex` before .Text, and .Text is a +-- text_end string printed with PrintText immediately followed by YesNoChoice. +-- No POKEPORT_SPEED here: the dex page and the YES/NO pop are what is judged. +-- SHOT_DIR=/tmp/shots POKEPORT_DRIVER=tests/drivers/dojo_balls_bug853_test.lua POKEPORT_IDENTITY=bug853 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local TextBox = require("src.render.TextBox") + local ChoiceBox = require("src.ui.ChoiceBox") + local DexEntryMenu = require("src.ui.DexEntryMenu") + local MapScripts = require("src.script.MapScripts") + local Screens = require("src.ui.Screens") + local OW = require("src.world.OverworldController") + local Pokemon = require("src.pokemon.Pokemon") + + -- pokered data/maps/objects/FightingDojo.asm: the two SPRITE_POKE_BALL + -- objects sit at (4, 1) HITMONLEE and (5, 1) HITMONCHAN, on the north wall + -- under the posters. The only approach is from the mat below them. + local MAP = "FIGHTING_DOJO" + local LEE = { name = "FIGHTINGDOJO_HITMONLEE_POKE_BALL", x = 4, y = 1 } + local CHAN = { name = "FIGHTINGDOJO_HITMONCHAN_POKE_BALL", x = 5, y = 1 } + local START = { x = 4, y = 4 } -- walk up from here to (4, 2), facing LEE + + local failures = {} + local function check(cond, msg) + if cond then U.log("PASS", msg) else + failures[#failures + 1] = msg + U.log("FAIL", msg) + end + return cond + end + + local function topIs(mt) return getmetatable(game.stack:top()) == mt end + local function under() + return game.stack.states[#game.stack.states - 1] + end + + local function npcByName(ow, name) + for _, n in ipairs(ow.npcs or {}) do + if n.def and n.def.name == name then return n end + end + end + + local function pageText() + local top = game.stack:top() + if getmetatable(top) ~= TextBox then return "" end + local page = top.pages and top.pages[top.pageIndex] + return page and table.concat(page, "\n") or "" + end + + local function waitFor(cond, cap) + for _ = 1, (cap or 200) do + if cond() then return true end + U.wait(2) + end + return cond() + end + + local function mashUntil(cond, cap) + for _ = 1, (cap or 100) do + if cond() then return true end + U.tap(game, "a") + U.wait(2) + end + return cond() + end + + -- fresh dojo with the master already beaten and neither prize taken + local function seed(x, y, facing) + while game.stack:top() do game.stack:pop() end + game.save.flags = { + EVENT_BEAT_KARATE_MASTER = true, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_0 = true, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_1 = true, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_2 = true, + EVENT_BEAT_FIGHTING_DOJO_TRAINER_3 = true, + } + game.save.defeatedTrainers = { FIGHTING_DOJO_obj_1 = true } + game.save.objectToggles = {} + game.save.player.name = game.save.player.name or "RED" + -- one mon so give_pokemon has a party to append to, and room for a prize + game.save.party = { Pokemon.new(game.data, "CHARIZARD", 50) } + game.stack:push(OW, MAP, x, y, facing or "up") + U.wait(10) + return game.stack:top() + end + + local ow = seed(START.x, START.y, "up") + + ------------------------------------------------------------------ + -- machine-checkable half: seed, objects, script rows, text, screen id + ------------------------------------------------------------------ + check(game.save.flags.EVENT_BEAT_KARATE_MASTER == true, + "EVENT_BEAT_KARATE_MASTER is set (the balls answer at all)") + check(not game.save.flags.EVENT_GOT_HITMONLEE + and not game.save.flags.EVENT_GOT_HITMONCHAN, + "neither prize taken yet (no 'greedy' refusal path)") + + local leeBall, chanBall = npcByName(ow, LEE.name), npcByName(ow, CHAN.name) + check(leeBall ~= nil, "HITMONLEE ball object loaded") + check(chanBall ~= nil, "HITMONCHAN ball object loaded") + check(leeBall and leeBall.cellX == LEE.x and leeBall.cellY == LEE.y, + ("HITMONLEE ball sits at the asm cell (%d, %d)"):format(LEE.x, LEE.y)) + check(chanBall and chanBall.cellX == CHAN.x and chanBall.cellY == CHAN.y, + ("HITMONCHAN ball sits at the asm cell (%d, %d)"):format(CHAN.x, CHAN.y)) + + check(type(MapScripts.talkScript(MAP, "TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL")) + == "function", + "TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL has a hand-ported talk script") + check(type(MapScripts.talkScript(MAP, "TEXT_FIGHTINGDOJO_HITMONCHAN_POKE_BALL")) + == "function", + "TEXT_FIGHTINGDOJO_HITMONCHAN_POKE_BALL has a hand-ported talk script") + + -- the ask() string is the extracted descriptor, not the "You want X?" stub + local leeText = game.data.text._FightingDojoHitmonleePokeBallText + local chanText = game.data.text._FightingDojoHitmonchanPokeBallText + check(type(leeText) == "string" and leeText ~= "", + "_FightingDojoHitmonleePokeBallText resolves") + check(type(chanText) == "string" and chanText ~= "", + "_FightingDojoHitmonchanPokeBallText resolves") + if type(leeText) == "string" then + U.log("lee prompt reads:", (leeText:gsub("\n", " / "))) + end + local dexOk = pcall(Screens.get, game, "DexEntryMenu") + check(dexOk, "DexEntryMenu resolves through the Screens registry") + + ------------------------------------------------------------------ + -- rehearsal on the HITMONCHAN ball, answered NO so nothing is consumed + ------------------------------------------------------------------ + if chanBall then + ow:talkTo(chanBall) + check(waitFor(function() return topIs(DexEntryMenu) end, 60), + "#853: the ball opens the HITMONCHAN dex page before any question") + U.shot(game, DIR .. "/dojo_balls_1_dex.png") + U.tap(game, "b") + check(waitFor(function() return topIs(TextBox) end, 60), + "#853: closing the dex page leads into the offer text") + mashUntil(function() return topIs(ChoiceBox) end, 60) + check(topIs(ChoiceBox), "#854: the YES/NO menu opens on the offer") + check(getmetatable(under()) == TextBox, + "#854: the question box is still on the stack under the YES/NO menu") + U.shot(game, DIR .. "/dojo_balls_2_choice.png") + U.tap(game, "b") -- B answers NO; the prize stays unclaimed + waitFor(function() return game.stack:top() == ow end, 120) + check(not game.save.flags.EVENT_GOT_HITMONCHAN, + "answering NO leaves the HITMONCHAN prize unclaimed") + check(#game.save.party == 1, "answering NO adds nothing to the party") + end + + ------------------------------------------------------------------ + -- hand-off: walk to the HITMONLEE ball and open it for real + ------------------------------------------------------------------ + ow = seed(START.x, START.y, "up") + for _ = 1, 12 do + if ow.player.cellY <= LEE.y + 1 then break end + U.hold(game, "up", 16) + U.wait(4) + end + + local function facingTheBall() + local cur = game.overworld + local ball = cur and npcByName(cur, LEE.name) + if not ball then return false end + local fx, fy = cur.player:facingCell() + return cur:npcAtCell(fx, fy) == ball + end + + if not facingTheBall() then + -- a map edit or a mod moved the ball: stand on any free walkable + -- neighbour instead. {dx, dy, facing} is the offset from the ball to + -- the stand cell plus the direction that looks back at it. + local sides = { + { 0, 1, "up" }, { 1, 0, "left" }, { -1, 0, "right" }, { 0, -1, "down" }, + } + for _, s in ipairs(sides) do + local cx, cy = LEE.x + s[1], LEE.y + s[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + U.log("walk up stopped short; standing on", cx, cy, "facing", s[3]) + ow = seed(cx, cy, s[3]) + break + end + end + end + check(facingTheBall(), "player is standing against the HITMONLEE ball") + + U.tap(game, "a") + check(waitFor(function() return topIs(DexEntryMenu) end, 60), + "#853: pressing A opens the HITMONLEE dex page") + U.shot(game, DIR .. "/dojo_balls_3_handoff.png") + + if #failures == 0 then + U.log("all checks passed") + else + U.log(("%d check(s) failed:"):format(#failures), table.concat(failures, "; ")) + end + + U.log("On screen now: the HITMONLEE dex page the ball opened, name and") + U.log("sprite only, since the mon is seen but not owned yet. Press B: the") + U.log("offer types out, and the YES/NO menu should appear above it with the") + U.log("question still readable -- the old bug swapped the text away for a") + U.log("bare YES/NO over the overworld. Answer YES to take HITMONLEE; the") + U.log("HITMONCHAN ball beside it stays put and gives the greedy refusal.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/evolution_black_bug279_test.lua b/tests/drivers/evolution_black_bug279_test.lua index 0d26a32b..8d9ca4e5 100644 --- a/tests/drivers/evolution_black_bug279_test.lua +++ b/tests/drivers/evolution_black_bug279_test.lua @@ -248,7 +248,7 @@ return function(game) -- ---- case 2: B-cancel settles on the OLD form's colours ----------------- local karp = startEvo("MAGIKARP", "GYARADOS") - U.wait(24) + U.wait(100) -- past the 80-frame pre-animLoop delay before the poll starts U.hold(game, "b", 20) -- evolution.asm Evolution_CheckForCancel if not waitFor(function() return findText("stopped evolving") ~= nil end, 240) then check(false, "holding B prints \"stopped evolving\"") diff --git a/tests/drivers/evolution_cancel_bug213_test.lua b/tests/drivers/evolution_cancel_bug213_test.lua index b80ca940..46dbc006 100644 --- a/tests/drivers/evolution_cancel_bug213_test.lua +++ b/tests/drivers/evolution_cancel_bug213_test.lua @@ -1,13 +1,14 @@ -- Driver: cancel an evolution with the B button (#213). -- --- pokered engine/pokemon/evos_moves.asm polls hJoyHeld during the pic --- flash: holding B aborts the evolution (the mon keeps its species and +-- pokered engine/movie/evolution.asm polls hJoy5 during the pic flash: a +-- fresh B press aborts the evolution (the mon keeps its species and -- _StoppedEvolvingText prints). Trade evolutions (wLinkState == -- LINK_STATE_TRADING) skip that poll and cannot be cancelled. -- --- Case 1 (level path, cancelable): open EvolutionState directly, wait a --- few frames into the flash (t well under FLASH_FRAMES=220), hold B, and --- assert the mon stays CATERPIE with "stopped evolving" text on screen. +-- Case 1 (level path, cancelable): open EvolutionState directly, wait past +-- the 80-frame pre-animLoop delay (still well under FLASH_FRAMES=220), +-- press B, and assert the mon stays CATERPIE with the "stopped evolving" +-- text on screen. -- Case 1b: after cancel, checkParty with no level-ups must not re-offer; -- a subsequent level-up set must offer again (EvolveAfterBattle parity). -- Case 2 (control): let the flash run to completion with no input and @@ -80,11 +81,11 @@ return function(game) Evolution.evolve(game, mon, "METAPOD", function() done1 = true end) if not waitFor(evoTop, 300) then error("EvolutionState never opened (case1)") end - U.wait(20) -- into the flash, well under FLASH_FRAMES=220 + U.wait(100) -- past the 80-frame pre-animLoop delay, still under 220 U.log("case1 flash", "t=", top().t, "species=", mon.species) U.shot(game, DIR .. "/evo213_1_evolving.png") - U.hold(game, "b", 20) -- Gen1 hJoyHeld B-cancel + U.hold(game, "b", 20) -- Gen1 hJoy5 B-cancel -- the flash aborts: EvolutionState is no longer the top (the stopped -- text overlays it and then pops it) @@ -118,7 +119,7 @@ return function(game) if not waitFor(evoTop, 300) then error("EvolutionState never opened after level-up re-offer") end - U.wait(20) + U.wait(100) -- past the same 80-frame pre-animLoop delay U.hold(game, "b", 20) -- cancel so case 2 stays independent if not waitFor(function() return not evoTop() end, 240) then error("level-up re-offer did not abort on B") diff --git a/tests/drivers/export_sav_bug889_test.lua b/tests/drivers/export_sav_bug889_test.lua new file mode 100644 index 00000000..f258f318 --- /dev/null +++ b/tests/drivers/export_sav_bug889_test.lua @@ -0,0 +1,51 @@ +-- Driver (#889): export a .sav from a save that never came from a ROM import, +-- the case that used to write a save with no map context at all -- no map +-- header, no tileset header, sound id 0 -- so a real Game Boy continued into a +-- garbled map and hung on a white screen. +-- +-- Saves the game in two places (an interior and an outdoor map with +-- connections), exports each, and prints the bytes the engine reads back on +-- Continue so a human can eyeball them, plus the export path to load in an +-- emulator. +return function(game) + local U = dofile("tests/drivers/util.lua") + local SaveFileIO = require("src.import.SaveFileIO") + local SaveData = require("src.core.SaveData") + local GameVersion = require("src.core.GameVersion") + + local version = GameVersion.get() + local spots = { + { "REDS_HOUSE_2F", 3, 6 }, + { "PALLET_TOWN", 5, 6 }, + } + + for _, spot in ipairs(spots) do + local map, x, y = spot[1], spot[2], spot[3] + U.teleport(game, map, x, y, "down") + U.wait(30) + -- the same sync the in-game SAVE does before writing (OverworldState: + -- captureSave), so the slot on disk holds the position we just walked to + local top = game.stack:top() + if top and top.captureSave then top:captureSave(game.save) end + SaveData.save(game.save) + local ok, pathOrErr = SaveFileIO.exportActiveSlot(version) + if not ok then + U.log(("export_sav_bug889: %s FAILED: %s"):format(map, tostring(pathOrErr))) + else + local bytes = love.filesystem.read( + ("exports/%s/gen1recomp-%s-%s.sav"):format( + version, version, SaveData.activeSlot(version) or "save")) + local main = 0x2598 + 11 + local function u8(off) return bytes:byte(main + off + 1) end + local hdr = {} + for i = 0, 9 do hdr[#hdr + 1] = ("%02X"):format(u8(112 + i)) end + U.log(("export_sav_bug889: %s -> %s"):format(map, pathOrErr)) + U.log((" wCurMap=%02X wCurMapHeader=%s music=%02X/%02X tilesetBank=%02X"): + format(u8(103), table.concat(hdr, " "), u8(100), u8(101), u8(564))) + end + end + + U.log("export_sav_bug889: done") + love.event.quit() + while true do coroutine.yield() end +end diff --git a/tests/drivers/faithful_res_mobile_veil_bug864_test.lua b/tests/drivers/faithful_res_mobile_veil_bug864_test.lua new file mode 100644 index 00000000..d176de08 --- /dev/null +++ b/tests/drivers/faithful_res_mobile_veil_bug864_test.lua @@ -0,0 +1,296 @@ +-- Eye check: FAITHFUL RATIO's mobile scale lock keeps the display outside the +-- locked 160x144 viewport black through the pre-battle flash (pokered +-- BattleTransition_FlashScreen_, engine/battle/battle_transitions.asm), the +-- post-battle fade (GBFadeInFromWhite, home/fade.asm) and Oak speech (#864). +-- POKEPORT_DRIVER=tests/drivers/faithful_res_mobile_veil_bug864_test.lua POKEPORT_FORCE_MOBILE=1 POKEPORT_IDENTITY=bug864 POKEPORT_TOUCH=0 POKEPORT_VERSION=red SHOT_DIR=/tmp/shots love . +-- No POKEPORT_SPEED anywhere: the flash and the fade ARE the frames under test. +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Renderer = require("src.render.Renderer") + local FaithfulRes = require("src.core.FaithfulRes") + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- FaithfulRes.isMobile reads the env per call, so the desktop build only + -- takes the scale-cap branch when the launcher command set it. Without it + -- every check below tests the ordinary letterbox and proves nothing. + if not check("POKEPORT_FORCE_MOBILE=1 is set (the branch under test)", + os.getenv("POKEPORT_FORCE_MOBILE") == "1") then + U.log("Re-run with POKEPORT_FORCE_MOBILE=1; nothing below is meaningful.") + while true do coroutine.yield() end + end + + -- A phone-shaped window, so the locked viewport (160x144 at the largest + -- whole multiple, here 3x = 480x432) leaves tall bars above and below -- + -- the "dead display" FaithfulRes.lua's contract says must stay black. + -- 480x960 keeps the width an exact 3x so the bars are purely vertical. + if love.window and love.window.setMode then + love.window.setMode(480, 960, { resizable = true, + minwidth = FaithfulRes.MIN_W, + minheight = FaithfulRes.MIN_H }) + end + U.wait(3) + + -- New Game replaces game.save (and Game:applyOptions re-applies its fresh + -- options, which releases the lock), so re-arm before every shot rather + -- than trusting one application to survive the whole run. + local function lock() + game.save.options = game.save.options or {} + game.save.options.faithfulRes = 1 + game.save.options.battleBg = "black" + FaithfulRes.applyOptions(game.save.options) + return FaithfulRes.scaleCap() + end + check("the mobile scale lock engaged (FaithfulRes.scaleCap ~= nil)", + lock() ~= nil) + + local opts = game.save.options + if (opts.musicVol or 0) == 0 or (opts.sfxVol or 0) == 0 then + U.log("WARN music/sfx volume is zero; the flash's battle theme will be silent") + end + + -- Load a captured PNG back as ImageData; love.image cannot read absolute + -- paths, so go through io.open + newFileData. + local function loadShot(path) + local f = io.open(path, "rb") + if not f then return nil end + local bytes = f:read("*a") + f:close() + local ok, img = pcall(function() + return love.image.newImageData( + love.filesystem.newFileData(bytes, "shot.png")) + end) + return ok and img or nil + end + + local function regionMean(img, x, y, w, h) + local sum, n = 0, 0 + local x2 = math.min(x + w, img:getWidth()) - 1 + local y2 = math.min(y + h, img:getHeight()) - 1 + for yy = math.max(y, 0), y2 do + for xx = math.max(x, 0), x2 do + local r, g, b = img:getPixel(xx, yy) + sum = sum + (r + g + b) / 3 + n = n + 1 + end + end + return n > 0 and sum / n or 0, n + end + + -- The locked viewport in framebuffer pixels: the same uiSize * fitScale + -- centring endFrame uses for ox/oy/vpw/vph, which is the rectangle the + -- veil is clamped to under the lock. Screenshots are framebuffer-sized, + -- so no dpi divide. + local function viewBox(img) + local pw, ph = img:getWidth(), img:getHeight() + local uiw, uih = Renderer:uiSize() + local S = Renderer:fitScale() + local bw, bh = uiw * S, uih * S + return math.floor((pw - bw) / 2), math.floor((ph - bh) / 2), bw, bh, pw, ph + end + + -- Mean over every bar strip the window has (top/bottom always here, + -- left/right only if the width is not an exact multiple). Inset by 2px so + -- the viewport's own edge pixels cannot bleed into the bar sample. + local function barMean(img) + local bx, by, bw, bh, pw, ph = viewBox(img) + local sum, n = 0, 0 + local function add(x, y, w, h) + if w < 1 or h < 1 then return end + local m, c = regionMean(img, x, y, w, h) + sum, n = sum + m * c, n + c + end + if by >= 8 then + add(0, 0, pw, by - 2) + add(0, by + bh + 2, pw, ph - (by + bh) - 2) + end + if bx >= 8 then + add(0, by, bx - 2, bh) + add(bx + bw + 2, by, bx - 2, bh) + end + return n > 0 and sum / n or -1, n + end + + local function innerMean(img) + local bx, by, bw, bh = viewBox(img) + return regionMean(img, bx + math.floor(bw / 4), by + math.floor(bh / 4), + math.floor(bw / 2), math.floor(bh / 2)) + end + + -- shot + the two-sided assertion every moment shares: bars dead black, + -- viewport interior at least `bright` (the effect visibly inside the frame) + local function shotAndCheck(name, bright) + check("lock still held at " .. name, lock() ~= nil) + local path = DIR .. "/bug864_" .. name .. ".png" + U.shot(game, path) + local img = loadShot(path) + if not check(name .. " shot decoded", img ~= nil) then return nil end + local bars, n = barMean(img) + local inner = innerMean(img) + U.log((" %s: bar mean %.3f over %d px, viewport interior %.3f") + :format(name, bars, n, inner)) + check(name .. ": window has bars to sample", n > 0) + check(name .. ": bars stay dead black (#864)", n > 0 and bars < 0.05) + check(name .. ": the effect still lights the viewport", inner > bright) + return img + end + + -- ---- (3rd symptom first: it is where a boot starts) New Game ----------- + -- OakSpeech sets letterboxWhite; before #864 that painted the WHOLE phone + -- paper white, leaving the locked frame indistinguishable from its bars. + U.wait(5) + U.tap(game, "start") -- skip intro movie + U.wait(10) + U.tap(game, "a") -- title -> menu + U.wait(5) + U.tap(game, "a") -- NEW GAME (POKEPORT_IDENTITY=bug864 has no save) + local oak + for _ = 1, 300 do + for i = #game.stack.states, 1, -1 do + local s = game.stack.states[i] + if s and s.letterboxWhite then oak = s break end + end + if oak then break end + U.tap(game, "a") + U.wait(2) + end + check("Oak speech reached (a letterboxWhite state is on the stack)", + oak ~= nil) + U.wait(40) -- let Oak's pic and a line of text land inside the frame + shotAndCheck("oakspeech", 0.5) + + -- mash through the rest of the speech into the overworld; the naming + -- screens and the closing shrink-away beat (~103 unskippable frames) eat + -- most of this, so the headroom is generous on purpose + for _ = 1, 900 do + U.tap(game, "a") + U.wait(2) + if game.overworld and game.stack:top() == game.overworld then break end + end + check("New Game landed in the overworld", + game.overworld ~= nil and game.stack:top() == game.overworld) + + -- ---- the pre-battle flash ---------------------------------------------- + -- pokered data/maps/objects/Route1.asm puts its youngsters at (5,24) and + -- (15,13) and the sign at (9,27), so the top of the road is empty; the + -- battle is pushed straight in, the cell is only somewhere to stand. + local MAP = "ROUTE_1" + local STAND = { x = 5, y = 6, facing = "down" } + + game.save.party = { Pokemon.new(game.data, "BULBASAUR", 12) } + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(10) + local ow = game.overworld + check("the overworld is up on " .. MAP, ow ~= nil and ow.map.id == MAP) + if ow and not ow.map:isWalkableCell(STAND.x, STAND.y) then + -- a map edit moved the road: any free neighbour serves, the cell is not + -- itself under test + for _, d in ipairs({ {0,1}, {0,-1}, {1,0}, {-1,0} }) do + local cx, cy = STAND.x + d[1], STAND.y + d[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + U.log(("stand cell (%d, %d) blocked, using (%d, %d)") + :format(STAND.x, STAND.y, cx, cy)) + U.teleport(game, MAP, cx, cy, STAND.facing) + U.wait(10) + ow = game.overworld + break + end + end + end + + U.shot(game, DIR .. "/bug864_route1_base.png") -- unlit reference frame + lock() + + -- wild, weaker (L5 vs the L12 lead), not a dungeon map: the 3-bit select + -- (battle_transitions.asm) lands on %000 doublecircle, one of the two + -- wipes that call BattleTransition_FlashScreen first + local battle = BattleState.newWild(game, "RATTATA", 5) + ow:pushBattle(battle) + local trans = game.stack:top() + check("the transition is a flashing wipe (doublecircle)", + trans ~= nil and trans.def ~= nil and trans.def.flash == true) + + -- screenVeil is written during draw and cleared at beginFrame, so at + -- update time it holds the LAST rendered frame's veil; catch the white + -- peak (shade 1, near-full alpha) and shoot the very next frames while + -- the 2-frame palette holds keep it bright + local caught = false + for _ = 1, 400 do + local v = game.renderer and game.renderer.screenVeil + if v and v[1] == 1 and v[2] >= 0.9 then caught = true break end + U.wait(1) + end + check("caught the flash at its white peak", caught) + local flashImg = shotAndCheck("flash", 0.5) + local baseImg = loadShot(DIR .. "/bug864_route1_base.png") + if flashImg and baseImg then + local lit, plain = innerMean(flashImg), innerMean(baseImg) + U.log((" viewport interior %.3f unlit -> %.3f mid-flash") + :format(plain, lit)) + check("the flash visibly brightens the viewport over the base frame", + lit > plain + 0.15) + end + + -- ---- the post-battle fade in from white -------------------------------- + for _ = 1, 600 do + if game.stack:top() == battle then break end + U.wait(1) + end + check("the battle reached the screen", game.stack:top() == battle) + for _ = 1, 200 do + if battle.phase == "menu" then break end + U.tap(game, "a") + U.wait(6) + end + check("the battle reached its FIGHT/PKMN/ITEM/RUN menu", + battle.phase == "menu") + + -- run away (down+right lands on RUN from anywhere in the 2x2 grid; the + -- L12 lead outspeeds the L5 wild mon, so the escape always succeeds), + -- then watch for BattleReturn's white veil -- battle over, shade 1, + -- alpha 1 through its hold frames + local sawReturn = false + for _ = 1, 1500 do + local top = game.stack:top() + local v = game.renderer and game.renderer.screenVeil + if top ~= battle and v and v[1] == 1 and v[2] >= 0.9 then + sawReturn = true + break + end + if top == battle then + if battle.phase == "menu" then + U.tap(game, "down") + U.wait(1) + U.tap(game, "right") + U.wait(1) + U.tap(game, "a") + U.wait(2) + else + U.tap(game, "a") + U.wait(3) + end + else + U.wait(1) + end + end + check("caught the post-battle fade in from white", sawReturn) + shotAndCheck("return", 0.8) + + -- ---- over to you -------------------------------------------------------- + U.log("You are back on Route 1 with the picture locked to a 480x432 frame in") + U.log("the middle of a tall window. Open the three shots in " .. DIR .. ":") + U.log("bug864_oakspeech, bug864_flash and bug864_return should each show a lit") + U.log("frame -- paper, white flash, white fade -- with dead-black bars above") + U.log("and below. Before #864 the white spilled over the whole window and the") + U.log("frame had no edge at all. Walking into grass here replays the flash live.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/fighting_dojo_bug197_test.lua b/tests/drivers/fighting_dojo_bug197_test.lua index ce529e1d..a234b4f9 100644 --- a/tests/drivers/fighting_dojo_bug197_test.lua +++ b/tests/drivers/fighting_dojo_bug197_test.lua @@ -3,7 +3,8 @@ -- BUG1 gate -- the master stops the player on the tile to his left -- BUG2 no speech -- no won text + no prize dialogue after the win -- BUG3 wrong re-talk -- shows the pre-battle challenge, not the after line --- BUG4 (verify) -- the ball ask() is the Gen1 descriptor, not a dex entry +-- BUG4 (verify) -- the ball ask() is the Gen1 descriptor, shown after +-- the species' dex entry (DisplayPokedex) -- BUG5 both balls -- the chosen ball AND the other one both vanish; the -- other should stay and give the "greedy" refusal -- BUG6 poster -- the north-wall posters ("Enemies on every side!") are @@ -163,8 +164,9 @@ return function(game) mashUntil(function() return game.stack:top() == ow end) ------------------------------------------------------------------ - -- BUG4 (verify-only): the Hitmonlee ball prompt is the Gen1 descriptor - -- ("You want the hard kicking HITMONLEE?"), not a Pokedex entry screen. + -- BUG4 (verify-only): the Hitmonlee ball shows the species' Pokedex + -- entry first (DisplayPokedex, #853), then the Gen1 descriptor prompt + -- ("You want the hard kicking HITMONLEE?"). ------------------------------------------------------------------ ow = resetDojo(4, 2, "up", { EVENT_BEAT_KARATE_MASTER = true }) local leeBall = npcByName(ow, "FIGHTINGDOJO_HITMONLEE_POKE_BALL") @@ -173,7 +175,7 @@ return function(game) if leeBall then ow:talkTo(leeBall) check(sawText("hard kicking") or sawText("HITMONLEE"), - "BUG4: ball asks the Gen1 descriptor prompt (no dex entry)") + "BUG4: ball asks the Gen1 descriptor prompt after the dex entry") U.shot(game, DIR .. "/dojo_4_prompt.png") ------------------------------------------------------------------ -- BUG5: choose YES -> only the chosen ball vanishes; the other stays diff --git a/tests/drivers/fly_indigo_bug203_test.lua b/tests/drivers/fly_indigo_bug203_test.lua index ba4b79c1..a9a02004 100644 --- a/tests/drivers/fly_indigo_bug203_test.lua +++ b/tests/drivers/fly_indigo_bug203_test.lua @@ -29,8 +29,8 @@ return function(game) } -- Exercise the real visited-marking path (OverworldController marks any - -- flyWarps map visited on entry) by standing on the Plateau exterior first, - -- then hop back to Pallet for a clean starting point. + -- flyWarps TOWN visited on entry, Map.isFlyTown) by standing on the Plateau + -- exterior first, then hop back to Pallet for a clean starting point. U.teleport(game, "INDIGO_PLATEAU", 9, 6, "down") U.wait(5) assert(game.save.visited.INDIGO_PLATEAU, @@ -38,14 +38,11 @@ return function(game) U.teleport(game, "PALLET_TOWN", 10, 8, "down") U.wait(5) - -- open the party menu and pick FLY on slot 1 (submenu: STATS / SWITCH / FLY) + -- open the party menu and pick FLY on slot 1 (submenu: FLY / STATS / SWITCH, + -- field moves on top like start_sub_menus.asm -- #768) Screens.push(game, "PartyMenu") U.wait(5) - U.tap(game, "a") -- open the per-mon submenu - U.wait(2) - U.tap(game, "down") -- STATS -> SWITCH - U.wait(2) - U.tap(game, "down") -- SWITCH -> FLY + U.tap(game, "a") -- open the per-mon submenu, cursor on FLY U.wait(2) U.tap(game, "a") -- choose FLY U.wait(5) diff --git a/tests/drivers/fly_townmap_bug195_test.lua b/tests/drivers/fly_townmap_bug195_test.lua index 1c12b228..cbbf7b92 100644 --- a/tests/drivers/fly_townmap_bug195_test.lua +++ b/tests/drivers/fly_townmap_bug195_test.lua @@ -30,14 +30,11 @@ return function(game) U.teleport(game, "PALLET_TOWN", 10, 8, "down") U.wait(5) - -- open the party menu and pick FLY on slot 1 (submenu: STATS / SWITCH / FLY) + -- open the party menu and pick FLY on slot 1 (submenu: FLY / STATS / SWITCH, + -- field moves on top like start_sub_menus.asm -- #768) Screens.push(game, "PartyMenu") U.wait(5) - U.tap(game, "a") -- open the per-mon submenu - U.wait(2) - U.tap(game, "down") -- STATS -> SWITCH - U.wait(2) - U.tap(game, "down") -- SWITCH -> FLY + U.tap(game, "a") -- open the per-mon submenu, cursor on FLY U.wait(2) U.tap(game, "a") -- choose FLY U.wait(5) diff --git a/tests/drivers/game_corner_grunt_bug862_test.lua b/tests/drivers/game_corner_grunt_bug862_test.lua new file mode 100644 index 00000000..3c00bcfa --- /dev/null +++ b/tests/drivers/game_corner_grunt_bug862_test.lua @@ -0,0 +1,321 @@ +-- Driver: #862 Celadon Game Corner poster grunt, loss line + exit walk. +-- GameCornerRocketText saves _GameCornerRocketBattleEndText ("Dang!") for +-- PrintEndBattleText, and GameCornerRocketBattleScript picks the exit walk +-- from the player's cell (pokered/scripts/GameCorner.asm:54-102): east of +-- him it is WalkAroundPlayer, DOWN/R/R/UP/R/R/R/R, never UP into the poster. +-- No POKEPORT_SPEED: the walk and the battle text are what is under test. +-- SHOT_DIR=/tmp/shots POKEPORT_IDENTITY=bug862 POKEPORT_TOUCH=0 \ +-- POKEPORT_DRIVER=tests/drivers/game_corner_grunt_bug862_test.lua love . + +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + os.execute("mkdir -p " .. DIR) + + local Pokemon = require("src.pokemon.Pokemon") + local TextBox = require("src.render.TextBox") + local BattleState = require("src.battle.BattleState") + + local pass, fail = 0, 0 + local function check(label, ok, detail) + if ok then pass = pass + 1 else fail = fail + 1 end + U.log(ok and "PASS" or "FAIL", label, detail or "") + return ok + end + + -- pokered/data/maps/objects/GameCorner.asm:36 -- the grunt is + -- object_event 9, 5, SPRITE_ROCKET, STAY, UP, facing the poster bg_event + -- at (9,4), which is wall. Standing east of him on (10,5) is the branch + -- that matters: wYCoord ~= 6 and wXCoord ~= 8, so the script takes + -- GameCornerMovement_Rocket_WalkAroundPlayer. + local MAP = "GAME_CORNER" + local NAME = "GAMECORNER_ROCKET" + local GX, GY = 9, 5 + local STAND = { x = 10, y = 5, facing = "left" } + local POSTER = { x = 9, y = 4 } + -- DOWN, RIGHT, RIGHT, UP, RIGHT x4 from (9,5), ending on (15,5) + local AROUND = { + { 9, 6 }, { 10, 6 }, { 11, 6 }, { 11, 5 }, + { 12, 5 }, { 13, 5 }, { 14, 5 }, { 15, 5 }, + } + + -- clean slate: he must not read as already defeated or already hidden + game.save.defeatedTrainers = {} + game.save.objectToggles = game.save.objectToggles or {} + game.save.objectToggles.GAME_CORNER = nil + game.save.player = game.save.player or {} + game.save.player.name = game.save.player.name or "RED" + game.save.money = game.save.money or 3000 + + -- a tank that one-shots OPP_ROCKET #7, so the mash win below is quick and + -- the same every run whatever the type matchups are + local tank = Pokemon.new(game.data, "MEWTWO", 100) + tank.moves = { + { id = "PSYCHIC_M", pp = 99 }, + { id = "THUNDERBOLT", pp = 99 }, + { id = "ICE_BEAM", pp = 99 }, + { id = "EARTHQUAKE", pp = 99 }, + } + game.save.party = { tank } + + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + local ow = game.overworld + + local function findGrunt() + for _, n in ipairs(ow.npcs or {}) do + if n.def and n.def.name == NAME then return n end + end + return nil + end + + local grunt = findGrunt() + check("GAMECORNER_ROCKET is on the floor", grunt ~= nil) + if grunt then + check("he stands on (9,5)", grunt.cellX == GX and grunt.cellY == GY, + ("at (%d,%d)"):format(grunt.cellX, grunt.cellY)) + end + + -- a map edit or a mod could take (10,5) away; anything east of him keeps + -- the WalkAroundPlayer branch, so fall back to a free walkable neighbour + -- and say which branch that lands on + local function facingGrunt() + local g = findGrunt() + if not g then return false end + local fx, fy = ow.player:facingCell() + return ow:npcAtCell(fx, fy) == g + end + if grunt and not facingGrunt() then + local sides = { + { 1, 0, "left" }, { 0, 1, "up" }, { -1, 0, "right" }, { 0, -1, "down" }, + } + for _, s in ipairs(sides) do + local cx, cy = grunt.cellX + s[1], grunt.cellY + s[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + U.log(("(%d,%d) is blocked; standing on"):format(STAND.x, STAND.y), + cx, cy, "facing", s[3]) + U.teleport(game, MAP, cx, cy, s[3]) + ow = game.overworld + grunt = findGrunt() + break + end + end + end + check("the player is face to face with him", facingGrunt()) + local px, py = ow.player.cellX, ow.player.cellY + local around = not (py == 6 or px == 8) + U.log(("talking from (%d,%d): the script should take %s"):format( + px, py, around and "WalkAroundPlayer (down, right, right, up, " + .. "right x4)" or "WalkDirect (right x5)")) + + -- the two strings the fix depends on, and the poster cell the pre-fix + -- single UP step walked him into + local t = game.data.text + check("_GameCornerRocketBattleEndText resolves", + type(t._GameCornerRocketBattleEndText) == "string" + and t._GameCornerRocketBattleEndText ~= "", + tostring(t._GameCornerRocketBattleEndText)) + check("_GameCornerRocketAfterBattleText resolves", + type(t._GameCornerRocketAfterBattleText) == "string" + and t._GameCornerRocketAfterBattleText ~= "") + check("(9,4) is the poster wall, not a cell he can stand on", + not ow.map:isWalkableCell(POSTER.x, POSTER.y)) + + -- engageTrainer has to accept the script-supplied loss line; a stale + -- two-parameter copy would silently drop it and print nothing + local info = debug.getinfo(ow.engageTrainer, "S") + local sigOk = false + if info and info.short_src then + local src = io.open((info.short_src:gsub("^@", "")), "r") + if src then + local n = 0 + for line in src:lines() do + n = n + 1 + if n == info.linedefined then + sigOk = line:find("endBattleText", 1, true) ~= nil + break + end + end + src:close() + end + end + check("engageTrainer takes an endBattleText argument", sigOk) + + U.shot(game, DIR .. "/bug862_0_before.png") + + -- Talk and mash to a win, recording every battle message in order and + -- pausing on the loss line long enough to photograph it. + local said, battle = {}, nil + local lastSaid, dangShot = nil, false + local function sample() + local top = game.stack:top() + if getmetatable(top) == BattleState then + battle = battle or top + local cur = top.current + local text = type(cur) == "table" and cur.text + if type(text) == "string" and text ~= lastSaid then + lastSaid = text + said[#said + 1] = text + U.log("battle says:", (text:gsub("\n", " "))) + end + end + end + + local function pageText() + local top = game.stack:top() + if getmetatable(top) ~= TextBox then return "" end + local parts = {} + for _, page in ipairs(top.pages or {}) do + if type(page) == "table" then + for _, line in ipairs(page) do parts[#parts + 1] = tostring(line) end + end + end + return table.concat(parts, " ") + end + + local function idle() + return game.stack:top() == ow and not ow.runner:isRunning() + and #ow.scriptMoves == 0 and not ow.transitioning + end + + U.tap(game, "a") + local sawAfter = false + for f = 1, 4000 do + sample() + if pageText():find("hideout", 1, true) then sawAfter = true break end + local top = game.stack:top() + if lastSaid and lastSaid:find("Dang", 1, true) and not dangShot then + -- stop mashing for a moment: the loss line is on the battle screen. + -- The row is picked up the frame it starts typing, so let it finish + -- before the capture or the shot is one letter wide. + dangShot = true + U.wait(60) + U.shot(game, DIR .. "/bug862_1_dang.png") + elseif top and top.phase then + if top.phase == "menu" then top.menuIndex = 1 + elseif top.phase == "moveSelect" then top.moveIndex = 1 end + U.tap(game, "a") + if f > 2400 and top.onFinish then + U.log("force-finishing a stalled battle") + top.onFinish("win") + if game.stack:top() == top then game.stack:pop() end + end + else + U.tap(game, "a") + end + U.wait(2) + sample() + end + check("reached the after-battle 'hideout' line", sawAfter) + check("the battle carried the script's loss line", + battle ~= nil and type(battle.endBattleText) == "string" + and battle.endBattleText:find("Dang", 1, true) ~= nil, + battle and tostring(battle.endBattleText) or "no battle seen") + + -- PrintEndBattleText sits between TrainerDefeatedText and + -- MoneyForWinningText (engine/battle/core.asm TrainerBattleVictory) + local iDefeat, iDang, iMoney + for i, line in ipairs(said) do + if not iDefeat and line:find("defeated", 1, true) then iDefeat = i end + if not iDang and line:find("Dang", 1, true) then iDang = i end + if not iMoney and line:find("winning", 1, true) then iMoney = i end + end + check("the loss line printed on the battle screen", iDang ~= nil) + check("it printed with the ROCKET: name tag", + iDang ~= nil and said[iDang]:find(":", 1, true) ~= nil, + iDang and said[iDang] or "") + check("order is defeated -> Dang! -> payout", + iDefeat ~= nil and iDang ~= nil and iMoney ~= nil + and iDefeat < iDang and iDang < iMoney, + ("defeated=%s dang=%s payout=%s"):format(tostring(iDefeat), + tostring(iDang), + tostring(iMoney))) + U.shot(game, DIR .. "/bug862_2_afterbattle.png") + + -- Dismiss the after-battle box and watch the exit walk cell by cell. + U.tap(game, "a") + local visited, order, lowShot = {}, {}, false + local function mark(cx, cy) + local key = cx .. "," .. cy + if not visited[key] then + visited[key] = true + order[#order + 1] = key + end + end + -- the last step's hide_object rides its own onDone, so the grunt leaves + -- ow.npcs on the frame he lands: count the cell he is walking INTO as + -- visited too, or the destination never shows up in the sample + local last = { GX, GY } + for _ = 1, 900 do + local g = findGrunt() + if g then + mark(g.cellX, g.cellY) + last = { g.cellX, g.cellY } + if g.targetX and g.targetY then + mark(g.targetX, g.targetY) + last = { g.targetX, g.targetY } + end + if g.cellY > GY and not lowShot then + lowShot = true + U.shot(game, DIR .. "/bug862_3_walk.png") + end + elseif idle() then + break + end + if game.stack:top() ~= ow then U.tap(game, "a") end + U.wait(1) + end + for _ = 1, 400 do + if idle() then break end + if game.stack:top() ~= ow then U.tap(game, "a") end + U.wait(2) + end + U.wait(5) + U.shot(game, DIR .. "/bug862_4_gone.png") + + U.log("cells he stood on:", table.concat(order, " ")) + check("he never stood on the poster cell (9,4)", + not visited[POSTER.x .. "," .. POSTER.y]) + check("he never stepped north of his start row", (function() + for key in pairs(visited) do + local y = tonumber(key:match(",(%d+)$")) + if y and y < GY then return false end + end + return true + end)()) + if around then + check("he stepped down to (9,6) to get past the player", visited["9,6"]) + check("he came back up onto row 5 and finished on (15,5)", + last[1] == 15 and last[2] == 5, + ("last seen on (%d,%d)"):format(last[1], last[2])) + else + check("he walked straight along row 5 to (15,5)", + last[1] == 15 and last[2] == 5 and not visited["9,6"], + ("last seen on (%d,%d)"):format(last[1], last[2])) + end + local toggles = game.save.objectToggles.GAME_CORNER + check("he despawned only after the last step", findGrunt() == nil) + check("his objectToggle is hidden", + toggles ~= nil and toggles.GAMECORNER_ROCKET == false) + check("he is recorded as defeated", + game.save.defeatedTrainers["GAME_CORNER_obj_11"] == true) + U.log(("checks: %d passed, %d failed"):format(pass, fail)) + + -- Hand the pad over on a clean copy of the same setup so the whole beat + -- can be watched at speed. + game.save.defeatedTrainers = {} + game.save.objectToggles.GAME_CORNER = nil + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.log("You are east of the grunt again, facing him. Press A and win.") + U.log("Right looks like: he says his piece on the battle screen after") + U.log("\"RED defeated ROCKET!\" -- one box, \"ROCKET: Dang!\" -- and the") + U.log("¥ payout comes after it, not before. Then the hideout line, then") + U.log("he steps DOWN off row 5, right past you, back up and out east.") + U.log("The near miss to watch for: he steps UP into the poster, or the") + U.log("Dang! box turns up in the overworld after the battle has torn down.") + U.log("Talk to him from (9,6) below instead and he takes the straight") + U.log("five-step version east; both are correct, the branch is your cell.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/giovanni_silph11f_bug869_test.lua b/tests/drivers/giovanni_silph11f_bug869_test.lua new file mode 100644 index 00000000..16d70fcd --- /dev/null +++ b/tests/drivers/giovanni_silph11f_bug869_test.lua @@ -0,0 +1,224 @@ +-- Giovanni's Silph Co 11F coordinate trigger speaks BEFORE he walks (#869). +-- pokered scripts/SilphCo11F.asm SilphCo11FDefaultScript: DisplayTextID +-- TEXT_SILPHCO11F_GIOVANNI first, then MoveSprite .GiovanniMovement (3x down) +-- and EngageMapTrainer with no second box. Do not set POKEPORT_SPEED: the +-- box-vs-walk ordering is exactly the moment under test. +-- POKEPORT_DRIVER=tests/drivers/giovanni_silph11f_bug869_test.lua POKEPORT_IDENTITY=bug869 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local TextBox = require("src.render.TextBox") + local BattleTransition = require("src.render.BattleTransition") + local BattleState = require("src.battle.BattleState") + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + -- Positions from ../pokered/data/maps/objects/SilphCo11F.asm: Giovanni + -- object_event (6, 9), SILPHCO11F_ROCKET1 (3, 16). Trigger tiles from + -- ../pokered/scripts/SilphCo11F.asm .PlayerCoordsArray: (6, 13) and + -- (7, 12). data/generated/maps.lua stores the same cells 1:1. + local MAP = "SILPH_CO_11F" + local GIO_HOME = { x = 6, y = 9 } + local GIO_STOP = { x = 6, y = 12 } -- home + 3x NPC_MOVEMENT_DOWN + + local failed = 0 + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + if not ok then failed = failed + 1 end + return ok + end + + -- party strong enough that the human can win the OPP_GIOVANNI#2 fight + -- and watch the unchanged aftermath (victories.lua "Arrgh!!", then the + -- "Blast it all!" speech and the rockets leaving) + game.save.party = { + Pokemon.new(game.data, "MEWTWO", 80), + Pokemon.new(game.data, "SNORLAX", 77), + Pokemon.new(game.data, "CHARIZARD", 70), + } + game.save.player.name = "RED" + + -- (6,13) and (7,13) sit in the card-key doorway of the boss room: block + -- (3,6) stays the closed id 32 until EVENT_SILPH_CO_11_UNLOCKED_DOOR is + -- set (stampClosedDoors, mirroring pokered engine/events/card_key.asm), + -- and a closed door refuses the step this test needs. A real player has + -- opened it before the trigger can fire, so open it here too. + game.save.flags.EVENT_SILPH_CO_11_UNLOCKED_DOOR = true + + check("EVENT_BEAT_SILPH_CO_GIOVANNI starts unset -- trigger is armed", + not game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI) + local opts = game.save.options or {} + if (opts.sfxVol or 0) == 0 then + U.log("sfxVol is 0 -- the evil-trainer sting will be inaudible") + end + if (opts.musicVol or 0) == 0 then + U.log("musicVol is 0 -- the encounter sting and battle theme are muted") + end + + local function findNpc(ow, name) + for _, n in ipairs(ow.npcs or {}) do + if n.def and n.def.name == name then return n end + end + return nil + end + + local function topBox() + local t = game.stack:top() + if getmetatable(t) == TextBox then return t end + return nil + end + + local function boxText(box) + local shown = {} + for _, page in ipairs(box.pages or {}) do + for _, line in ipairs(page) do shown[#shown + 1] = line end + end + return table.concat(shown, " / ") + end + + -- Stand next to (tx, ty) and take one real walking step onto it; onStep + -- hooks fire on a finished step, so a bare teleport onto the tile would + -- prove nothing. `sides` are {dx, dy, facing} in preference order, each + -- checked for walkability so a map edit only degrades to the next side. + local function stepOnto(tx, ty, sides) + U.teleport(game, MAP, tx + sides[1][1], ty + sides[1][2], sides[1][3]) + U.wait(10) + local ow = game.overworld + for _, s in ipairs(sides) do + local cx, cy = tx + s[1], ty + s[2] + if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then + if ow.player.cellX ~= cx or ow.player.cellY ~= cy then + U.teleport(game, MAP, cx, cy, s[3]) + U.wait(10) + end + U.hold(game, s[3], 24) + U.wait(10) + return true + end + end + return false + end + + -- Regression on the shared engageTrainer path first: an ordinary trainer + -- with no skipBattleText must still get its normal pre-battle box. + -- ROCKET1 faces up, so talk to him from above. + check("regression: reached ROCKET1's cell", + stepOnto(3, 15, { { 0, 1, "up" }, { 0, -1, "down" }, + { 1, 0, "left" }, { -1, 0, "right" } })) + do + local ow = game.overworld + local rocket = findNpc(ow, "SILPHCO11F_ROCKET1") + check("regression: ROCKET1 object loaded", rocket ~= nil) + if rocket then + -- walk down one so we face him from (3, 15) + local fx, fy = ow.player:facingCell() + if ow:npcAtCell(fx, fy) ~= rocket then + -- stepOnto left us adjacent to (3, 15); face the rocket directly + local dx = rocket.cellX - ow.player.cellX + local dy = rocket.cellY - ow.player.cellY + local face = (dy > 0 and "down") or (dy < 0 and "up") + or (dx > 0 and "right") or "left" + U.tap(game, face) + U.wait(10) + end + U.tap(game, "a") + U.wait(30) + local box = topBox() + check("regression: talking to ROCKET1 still opens the pre-battle box", + box ~= nil) + if box then + local t = boxText(box) + U.log("rocket box reads:", t) + check("regression: it is his battle line (\"Stop right there!\")", + t:find("Stop right there", 1, true) ~= nil) + end + end + end + + -- Both trigger tiles must fire with Giovanni still at his desk. The + -- (7, 12) probe is abandoned before the box is dismissed (the teleport + -- rebuilds the map state), so the trigger re-arms for the main run -- + -- vanilla re-arms too, since only a win sets the event flag. + do + check("(7,12) approach: stepped onto the trigger from the right", + stepOnto(7, 12, { { 1, 0, "left" }, { 0, 1, "up" }, + { -1, 0, "right" } })) + local box = topBox() + check("(7,12) approach: intro box opened", box ~= nil) + local gio = findNpc(game.overworld, "SILPHCO11F_GIOVANNI") + check("(7,12) approach: Giovanni is still at his desk (6,9)", + gio ~= nil and gio.cellX == GIO_HOME.x and gio.cellY == GIO_HOME.y) + end + + -- Main run, the route the issue screenshots show: (6,15) facing up, two + -- steps onto (6,13). + U.teleport(game, MAP, 6, 15, "up") + U.wait(10) + U.hold(game, "up", 24) + U.hold(game, "up", 24) + U.wait(15) + if not topBox() then + -- blocked approach fallback: one step onto (6,13) from any free side + stepOnto(6, 13, { { 0, 1, "up" }, { -1, 0, "right" }, { 1, 0, "left" } }) + end + + local ow = game.overworld + local gio = findNpc(ow, "SILPHCO11F_GIOVANNI") + check("Giovanni object loaded on " .. MAP, gio ~= nil) + local box = topBox() + check("stepping onto (6,13) opened a text box", box ~= nil) + if box then + local t = boxText(box) + U.log("intro box reads:", t) + check("it is the Giovanni intro (\"So we meet again!\")", + t:find("So we meet again", 1, true) ~= nil) + end + check("the box opened with Giovanni STILL at his desk (6,9) -- the fix", + gio ~= nil and gio.cellX == GIO_HOME.x and gio.cellY == GIO_HOME.y) + U.shot(game, SHOT_DIR .. "/bug869_box_before_walk.png") + U.wait(30) + check("he holds the desk for the whole box, not just its first frame", + gio ~= nil and gio.cellX == GIO_HOME.x and gio.cellY == GIO_HOME.y) + + -- dismiss every page; the walk and the battle must follow with NO + -- further dialogue box in between (EngageMapTrainer runs bare in the + -- original, so engageTrainer is called with skipBattleText here) + for _ = 1, 300 do + if not topBox() then break end + U.tap(game, "a") + U.wait(5) + end + check("intro box dismissed", topBox() == nil) + local sawSecondBox, battleReached = false, false + for _ = 1, 900 do + local t = game.stack:top() + local mt = getmetatable(t) + if mt == TextBox and not sawSecondBox then + sawSecondBox = true + U.log("unexpected box reads:", boxText(t)) + end + if mt == BattleTransition or mt == BattleState then + battleReached = true + break + end + U.wait(1) + end + check("battle wipe started after the box", battleReached) + check("no second dialogue box between the walk and the battle", not sawSecondBox) + check("Giovanni walked the three tiles down to (6,12) first", + gio ~= nil and gio.cellX == GIO_STOP.x and gio.cellY == GIO_STOP.y) + + U.log(failed == 0 and "PASS all machine checks clean" + or ("FAIL " .. failed .. " machine check(s) above")) + + U.log("The battle wipe is running now; take the pad and win the fight.") + U.log("Right looks like what just played: his speech opened while he was") + U.log("behind the desk, then he walked down and the fight began straight") + U.log("away, with the evil-trainer sting and no extra dialogue. After the") + U.log("win you should get \"Arrgh!!\" on the battle screen, the \"Blast it") + U.log("all!\" speech, a fade, and every rocket gone. Wrong is the old bug:") + U.log("he crosses the room in silence and only then talks, point-blank.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/hall_of_fame_bug704_test.lua b/tests/drivers/hall_of_fame_bug704_test.lua index 765c4fc2..69f4a2a4 100644 --- a/tests/drivers/hall_of_fame_bug704_test.lua +++ b/tests/drivers/hall_of_fame_bug704_test.lua @@ -195,7 +195,7 @@ return function(game) ow:queueScript(slice, { npc = rival }) local startY = ow.player.cellY - local minY, walkShot = startY, false + local minY, walkShot, sharedCell = startY, false, false local hof for i = 1, 5000 do local top = game.stack:top() @@ -205,8 +205,9 @@ return function(game) end local w = game.overworld if w and w.map and w.map.id == "CHAMPIONS_ROOM" then - local y = w.player.cellY + local x, y = w.player.cellX, w.player.cellY if y < minY then minY = y end + if x == rival.cellX and y == rival.cellY then sharedCell = true end if y <= 2 and not walkShot then walkShot = U.shot(game, DIR .. "/hof704_follows_oak.png") end @@ -215,6 +216,7 @@ return function(game) end check("the player walked out of the room before the warp (#704)", minY < startY) + check("the player routed around the rival", not sharedCell) check("walk-out screenshot", walkShot) check("the induction started", hof ~= nil) if not hof then @@ -226,6 +228,18 @@ return function(game) -- --------------------------------------------------------------- -- mid scroll: .ScrollPic nudges hSCX 4px a frame, and the exemption has to -- travel with the pic instead of sitting at its resting column (#637) + -- #847: HoFShowMonOrPlayer sweeps the BACK pic across the screen (low, at + -- y=88) before the front pic scrolls in. Catch it mid-sweep, wait the + -- sweep out, then catch the front pic partway through its own scroll. + for _ = 1, 300 do + if hof.phase ~= "back" or (hof.scrollX or 0) <= 56 then break end + U.wait(1) + end + check("back-pic sweep screenshot", U.shot(game, DIR .. "/hof847_back.png")) + for _ = 1, 300 do + if hof.phase ~= "back" then break end + U.wait(1) + end for _ = 1, 200 do if (hof.scrollX or PIC_X) > 8 then break end U.wait(1) diff --git a/tests/drivers/hit_sfx_bug826_test.lua b/tests/drivers/hit_sfx_bug826_test.lua new file mode 100644 index 00000000..fc99603d --- /dev/null +++ b/tests/drivers/hit_sfx_bug826_test.lua @@ -0,0 +1,262 @@ +-- The super effective / not very effective hit sounds played at the wrong +-- pitch, so the weak-sounding hit landed on the weakness (#826). pokered +-- PlayApplyingAttackSound (engine/battle/animations.asm) sets +-- wFrequencyModifier with the sound, and audio/engine_2.asm +-- Audio2_ApplyFrequencyModifier adds it to the noise channel's polynomial +-- counter. Ears only, so never under POKEPORT_SPEED -- the pitch is the test. +-- POKEPORT_DRIVER=tests/drivers/hit_sfx_bug826_test.lua POKEPORT_IDENTITY=bug826 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + local ChipSynth = require("src.core.ChipSynth") + local TypeChart = require("src.battle.TypeChart") + local Sound = require("src.core.Sound") + + -- GOLEM is ROCK/GROUND, so one attacker covers both ends of the routine + -- with no switching: WATER_GUN is 2x on each type and EMBER is 0.5x on + -- ROCK. SPLASH on the foe keeps the lead alive for as many replays as + -- the listener wants. + local FOE, FOE_LEVEL = "GOLEM", 60 + local LEAD, LEAD_LEVEL = "BULBASAUR", 25 + local WEAK_MOVE, STRONG_MOVE = "EMBER", "WATER_GUN" + -- PlayApplyingAttackSound's wFrequencyModifier per sound, and the + -- polynomial-counter byte of each program's first note (audio/sfx/ + -- {damage,super_effective,not_very_effective}.asm) before and after it. + local SOUNDS = { + { name = "Damage", pitch = 0x20, raw = 0x44, want = 0x64 }, + { name = "Super_Effective", pitch = 0xe0, raw = 0x34, want = 0x14 }, + { name = "Not_Very_Effective", pitch = 0x50, raw = 0x55, want = 0xa5 }, + } + + local pass, fail = 0, 0 + local function check(label, ok) + if ok then pass = pass + 1 else fail = fail + 1 end + U.log(ok and "PASS" or "FAIL", label) + return ok + end + local function hex(v) return v and ("$%02x"):format(v) or "nil" end + + -- ---- data the moment depends on ---------------------------------------- + local sfx = game.data.audio and game.data.audio.sfx or {} + for _, row in ipairs(SOUNDS) do + local def = sfx[row.name] + check(row.name .. " resolves to a chip program in the generated audio", + type(def) == "table" and def.address ~= nil and def.bank ~= nil) + end + local moveWeak, moveStrong = game.data.moves[WEAK_MOVE], game.data.moves[STRONG_MOVE] + local foeDef = game.data.pokemon[FOE] + check(WEAK_MOVE .. " and " .. STRONG_MOVE .. " resolve in the move table", + moveWeak ~= nil and moveStrong ~= nil) + check(FOE .. " resolves with a dual type", foeDef ~= nil and #foeDef.types == 2) + local weakMult, strongMult + if moveWeak and moveStrong and foeDef then + weakMult = TypeChart.effectiveness(moveWeak.type, foeDef.types) + strongMult = TypeChart.effectiveness(moveStrong.type, foeDef.types) + U.log(("%s on %s is x%.1f, %s is x%.1f (the x10 scale Damage.lua uses)") + :format(WEAK_MOVE, FOE, weakMult / 10, STRONG_MOVE, strongMult / 10)) + end + check(WEAK_MOVE .. " is the resisted side of the pair", (weakMult or 10) < 10) + check(STRONG_MOVE .. " is the super effective side", (strongMult or 10) > 10) + + local vol = game.save.options and game.save.options.sfxVol + check("sfx volume is up (" .. tostring(vol) .. "/7)", (vol or 0) > 0) + if (vol or 0) == 0 then + U.log("with sfxVol 0 both hits are silent and this run proves nothing;", + "raise it in OPTION and start over") + end + + -- ---- the synth half: does the modifier reach the noise channel? --------- + -- Sample each program once at offset 0 and again at its own modifier. A + -- port that drops wFrequencyModifier reports the same byte twice, which is + -- the whole of #826: unpitched, Super_Effective ends duller than + -- Not_Very_Effective ends. + local function firstNoise(header, offset) + if not header then return nil end + local engine = ChipSynth.newEngine(game.data, header, { + sfx = true, allowLoops = false, frequencyOffset = offset, + }) + for _, channel in ipairs(engine.channels) do + channel:sample() + local event = channel.event + if event and event.noiseParameter then return event.noiseParameter end + end + return nil + end + for _, row in ipairs(SOUNDS) do + local bare = firstNoise(sfx[row.name], 0) + local pitched = firstNoise(sfx[row.name], row.pitch) + check(("%s reads NR43 %s unmodified, as in the asm") + :format(row.name, hex(row.raw)), bare == row.raw) + check(("...and %s once %s is applied"):format(hex(row.want), hex(row.pitch)), + pitched == row.want) + U.log(("%s: %s -> %s, shift clock %d -> %d (higher shift = duller)") + :format(row.name, hex(bare), hex(pitched), + math.floor((bare or 0) / 16), math.floor((pitched or 0) / 16))) + end + + -- ---- the battle half: what does a hit row actually carry? --------------- + -- Offscreen scratch turn, no animation timing in the way. The row has to + -- name the sound AND its modifier byte, and must not carry a tempo byte: + -- Audio2_note_length skips Audio2_SetSfxTempo on CHAN8 (`cp CHAN8 / jr z, + -- .skip`), so the hardware never retimes these three. + -- the move is handed in whole, so the party lead keeps both its slots for + -- the live battle below + local function rowSfx(moveId) + local scratch = BattleState.newWild(game, FOE, FOE_LEVEL) + scratch.onFinish = function() end + -- Damage.accuracyRoll is `rng(0, 255) < acc` (src/battle/Damage.lua:105), + -- so the roll has to be pinned LOW to guarantee a hit. Pinning it high + -- misses every time, the row never gets a .hit, and this scan reads nil. + scratch.rng = function(lo) return lo end + scratch:performMove(scratch.player, scratch.enemy, { id = moveId, pp = 20 }) + for _, row in ipairs(scratch.queue) do + if row.hit and row.hit.sfx then return row.hit.sfx end + end + return nil + end + do + local lead = Pokemon.new(game.data, LEAD, LEAD_LEVEL) + lead.moves = { + { id = WEAK_MOVE, pp = 25, maxPP = 25 }, + { id = STRONG_MOVE, pp = 25, maxPP = 25 }, + } + game.save.party = { lead } + for _, case in ipairs({ + { move = STRONG_MOVE, want = "Super_Effective", pitch = 0xe0 }, + { move = WEAK_MOVE, want = "Not_Very_Effective", pitch = 0x50 }, + }) do + local row = rowSfx(case.move) + check(case.move .. " queues a hit sound with its modifier", + type(row) == "table" and row.sound == case.want + and row.pitch == case.pitch) + check("...and no tempo byte, the way CHAN8 ignores one", + type(row) == "table" and row.tempo == nil) + U.log(("%s -> %s pitch %s"):format(case.move, + type(row) == "table" and tostring(row.sound) or tostring(row), + type(row) == "table" and hex(row.pitch) or "nil")) + end + end + + -- ---- reach the moment --------------------------------------------------- + -- ROUTE_1 is open field (data/generated/maps.lua ROUTE_1); the stand cell + -- is read back off the loaded map, and a map edit degrades to the first + -- free cell instead of dropping the player into a wall. + U.teleport(game, "ROUTE_1", 5, 5, "down") + U.wait(10) + local map = game.overworld.map + if not map:isWalkableCell(5, 5) then + local fx, fy + for cy = 0, map.heightCells - 1 do + for cx = 0, map.widthCells - 1 do + if map:isWalkableCell(cx, cy) then fx, fy = cx, cy break end + end + if fx then break end + end + if fx then + U.log("cell (5, 5) is not walkable, standing on", fx, fy) + U.teleport(game, "ROUTE_1", fx, fy, "down") + U.wait(10) + end + end + local ow = game.overworld + check("player stands on a walkable ROUTE_1 cell", + ow.map:isWalkableCell(ow.player.cellX, ow.player.cellY)) + + -- listen in on the real playback path so the log can tell "the fix is not + -- wired to the battle" from "the fix is wired but you did not like it" + local heard = {} + local realPlayMove = Sound.playMove + Sound.playMove = function(data, anim) + if type(anim) == "table" and anim.sound then + for _, row in ipairs(SOUNDS) do + if anim.sound == row.name then + heard[#heard + 1] = { sound = anim.sound, pitch = anim.pitch } + end + end + end + return realPlayMove(data, anim) + end + + local function mashUntil(cond, max) + for _ = 1, max or 160 do + if cond() then return true end + U.tap(game, "a") + U.wait(4) + end + return cond() + end + + local function newFight() + local battle = BattleState.newWild(game, FOE, FOE_LEVEL) + battle.onFinish = function(result) ow:afterBattle(result, battle) end + -- SPLASH so the foe's turn cannot end the run, or drown the hit under a + -- damage sound of its own + battle.enemy.mon.moves = { { id = "SPLASH", pp = 40, maxPP = 40 } } + battle.enemy.curMoves = battle.enemy.mon.moves + ow:pushBattle(battle) + U.wait(220) -- the send-out intro plays before the menu is reachable + mashUntil(function() return battle.phase == "menu" end) + return battle + end + + local battle = newFight() + check("the wild " .. FOE .. " battle reached its FIGHT menu", + battle.phase == "menu") + U.shot(game, DIR .. "/bug826_menu.png") + + -- slot 1 first: the resisted hit, so the pair is heard weak then strong + local function swing(slotDown, label) + local before = #heard + U.tap(game, "a") -- FIGHT + U.wait(16) + if slotDown then U.tap(game, "down"); U.wait(8) end + U.tap(game, "a") + for frame = 1, 1200 do + if battle.phase == "menu" and #battle.queue == 0 and not battle.draining then + break + end + if not battle.draining and frame % 8 == 0 then U.tap(game, "a") end + U.wait(1) + end + local row = heard[before + 1] + U.log(("%s played %s at pitch %s"):format(label, + row and row.sound or "nothing", + row and hex(row.pitch) or "nil")) + return row + end + + local weakHeard = swing(false, WEAK_MOVE) + U.shot(game, DIR .. "/bug826_not_very_effective.png") + local strongHeard = swing(true, STRONG_MOVE) + U.shot(game, DIR .. "/bug826_super_effective.png") + check("the resisted hit reached the mixer as Not_Very_Effective $50", + weakHeard ~= nil and weakHeard.sound == "Not_Very_Effective" + and weakHeard.pitch == 0x50) + check("the super effective hit reached it as Super_Effective $e0", + strongHeard ~= nil and strongHeard.sound == "Super_Effective" + and strongHeard.pitch == 0xe0) + U.log(("machine checks: %d passed, %d failed"):format(pass, fail)) + + -- ---- hand the pad over -------------------------------------------------- + Sound.playMove = realPlayMove + if battle.phase ~= "menu" then + U.log("(the menu did not come back on its own: mash A to reach FIGHT)") + end + U.log("Both hits have already sounded once. GOLEM is still standing and") + U.log("EMBER and WATER_GUN sit in slots 1 and 2, so play them back to back") + U.log("as often as you like.") + U.log("WATER_GUN, under \"It's super effective!\", should be the brighter") + U.log("and sharper of the two -- a high crack. EMBER, under \"It's not very") + U.log("effective...\", should be a low dull rumble underneath it.") + U.log("The near miss to listen for: the two are close in brightness, or the") + U.log("crack lands on EMBER and the thud on WATER_GUN. That is the modifier") + U.log("going missing again, and it is what #826 sounded like.") + U.log("The neutral hit changed too: any move that is neither, on any foe,") + U.log("is now a shade duller than it used to be, and that is correct.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/jessie_james_bug866_test.lua b/tests/drivers/jessie_james_bug866_test.lua new file mode 100644 index 00000000..ec415d3b --- /dev/null +++ b/tests/drivers/jessie_james_bug866_test.lua @@ -0,0 +1,177 @@ +-- Manual check of the Rocket Hideout B4F Jessie & James ambush: James walks +-- the full four tiles to the player's side (#865) and their loss line prints +-- on the battle screen before the prize money (#866). +-- pokeyellow scripts/RocketHideoutB4F.asm (MovementData_45605 falls through +-- into _45606) and data/maps/objects/RocketHideoutB4F.asm. No fast-forward: +-- POKEPORT_DRIVER=tests/drivers/jessie_james_bug866_test.lua POKEPORT_VERSION=yellow love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + local Commands = require("src.script.Commands") + local GameVersion = require("src.core.GameVersion") + local mapScripts = require("data.scripts.init") + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local MAP = "ROCKET_HIDEOUT_B4F" + local BEAT = "EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES" + local JAMES, JESSIE = "ROCKETHIDEOUTB4F_JAMES", "ROCKETHIDEOUTB4F_JESSIE" + -- RocketHideoutB4FScript_455a5 fires on wYCoord $e with wXCoord $18 or $19. + -- x=24 leaves EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT clear, which is + -- the branch that hands the four-step blob to James (object 2, spawned at + -- 25,10) and the three-step one to Jessie (object 3, at 24,10). + local TRIGGER = { x = 24, y = 14 } + local EXPECT = { + [JAMES] = { x = 25, y = 14, facing = "left" }, + [JESSIE] = { x = 24, y = 13, facing = "down" }, + } + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + check("running the Yellow cache (the duo exists nowhere else)", + GameVersion.isYellow()) + if not GameVersion.isYellow() then + U.log("re-run with POKEPORT_VERSION=yellow; nothing below will be true") + end + + local hooks = mapScripts.get(MAP) + check("yellow_jessie_james registered an onStep for " .. MAP, + type(hooks) == "table" and type(hooks.onStep) == "function") + check("their talk entries are registered too", + type(hooks) == "table" and type(hooks.talk) == "table" + and hooks.talk.TEXT_ROCKETHIDEOUTB4F_JAMES ~= nil + and hooks.talk.TEXT_ROCKETHIDEOUTB4F_JESSIE ~= nil) + + -- the #866 fix is a new script verb; if a mod shadowed it or the registry + -- never picked it up, the row would silently no-op and the line would come + -- back after the money instead of before it + local verb = Commands.resolve(game.data, "save_end_battle_text") + check("save_end_battle_text resolves as a script verb", type(verb) == "function") + + local texts = {} + for i = 1, 4 do + local key = "_RocketHideoutJessieJamesText" .. i + texts[i] = game.data.text[key] + check(key .. " resolves to a string", + type(texts[i]) == "string" and texts[i] ~= "") + end + if type(texts[3]) == "string" then + U.log("the armed loss line reads:", (texts[3]:gsub("\n", " / "))) + end + + local objs = (game.data.maps[MAP] or {}).objects or {} + local defs = {} + for _, o in ipairs(objs) do + if o.name == JAMES or o.name == JESSIE then defs[o.name] = o end + end + check("James is object 2 of " .. MAP .. ", hidden at (25,10)", + defs[JAMES] ~= nil and defs[JAMES].index == 2 + and defs[JAMES].x == 25 and defs[JAMES].y == 10 + and defs[JAMES].hidden == true) + check("Jessie is object 3, hidden at (24,10)", + defs[JESSIE] ~= nil and defs[JESSIE].index == 3 + and defs[JESSIE].x == 24 and defs[JESSIE].y == 10 + and defs[JESSIE].hidden == true) + + local rocket = game.data.trainers.OPP_ROCKET + check("OPP_ROCKET party 43 (the duo's shared team) exists", + rocket ~= nil and rocket.parties ~= nil and rocket.parties[43] ~= nil) + + -- arm the site: the ambush is gated only on its beat flag, so no story + -- progress is needed to make it live + game.save.flags[BEAT] = nil + game.save.flags.EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT = nil + check(BEAT .. " cleared, so the trigger is live", + game.save.flags[BEAT] == nil) + + -- a real party, because the human has to win the battle for the loss line + -- to print at all + game.save.party = { + Pokemon.new(game.data, "CHARIZARD", 60), + Pokemon.new(game.data, "NIDOKING", 58), + Pokemon.new(game.data, "STARMIE", 58), + } + game.save.player.name = "RED" + + -- walk in from the north; the two elevator warps sit on row 15, so the + -- approach cannot come from below + U.teleport(game, MAP, TRIGGER.x, TRIGGER.y - 1, "down") + local ow = game.overworld + if not ow.map:isWalkableCell(TRIGGER.x, TRIGGER.y - 1) then + -- a map edit moved the free cell: any walkable neighbour of the trigger + -- works, the script only reads the tile the player lands on + local sides = { { 0, -1, "down" }, { -1, 0, "right" }, { 1, 0, "left" } } + for _, s in ipairs(sides) do + local cx, cy = TRIGGER.x + s[1], TRIGGER.y + s[2] + if ow.map:isWalkableCell(cx, cy) then + U.log("standing on", cx, cy, "facing", s[3], "instead") + U.teleport(game, MAP, cx, cy, s[3]) + ow = game.overworld + U.hold(game, s[3] == "down" and "down" or (s[3] == "right" and "right" or "left"), 20) + break + end + end + else + U.hold(game, "down", 20) + end + U.wait(10) + check("player stepped onto the trigger tile (24,14)", + ow.player.cellX == TRIGGER.x and ow.player.cellY == TRIGGER.y) + check("the ambush script is running", ow.runner:isRunning()) + + U.log("The cutscene is yours now: press A to read, then fight and win.") + U.log("Right looks like both Rockets closing in -- Jessie stopping one tile") + U.log("above you, James coming all the way down to stand at your right -- and") + U.log("after you win, \"ROCKET: Such a dreadful twerp!\" appearing on the") + U.log("battle screen just before the money line. The near-miss to watch for") + U.log("is James halting three tiles up by the wall, or that line showing up") + U.log("in the overworld box after the payout with no ROCKET: tag on it.") + U.log("Two more checks print below as you get to them.") + + local function npcNamed(name) + for _, n in ipairs(game.overworld and game.overworld.npcs or {}) do + if n.def and n.def.name == name then return n end + end + return nil + end + + local approachDone, battleSeen = false, false + while true do + if not approachDone then + local j, s = npcNamed(JAMES), npcNamed(JESSIE) + local ow2 = game.overworld + if j and s and not j.moving and not s.moving and ow2 + and #(ow2.scriptMoves or {}) == 0 + and (j.cellY > 10 or s.cellY > 10) then + approachDone = true + check("James walked the full four tiles to (25,14) facing left", + j.cellX == EXPECT[JAMES].x and j.cellY == EXPECT[JAMES].y + and j.facing == EXPECT[JAMES].facing) + check("Jessie stopped three down at (24,13) facing the player", + s.cellX == EXPECT[JESSIE].x and s.cellY == EXPECT[JESSIE].y + and s.facing == EXPECT[JESSIE].facing) + U.log("James at", j.cellX, j.cellY, j.facing, + "Jessie at", s.cellX, s.cellY, s.facing) + U.shot(game, SHOT_DIR .. "/jj866_approach.png") + end + end + if not battleSeen then + local top = game.stack:top() + if getmetatable(top) == BattleState then + battleSeen = true + -- BattleState prints endBattleText between _TrainerDefeatedText and + -- _MoneyForWinningText, so an armed field IS the ordering fix + check("the battle carries the loss line as its end-battle text", + type(top.endBattleText) == "string" and top.endBattleText ~= "" + and top.endBattleText == texts[3]) + if type(top.endBattleText) == "string" then + U.log("armed:", (top.endBattleText:gsub("\n", " / "))) + end + end + end + coroutine.yield() + end +end diff --git a/tests/drivers/marowak_departed_bug867_test.lua b/tests/drivers/marowak_departed_bug867_test.lua new file mode 100644 index 00000000..ae9bbbc0 --- /dev/null +++ b/tests/drivers/marowak_departed_bug867_test.lua @@ -0,0 +1,188 @@ +-- Manual check of the ghost MAROWAK send-off on POKEMON_TOWER_6F (#867). +-- PokemonTower6FMarowakDepartedText (pokered scripts/PokemonTower6F.asm) is +-- two texts: the CUBONE's-mother line, then PlayCry RESTLESS_SOUL + 30 frames +-- before the calmed line; the port showed only the calmed line and no cry. +-- Never under POKEPORT_SPEED -- the cry-then-text beat is the thing under test. +-- POKEPORT_DRIVER=tests/drivers/marowak_departed_bug867_test.lua POKEPORT_IDENTITY=bug867 POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + local TextBox = require("src.render.TextBox") + + local pass, fail = 0, 0 + local function check(label, ok) + if ok then pass = pass + 1 else fail = fail + 1 end + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- ---- machine checks ---------------------------------------------------- + -- Both keys come out of the stock extractor (data/generated/text.lua); a + -- rename there would drop story3.lua onto its hand fallbacks, which still + -- displays but is worth knowing about. + local mother = game.data.text._PokemonTower6FGhostWasCubonesMotherText + local calmed = game.data.text._PokemonTower6FSoulWasCalmedText + check("_PokemonTower6FGhostWasCubonesMotherText extracted", + type(mother) == "string" and mother ~= "") + check("_PokemonTower6FSoulWasCalmedText extracted", + type(calmed) == "string" and calmed ~= "") + if type(mother) == "string" then + U.log("first line reads:", (mother:gsub("[\n\011\012]", " / "))) + end + if type(calmed) == "string" then + U.log("second line reads:", (calmed:gsub("[\n\011\012]", " / "))) + end + check("MAROWAK is a real species (RESTLESS_SOUL EQU MAROWAK)", + game.data.pokemon.MAROWAK ~= nil) + + local opts = game.save.options or {} + U.log("sfxVol", tostring(opts.sfxVol), "musicVol", tostring(opts.musicVol)) + if opts.sfxVol == 0 then + U.log("sfxVol is 0: raise it in OPTION or the cry cannot be judged") + end + + -- ---- reach the trigger ------------------------------------------------- + -- pokered scripts/PokemonTower6F.asm PokemonTower6FMarowakCoords: + -- dbmapcoord 10, 16 (a coord array, not an object). Row 17 is solid wall + -- and (9, 16) is the stairwell warp, so the approach is from (10, 15) + -- facing down, same as tests/drivers/ghost_unveil_bug492_test.lua. + local MAP = "POKEMON_TOWER_6F" + local TRIGGER = { x = 10, y = 16 } + local STAND = { x = 10, y = 15, facing = "down", step = "down" } + + -- one mon, one damaging move: the A-mash below always picks FIGHT slot 1, + -- and a stat move there stalls the run (route.lua learned this the hard way) + local mon = Pokemon.new(game.data, "MEWTWO", 100) + if game.data.moves.PSYCHIC_M then + mon.moves = { { id = "PSYCHIC_M", pp = 99 } } + end + game.save.party = { mon } + game.save.player.name = "RED" + -- the scope buys the unveil so the ghost can be damaged at all (#492) + game.save.inventory.SILPH_SCOPE = 1 + game.save.flags.EVENT_BEAT_GHOST_MAROWAK = nil + + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(10) + local ow = game.overworld + check("the overworld is up on " .. MAP, ow ~= nil and ow.map ~= nil) + + if ow and ow.map and not ow.map:isWalkableCell(STAND.x, STAND.y) then + -- a map edit moved the approach: stand on any walkable neighbour of the + -- trigger that is not the stairwell warp and step back onto it. + -- {dx, dy, facing} is the trigger-to-stand offset plus the direction + -- that walks back onto the trigger cell. + local sides = { { 0, -1, "down" }, { 1, 0, "left" }, + { -1, 0, "right" }, { 0, 1, "up" } } + for _, s in ipairs(sides) do + local cx, cy = TRIGGER.x + s[1], TRIGGER.y + s[2] + if ow.map:isWalkableCell(cx, cy) and not ow.map:warpAtCell(cx, cy) then + U.log(("(%d, %d) is blocked, approaching from"):format(STAND.x, STAND.y), + cx, cy, "stepping", s[3]) + STAND = { x = cx, y = cy, facing = s[3], step = s[3] } + U.teleport(game, MAP, cx, cy, s[3]) + U.wait(10) + ow = game.overworld + break + end + end + end + + -- walk onto the trigger; MapScripts onStep fires on the completed step + U.hold(game, STAND.step, 20) + U.wait(20) + check("the Be-gone text opened", game.stack:top() ~= game.overworld) + + -- ---- win the battle ---------------------------------------------------- + local sawBattle = false + for _ = 1, 1500 do + -- onFinish sets the flag and queues the departed rows in the same call, + -- so break on the flag BEFORE tapping: a stray A here would eat the + -- CUBONE's-mother box before it is recorded + if game.save.flags.EVENT_BEAT_GHOST_MAROWAK then break end + if getmetatable(game.stack:top()) == BattleState then sawBattle = true end + U.tap(game, "a") + U.wait(4) + end + check("the MAROWAK battle opened", sawBattle) + check("the battle was won (EVENT_BEAT_GHOST_MAROWAK set)", + game.save.flags.EVENT_BEAT_GHOST_MAROWAK == true) + + -- ---- record the send-off boxes ----------------------------------------- + -- Each box is sampled the frame it is first seen: the calmed box carries + -- the armed cry as opts.auto {sound, wait} (src/script/Commands.lua), and + -- TextBox clears .auto once the cry has played, so a late read looks like + -- no cry at all. + local boxes = {} + local lastBox = nil + local budget = 1200 + while budget > 0 do + local top = game.stack:top() + if getmetatable(top) == TextBox then + if top ~= lastBox then + lastBox = top + local shown = {} + for _, page in ipairs(top.pages or {}) do + for _, line in ipairs(page) do shown[#shown + 1] = line end + end + boxes[#boxes + 1] = { + text = table.concat(shown, " / "), + cry = top.auto ~= nil and top.auto.sound ~= nil, + } + U.log(("box %d reads:"):format(#boxes), boxes[#boxes].text) + -- let the typewriter finish before the shot so the capture shows the + -- line; the auto sample above already happened on the open frame + for _ = 1, 240 do + if top.waiting or top.done or game.stack:top() ~= top then break end + U.wait(1) + budget = budget - 1 + end + U.shot(game, DIR .. ("/bug867_box%d.png"):format(#boxes)) + end + U.tap(game, "a") + U.wait(3) + budget = budget - 4 + else + if #boxes >= 2 then break end + U.wait(1) + budget = budget - 1 + end + end + + check("the send-off is two boxes, not one (#867)", #boxes == 2) + check("box 1 is the CUBONE's-mother line", + boxes[1] ~= nil and boxes[1].text:find("CUBONE", 1, true) ~= nil) + check("box 1 opens silent (the asm plays no cry before it)", + boxes[1] ~= nil and not boxes[1].cry) + check("box 2 is the calmed line", + boxes[2] ~= nil and boxes[2].text:find("calmed", 1, true) ~= nil) + check("box 2 opens with the MAROWAK cry armed", + boxes[2] ~= nil and boxes[2].cry == true) + + -- ---- the trigger is spent ---------------------------------------------- + -- step off and back onto (10, 16): with the flag set, onStep must pass + local back = ({ down = "up", up = "down", left = "right", right = "left" }) + [STAND.step] + U.hold(game, back, 20) + U.wait(10) + U.hold(game, STAND.step, 20) + U.wait(20) + check("re-stepping the trigger cell stays quiet", + getmetatable(game.stack:top()) ~= TextBox) + U.shot(game, DIR .. "/bug867_after.png") + U.log(("machine checks: %d passed, %d failed"):format(pass, fail)) + + -- ---- hand off ---------------------------------------------------------- + U.log("The pad is yours on 6F with the ghost already sent off. What should") + U.log("have happened: after the win, \"The GHOST was the / restless soul of /") + U.log("CUBONE's mother!\" first, then the MAROWAK cry sounds as the second box") + U.log("opens with \"The mother's soul / was calmed.\" The old bug jumped") + U.log("straight to the calmed line, no mother line and no cry at all.") + U.log("Screenshots: " .. DIR .. "/bug867_*.png") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/menu_sfx_bug960_bug961_bug1044_bug1045_test.lua b/tests/drivers/menu_sfx_bug960_bug961_bug1044_bug1045_test.lua new file mode 100644 index 00000000..71e60f48 --- /dev/null +++ b/tests/drivers/menu_sfx_bug960_bug961_bug1044_bug1045_test.lua @@ -0,0 +1,484 @@ +-- Ear check for the PC, bump, door/stairs and battle menu SFX (#960, #961, #1044, #1045). +-- POKEPORT_DRIVER=tests/drivers/menu_sfx_bug960_bug961_bug1044_bug1045_test.lua POKEPORT_IDENTITY=sfx960 POKEPORT_TOUCH=0 POKEPORT_VERSION=red SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local BattleState = require("src.battle.BattleState") + local Boxes = require("src.pokemon.Boxes") + local Menu = require("src.ui.Menu") + local Pokemon = require("src.pokemon.Pokemon") + local Sound = require("src.core.Sound") + local Strings = require("src.core.Strings") + local Timing = require("src.core.Timing") + + local FADE = Timing.WARP_FADE_OUT + + local pass, fail = 0, 0 + local function check(label, ok) + if ok then pass = pass + 1 else fail = fail + 1 end + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- every cue this run makes, forwarded so playback is untouched; the frame + local cues = {} + local realPlay = Sound.play + Sound.play = function(data, name) + cues[#cues + 1] = { name = name, frame = U.frame() } + return realPlay(data, name) + end + local function since(mark) + local names = {} + for i = mark + 1, #cues do names[#names + 1] = cues[i].name end + return #names > 0 and table.concat(names, ", ") or "nothing" + end + local function heard(mark, want) + for i = mark + 1, #cues do + if cues[i].name == want then return cues[i] end + end + return nil + end + local function countOf(mark, want) + local n = 0 + for i = mark + 1, #cues do if cues[i].name == want then n = n + 1 end end + return n + end + + -- ---- what the ear cannot check ----------------------------------------- + local opts = game.save.options or {} + local sfxVol, musicVol = opts.sfxVol or 0, opts.musicVol or 0 + if sfxVol == 0 then + U.log("FAIL SFX volume is 0. Every line below is about a sound that is or") + U.log(" is not there, and at 0 none of them are. Set SFX to 7 in OPTION") + U.log(" and start over, or this run proves nothing at all.") + end + check(("sfx volume %d/7, music volume %d/7"):format(sfxVol, musicVol), + sfxVol > 0) + + -- an unresolved key is silent in exactly the way these bugs were + local sfx = (game.data.audio or {}).sfx or {} + for _, key in ipairs({ "Turn_On_PC", "Turn_Off_PC", "Enter_PC", "Collision", + "Save", "Go_Inside", "Go_Outside", "Press_AB" }) do + check("sfx " .. key .. " is in the generated audio", sfx[key] ~= nil) + end + + -- positions come from data/events/hidden_events.asm and data/maps/objects/*.asm + local extras = game.data.field.hiddenExtras or {} + local pcTiles = extras.pcTiles or {} + local bedroomPC = (pcTiles.REDS_HOUSE_2F or {})[1] + local centerPC = (pcTiles.VIRIDIAN_POKECENTER or {})[1] + check("REDS_HOUSE_2F carries the OpenRedsPC tile", bedroomPC ~= nil) + check("VIRIDIAN_POKECENTER carries a PC tile", centerPC ~= nil) + + local function warpTo(mapId, destMap) + for _, w in ipairs((game.data.maps[mapId] or {}).warps or {}) do + if w.destMap == destMap then return w end + end + return nil + end + local houseDoor = warpTo("PALLET_TOWN", "REDS_HOUSE_1F") + local houseExit = warpTo("REDS_HOUSE_1F", "LAST_MAP") + local houseStairs = warpTo("REDS_HOUSE_1F", "REDS_HOUSE_2F") + local centerDoor = warpTo("VIRIDIAN_CITY", "VIRIDIAN_POKECENTER") + check("PALLET_TOWN has the door into REDS_HOUSE_1F", houseDoor ~= nil) + check("REDS_HOUSE_1F has an exit mat and the stairs up", + houseExit ~= nil and houseStairs ~= nil) + check("VIRIDIAN_CITY has the door into its POKéMON CENTER", centerDoor ~= nil) + + -- ---- helpers ------------------------------------------------------------ + local DIRS = { up = { 0, -1 }, down = { 0, 1 }, + left = { -1, 0 }, right = { 1, 0 } } + local ORDER = { "down", "up", "left", "right" } + + local function pressStep(dir) + table.insert(game.input.pressQueue, dir) + game.input.state[dir] = true + U.wait(1) + end + + local function stand(mapId, x, y, facing) + U.teleport(game, mapId, x, y, facing) + U.wait(15) + return game.overworld + end + + -- the cell you stand on to face (cx, cy) from `dir`, i.e. one step back + local function cellBehind(cx, cy, dir) + local d = DIRS[dir] + return cx - d[1], cy - d[2] + end + + -- first walkable free neighbour of (cx, cy), plus the facing that looks at + local function approach(ow, cx, cy) + for _, dir in ipairs(ORDER) do + local sx, sy = cellBehind(cx, cy, dir) + if ow.map:isWalkableCell(sx, sy) and not ow:npcAtCell(sx, sy) then + return sx, sy, dir + end + end + end + + -- any free walkable cell on the map with a solid one next to it, so the + local function findWall(ow) + local map = ow.map + for cy = 0, map.heightCells - 1 do + for cx = 0, map.widthCells - 1 do + if map:isWalkableCell(cx, cy) and not map:warpAtCell(cx, cy) + and not ow:npcAtCell(cx, cy) then + for _, d in ipairs(ORDER) do + local dd = DIRS[d] + if not map:isWalkableCell(cx + dd[1], cy + dd[2]) then + return cx, cy, d + end + end + end + end + end + end + + local function npcNamed(ow, name) + for _, n in ipairs(ow.npcs or {}) do + if n.def and n.def.name == name then return n end + end + end + + -- PlayMapChangeSound (home/overworld.asm:690) plays before GBFadeOutToBlack, + local function takeDoor(dir, want, label) + local mark = #cues + local from = game.overworld.map.id + local cue, switched + for _ = 1, 300 do + if cue or switched then + game.input.state[dir] = false + U.wait(1) + else + pressStep(dir) + end + cue = cue or heard(mark, want) + local ow = game.overworld + if not switched and ow and ow.map.id ~= from then switched = U.frame() end + if cue and switched then break end + end + game.input.state[dir] = false + U.wait(50) -- PlayerStepOutFromDoor walks off the mat before anything else + if not (cue and switched) then + check(("%s: %s played and the map changed"):format(label, want), false) + U.log(" cues on the way through:", since(mark)) + return + end + check(("%s: %s fired %d frames before the map switched (the fade is %d)") + :format(label, want, switched - cue.frame, FADE), + switched - cue.frame >= FADE - 8) + end + + -- CollisionCheckOnLand (home/overworld.asm): a sprite in the way takes the + local function bumpInto(dir, label) + local mark = #cues + for _ = 1, 10 do pressStep(dir) end + game.input.state[dir] = false + U.wait(24) -- past the 16-frame bumpCooldown, so the next bump is its own + check(label .. " rings Collision", heard(mark, "Collision") ~= nil) + end + + local function topIs(class) + local top = game.stack:top() + return top ~= nil and getmetatable(top) == class + end + local function rowIndex(menu, label) + for i, item in ipairs(menu.items or {}) do + if item.label == label then return i end + end + end + -- rows come and go with save state and the ui.pc.items hook, so pick each + local function choose(menu, label) + local i = rowIndex(menu, label) + if not i then + check("the menu has a " .. label .. " row", false) + return false + end + menu.index = i + menu:clampScroll() + U.wait(2) + U.tap(game, "a") + U.wait(26) + return true + end + local function mash(cond, tries) + for _ = 1, tries or 40 do + if cond() then return true end + U.tap(game, "a") + U.wait(6) + end + return cond() + end + + game.save.party = { Pokemon.new(game.data, "BULBASAUR", 20) } + game.save.party[1].moves = { + { id = "TACKLE", pp = 35, maxPP = 35 }, + { id = "VINE_WHIP", pp = 10, maxPP = 10 }, + } + Boxes.ensure(game.save) + game.save.currentBox = 1 + + -- ---- the door into the house, from outside (#961) ----------------------- + if houseDoor then + local sx, sy = cellBehind(houseDoor.x, houseDoor.y, "up") + stand("PALLET_TOWN", sx, sy, "up") + takeDoor("up", "Go_Inside", "walking into RED's house") + end + + -- walking into MOM, and into a wall (#960); teleport is only a fallback + local ow = game.overworld + if ow.map.id ~= "REDS_HOUSE_1F" and houseExit then + ow = stand("REDS_HOUSE_1F", houseExit.x, houseExit.y, "up") + end + local mom = npcNamed(ow, "REDSHOUSE1F_MOM") + check("MOM is loaded on REDS_HOUSE_1F", mom ~= nil) + if mom then + local sx, sy, dir = approach(ow, mom.cellX, mom.cellY) + if sx then + ow = stand("REDS_HOUSE_1F", sx, sy, dir) + -- the teleport rebuilt the npc list, so pin her on the state we bump + local pinned = npcNamed(ow, "REDSHOUSE1F_MOM") + if pinned then pinned.frozen = true end + U.shot(game, DIR .. "/bug960_mom.png") + bumpInto(dir, "walking into MOM") + else + check("MOM has a free cell to be walked into from", false) + end + -- the control: a wall bump was audible before #960 too, so silence here + local wx, wy, wdir = findWall(game.overworld) + if wdir then + ow = stand("REDS_HOUSE_1F", wx, wy, wdir) + bumpInto(wdir, "walking into the wall to the " .. wdir) + else + check("REDS_HOUSE_1F has a wall to bump into", false) + end + end + + -- ---- the stairs up (#961) ----------------------------------------------- + if houseStairs then + local sx, sy = cellBehind(houseStairs.x, houseStairs.y, "up") + stand("REDS_HOUSE_1F", sx, sy, "up") + -- the destination decides the cue here, not the tile underfoot, so an + takeDoor("up", "Go_Inside", "taking the stairs up") + end + + -- ---- the bedroom PC (#960) --------------------------------------------- + if bedroomPC then + -- OpenRedsPC's hidden_event is gated on SPRITE_FACING_UP, so the cell + local sx, sy = cellBehind(bedroomPC.x, bedroomPC.y, "up") + ow = stand("REDS_HOUSE_2F", sx, sy, "up") + check("the cell below the bedroom PC is walkable", + ow.map:isWalkableCell(sx, sy)) + local mark = #cues + U.tap(game, "a") + U.wait(26) + check("A on the bedroom PC opens a menu", topIs(Menu)) + check("...and turns it on with Turn_On_PC", heard(mark, "Turn_On_PC") ~= nil) + U.shot(game, DIR .. "/bug960_bedroom_pc.png") + if topIs(Menu) then + mark = #cues + choose(game.stack:top(), Strings("LOG OFF")) + check("LOG OFF on the bedroom PC rings Turn_Off_PC (#960)", + heard(mark, "Turn_Off_PC") ~= nil) + U.log(" cues:", since(mark)) + check("...and the PC closed", game.stack:top() == game.overworld) + end + -- B out of the same menu is ExitPlayerPC's other entry and rings it too + U.tap(game, "a") + U.wait(26) + if topIs(Menu) then + local mark2 = #cues + U.tap(game, "b") + U.wait(26) + check("backing out of the bedroom PC with B rings it as well", + heard(mark2, "Turn_Off_PC") ~= nil) + end + end + + -- ---- back out of the house, to the street (#961) ------------------------ + if houseExit then + local sx, sy = cellBehind(houseExit.x, houseExit.y, "down") + ow = stand("REDS_HOUSE_1F", sx, sy, "down") + if not ow.map:isWalkableCell(sx, sy) then + U.log(("(%d, %d) is blocked; using the other half of the mat") + :format(sx, sy)) + ow = stand("REDS_HOUSE_1F", sx + 1, sy, "down") + end + takeDoor("down", "Go_Outside", "stepping out onto the street") + end + + -- ---- into the POKéMON CENTER, for the PC main menu ----------------------- + if centerDoor then + local sx, sy = cellBehind(centerDoor.x, centerDoor.y, "up") + stand("VIRIDIAN_CITY", sx, sy, "up") + takeDoor("up", "Go_Inside", "walking into the POKéMON CENTER") + end + + -- ---- the PC main menu: Enter_PC, and the silence under it (#960) -------- + if centerPC then + local sx, sy = cellBehind(centerPC.x, centerPC.y, "up") + ow = stand("VIRIDIAN_POKECENTER", sx, sy, "up") + for _, n in ipairs(ow.npcs or {}) do n.frozen = true end -- the GENTLEMAN walks + local mark = #cues + U.tap(game, "a") + U.wait(26) + check("A on the Center PC opens the PC main menu", topIs(Menu)) + check("...with Turn_On_PC", heard(mark, "Turn_On_PC") ~= nil) + + local mine = (game.save.player.name or "RED") .. "'s PC" + if topIs(Menu) then + mark = #cues + choose(game.stack:top(), mine) + check(mine .. " rings Enter_PC (#960)", heard(mark, "Enter_PC") ~= nil) + check("...and the item PC opened", topIs(Menu)) + -- BIT_USING_GENERIC_PC: reached this way, ExitPlayerPC is silent and + mark = #cues + U.tap(game, "b") + U.wait(26) + check("backing out of it again is silent, as the ROM is", + heard(mark, "Turn_Off_PC") == nil) + U.log(" cues:", since(mark)) + end + + -- ---- CHANGE BOX (#1044) ---------------------------------------------- + -- the box PC reads SOMEONE'S PC until EVENT_MET_BILL (pokemon_pc.asm) + local flags = game.save.flags or {} + local boxPC = (flags.EVENT_MET_BILL or flags.EVENT_GOT_SS_TICKET) + and "BILL'S PC" or Strings("SOMEONE'S PC") + if topIs(Menu) then + mark = #cues + choose(game.stack:top(), boxPC) + check(boxPC .. " rings Enter_PC too", heard(mark, "Enter_PC") ~= nil) + check("...and the box menu opened", topIs(Menu)) + end + if topIs(Menu) and rowIndex(game.stack:top(), Strings("CHANGE BOX")) then + choose(game.stack:top(), Strings("CHANGE BOX")) + U.shot(game, DIR .. "/bug1044_change_box.png") + local before = game.save.currentBox + U.tap(game, "down") -- BOX 1 is the current one, so move off it + U.wait(8) + mark = #cues + -- A picks the box, then the "data will be saved" prompt and its YES + U.tap(game, "a") + U.wait(20) + mash(function() return game.save.currentBox ~= before end, 40) + U.wait(60) -- the 15-frame answer hold, then the write + check(("CHANGE BOX switched box %s -> %s") + :format(tostring(before), tostring(game.save.currentBox)), + game.save.currentBox ~= before) + check("...and rang the SAVE jingle (#1044)", heard(mark, "Save") ~= nil) + U.log(" cues:", since(mark)) + end + + -- ---- LOG OFF from the main menu -------------------------------------- + for _ = 1, 6 do + if game.stack:top() == game.overworld then break end + U.tap(game, "b") + U.wait(20) + end + U.tap(game, "a") + U.wait(26) + if topIs(Menu) and rowIndex(game.stack:top(), Strings("LOG OFF")) then + local mark2 = #cues + choose(game.stack:top(), Strings("LOG OFF")) + check("LOG OFF on the PC main menu rings Turn_Off_PC", + heard(mark2, "Turn_Off_PC") ~= nil) + end + for _ = 1, 8 do + if game.stack:top() == game.overworld then break end + U.tap(game, "b") + U.wait(20) + end + end + + -- the battle menu click (#1045) already landed in HEAD 12c2677; confirmation only + ow = stand("ROUTE_1", 5, 5, "down") + if not ow.map:isWalkableCell(5, 5) then + local fx, fy + for cy = 0, ow.map.heightCells - 1 do + for cx = 0, ow.map.widthCells - 1 do + if ow.map:isWalkableCell(cx, cy) then fx, fy = cx, cy break end + end + if fx then break end + end + if fx then ow = stand("ROUTE_1", fx, fy, "down") end + end + local battle = BattleState.newWild(game, "SNORLAX", 50) + battle.onFinish = function(result) ow:afterBattle(result, battle) end + -- SPLASH so the foe's turn can never end the run under the menu presses + battle.enemy.mon.moves = { { id = "SPLASH", pp = 40, maxPP = 40 } } + battle.enemy.curMoves = battle.enemy.mon.moves + ow:pushBattle(battle) + U.wait(220) -- the send-out intro runs before the menu is reachable + mash(function() return battle.phase == "menu" end, 60) + check("the wild SNORLAX battle reached its FIGHT menu", + battle.phase == "menu") + + -- one press, then long enough for the click to be its own sound + local function click(btn, label, want) + local mark = #cues + U.tap(game, btn) + U.wait(28) + local n = countOf(mark, "Press_AB") + check(("%s: %s gave %d click%s, expected %d") + :format(label, btn:upper(), n, n == 1 and "" or "s", want), + n == want) + end + if battle.phase == "menu" then + click("a", "A on FIGHT (#1045a)", 1) + check("...and the move list opened", battle.phase == "moveSelect") + U.shot(game, DIR .. "/bug1045_move_list.png") + click("b", "B out of the move list (#1045c)", 1) + check("...and the FIGHT menu came back", battle.phase == "menu") + click("a", "A on FIGHT again", 1) + click("a", "A on a move (#1045b)", 1) + mash(function() return battle.phase == "menu" end, 120) + if battle.phase == "menu" then + battle.menuIndex = 4 -- RUN shares the FIGHT/PKMN/ITEM call site + U.wait(4) + click("a", "A on RUN", 1) + end + end + U.log(("machine checks: %d passed, %d failed"):format(pass, fail)) + + -- ---- over to you -------------------------------------------------------- + if centerPC then + local sx, sy = cellBehind(centerPC.x, centerPC.y, "up") + ow = stand("VIRIDIAN_POKECENTER", sx, sy, "up") + for _, n in ipairs(ow.npcs or {}) do n.frozen = true end + end + U.log("Everything above has been pressed once already; you are parked at the") + U.log("Viridian POKéMON CENTER PC to do it again by ear.") + U.log("A opens the PC main menu. RED's PC clicks in on the two-note ENTER PC") + U.log("chirp and LOG OFF closes with the descending power-down. B out of RED's") + U.log("PC is silent on purpose; a power-down there is the near miss, not a pass.") + U.log("SOMEONE'S PC, CHANGE BOX, any other box, YES: the SAVE jingle rings") + U.log("after the box has changed, the same jingle the SAVE menu plays. A") + U.log("jingle before the switch, or none at all, is #1044 back.") + U.log("The bedroom PC upstairs at home is the other half of #960: it beeps on,") + U.log("and LOG OFF or B rings the power-down there. That one is not silent.") + U.log("The COOLTRAINER at (4,3) and the NURSE at (3,1) are pinned; walk into") + U.log("either and it thuds like a wall. Walking into a wall is the control --") + U.log("it always thudded, so a silent wall means the device, not the fix.") + U.log(("Walk out over the exit mat at the bottom of the room: the door sound") + .. (" starts as the screen begins to darken, %d frames ahead of") + :format(FADE)) + U.log("Viridian City. One that lands on the new map, or after it, is #961.") + U.log("In any battle, A on FIGHT/PKMN/ITEM/RUN, A on a move and B out of the") + U.log("move list all click. Silence on any of the three is #1045.") + U.log("Shots: " .. DIR .. "/bug960_*.png, bug1044_*.png, bug1045_*.png") + + -- keeps naming cues with their frame after the hand-off, so a door sound + local reported = #cues + while true do + if #cues > reported then + for i = reported + 1, #cues do + U.log("cue", cues[i].name, "frame", cues[i].frame) + end + reported = #cues + end + coroutine.yield() + end +end diff --git a/tests/drivers/move_swap_arrow_bug814_test.lua b/tests/drivers/move_swap_arrow_bug814_test.lua new file mode 100644 index 00000000..07cc76a1 --- /dev/null +++ b/tests/drivers/move_swap_arrow_bug814_test.lua @@ -0,0 +1,96 @@ +-- Driver: the swap cursor in the FIGHT move list (#814). pokered +-- SelectMenuItem parks the hollow arrow 0xEC on the marked row (engine/battle/ +-- core.asm:2600-2607) and HandleMenuInput's PlaceMenuCursor writes the filled +-- arrow 0xED into the tilemap over it whenever the cursor sits there +-- (home/window.asm:184-185), so the row under the cursor is always filled. +-- Font.drawCode blits black-on-transparent, so drawing both glyphs on one cell +-- merged the two arrows. Eye check, no SPEED. +-- POKEPORT_DRIVER=tests/drivers/move_swap_arrow_bug814_test.lua \ +-- POKEPORT_IDENTITY=bug814 POKEPORT_TOUCH=0 POKEPORT_VERSION=red \ +-- SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + game.save.player.name = "bryan" + -- CHARIZARD at 50 knows four moves, so marking row 1 and parking the cursor + -- on row 2 leaves both arrows on screen at once + game.save.party = { Pokemon.new(game.data, "CHARIZARD", 50) } + local lead = game.save.party[1] + check("the lead knows at least 3 moves (needs rows to scroll between)", + #lead.moves >= 3) + + U.teleport(game, "PALLET_TOWN", 10, 8, "down") + U.wait(20) + local ow = game.overworld + check("overworld is up to push the battle from", ow ~= nil) + + local battle = BattleState.newWild(game, "PIDGEY", 8) + battle.onFinish = function() end + ow:pushBattle(battle) + + local function tapUntil(cond, taps, gap) + for _ = 1, (taps or 60) do + if cond() then return true end + U.tap(game, "a") + for _ = 1, (gap or 6) do + if cond() then return true end + U.wait(1) + end + end + return cond() + end + + U.wait(220) -- the send-out intro plays before the menu is reachable + check("reached the FIGHT/PKMN/ITEM/RUN menu", + tapUntil(function() return battle.phase == "menu" end, 120)) + check("cursor starts on FIGHT", battle.menuIndex == 1) + + U.tap(game, "a") + U.wait(20) + check("the FIGHT move list is open (#814 lives on this screen)", + battle.phase == "moveSelect") + check("cursor starts on move 1", battle.moveIndex == 1) + + -- SELECT marks the move under the cursor for swapping + U.tap(game, "select") + U.wait(10) + check("SELECT marked move 1 (moveSwapIndex == 1)", + battle.moveSwapIndex == 1) + + -- cursor down to row 2: hollow arrow stays on row 1, filled follows to row 2 + U.tap(game, "down") + U.wait(10) + check("cursor moved to move 2", battle.moveIndex == 2) + check("the mark stayed on move 1", battle.moveSwapIndex == 1) + check("split-arrow screenshot reached disk", + U.shot(game, DIR .. "/bug814_marked_row1_cursor_row2.png")) + U.log("captured", DIR .. "/bug814_marked_row1_cursor_row2.png") + + -- cursor back up onto the marked row: this is the shot the fix is judged + -- on -- the shared cell must show only the filled arrow + U.tap(game, "up") + U.wait(10) + check("cursor is back on move 1", battle.moveIndex == 1) + check("the mark is still on move 1", battle.moveSwapIndex == 1) + check("cursor-on-marked-row screenshot reached disk", + U.shot(game, DIR .. "/bug814_cursor_on_marked_row.png")) + U.log("captured", DIR .. "/bug814_cursor_on_marked_row.png") + + -- ---- hand off ---------------------------------------------------------- + U.log("Move 1 is marked for swap and the cursor sits on it. That row wants") + U.log("one solid black arrow only; a hollow outline there, or a smudge of") + U.log("both shapes, is the bug (#814). Scroll DOWN and the hollow arrow") + U.log("should reappear on row 1 while the solid one follows the cursor.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/online_match_host.lua b/tests/drivers/online_match_host.lua index 8e9be6de..0a6c9123 100644 --- a/tests/drivers/online_match_host.lua +++ b/tests/drivers/online_match_host.lua @@ -55,8 +55,12 @@ return function(game) U.wait(10) -- the GAME SPEED forcing under test: a link session pins the logic clock - -- to 1X no matter what the option or POKEPORT_SPEED says - game.save.options.speed = 10 + -- to 1X no matter what the option or POKEPORT_SPEED says. RFC 0007: set + -- all three per-category speeds high, since the link lock has to win over + -- every one of them, not just whichever category happens to be active. + game.save.options.speedOverworld = 10 + game.save.options.speedBattle = 10 + game.save.options.speedMenu = 10 U.wait(2) log("logicSpeed with GAME SPEED=10 during link:", game:logicSpeed()) diff --git a/tests/drivers/online_match_join.lua b/tests/drivers/online_match_join.lua index 48f8545d..31d3a593 100644 --- a/tests/drivers/online_match_join.lua +++ b/tests/drivers/online_match_join.lua @@ -55,7 +55,11 @@ return function(game) local link = LinkState.new(game) game.stack:push(link) U.wait(10) - game.save.options.speed = 20 + -- RFC 0007: set all three per-category speeds high, since the link lock + -- has to win over every one of them, not just whichever is active. + game.save.options.speedOverworld = 20 + game.save.options.speedBattle = 20 + game.save.options.speedMenu = 20 U.wait(2) log("logicSpeed with GAME SPEED=20 during link:", game:logicSpeed()) diff --git a/tests/drivers/party_cursor_bug768_test.lua b/tests/drivers/party_cursor_bug768_test.lua new file mode 100644 index 00000000..b39e39a7 --- /dev/null +++ b/tests/drivers/party_cursor_bug768_test.lua @@ -0,0 +1,76 @@ +-- Manual check for #768: the field party menu keeps its cursor across +-- close/reopen (wPartyAndBillsPCSavedMenuItem: PartyMenuInit reads it, +-- HandlePartyMenuInput writes it back, only a battle zeroes it via +-- InitBattleVariables / end_of_battle.asm), and the per-mon submenu lists +-- field moves ABOVE STATS/SWITCH (DisplayFieldMoveMonMenu prints the move +-- names above PokemonMenuEntries, engine/menus/text_box.asm). +-- POKEPORT_DRIVER=tests/drivers/party_cursor_bug768_test.lua POKEPORT_IDENTITY=bug768 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local Screens = require("src.ui.Screens") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- Slot 1 knows FLY (movesAtLevel never grants it, so inject it, same as + -- the #203 driver); the badge gates the submenu entry. + local flyer = Pokemon.new(game.data, "PIDGEOT", 40) + flyer.moves[1] = { id = "FLY", pp = 15 } + game.save.party = { + flyer, + Pokemon.new(game.data, "PIKACHU", 30), + Pokemon.new(game.data, "SNORLAX", 77), + } + game.save.player.name = "bryan" + game.save.inventory = game.save.inventory or {} + game.save.inventory.THUNDERBADGE = true + + -- Pallet Town is OVERWORLD, so FLY is listed (CheckIfInOutsideMap) + U.teleport(game, "PALLET_TOWN", 10, 8, "down") + U.wait(10) + + -- half 1: the submenu puts the field move on top + Screens.push(game, "PartyMenu") + U.wait(5) + local pm = game.stack:top() + U.tap(game, "a") -- open the per-mon submenu on the FLY mon + U.wait(2) + local items = pm.subItems or {} + check("submenu row 1 is FLY, not STATS", + items[1] ~= nil and items[1].action == "fly") + check("STATS/SWITCH close the list under the field move", + #items == 3 and items[2].action == "stats" + and items[3].action == "switch") + U.shot(game, DIR .. "/bug768_submenu.png") + U.tap(game, "b") -- back out of the submenu + U.wait(2) + + -- half 2: the cursor survives closing and reopening the menu + U.tap(game, "down") + U.wait(2) + U.tap(game, "down") + U.wait(2) + check("cursor moved to slot 3", pm.index == 3) + U.tap(game, "b") -- close the party menu entirely + U.wait(5) + Screens.push(game, "PartyMenu") + U.wait(5) + local pm2 = game.stack:top() + check("reopened menu is still on slot 3 (SNORLAX)", + pm2 ~= pm and pm2.index == 3) + U.shot(game, DIR .. "/bug768_reopened.png") + + U.log("The party menu on screen was just reopened; the cursor should sit") + U.log("on slot 3 (SNORLAX), not slot 1. A on slot 1 shows FLY above") + U.log("STATS/SWITCH. Input is yours now: walk north into the Route 1") + U.log("grass, win or run from a wild battle, then reopen the party menu --") + U.log("the cursor should be back on slot 1 (the battle cleared it).") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/pika_entrance_cry_bug837_test.lua b/tests/drivers/pika_entrance_cry_bug837_test.lua new file mode 100644 index 00000000..e98e7d20 --- /dev/null +++ b/tests/drivers/pika_entrance_cry_bug837_test.lua @@ -0,0 +1,172 @@ +-- Manual check that a Pikachu taking the field says the short "Pika!" (#837). +-- pokeyellow engine/battle/core.asm SendOutMon .starterPikachu (:1807-1817) +-- voices PikachuCry11, or PikachuCry37 when IsPlayerPikachuAsleepInParty; the +-- port called PlayCry bare and got clip 1, the long title "Pikachuuu" +-- (engine/movie/title.asm:146). Never add POKEPORT_SPEED here: it scales the +-- logic clock and not audio, so the cries stop lining up with what you see. +-- POKEPORT_DRIVER=tests/drivers/pika_entrance_cry_bug837_test.lua POKEPORT_IDENTITY=bug837 POKEPORT_TOUCH=0 POKEPORT_VERSION=yellow love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local Sound = require("src.core.Sound") + local GameVersion = require("src.core.GameVersion") + local BattleState = require("src.battle.BattleState") + + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + local function idle() + while true do coroutine.yield() end + end + + -- Red and Blue carry no PCM clips at all (RomExtractor extractPikachuCries + -- only runs on Yellow), so playPikaCry returns nil there and every Pikachu + -- keeps its chip cry from CryData: nothing below is observable off Yellow. + local audio = game.data.audio + local clips = audio and audio.pikaCries + local onYellow = check("running Yellow", GameVersion.isYellow()) + local haveClips = check("the cache carries the PCM clip set", + type(clips) == "number") + if not (onYellow and haveClips) then + U.log("Import a Yellow ROM and rerun with POKEPORT_VERSION=yellow. On Red") + U.log("and Blue there is no voiced Pikachu to get wrong.") + idle() + end + -- NUM_PIKA_CRIES is 42 (pokeyellow constants/music_constants.asm), and the + -- importer writes cry_01..cry_42.wav in PikachuCriesPointerTable order, so + -- clip 37 existing is what makes the asleep case reachable at all. + check("clip 37 fits inside the " .. tostring(clips) .. " clips extracted", + clips >= 37) + + -- resolve the two asset keys for real: a missing or unreadable wav makes + -- playPikaCry return nil and the cry falls through to the chip cry, which + -- is a different wrong sound from the one this issue is about. Stopped in + -- the same frame, so neither is audible here. + local function resolves(n) + local src = Sound.playPikaCry(game.data, n) + if src then src:stop() end + return src ~= nil + end + check("pika_cries/cry_11.wav loads", resolves(11)) + check("pika_cries/cry_37.wav loads", resolves(37)) + + local opts = game.save.options or {} + check("sfxVol is not muted (it reads " .. tostring(opts.sfxVol) .. ")", + (opts.sfxVol or 0) > 0) + check("PIKACHU VOL is not muted (it reads " .. tostring(opts.pikaVol) .. ")", + (opts.pikaVol or 0) > 0) + + -- Sound.playPikaCry emits "sound.played" with name = "PIKACHU_PCM_"; + -- this is the same feed mods read and tests/mod_audio_tests.lua subscribes + -- to, so the number below is the clip the engine actually asked for. + local heardCries = {} + local events = game.mods and game.mods.events + if not check("the sound.played feed is live", events ~= nil and events.on ~= nil) then + idle() + end + events:on("sound.played", function(p) + if p and p.kind == "cry" then heardCries[#heardCries + 1] = p.name end + end, nil, "bug837driver") + + game.save.party = { + Pokemon.new(game.data, "PIKACHU", 20), + Pokemon.new(game.data, "CHARMANDER", 20), + } + game.save.player.name = "RED" + check("PIKACHU leads the party", game.save.party[1].species == "PIKACHU") + + -- Route 1 so the handoff below has tall grass in reach. The route sign + -- sits at (9, 27) (pokered data/maps/objects/Route1.asm bg_event), and the + -- cell under it is the open path you read it from. + local MAP = "ROUTE_1" + local STAND = { x = 9, y = 28, facing = "up" } + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(10) + local ow = game.overworld + if not check("the overworld is up on " .. MAP, ow ~= nil) then idle() end + + -- a map edit or a mod can wall that cell off; widen out to any free + -- walkable neighbour rather than stranding the player inside scenery + local function freeNear(map, x, y) + for r = 1, 6 do + for dy = -r, r do + for dx = -r, r do + local cx, cy = x + dx, y + dy + if map:inBounds(cx, cy) and map:isWalkableCell(cx, cy) + and not ow:npcAtCell(cx, cy) then + return cx, cy + end + end + end + end + return nil + end + if not ow.map:isWalkableCell(STAND.x, STAND.y) then + local cx, cy = freeNear(ow.map, STAND.x, STAND.y) + if cx then + U.log(("(%d, %d) is blocked, standing on"):format(STAND.x, STAND.y), + cx, cy) + U.teleport(game, MAP, cx, cy, STAND.facing) + U.wait(10) + ow = game.overworld + end + end + check("the player is standing somewhere walkable", + ow.map:isWalkableCell(ow.player.cellX, ow.player.cellY)) + + -- Push the encounter rather than walking into grass: the cry under test is + -- the player's own send-out, and a stepped encounter would put the wild + -- rolls and a second cry ahead of it. RATTATA keeps the enemy's cry a chip + -- cry, so it can never be confused with the PCM clip being checked. + local function runEntrance(asleep, label, shotPath) + for i = #heardCries, 1, -1 do heardCries[i] = nil end + game.save.party[1].status = asleep and "SLP" or nil + local wild = BattleState.newWild(game, "RATTATA", 3) + wild.onFinish = function() end + game.overworld:pushBattle(wild) + local pcm + for _ = 1, 500 do + for _, name in ipairs(heardCries) do + if name:find("PIKACHU_PCM_", 1, true) == 1 then pcm = name end + end + if pcm then break end + U.tap(game, "a") + U.wait(3) + end + U.log(label .. " recorded:", table.concat(heardCries, ", ")) + if shotPath then U.shot(game, shotPath) end + return pcm, wild + end + + -- asleep first, so the run ends on the everyday case and what is ringing + -- during the handoff is the clip the issue is really about + local slept = runEntrance(true, "asleep send-out", + DIR .. "/bug837_1_asleep.png") + check("an asleep PIKACHU is sent out with PCM clip 37 (PikachuCry37)", + slept == "PIKACHU_PCM_37") + + U.teleport(game, MAP, ow.player.cellX, ow.player.cellY, STAND.facing) + U.wait(10) + local awake = runEntrance(false, "awake send-out", + DIR .. "/bug837_2_awake.png") + check("a healthy PIKACHU is sent out with PCM clip 11 (PikachuCry11)", + awake == "PIKACHU_PCM_11") + check("clip 1, the long title-screen cry, is not what was played", + awake ~= "PIKACHU_PCM_1") + + U.log("You are in the second battle, the one with PIKACHU awake. The cry") + U.log("as it grew out of the ball should be the short bright \"Pika!\", the") + U.log("same one you hear pressing START on the Yellow title screen. The bug") + U.log("played the other title cry instead: the long drawn-out \"Pikachuuu\"") + U.log("that opens the title, roughly a second and a half of it, which is") + U.log("easy to miss as merely slow rather than wrong. Run away and walk") + U.log("into the grass north of here for as many more send-outs as you like;") + U.log("put PIKACHU to sleep and it turns into the sleepy clip 37 instead.") + U.log("Screenshots of both entrances are in " .. DIR .. ".") + + idle() +end diff --git a/tests/drivers/pikachu_lab_debut_bug1009_bug1021_test.lua b/tests/drivers/pikachu_lab_debut_bug1009_bug1021_test.lua new file mode 100644 index 00000000..963c311d --- /dev/null +++ b/tests/drivers/pikachu_lab_debut_bug1009_bug1021_test.lua @@ -0,0 +1,410 @@ +-- Pikachu stays in its ball until the rival fight (#1009) and clears (4,3) for the rival (#1021); the Route 15 leg only instruments #920. +-- pokeyellow scripts/OaksLab.asm, OaksLab_2.asm. +return function(game) + local U = dofile("tests/drivers/util.lua") + local GameVersion = require("src.core.GameVersion") + local MapScripts = require("src.script.MapScripts") + local Commands = require("src.script.Commands") + local PF = require("src.world.PikachuFollower") + local Pokemon = require("src.pokemon.Pokemon") + local Bag = require("src.inventory.Bag") + local BattleState = require("src.battle.BattleState") + + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local LAB = "OAKS_LAB" + local RIVAL = 1 -- OAKSLAB_RIVAL, object index 1 + local GIFT = { x = 5, y = 3 } -- where OaksLabRLE_PlayerWalksToOak ends + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + local function idle() + while true do coroutine.yield() end + end + local function ow() return game.overworld end + local function follower() return PF.current(game.overworld) end + local function ctx() + return { game = game, save = game.save, overworld = game.overworld } + end + local function where(npc) + if not npc then return "gone" end + return "(" .. npc.cellX .. "," .. npc.cellY .. ") facing " + .. tostring(npc.facing) + end + + -- one player step; false when refused, so a caller can route around a map edit + local function stepOnce(dir) + local p = ow().player + local x0, y0 = p.cellX, p.cellY + for _ = 1, 60 do + table.insert(game.input.pressQueue, dir) + game.input.state[dir] = true + coroutine.yield() + p = ow().player + if p.cellX ~= x0 or p.cellY ~= y0 then break end + end + game.input.state[dir] = false + for _ = 1, 40 do + if not ow().player.moving then break end + U.wait(1) + end + U.wait(3) + p = ow().player + return p.cellX ~= x0 or p.cellY ~= y0 + end + + -- ---------------------------------------------------------------- checks + if not check("running the Yellow cache (POKEPORT_VERSION=yellow)", + GameVersion.isYellow()) then + U.log("Red and Blue have no follower and no Yellow lab script, so every") + U.log("line below would fail for the wrong reason.") + idle() + end + check("SPRITE_PIKACHU resolves in the sprite table", + game.data.sprites ~= nil and game.data.sprites.SPRITE_PIKACHU ~= nil) + check("PikachuFollower.oaksLabMakeWay exists", + type(PF.oaksLabMakeWay) == "function") + check("the pikachu_make_way verb exists", + type(Commands.pikachu_make_way) == "function") + check("and it blocks the runner while the walk plays", + Commands.meta.pikachu_make_way ~= nil + and Commands.meta.pikachu_make_way.blocking == true) + + local lab = require("data.scripts.oaks_lab_yellow") + local oakRows = lab.talk.TEXT_OAKSLAB_OAK1 + local function rowIndex(pred) + for i, r in ipairs(oakRows) do + if pred(r) then return i end + end + return nil + end + local iGramps = rowIndex(function(r) + return r[1] == "show_text" and r[2] == "_OaksLabRivalGrampsText" + end) + local iMakeWay = rowIndex(function(r) return r[1] == "pikachu_make_way" end) + local iShow = rowIndex(function(r) + return r[1] == "show_object" and r[3] == "OAKSLAB_RIVAL" + end) + check("Oak's parcel branch has a make-way row", iMakeWay ~= nil) + -- the callfar sits after the GRAMPS text and before ShowObject; a row on + check("it sits between the GRAMPS text and the rival's ShowObject", + iGramps ~= nil and iMakeWay ~= nil and iShow ~= nil + and iGramps < iMakeWay and iMakeWay < iShow) + + local sfx = game.save.options and game.save.options.sfxVol + if sfx == 0 then + U.log("sfxVol is 0. Pikachu's cry as it bursts out of the ball will be") + U.log("silent and a mute run reads exactly like a missing cry -- turn") + U.log("the sound back up before judging the escape scene.") + end + + -- leg 1: in the ball. Level 30 only so the lab battle ends fast + game.save.flags = game.save.flags or {} + local flags = game.save.flags + flags.EVENT_GOT_STARTER = true + flags.EVENT_CHOSE_PIKACHU = true + flags.EVENT_FOLLOWED_OAK_INTO_LAB = true + flags.EVENT_FOLLOWED_OAK_INTO_LAB_2 = true + flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = nil + flags.EVENT_OAK_ASKED_TO_CHOOSE_MON = true + flags.EVENT_GOT_POKEDEX = nil + flags.EVENT_OAK_GOT_PARCEL = nil + game.save.party = { Pokemon.new(game.data, "PIKACHU", 30) } + game.save.player.name = "bryan" + game.save.onBike = false + game.save.pikachuInBall = true + + U.teleport(game, LAB, GIFT.x, GIFT.y, "up") + U.wait(10) + Commands.show_object(ctx(), LAB, "OAKSLAB_OAK1") + U.wait(5) + + -- asked after the teleport: the registry only fills on first require + check("the Yellow lab module is the one bound to the map", + MapScripts.talkScript(LAB, "TEXT_OAKSLAB_OAK1") == oakRows) + + check("straight after the gift there is no follower on the map", + follower() == nil) + + -- save compat: nil pikachuInBall falls back to the rival-fight flag, not false + game.save.pikachuInBall = nil + U.wait(10) + check("a pre-#1009 save before the rival fight still has none", + follower() == nil) + flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = true + U.wait(20) + check("a pre-#1009 save past the rival fight keeps its follower", + follower() ~= nil) + flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = nil + game.save.pikachuInBall = true + U.wait(20) + check("and the in-ball byte puts it away again", follower() == nil) + + U.shot(game, SHOT_DIR .. "/bug1009_in_ball.png") + U.log("captured", SHOT_DIR .. "/bug1009_in_ball.png", + "- player alone at the gift spot") + + -- --------------------------------------------- leg 2: out of the ball + Commands.show_object(ctx(), LAB, "OAKSLAB_RIVAL") + U.wait(5) + + -- OaksLabRivalChallengesPlayerScript fires from y >= 6 with the starter held + local walkedClean = true + for _ = 1, 10 do + if ow().player.cellY >= 6 then break end + if follower() then walkedClean = false end + if not stepOnce("down") then + if not stepOnce("left") then break end + end + end + check("crossed the lab to the door row with no follower behind", + walkedClean and follower() == nil) + check("reached the row the rival challenges from", ow().player.cellY >= 6) + + -- the escape only spawns the companion once the overworld is back on top + local sawBattle, escapedAt, spawnCell = false, nil, nil + for _ = 1, 2500 do + local o = game.overworld + local top = game.stack:top() + if top ~= o then + if getmetatable(top) == BattleState then sawBattle = true end + U.tap(game, "a") + U.wait(3) + else + local npc = follower() + if npc and not escapedAt then + escapedAt = U.frame() + spawnCell = { x = npc.cellX, y = npc.cellY, facing = npc.facing } + U.shot(game, SHOT_DIR .. "/bug1009_escaped.png") + end + if escapedAt and not o.runner:isRunning() and #o.scriptMoves == 0 then + break + end + U.wait(2) + end + end + + check("the rival battle actually ran", sawBattle) + check("the escape scene put a follower on the map", escapedAt ~= nil) + check("EVENT_BATTLED_RIVAL_IN_OAKS_LAB is set", + flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB == true) + check("the ball is open (save.pikachuInBall false, not nil)", + game.save.pikachuInBall == false) + if spawnCell then + local p = ow().player + U.log("it appeared at", spawnCell.x, spawnCell.y, "with the player at", + p.cellX, p.cellY, "facing", p.facing) + -- OaksLabPikachuEscapesPokeballScript faces the player up and uses spawn + check("it burst out on the cell behind the player, not beside him", + spawnCell.x == p.cellX and spawnCell.y == p.cellY + 1) + U.log("captured", SHOT_DIR .. "/bug1009_escaped.png") + end + + -- leg 3: Oak's .DeliverParcelText needs parcel held, no balls, no Pokedex + flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = true + flags.EVENT_PALLET_AFTER_GETTING_POKEBALLS = nil + flags.EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE = nil + flags.EVENT_GOT_POKEDEX = nil + game.save.pikachuInBall = false + game.save.inventory = game.save.inventory or {} + game.save.inventory.POKE_BALL = nil + if game.save.pokedex then game.save.pokedex.owned = {} end + Bag.add(game.save, "OAKS_PARCEL", 1, game.data) + + -- the SPRITE_FACING_LEFT case of TryApplyPikachuMovementData + U.teleport(game, LAB, 4, 3, "right") + U.wait(10) + Commands.show_object(ctx(), LAB, "OAKSLAB_OAK1") + -- he walked out after the lab battle; ShowObject brings him back mid-scene + Commands.hide_object(ctx(), LAB, "OAKSLAB_RIVAL") + U.wait(5) + check("the follower is back for the parcel scene", follower() ~= nil) + stepOnce("right") + -- Oak stands on (5,2) and blocks, so this only turns the player up + U.hold(game, "up", 10) + U.wait(10) + + local p = ow().player + local pika = follower() + U.log("player at", p.cellX, p.cellY, "facing", p.facing, + "| Pikachu at", where(pika)) + check("the player is below Oak on row 3", p.cellY == 3 and p.facing == "up") + local onRivalCell = pika ~= nil and pika.cellX == 4 and pika.cellY == 3 + check("Pikachu is standing on the rival's landing cell (4,3)", onRivalCell) + if not onRivalCell then + U.log("Without it on (4,3) the movement data does not apply and the") + U.log("scene below proves nothing about #1021.") + end + U.shot(game, SHOT_DIR .. "/bug1021_before.png") + + -- the rival's own walk sits in the same scriptMoves list, so ask only + local function stillWalking(o, npc) + if npc.moving then return true end + for _, mv in ipairs(o.scriptMoves) do + if mv.entity == npc then return true end + end + return false + end + + -- end pose snapshotted at walk stop; the idle roll turns it later (Func_fc803) + U.tap(game, "a") + local leftCell, rivalSeen, rivalArrived, midShot = nil, nil, nil, false + local settled + local trace, last = {}, nil + for f = 1, 1200 do + local o = game.overworld + local top = game.stack:top() + local npc = follower() + if npc then + local cell = npc.cellX .. "," .. npc.cellY + if cell ~= last then + last = cell + trace[#trace + 1] = "f" .. f .. " " .. cell + end + if not leftCell and not (npc.cellX == 4 and npc.cellY == 3) then + leftCell = f + end + if leftCell and not settled and not stillWalking(o, npc) then + settled = { x = npc.cellX, y = npc.cellY, facing = npc.facing } + end + end + local rival = o:npcByIndex(RIVAL) + if rival and not rivalSeen then rivalSeen = f end + if rival and not rivalArrived and rival.cellX == 4 and rival.cellY == 3 + and not rival.moving then + rivalArrived = f + end + if leftCell and rival and rival.moving and not midShot then + midShot = true + U.shot(game, SHOT_DIR .. "/bug1021_stepped_aside.png") + end + if rivalArrived and settled then break end + if top ~= o then U.tap(game, "a") end + U.wait(2) + end + + U.log("Pikachu trace:", table.concat(trace, " | ")) + check("Pikachu moved off (4,3)", leftCell ~= nil) + check("the rival came in and reached (4,3)", rivalArrived ~= nil) + if leftCell and rivalSeen then + -- the callfar runs before ShowObject: the cell is clear before the rival + check("it started clearing the cell before the rival appeared", + leftCell <= rivalSeen) + end + if settled then + U.log("the walk ended with Pikachu at", settled.x, settled.y, + "facing", settled.facing) + end + -- OaksLabPikachuMovementData2: STEP_DOWN, STEP_RIGHT, LOOK_UP + check("it ended one below the player looking up", + settled ~= nil and settled.x == 5 and settled.y == 4 + and settled.facing == "up") + U.shot(game, SHOT_DIR .. "/bug1021_rival_in_place.png") + + -- leg 4: nothing is fixed for #920; this only records ow.npcs and trail.ledgeHop + local eastMap = game.data.maps.FUCHSIA_CITY + and game.data.maps.FUCHSIA_CITY.connections + and game.data.maps.FUCHSIA_CITY.connections.east + eastMap = eastMap and eastMap.map or "ROUTE_15" + U.log("Fuchsia's east connection is", eastMap) + + U.teleport(game, "FUCHSIA_CITY", 10, 12, "down") + U.wait(10) + local city = ow().map + local row = nil + for y = 0, city.heightCells - 1 do + if city:isWalkableCell(city.widthCells - 1, y) + and city:isWalkableCell(city.widthCells - 2, y) then + row = row or y + end + end + if row then + U.log("crossing the east seam on row", row) + U.teleport(game, "FUCHSIA_CITY", city.widthCells - 2, row, "right") + U.wait(10) + for _ = 1, 6 do + if ow().map.id == eastMap then break end + if not stepOnce("right") then break end + end + end + -- the seam row is blocked (gate rebuild, mod): drop in on the route itself + local function dropOnRoute() + U.teleport(game, eastMap, 4, 8, "right") + U.wait(10) + local m = ow().map + if m:isWalkableCell(4, 8) then return end + for y = 0, m.heightCells - 1 do + for x = 0, 9 do + if m:isWalkableCell(x, y) then + U.teleport(game, eastMap, x, y, "right") + U.wait(10) + return + end + end + end + end + if ow().map.id ~= eastMap then + U.log("could not walk the seam; dropping straight on to", eastMap) + dropOnRoute() + end + check("standing on " .. eastMap, ow().map.id == eastMap) + + local worstGap, lostAt, steps = 0, nil, 0 + local back = { x = ow().player.cellX, y = ow().player.cellY } + for i = 1, 10 do + if not stepOnce("right") then + if not stepOnce("down") then break end + end + local o = game.overworld + if o.map.id ~= eastMap then + -- the route's gate is a warp, and an arrival respawn is not the stall + U.log("step", i, "walked into", o.map.id, "- stepping back out") + U.teleport(game, eastMap, back.x, back.y, "left") + U.wait(10) + break + end + steps = i + back.x, back.y = o.player.cellX, o.player.cellY + local npc = follower() + local p2 = o.player + local hop = o.pikachuTrail and o.pikachuTrail.ledgeHop + if npc then + local gap = math.abs(npc.cellX - p2.cellX) + + math.abs(npc.cellY - p2.cellY) + if gap > worstGap then worstGap = gap end + U.log("step", i, "player", p2.cellX, p2.cellY, "| Pikachu", where(npc), + "| gap", gap, "| ledgeHop", tostring(hop)) + else + lostAt = lostAt or i + U.log("step", i, "player", p2.cellX, p2.cellY, + "| Pikachu is not in ow.npcs at all | ledgeHop", tostring(hop)) + end + end + U.log("walked", steps, "steps east on", eastMap) + check("the follower stayed in ow.npcs the whole way", lostAt == nil) + check("it never fell more than two cells behind", worstGap <= 2) + if lostAt then + U.log("it dropped out of ow.npcs on step", lostAt, + "- that is the shape #920 would take") + end + -- an arrival parks the follower under the player (#863), so walk one step + stepOnce("left") + U.wait(20) + U.shot(game, SHOT_DIR .. "/bug920_east_of_fuchsia.png") + + U.log("Six shots, in story order. bug1009_in_ball: the player alone in the") + U.log("lab with the starter already in the party. bug1009_escaped: the cry,") + U.log("then Pikachu on the cell behind him. bug1021_stepped_aside: it walks") + U.log("down and right on the GRAMPS text while the rival is still off the") + U.log("map, ending below the player looking up. The near miss to watch for") + U.log("is the rival arriving first and Pikachu shuffling around him after,") + U.log("or the walk playing under the box instead of after it.") + U.log("Talk to Pikachu here for the control case: it answers with a bubble") + U.log("and a cry, which is the same sprite and the same audio path.") + U.log("#920 is unfixed. The pad is yours on Route 15; the step log above is") + U.log("what the triage wants from a stall -- whether it is still in ow.npcs") + U.log("and whether trail.ledgeHop stayed set after a ledge.") + + idle() +end diff --git a/tests/drivers/pikachu_warp_spawn_bug863_test.lua b/tests/drivers/pikachu_warp_spawn_bug863_test.lua new file mode 100644 index 00000000..2e2bd903 --- /dev/null +++ b/tests/drivers/pikachu_warp_spawn_bug863_test.lua @@ -0,0 +1,164 @@ +-- Manual check that a warp arrival hides Pikachu under the player (#863): +-- pokeyellow spawns on the player's own coords and the follow buffer walks +-- it out, but before the fix it popped in already beside him. +-- Warp cells: pokered data/maps/objects/RedsHouse1F.asm / RedsHouse2F.asm. +-- POKEPORT_DRIVER=tests/drivers/pikachu_warp_spawn_bug863_test.lua POKEPORT_IDENTITY=bug863 POKEPORT_TOUCH=0 POKEPORT_VERSION=yellow love . +-- Never add POKEPORT_SPEED; the identity needs an imported Yellow cache. +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local GameVersion = require("src.core.GameVersion") + local PF = require("src.world.PikachuFollower") + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + check("running as Yellow (needs POKEPORT_VERSION=yellow)", + GameVersion.isYellow()) + + -- the follower only spawns behind EVENT_GOT_STARTER with a healthy + -- PIKACHU in the party (PikachuFollower shouldSpawn) + game.save.flags = game.save.flags or {} + game.save.flags.EVENT_GOT_STARTER = true + game.save.party = { Pokemon.new(game.data, "PIKACHU", 20) } + game.save.onBike = false + game.save.player.name = "bryan" + + -- pokered RedsHouse1F.asm: warp_event 7, 1 -> REDS_HOUSE_2F, and + -- RedsHouse2F.asm: warp_event 7, 1 back down. Read the live map data + -- so a hack or mod that moved the stairs still points us at them. + local function warpTo(fromMap, destMap) + local def = game.data.maps[fromMap] + for _, w in ipairs(def and def.warps or {}) do + if w.destMap == destMap then return w.x, w.y end + end + return nil + end + local wx, wy = warpTo("REDS_HOUSE_1F", "REDS_HOUSE_2F") + check("REDS_HOUSE_1F has a warp to REDS_HOUSE_2F", wx ~= nil) + wx, wy = wx or 7, wy or 1 + + local follower = function() return PF.current(game.overworld) end + + local function overlapsPlayer() + local npc = follower() + local p = game.overworld and game.overworld.player + return npc and p and npc.cellX == p.cellX and npc.cellY == p.cellY + end + + -- press-and-hold dir until the map flips, releasing the instant it + -- does so no queued step drags the player off the arrival warp cell + local function walkUntilMap(dir, targetMap, maxFrames) + for _ = 1, maxFrames do + local ow = game.overworld + if ow and ow.map and ow.map.id == targetMap then break end + table.insert(game.input.pressQueue, dir) + game.input.state[dir] = true + coroutine.yield() + end + game.input.state[dir] = false + U.wait(45) -- warp fade + arrival settle + local ow = game.overworld + return ow and ow.map and ow.map.id == targetMap + end + + -- stand two below the stairs facing up; fall back to any walkable cell + -- below the warp if a mod reshaped the room + local sx, sy = wx, wy + 2 + U.teleport(game, "REDS_HOUSE_1F", sx, sy, "up") + U.wait(10) + local ow = game.overworld + if not ow.map:isWalkableCell(sx, sy) then + for dy = 1, 3 do + if ow.map:isWalkableCell(wx, wy + dy) then + sx, sy = wx, wy + dy + U.teleport(game, "REDS_HOUSE_1F", sx, sy, "up") + U.wait(10) + break + end + end + end + -- U.teleport is itself a fresh map load, so the fixed spawn already + -- parks Pikachu on the player's cell here + check("follower spawned on map load", follower() ~= nil) + check("map-load spawn is on the player's own cell", overlapsPlayer()) + + -- climb the stairs + check("walked up into REDS_HOUSE_2F", + walkUntilMap("up", "REDS_HOUSE_2F", 240)) + check("warp arrival upstairs: Pikachu hidden under the player", + overlapsPlayer()) + U.shot(game, SHOT_DIR .. "/bug863_2f_arrival.png") + + -- the draw sort tie-break (#863): sharing the player's py must list the + -- follower first so he draws over it. The sort runs in draw, and the + -- shot above rendered a frame, so entities order is post-sort here. + do + local npc = follower() + local p = game.overworld.player + if npc and p and npc.py == p.py then + local ni, pi + for i, e in ipairs(game.overworld.entities) do + if e == npc then ni = i elseif e == p then pi = i end + end + check("draw sort puts the hidden follower under the player", + ni ~= nil and pi ~= nil and ni < pi) + end + end + + -- walk off the stairs: the trail should pull Pikachu out one behind + U.hold(game, "down", 20) + U.wait(20) + U.hold(game, "down", 20) + U.wait(30) + do + local npc = follower() + local p = game.overworld.player + check("two steps later Pikachu trails one cell behind", + npc and p and npc.cellX == p.cellX and npc.cellY == p.cellY - 1) + check("and it faces down, walking out of the stairwell", + npc and npc.facing == "down") + end + U.shot(game, SHOT_DIR .. "/bug863_2f_trailing.png") + + -- back down the same stairs: descent must hide it the same way + check("walked back down into REDS_HOUSE_1F", + walkUntilMap("up", "REDS_HOUSE_1F", 240)) + check("warp arrival downstairs: Pikachu hidden under the player", + overlapsPlayer()) + U.shot(game, SHOT_DIR .. "/bug863_1f_return.png") + + -- regression: a connection seam is the keepPikachu path (#427), not a + -- respawn, so the follower must ride across it, not vanish or repark + U.teleport(game, "PALLET_TOWN", 10, 2, "up") + U.wait(10) + check("crossed the Pallet Town north seam into ROUTE_1", + walkUntilMap("up", "ROUTE_1", 300)) + do + local npc = follower() + local p = game.overworld.player + check("follower survived the connection crossing", npc ~= nil) + check("and stayed within trailing range of the player", + npc and p and math.abs(npc.cellX - p.cellX) + + math.abs(npc.cellY - p.cellY) <= 2) + end + U.shot(game, SHOT_DIR .. "/bug863_route1_seam.png") + + local sfx = game.save.options and game.save.options.sfxVol + if sfx == 0 then + U.log("note: sfxVol is 0, Pikachu's steps and voice will be silent") + end + + U.log("The shots above tell the story: on both stair arrivals only the") + U.log("player should be visible, Pikachu is tucked under him until he") + U.log("steps away, then it follows one cell behind facing his way.") + U.log("If a Pikachu sits beside him the moment a warp lands, that is") + U.log("the old bug. The pad is yours; warp around and watch spawns.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/plainpixel_font_test.lua b/tests/drivers/plainpixel_font_test.lua new file mode 100644 index 00000000..f6b42e7b --- /dev/null +++ b/tests/drivers/plainpixel_font_test.lua @@ -0,0 +1,83 @@ +-- Driver: TTF text mode through the bundled Plain Pixel font. Teleports +-- straight into the overworld (no intro) and shows one dialogue line per +-- language back to back, shooting each: lang_english.png, lang_french.png, +-- lang_german.png, lang_spanish.png, lang_russian.png, lang_japanese.png, +-- lang_chinese.png in SHOT_DIR. Judge glyph coverage, spacing, baseline, +-- and that the tile box border survives every one of them. +-- POKEPORT_DRIVER=tests/drivers/plainpixel_font_test.lua \ +-- POKEPORT_IDENTITY=ttfdemo POKEPORT_TOUCH=0 POKEPORT_VERSION=red \ +-- SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Font = require("src.render.Font") + local TextBox = require("src.render.TextBox") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- ---- preconditions ----------------------------------------------------- + check("the bundled TTF is present", + love.filesystem.getInfo(Font.PLAINPIXEL) ~= nil) + local okLoad, ttf = pcall(love.graphics.newFont, Font.PLAINPIXEL, + Font.PLAINPIXEL_SIZE) + check("real LOVE can load it", okLoad and ttf ~= nil) + + game.save.player.name = "bryan" + U.teleport(game, "PALLET_TOWN", 10, 8, "down") + U.wait(10) + + -- what a translation's register("ttf", {}) merges to + game.data.font.ttf = {} + Font.invalidate() + check("ttf active after the merge", Font.ttfActive()) + + local LINES = { + { "english", "The quick brown fox\njumps over the lazy dog!" }, + { "french", "Un \195\169clair enveloppe le\nPOK\195\169MON sauvage! \195\135a alors!" }, + { "german", "Gr\195\182\195\159e und Gewicht des\nPOK\195\169MON \195\188berpr\195\188ft!" }, + { "spanish", "\194\161El POK\195\169MON enemigo\nest\195\161 paralizado! \194\191Y ahora?" }, + { "russian", "\208\148\208\184\208\186\208\184\208\185 " + .. "\208\159\208\158\208\154\208\149\208\156\208\158\208\157 " + .. "\208\191\208\190\209\143\208\178\208\184\208\187\209\129\209\143!" }, + { "japanese", "\227\129\147\227\130\147\227\129\171\227\129\161\227\129\175! " + .. "\227\131\157\227\130\177\227\131\162\227\131\179\227\129\174\n" + .. "\227\129\155\227\129\139\227\129\132\227\129\184 " + .. "\227\130\136\227\129\134\227\129\147\227\129\157!" }, + -- NB: Plain Pixel 0.009's CJK block is partial (e.g. U+62D3, U+5922 + -- draw as tofu); stick to characters it covers + { "chinese", "\228\189\160\229\165\189! \230\136\145\230\152\175" + .. "\229\164\167\229\178\169\229\141\154\229\163\171!" }, + } + + for _, entry in ipairs(LINES) do + local name, text = entry[1], entry[2] + local done = false + local box = TextBox.new(game, text, function() done = true end) + game.stack:push(box) + -- let the typewriter finish the page before shooting it + for _ = 1, 300 do + if box.waiting or box.done then break end + U.wait(1) + end + U.wait(5) + U.shot(game, DIR .. "/lang_" .. name .. ".png") + for _ = 1, 20 do + if done then break end + U.tap(game, "a") + U.wait(10) + end + U.wait(5) + check("dismissed " .. name, done) + end + + game.data.font.ttf = nil + Font.invalidate() + check("clearing the entry restores tile mode", not Font.ttfActive()) + + U.log("shots in", DIR) + U.log("DONE") + love.event.quit(0) +end diff --git a/tests/drivers/rock_tunnel_dark_battle_bug773_test.lua b/tests/drivers/rock_tunnel_dark_battle_bug773_test.lua new file mode 100644 index 00000000..27a18663 --- /dev/null +++ b/tests/drivers/rock_tunnel_dark_battle_bug773_test.lua @@ -0,0 +1,84 @@ +-- Driver: BATTLE BG "world" plus an un-flashed Rock Tunnel put a dark map in +-- the same frame as the battle, and OverworldState:drawWorld's rBGP shift then +-- coloured the battle itself (#773). On hardware InitBattleCommon +-- (engine/battle/core.asm) pushes wMapPalOffset, InitBattleVariables zeroes it +-- and core.asm pops it back after EndOfBattle, so the battle is lit. +-- POKEPORT_DRIVER=tests/drivers/rock_tunnel_dark_battle_bug773_test.lua \ +-- POKEPORT_IDENTITY=bug773 POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Probe = dofile("tests/drivers/shot_probe.lua") + local PaletteFX = require("src.render.PaletteFX") + local BattleState = require("src.battle.BattleState") + local Pokemon = require("src.pokemon.Pokemon") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local fails = 0 + local function check(ok, msg) + U.log(ok and "PASS" or "FAIL", msg) + if not ok then fails = fails + 1 end + return ok + end + + game.save.flags.EVENT_GOT_STARTER = true + if #game.save.party == 0 then + table.insert(game.save.party, Pokemon.new(game.data, "CHARMANDER", 20)) + end + game.save.options = game.save.options or {} + game.save.options.textSpeed = 1 + game.save.options.colors = "gbc" + game.save.options.battleBg = "world" + PaletteFX.setMode("gbc") + + local CAVE = PaletteFX.pal(game.data, "CAVE") + local caveDark = PaletteFX.permute(CAVE, PaletteFX.DARK_BGP) + + -- data/maps/objects/RockTunnel1F.asm: the Route 10 entrance warp is 15, 3, + -- so two cells south of it is floor that does not re-trigger the warp. + game.save.flashLit = nil + U.teleport(game, "ROCK_TUNNEL_1F", 15, 5, "down") + U.wait(20) + local ow = game.overworld + check(ow ~= nil and ow.dark == true, "standing in an un-flashed ROCK_TUNNEL_1F") + U.shot(game, DIR .. "/bug773_1_dark_map.png") + check(PaletteFX.shadeMap() == PaletteFX.DARK_BGP, + "the map frame really is drawn with DARK_BGP armed") + + local ok = pcall(function() + local battle = BattleState.newWild(game, "ZUBAT", 15) + battle.onFinish = function() end + game.overworld:pushBattle(battle) + end) + if not ok then + U.log("WARN could not force a wild battle; nothing to judge") + while true do coroutine.yield() end + end + + U.wait(120) -- through the transition wipe and the intro slide-in + U.shot(game, DIR .. "/bug773_2_battle_world_bg.png") + check(PaletteFX.shadeMap() == nil, + "no shade map is armed while the world-bg battle draws (#773)") + + -- The battle keeps its own 160x144 field in the middle of the window; the + -- dimmed map only fills the surround, so probe the centre. + local CENTRE = { 0.4, 0.4, 0.6, 0.6 } + local shot = Probe.grab() + if shot then + local c = Probe.count(shot, { litPaper = CAVE[1], darkPaper = caveDark[1] }, + 3, CENTRE) + check(c.litPaper > 0, + "the battle screen keeps its paper white -- it is not FadePal2'd") + local top = Probe.top(shot, 5, 3, CENTRE) + U.log("battle centre top colours:", Probe.fmt(top)) + else + U.log("WARN pixel probe unavailable; judge the shots by eye") + end + + U.log(fails == 0 and "#773 checks passed" or (fails .. " #773 check(s) FAILED")) + U.log("Look at " .. DIR .. "/bug773_2_battle_world_bg.png: the battle screen") + U.log("should read exactly like any other battle -- white paper, normal HUD") + U.log("and pic colours -- with the dimmed tunnel only in the surround.") + U.log("The separate uniform dim of the world backdrop is #777, not this.") + + while true do coroutine.yield() end +end diff --git a/tests/drivers/route1_seam_pacing_bug487_test.lua b/tests/drivers/route1_seam_pacing_bug487_test.lua index 981b0e15..48ea282a 100644 --- a/tests/drivers/route1_seam_pacing_bug487_test.lua +++ b/tests/drivers/route1_seam_pacing_bug487_test.lua @@ -46,7 +46,9 @@ return function(game) os.getenv("POKEPORT_DRIVER") == nil) check("no fast-forward multiplier is set", (tonumber(os.getenv("POKEPORT_SPEED")) or 1) == 1 - and (game.save.options.speed or 1) == 1) + and (game.save.options.speedOverworld or 1) == 1 + and (game.save.options.speedBattle or 1) == 1 + and (game.save.options.speedMenu or 1) == 1) check("a window is up to watch (this is a visual call)", love.window ~= nil and love.window.isOpen and love.window.isOpen()) U.log("MAX FPS reads", FrameCap.label(game.save.options.fpsCap), diff --git a/tests/drivers/surfing_bg_bug726_test.lua b/tests/drivers/surfing_bg_bug726_test.lua new file mode 100644 index 00000000..2fd8eaec --- /dev/null +++ b/tests/drivers/surfing_bg_bug726_test.lua @@ -0,0 +1,148 @@ +-- Manual check that the Surfing Pikachu minigame background is the +-- ROM's metatile scroller, not the old procedural stand-in (#726). +-- The stand-in tiled wave-face foam tiles ($02/$07) over the whole sea +-- and drew the swell as two LOVE ellipses, which read as zigzag noise +-- with white blobs. The fix ports SurfingMinigame_BGMetatileTable, the +-- WavePattern columns and the .WaveFunctions state table from +-- ../pokeyellow/engine/minigame/surfing_pikachu.asm; this driver +-- machine-checks the transcribed tables and the ride heights, then +-- parks a human in front of the water for the part only eyes can judge. +-- Needs a Yellow cache in the active identity (a sandboxed +-- POKEPORT_IDENTITY starts empty and would sit on the launcher). +-- SHOT_DIR=/tmp/shots POKEPORT_VERSION=yellow POKEPORT_TOUCH=0 POKEPORT_DRIVER=tests/drivers/surfing_bg_bug726_test.lua love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local SurfingMinigame = require("src.ui.SurfingMinigame") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- table integrity first, it needs no ROM state at all. surf_1a is + -- ripped as 65 tiles (src/import/RomExtractor.lua), so every tile id + -- a metatile names must be 0..64, every pattern entry must name a + -- metatile, and every wave state must name a pattern with plausible + -- ride heights (the asm range is FLAT_WATER_Y $74 down to -6 tiles). + local ok = true + for id, mt in pairs(SurfingMinigame.BG_METATILES) do + for i = 1, 4 do + if type(mt[i]) ~= "number" or mt[i] < 0 or mt[i] > 64 then + U.log("metatile", id, "slot", i, "has bad tile id", tostring(mt[i])) + ok = false + end + end + end + check("every metatile tile id lands inside the 65-tile surf_1a sheet", ok) + + ok = true + for id, pat in pairs(SurfingMinigame.WAVE_PATTERNS) do + if #pat ~= 8 then ok = false U.log("pattern", id, "is not 8 rows") end + for i = 1, 8 do + if not SurfingMinigame.BG_METATILES[pat[i]] then + U.log("pattern", id, "row", i, "names missing metatile", + tostring(pat[i])) + ok = false + end + end + end + check("every wave pattern row names a real metatile", ok) + + ok = true + for id, step in pairs(SurfingMinigame.WAVE_STEPS) do + if not SurfingMinigame.WAVE_PATTERNS[step[1]] then + U.log("wave state", id, "names missing pattern", tostring(step[1])) + ok = false + end + for i = 2, 3 do + if step[i] < 116 - 6 * 8 or step[i] > 116 then + U.log("wave state", id, "ride height", step[i], "out of range") + ok = false + end + end + end + check("every wave state names a real pattern with sane ride heights", ok) + + -- pokeyellow data/maps/objects/SummerBeachHouse.asm: the Surfin' Dude + -- is object_event 2, 3, so (2, 2) facing down talks to him. He only + -- offers the run to a party Pikachu that knows SURF. + game.save.party = { Pokemon.new(game.data, "PIKACHU", 50) } + game.save.party[1].moves = { { id = "SURF", pp = 15 } } + U.teleport(game, "SUMMER_BEACH_HOUSE", 2, 2, "down") + U.wait(5) + + -- mash A through the pitch and the YES into the game itself + local mg + for _ = 1, 300 do + local top = game.stack:top() + if top and top.seaY then mg = top break end + U.tap(game, "a") + U.wait(4) + end + check("the minigame opened", mg ~= nil) + if not mg then + U.log("could not reach the minigame; nothing more to show") + while true do coroutine.yield() end + end + + -- the run opens on prefilled flat water: every visible column should + -- be the open-water pattern, whose bottom rows are metatile $01 + -- (tile $0b everywhere), and Pikachu should sit on the flat waterline + ok = true + for c = 0, 10 do + local col = mg.cols[c] + if not (col and col.pat[8] == 0x01 and col.hl == 116) then ok = false end + end + check("the opening sea is flat open water, pattern 00 all the way", ok) + check("Pikachu's ride line starts on the flat waterline", + mg:seaY(68) == 100) + U.shot(game, DIR .. "/bug726_1_flat.png") + + -- paddle up and ride until the generator has rolled some swells; the + -- chooser leaves flat water only on a nonzero roll, so give it room + for _ = 1, 8 do U.tap(game, "a") U.wait(3) end + local sawSwell, seaTracks = false, true + for _ = 1, 1500 do + if mg.phase ~= "ride" and mg.phase ~= "air" then break end + for c, col in pairs(mg.cols) do + if col.hl < 116 then sawSwell = true end + end + -- the ride height must always come from the column under Pikachu + local tile = math.floor((mg.distance + 68) / 8) + local col = mg.cols[math.floor(tile / 2)] + if col then + local want = (tile % 2 == 0 and col.hl or col.hr) - 16 + if mg:seaY(68) ~= want then seaTracks = false end + end + if sawSwell and mg.distance > 400 then break end + U.wait(1) + if (U.frame() % 5) == 0 then U.tap(game, "a") end + end + check("the wave generator produced swells (ride heights above flat)", + sawSwell) + check("seaY always follows the generated column heights", seaTracks) + U.shot(game, DIR .. "/bug726_2_swell.png") + + -- one jump for the air shot, then hand it over + if mg.phase == "ride" then + U.tap(game, "up") + U.hold(game, "right", 20) + U.shot(game, DIR .. "/bug726_3_air.png") + end + + U.log("shots in", DIR, "- bug726_1_flat, bug726_2_swell, bug726_3_air") + U.log("What to look for: the open sea is the flat speckled water tile,") + U.log("not a diagonal zigzag field; no white ellipse blobs and no loose") + U.log("blue squares floating on the surface; swells build from the left,") + U.log("crest with foam and flatten out again; Pikachu sits on the wave") + U.log("at every point of the swell; the HP strip sits in the bottom band") + U.log("over unbroken white. The game is still live: keep riding to the") + U.log("goal and the sand should slide in under the coast-in before the") + U.log("results card.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/switch_cursor_bug737_test.lua b/tests/drivers/switch_cursor_bug737_test.lua new file mode 100644 index 00000000..f3a509fe --- /dev/null +++ b/tests/drivers/switch_cursor_bug737_test.lua @@ -0,0 +1,86 @@ +-- Manual check that a voluntary switch resets both battle cursors (#737). +-- SendOutMon (pokered engine/battle/core.asm:1733-1735) zeroes +-- wBattleAndStartSavedMenuItem and, via the same hli/hl pair, the +-- wPlayerMoveListIndex byte behind it (wram.asm:242-244), so after any +-- player send-out the main menu reopens on FIGHT and the move list on +-- slot 1. The port used to keep both cursors where they were. +-- SHOT_DIR=/tmp/shots POKEPORT_DRIVER=tests/drivers/switch_cursor_bug737_test.lua love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + + -- CHARMANDER at 12 knows Scratch/Growl/Ember, so the move cursor can be + -- parked on slot 3 before the switch + game.save.party = { + Pokemon.new(game.data, "CHARMANDER", 12), + Pokemon.new(game.data, "SQUIRTLE", 10), + } + U.teleport(game, "ROUTE_1", 5, 5, "down") + local ow = game.overworld + + local battle = BattleState.newWild(game, "PIDGEY", 4) + battle.onFinish = function() end + ow:pushBattle(battle) + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + local function mashUntil(cond, max) + for _ = 1, max or 120 do + if cond() then return true end + U.tap(game, "a") + U.wait(4) + end + return false + end + + check("intro drains to the battle menu", + mashUntil(function() return battle.phase == "menu" end)) + + -- open FIGHT and park the cursor on move slot 3, then back out + U.tap(game, "a"); U.wait(6) + U.tap(game, "down"); U.wait(4) + U.tap(game, "down"); U.wait(4) + check("move cursor parked on slot 3", battle.moveIndex == 3) + U.tap(game, "b"); U.wait(6) + + -- FIGHT/PKMN/ITEM/RUN: right to PKMN, A opens the party + U.tap(game, "right"); U.wait(4) + check("battle menu parked on PKMN", battle.menuIndex == 2) + U.tap(game, "a"); U.wait(12) + local pm = game.stack:top() + check("party menu opened", pm ~= nil and pm.onSwitch ~= nil) + + -- pick SQUIRTLE, then SWITCH from the SWITCH/STATS/CANCEL submenu + U.tap(game, "down"); U.wait(4) + U.tap(game, "a"); U.wait(8) + U.tap(game, "a"); U.wait(8) + + -- the switch queues "Come back!" / "Go!" plus the enemy's free move; + -- drain back to the next command menu + check("switch turn drains back to the menu", + mashUntil(function() return battle.phase == "menu" end, 300)) + check("player is now SQUIRTLE", battle.player.mon.species == "SQUIRTLE") + + -- the machine-checkable half of #737 + check("battle menu is back on FIGHT", battle.menuIndex == 1) + check("move cursor is back on slot 1", battle.moveIndex == 1) + + U.shot(game, DIR .. "/bug737_menu_after_switch.png") + U.tap(game, "a"); U.wait(8) + U.shot(game, DIR .. "/bug737_moves_after_switch.png") + U.log("captured", DIR .. "/bug737_menu_after_switch.png", + "and", DIR .. "/bug737_moves_after_switch.png") + + U.log("The fight menu on screen is SQUIRTLE's, opened right after the") + U.log("switch. The cursor should sit on the first move; before #737 it") + U.log("kept CHARMANDER's old slot (3), and the main menu reopened on PKMN.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/text_advance_bug765_test.lua b/tests/drivers/text_advance_bug765_test.lua new file mode 100644 index 00000000..566a9e2c --- /dev/null +++ b/tests/drivers/text_advance_bug765_test.lua @@ -0,0 +1,161 @@ +-- Manual check for the pages that must NOT wait on a button (#765). +-- Only TX_PROMPT_BUTTON blinks the arrow and waits (home/text.asm:434-446); +-- the used-move line (engine/battle/used_move_text.asm) ends in `text_end` +-- and both save pages come from SaveMenu .save (engine/menus/save.asm:164-181), +-- where "Now saving..." is a bare PlaceString + DelayFrames 120 and +-- GameSavedText ends in `done`. Ordering is asserted headlessly in +-- tests/parity_battle_auto_text_bug765.lua; this run is for pacing. +-- No POKEPORT_SPEED: the save beat and the SFX_SAVE hold are the moment. +-- POKEPORT_DRIVER=tests/drivers/text_advance_bug765_test.lua POKEPORT_IDENTITY=bug765 POKEPORT_TOUCH=0 love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + local TextBox = require("src.render.TextBox") + local Menu = require("src.ui.Menu") + local ChoiceBox = require("src.ui.ChoiceBox") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + game.save.party = { Pokemon.new(game.data, "BULBASAUR", 50) } + game.save.player.name = "RED" + U.teleport(game, "PALLET_TOWN", 10, 8, "down") + U.wait(10) + + -- ---- part 1: START -> SAVE -> YES, hands off the pad ------------------- + U.tap(game, "start") + U.wait(10) + local menu = game.stack:top() + check("START opened the menu", getmetatable(menu) == Menu) + if getmetatable(menu) == Menu then + -- walk the cursor onto SAVE by label: which rows exist depends on + -- story flags, so counting from the top is not stable + local target + for i, item in ipairs(menu.items) do + if tostring(item.label) == "SAVE" then target = i end + end + check("the menu lists SAVE", target ~= nil) + -- the cursor position survives closing the menu + -- (wBattleAndStartSavedMenuItem), so it may start above OR below SAVE + for _ = 1, #menu.items do + if not target or menu.index == target then break end + U.tap(game, menu.index < target and "down" or "up") + U.wait(4) + end + U.tap(game, "a") + U.wait(10) + end + + -- the player/badges/dex/time panel types out, page-breaks (\f) into the + -- confirmation, then the YES/NO box opens; A through all of it, YES last + local chose = false + for _ = 1, 30 do + local top = game.stack:top() + if getmetatable(top) == ChoiceBox then + U.tap(game, "a") + chose = true + break + end + U.tap(game, "a") + U.wait(20) + end + check("the SAVE confirmation was reached and answered YES", chose) + + -- the answered box holds 15 frames before it pops and runs the choice + -- (DisplayTwoOptionMenu, engine/menus/text_box.asm:322-334), so wait it out + for _ = 1, 60 do + if getmetatable(game.stack:top()) ~= ChoiceBox then break end + U.wait(1) + end + + -- from here NOTHING is pressed: both boxes must clear themselves + U.wait(2) + local saving = game.stack:top() + check("the Now saving... box is up", getmetatable(saving) == TextBox) + local savingPopped + for f = 1, 300 do + if game.stack:top() ~= saving then savingPopped = f break end + U.wait(1) + end + check("it held about 2s and popped with no button (DelayFrames 120)", + savingPopped ~= nil and savingPopped > 60) + local savedPopped = false + for _ = 1, 600 do + local top = game.stack:top() + if getmetatable(top) ~= TextBox and getmetatable(top) ~= Menu then + savedPopped = true + break + end + U.wait(1) + end + check("the saved-the-game box cleared itself after SFX_SAVE", savedPopped) + + -- ---- part 2: a wild battle's used-move line ---------------------------- + local wild = BattleState.newWild(game, "RATTATA", 2) + wild.onFinish = function() end + local ow = game.overworld + if ow then ow:pushBattle(wild) end + for _ = 1, 400 do + if game.stack:top() == wild and (wild.introSlide or 0) == 0 then break end + U.wait(1) + end + check("the wild battle reached the screen", game.stack:top() == wild) + + -- the intro page ends in `prompt` (WildMonAppearedText), so it still + -- waits; A through it and the send-out, then pick FIGHT + first move + for _ = 1, 600 do + if wild.phase == "menu" then break end + if wild.msgPrompt then U.tap(game, "a") end + U.wait(1) + end + check("the intro still holds on its arrow and A walks it to the menu", + wild.phase == "menu") + U.tap(game, "a") -- FIGHT + U.wait(10) + U.tap(game, "a") -- first move + + -- from here NOTHING is pressed: "BULBASAUR used X!" must flow straight + -- into its animation with the line still on screen and no arrow + local sawUsed, promptedOnUsed, handedOff = false, false, false + for _ = 1, 900 do + local cur = wild.current + local t = cur and cur.text + if t and t:find("used", 1, true) then + sawUsed = true + if wild.msgPrompt then promptedOnUsed = true end + elseif sawUsed then + handedOff = true + break + end + U.wait(1) + end + check("the used-move line reached the screen", sawUsed) + check("it never raised the prompt arrow", not promptedOnUsed) + check("it handed off by itself, no A pressed", handedOff) + check("the line stays drawn under the animation (msgHold)", + wild.msgHold == true or wild.animPlaying == true) + + -- ...and the pages after it still wait: a level-50 BULBASAUR one-shots a + -- level-2 RATTATA, so the faint line (BattleMonFaintedText class, ends in + -- `prompt`) comes up next and must hold on its arrow + local promptAfter = false + for _ = 1, 900 do + if wild.msgPrompt then promptAfter = true break end + U.wait(1) + end + check("the page after it still holds on the arrow", promptAfter) + + U.log("Handing off. What just happened, and what to look for on a replay:") + U.log("the save flow ran with no button after YES: \"Now saving...\" held") + U.log("about 2 seconds, then \"RED saved the game!\" played the save jingle") + U.log("and cleared itself. In the battle, \"BULBASAUR used !\" flowed") + U.log("straight into the move animation with the line still up and no") + U.log("blinking arrow; the faint line after it is waiting on A right now.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/title_cycle_test.lua b/tests/drivers/title_cycle_test.lua new file mode 100644 index 00000000..82f68d23 --- /dev/null +++ b/tests/drivers/title_cycle_test.lua @@ -0,0 +1,66 @@ +-- ..(engine/movie/title.asm ln 28) +-- ..(engine/movie/title2.asm ln 13) +-- POKEPORT_DRIVER=tests/drivers/title_cycle_test.lua POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local shot = 0 + local function grab(tag) + shot = shot + 1 + U.shot(game, ("%s/title_%02d_%s.png"):format(DIR, shot, tag)) + end + + U.wait(30) + grab("copyright") + -- ..(engine/movie/splash.asm ln 230) + local movie = game.stack:top() + while movie.phase ~= 2 or movie.timer < 70 do U.wait(1) end + grab("star_topbar") + while movie.timer < 88 do U.wait(1) end + grab("star_middle") + while movie.timer < 100 do U.wait(1) end + grab("star_lowbar") + while movie.timer < 130 do U.wait(1) end + grab("gamefreak") + + U.tap(game, "start") + U.wait(2) + local title = game.stack:top() + U.log("top is", tostring(title and title.screenId)) + if not (title and title.scrollPhase) then + U.log("no TitleState on top; nothing below can run") + while true do coroutine.yield() end + end + + grab("drop_early") + U.wait(14) + grab("drop_late") + while title.phase == "drop" do U.wait(1) end + grab("settle") + while title.phase == "settle" do U.wait(1) end + grab("ribbon_start") + U.wait(10) + U.shot(game, DIR .. "/title_ribbon_mid.png") + while title.phase ~= "loop" do U.wait(1) end + grab("landed") + + title.cycleIndex = 1 + title.scrollPhase, title.scrollFrame, title.timer = "hold", 1, 0 + title.monOffset = 0 + while title.scrollPhase == "hold" do U.wait(1) end + grab("out_a") + U.wait(6) + grab("out_b") + while title.scrollPhase == "out" do U.wait(1) end + U.log("after the scroll out the phase is", title.scrollPhase) + for _ = 1, 5 do + grab("ball") + U.wait(1) + end + while title.scrollPhase == "ball" do U.wait(1) end + grab("in") + U.wait(30) + grab("next_mon") + U.log("captured", DIR) + while true do coroutine.yield() end +end diff --git a/tests/drivers/title_menu_palette_bug870_test.lua b/tests/drivers/title_menu_palette_bug870_test.lua new file mode 100644 index 00000000..2575c2a7 --- /dev/null +++ b/tests/drivers/title_menu_palette_bug870_test.lua @@ -0,0 +1,149 @@ +-- Manual check of the title main menu + CONTINUE info box colors (#870): +-- both must follow the COLORS display mode (CLASSIC pea greens) instead of +-- staying a raw white trueColor hole, while gbc keeps #133's white paper / +-- black ink (pokered engine/menus/main_menu.asm RunDefaultPaletteCommand). +-- Palette shading is the moment under test, so POKEPORT_SPEED stays unset. +-- POKEPORT_DRIVER=tests/drivers/title_menu_palette_bug870_test.lua POKEPORT_IDENTITY=bug870 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local P = require("src.render.PaletteFX") + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local fails = 0 + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + if not ok then fails = fails + 1 end + return ok + end + + -- Count near-white pixels in a captured frame. CLASSIC's lightest shade + -- is (155,188,15) and no blend of the four pea greens (or the letterbox) + -- reaches 250+, so any white here can only be an unshaded region -- the + -- exact white hole #870 is about. Reads the PNG back through + -- love.image so the check sees what actually hit the window. + local function whiteCount(path) + local f = io.open(path, "rb") + if not f then return nil end + local bytes = f:read("*a") + f:close() + local ok, img = pcall(function() + return love.image.newImageData( + love.filesystem.newFileData(bytes, "shot.png")) + end) + if not ok or not img then return nil end + local n = 0 + for y = 0, img:getHeight() - 1 do + for x = 0, img:getWidth() - 1 do + local r, g, b = img:getPixel(x, y) + if r > 0.98 and g > 0.98 and b > 0.98 then n = n + 1 end + end + end + return n + end + + -- A real save on disk first: hasSave in TitleState:openMenu checks the + -- save FILE, not the in-memory table, and only then lists CONTINUE. + -- No map coordinates anywhere in this test -- the bug lives on the title + -- screen, before any map. + U.newGame(game) + check("reached the overworld", game.overworld ~= nil) + check("save written so the menu lists CONTINUE", + require("src.core.SaveData").save(game.save)) + + -- flip COLORS the way the options screen does, then power-cycle to the + -- title (returnToTitle skips the intro movie, unlike a cold boot) + game.save.options.colors = "classic" + P.applyOptions(game.save.options) + game:returnToTitle() + U.wait(30) + + local TitleState = require("src.ui.TitleState") + local title = game.stack:top() + check("back on the title screen", getmetatable(title) == TitleState) + + U.tap(game, "start") + U.wait(10) + local menu = game.stack:top() + check("main menu opened and set a titleUiBox", + menu ~= title and menu ~= nil and menu.titleUiBox ~= nil) + + -- The fix itself: the box overlay must be a GRAYS palette zone the shade + -- shader runs on. A colors == false zone would make Renderer:blitCanvas + -- re-blit the rect with NO shader, so effectiveColors never substitutes + -- the mono/inverted modes there -- the pre-#870 white hole. + local zones = title.sgbPalettes and title:sgbPalettes(game) + local boxZone, bare = nil, false + for _, z in ipairs(zones or {}) do + if z.colors == false then bare = true end + if z.colors == P.GRAYS then boxZone = z end + end + check("no trueColor (colors == false) zone over the menu box", not bare) + check("the titleUiBox rides a GRAYS palette zone", boxZone ~= nil) + -- effectiveColors under classic substitutes CLASSIC; the trailing + -- permute is the identity while no shade map is armed, so == holds + check("CLASSIC substitutes the GRAYS box zone", + P.effectiveColors(P.GRAYS) == P.CLASSIC) + + local shot1 = SHOT_DIR .. "/bug870_menu_classic.png" + if U.shot(game, shot1) then + local n = whiteCount(shot1) + U.log("white pixels in the menu shot:", tostring(n)) + check("CLASSIC main menu shot has zero raw-white pixels", + n ~= nil and n == 0) + end + + -- with a save present CONTINUE is first, so the cursor is already on it + U.tap(game, "a") + U.wait(10) + local info = game.stack:top() + check("CONTINUE info box open with its titleUiBox", + info ~= nil and info ~= menu and info.titleUiBox ~= nil + and info.titleUiBox[1] == 4 and info.titleUiBox[2] == 7) + + local shot2 = SHOT_DIR .. "/bug870_info_classic.png" + if U.shot(game, shot2) then + local n = whiteCount(shot2) + U.log("white pixels in the info shot:", tostring(n)) + check("CLASSIC CONTINUE info shot has zero raw-white pixels", + n ~= nil and n == 0) + end + + -- #133 regression gate: under gbc the GRAYS zone must pass through the + -- shader unchanged, so the box comes back white paper / black ink while + -- the LOGO zones keep the title colored around it + P.applyOptions({ colors = "gbc" }) + U.wait(5) + check("gbc leaves the GRAYS box zone alone (the #133 white box)", + P.effectiveColors(P.GRAYS) == P.GRAYS) + local shot3 = SHOT_DIR .. "/bug870_info_gbc.png" + if U.shot(game, shot3) then + local n = whiteCount(shot3) + U.log("white pixels in the gbc shot:", tostring(n)) + check("gbc info box paper is white again", n ~= nil and n > 0) + end + + -- the inverted modes must now invert the box with the screen too + P.applyOptions({ colors = "og_inv" }) + U.wait(5) + local inv = P.effectiveColors(P.GRAYS) + check("OG INV inverts the box paper to black", + inv ~= nil and inv[1] ~= nil and inv[1][1] == 0) + U.shot(game, SHOT_DIR .. "/bug870_info_oginv.png") + + -- hand over in the bug's own mode + game.save.options.colors = "classic" + P.applyOptions(game.save.options) + U.wait(2) + + U.log(fails == 0 and "PASS all machine checks" + or ("FAIL " .. fails .. " machine check(s), see above")) + U.log("The CONTINUE info box on screen is in CLASSIC now; its paper should") + U.log("be the same pea green as the title behind it, with dark green ink,") + U.log("just like the in-game START menu. Before #870 this box and the") + U.log("main menu were a pure white rectangle over the green title.") + U.log("B backs out to the menu; OPTION flips COLORS to eyeball the rest.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/trade_anim_bug750_test.lua b/tests/drivers/trade_anim_bug750_test.lua new file mode 100644 index 00000000..ab52cb67 --- /dev/null +++ b/tests/drivers/trade_anim_bug750_test.lua @@ -0,0 +1,152 @@ +-- Manual check that the trade cinematic draws real art, not rectangles (#750). +-- The ROM importer never wrote assets/generated/trade/*, so on a player's +-- cache every tryImage in TradeAnim.new returned nil and the whole +-- InternalClockTradeAnim sequence fell back to love.graphics.rectangle: +-- an outlined box for the Game Boy, a flat bar for the cable, a blank +-- screen during the open-cable phase. The machine half below asserts the +-- ten art files are in the cache at the sizes trade.asm implies and that +-- the running TradeAnim actually loaded them; the shots are for the human +-- half. A cache imported before this fix re-imports on launch (the +-- REQUIRED_FILES entry), so a FAIL on the file checks means the re-import +-- has not happened yet. +-- SHOT_DIR=/tmp/trade750 POKEPORT_DRIVER=tests/drivers/trade_anim_bug750_test.lua POKEPORT_IDENTITY=bug750 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/trade750" + local Pokemon = require("src.pokemon.Pokemon") + local TradeAnim = require("src.ui.TradeAnim") + local TextBox = require("src.render.TextBox") + local PartyMenu = require("src.ui.PartyMenu") + local ChoiceBox = require("src.ui.ChoiceBox") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- Expected sizes: GameBoyTiles is a 6x8-tile plate and LinkCableTiles a + -- 12x3 one (pokered data/tilemaps.asm), Trade_DrawCableAcrossScreen fills + -- a 20-tile row, the ball and its bulge frame are one tile mirrored into + -- 2x2 OAM blocks, and TradeBubbleIconGFX is two 16x16 quadrant frames. + local FILES = { + { "assets/generated/trade/game_boy.png", 48, 64 }, + { "assets/generated/trade/open_cable.png", 96, 24 }, + { "assets/generated/trade/cable_horiz.png", 160, 8 }, + { "assets/generated/trade/cable_conn.png", 8, 8 }, + { "assets/generated/trade/cable_seg.png", 8, 8 }, + { "assets/generated/trade/cable_corner.png", 8, 8 }, + { "assets/generated/trade/cable_end.png", 8, 8 }, + { "assets/generated/trade/cable_vert.png", 8, 8 }, + { "assets/generated/trade/cable_ball.png", 16, 16 }, + { "assets/generated/trade/cable_ball_alt.png", 16, 16 }, + { "assets/generated/trade/bubble.png", 16, 32 }, + } + for _, spec in ipairs(FILES) do + local ok, image = pcall(love.graphics.newImage, spec[1]) + if check(spec[1] .. " is in the cache", ok and image ~= nil) then + local w, h = image:getDimensions() + check(("%s is %dx%d"):format(spec[1], spec[2], spec[3]), + w == spec[2] and h == spec[3]) + end + end + check("field.lua carries tradeArt paths", + game.data.field and game.data.field.tradeArt + and game.data.field.tradeArt.gameBoy ~= nil) + + local function topIs(cls) + return getmetatable(game.stack:top()) == cls + end + + -- pokered data/maps/objects/VermilionTradeHouse.asm: the LITTLE_GIRL + -- (SPEAROW -> DUX FARFETCH'D) stands at (3, 5) facing up, so (3, 6) + -- facing up puts the player in front of her. + game.save.party = { Pokemon.new(game.data, "SPEAROW", 10) } + U.teleport(game, "VERMILION_TRADE_HOUSE", 3, 6, "up") + U.wait(5) + + U.tap(game, "a") + U.wait(10) + for _ = 1, 200 do + if topIs(ChoiceBox) then break end + U.tap(game, "a") + U.wait(2) + end + check("trade offer choice appeared", topIs(ChoiceBox)) + U.tap(game, "a") -- YES + U.wait(6) + for _ = 1, 60 do + if topIs(PartyMenu) then break end + U.wait(1) + end + check("party menu opened", topIs(PartyMenu)) + U.tap(game, "a") -- pick the SPEAROW + U.wait(6) + for _ = 1, 200 do + if topIs(TradeAnim) then break end + U.tap(game, "a") + U.wait(2) + end + local anim = game.stack:top() + if not check("TradeAnim is on the stack", getmetatable(anim) == TradeAnim) then + U.log("cannot reach the cinematic; nothing more to verify") + while true do coroutine.yield() end + end + + -- the running state loaded the art rather than falling back + check("TradeAnim loaded the Game Boy plate", anim.img.gameBoy ~= nil) + check("TradeAnim loaded the open cable plate", anim.img.openCable ~= nil) + check("TradeAnim loaded the cable ball", anim.img.cableBall ~= nil) + check("TradeAnim loaded the bubble ring", anim.img.bubble ~= nil) + + -- step the phases at real speed and shoot the moments the reporter's + -- video shows broken + local function ffUntil(phase, cap) + for _ = 1, cap or 3000 do + if anim.phase == phase or anim.phase == "done" then break end + if anim.waitingText or topIs(TextBox) then + U.wait(1) + else + anim:update(1 / 60) + end + end + U.wait(1) + end + + ffUntil("open_cable", 800) + while anim.phase == "open_cable" and anim.scx > 0 do + anim:update(1 / 60) + end + U.wait(1) + U.shot(game, DIR .. "/bug750_open_cable.png") + + ffUntil("ball_enter", 200) + while anim.phase == "ball_enter" and anim.ballX < 0x80 do + anim:update(1 / 60) + end + U.wait(1) + U.shot(game, DIR .. "/bug750_ball_enter.png") + + ffUntil("transfer_lr", 400) + for _ = 1, 24 do anim:update(1 / 60) end + U.wait(1) + U.shot(game, DIR .. "/bug750_transfer_lr.png") + + ffUntil("transfer_rl", 4000) + for _ = 1, 140 do + if anim.phase ~= "transfer_rl" then break end + anim:update(1 / 60) + end + U.wait(1) + U.shot(game, DIR .. "/bug750_transfer_rl.png") + + U.log("shots in", DIR) + U.log("open_cable should be the link cable plate with its open end, not a") + U.log("blank screen; ball_enter a small ball riding the cable; the two") + U.log("transfer shots a real Game Boy body with the cable plugged in and") + U.log("the mon's 16x16 party icon inside a round 32x32 ring -- no outlined") + U.log("rectangles, no flat gray bar, no squashed battle pic.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/trainer_fanfare_bug764_test.lua b/tests/drivers/trainer_fanfare_bug764_test.lua new file mode 100644 index 00000000..6b74ac69 --- /dev/null +++ b/tests/drivers/trainer_fanfare_bug764_test.lua @@ -0,0 +1,89 @@ +-- Manual check that challenging a trainer by talking to them plays the +-- encounter sting (#764). TalkToTrainer (pokered home/trainers.asm:88) +-- prints the before-battle text and then EngageMapTrainer -> +-- PlayTrainerMusic; the port only did that on the sight-line path, so a +-- trainer approached from the side or back went into battle in map music. +-- POKEPORT_DRIVER=tests/drivers/trainer_fanfare_bug764_test.lua POKEPORT_IDENTITY=bug764 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + + -- pokered data/maps/objects/ViridianForest.asm: YOUNGSTER2 (the first + -- Bug Catcher) stands at (30, 33) facing LEFT, so his sight line runs + -- west; the cell below him, (30, 34), is outside it and lets us talk + -- our way into the battle instead of being spotted. + local MAP = "VIRIDIAN_FOREST" + local TRAINER = "VIRIDIANFOREST_YOUNGSTER2" + local STAND = { x = 30, y = 34, facing = "up" } + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + -- record every song the engine starts; wrapping keeps real playback so + -- the human half of this check still has something to hear + local Music = require("src.core.Music") + local played = {} + local realPlay = Music.play + Music.play = function(data, song, ...) + played[#played + 1] = song + return realPlay(data, song, ...) + end + + U.newGame(game) + check("music volume is audible (save.options.musicVol)", + (game.save.options.musicVol or 0) > 0) + + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(30) + + local ow = game.overworld + local npc + for _, n in ipairs(ow and ow.npcs or {}) do + if n.def and n.def.name == TRAINER then npc = n end + end + check("Bug Catcher object loaded on " .. MAP, npc ~= nil) + if npc then + check("standing on his blind side, facing him", + ow:npcAtCell(ow.player:facingCell()) == npc) + check("he did not spot us on the way in", not ow.engaging) + end + + -- talk; the sting must start only once the before-battle text closes + -- (TalkToTrainer prints first, then engages) + played = {} + U.tap(game, "a") + U.wait(30) + check("no sting while the dialogue is up", #played == 0) + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + U.shot(game, SHOT_DIR .. "/bug764_dialogue.png") + + -- close the text; a Bug Catcher is neither female-list nor evil-list, + -- so PlayTrainerMusic lands on the male sting. A presses both finish + -- the typewriter and turn pages, so keep tapping until the sting lands + -- or the drain gives up. + local sting + for _ = 1, 8 do + U.tap(game, "a") + U.wait(30) + for _, song in ipairs(played) do + if song:find("Music_Meet", 1, true) then sting = song end + end + if sting then break end + end + check("closing the text started an encounter sting", sting ~= nil) + check("it is the male trainer sting", sting == "Music_MeetMaleTrainer") + U.shot(game, SHOT_DIR .. "/bug764_transition.png") + U.log("songs started since the A press:", table.concat(played, ", ")) + + U.log("The Bug Catcher's line has just closed and the battle is opening.") + U.log("You should have heard the male trainer sting begin the moment the") + U.log("text box shut, carrying over the battle transition. Before #764") + U.log("the forest theme played straight through into the fight. To hear") + U.log("the sight path for comparison, lose or run, step west across his") + U.log("eyeline, and the same sting should fire once at the \"!\" bubble.") + + while true do + coroutine.yield() + end +end diff --git a/tests/drivers/util.lua b/tests/drivers/util.lua index 52a34344..33983c8a 100644 --- a/tests/drivers/util.lua +++ b/tests/drivers/util.lua @@ -65,8 +65,12 @@ function U.newGame(game) U.wait(5) U.tap(game, "start") -- skip intro movie U.wait(10) - U.tap(game, "a") -- title -> menu - U.wait(5) + local title = game.stack:top() + for _ = 1, 60 do + U.tap(game, "a") + U.wait(5) + if game.stack:top() ~= title then break end + end -- menu: CONTINUE may or may not exist; NEW GAME is first without a save U.tap(game, "a") U.wait(10) diff --git a/tests/drivers/viridian_fisher_bug775_test.lua b/tests/drivers/viridian_fisher_bug775_test.lua new file mode 100644 index 00000000..dd6acdc2 --- /dev/null +++ b/tests/drivers/viridian_fisher_bug775_test.lua @@ -0,0 +1,81 @@ +-- Manual check of the Viridian fisher's TM42 gift pre text (#775). +-- pokered ViridianCityFisherText (scripts/ViridianCity.asm) prints +-- .YouCanHaveThisText ("Yawn! I must have dozed off...") before GiveItem; +-- the port had no pre entry, so A jumped straight to "received TM42!". +-- POKEPORT_DRIVER=tests/drivers/viridian_fisher_bug775_test.lua POKEPORT_IDENTITY=bug775 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + + -- pokered data/maps/objects/ViridianCity.asm: the FISHER stays at (6, 23) + -- facing down, so stand one cell below him and look up + local MAP = "VIRIDIAN_CITY" + local STAND = { x = 6, y = 24, facing = "up" } + local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + U.newGame(game) + U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) + U.wait(10) + + local TextBox = require("src.render.TextBox") + local function boxText() + local top = game.stack:top() + if getmetatable(top) ~= TextBox then return nil end + local lines = {} + for _, page in ipairs(top.pages or {}) do + for _, line in ipairs(page) do lines[#lines + 1] = line end + end + return table.concat(lines, " / ") + end + + check("no TM42 in the bag before talking", + (game.save.inventory.TM_DREAM_EATER or 0) == 0) + + U.tap(game, "a") + U.wait(30) + + local first = boxText() + check("pressing A opened a text box", first ~= nil) + U.log("first box reads:", first or "(none)") + check("it opens on the pre text, not the receipt", + first ~= nil and first:find("Yawn!", 1, true) ~= nil) + check("the DROWZEE dream paragraph is in it", + first ~= nil and first:find("DROWZEE", 1, true) ~= nil) + check("the receipt has not fired yet", + first == nil or first:find("received", 1, true) == nil) + check("the flag is still unset mid pre text", + not game.save.flags.EVENT_GOT_TM42) + U.shot(game, SHOT_DIR .. "/bug775_pre.png") + + -- type out and dismiss every page of the pre text, then the receipt and + -- the explanation behind it + for _ = 1, 40 do + if not boxText() then break end + U.tap(game, "a") + U.wait(15) + end + check("TM42 reached the bag", (game.save.inventory.TM_DREAM_EATER or 0) == 1) + check("EVENT_GOT_TM42 is set", game.save.flags.EVENT_GOT_TM42 == true) + + -- second talk: the flag routes to the DREAM EATER explanation, no re-gift + U.tap(game, "a") + U.wait(30) + local again = boxText() + U.log("second talk reads:", again or "(none)") + check("a second talk shows the explanation, not Yawn! again", + again ~= nil and again:find("Yawn!", 1, true) == nil) + U.shot(game, SHOT_DIR .. "/bug775_repeat.png") + + U.log("The screen is on the fisher's repeat-visit line now. The first") + U.log("talk should have read three pages: Yawn / the DROWZEE dream /") + U.log("\"Here, you can have this TM.\", and only then the TM42 receipt.") + U.log("Shots are in " .. SHOT_DIR .. " as bug775_pre.png / bug775_repeat.png.") + + while true do + coroutine.yield() + end +end diff --git a/tests/engine/battle_checkpoint_boundary.lua b/tests/engine/battle_checkpoint_boundary.lua new file mode 100644 index 00000000..e8d1bf5f --- /dev/null +++ b/tests/engine/battle_checkpoint_boundary.lua @@ -0,0 +1,85 @@ +-- Battle checkpoints are exposed only at a settled, reconstructable player +-- decision boundary. This suite is ROM-free and exercises the public engine +-- checkpoint capability against the fixture battle implementation. + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local T = require("tests.harness").suite("battle checkpoint boundary") +local Fixtures = require("tests.modkit").fixtures +local BattleState = require("src.battle.BattleState") +local Checkpoint = require("src.core.Checkpoint") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local StateStack = require("src.core.StateStack") + +local Data = Fixtures.fresh() + +local function makeGame() + local save = SaveData.newGame() + save.meta.playthroughId = "battle-playthrough" + save.party = { Pokemon.new(Data, "FIXMON_A", 20) } + local stack = setmetatable({ states = {} }, { __index = StateStack }) + local overworld = { + map = { id = save.player.map }, + player = { + cellX = save.player.x, cellY = save.player.y, + facing = save.player.facing, surfing = false, + }, + runner = { isRunning = function() return false end }, + parallelRunners = {}, pendingScripts = {}, parallelQueue = {}, scriptMoves = {}, + } + local game = { data = Data, save = save, stack = stack, overworld = overworld } + stack.states[1] = overworld + local battle = BattleState.newWild(game, "FIXMON_B", 12) + battle.phase = "menu" + battle.queue = {} + battle.checkpointOrigin = { kind = "wild_encounter" } + battle.onFinish = function() end + stack.states[2] = battle + return game, overworld, battle +end + +local game, overworld, battle = makeGame() +T.same(Checkpoint.inspect(game), { + canCapture = true, canRestore = true, kind = "battle", +}, "settled standard wild battle is a checkpoint boundary") + +local function refused(mutator, code, label) + local game2, ow2, battle2 = makeGame() + mutator(game2, ow2, battle2) + local capability = Checkpoint.inspect(game2) + T.check(capability.canCapture == false and capability.reason == code, + label .. ": " .. tostring(capability.reason)) +end + +refused(function(_, _, b) b.phase = "messages" end, + "battle_phase_busy", "message phase is rejected") +refused(function(_, _, b) b.queue = { { text = "busy" } } end, + "battle_phase_busy", "nonempty action queue is rejected") +refused(function(_, _, b) b.waitFrames = 1 end, + "battle_phase_busy", "partial wait is rejected") +refused(function(_, _, b) b.enemy.mon.hp = b.enemy.mon.hp - 1 end, + "battle_phase_busy", "unfinished HP display synchronization is rejected") +refused(function(_, _, b) b.player.mustRecharge = true end, + "battle_phase_busy", "automatic locked action is rejected") +refused(function(_, ow) ow.runner = { isRunning = function() return true end } end, + "script_busy", "suspended script beneath battle is rejected") +refused(function(_, _, b) b.checkpointOrigin = nil end, + "battle_origin_unsupported", "unknown completion closure is rejected") +refused(function(_, _, b) b.safari = { balls = 30, steps = 10 } end, + "battle_variant_unsupported", "Safari battle is rejected") +refused(function(_, _, b) b.ghost = true end, + "battle_variant_unsupported", "ghost battle is rejected") +refused(function(_, _, b) b.demo = true end, + "battle_variant_unsupported", "old-man demo is rejected") +refused(function(_, _, b) b.kind = "link" end, + "link_battle_unsupported", "link battle is rejected") + +-- Ordinary overworld behavior remains unchanged by the battle branch. +game.stack.states[2] = nil +T.same(Checkpoint.inspect(game), { + canCapture = true, canRestore = true, kind = "overworld", +}, "settled overworld remains supported") + +T.finish() diff --git a/tests/engine/battle_checkpoint_capture.lua b/tests/engine/battle_checkpoint_capture.lua new file mode 100644 index 00000000..84319a6b --- /dev/null +++ b/tests/engine/battle_checkpoint_capture.lua @@ -0,0 +1,168 @@ +-- Data-only capture of a settled battle checkpoint, including deterministic +-- gameplay RNG and normalized object-reference sets. + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local T = require("tests.harness").suite("battle checkpoint capture") +local Fixtures = require("tests.modkit").fixtures +local BattleState = require("src.battle.BattleState") +local Checkpoint = require("src.core.Checkpoint") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local SaveSerializer = require("src.core.SaveSerializer") +local StateStack = require("src.core.StateStack") + +local Data = Fixtures.fresh() +local oldGet, oldSet = love.math.getRandomState, love.math.setRandomState +local randomState = "fixture-rng-A" +love.math.getRandomState = function() return randomState end +love.math.setRandomState = function(state) randomState = state end + +local function makeGame(kind) + local save = SaveData.newGame() + save.meta.playthroughId = "battle-playthrough" + save.player.map, save.player.x, save.player.y = "FIX_TOWN", 2, 3 + save.party = { + Pokemon.new(Data, "FIXMON_A", 20), + Pokemon.new(Data, "FIXMON_C", 15), + } + local stack = setmetatable({ states = {} }, { __index = StateStack }) + local overworld = { + map = { id = "FIX_TOWN" }, + player = { cellX = 2, cellY = 3, facing = "left", surfing = false }, + runner = { isRunning = function() return false end }, + parallelRunners = {}, pendingScripts = {}, parallelQueue = {}, scriptMoves = {}, + } + function overworld:captureSave(target) + target.player.map = self.map.id + target.player.x, target.player.y = self.player.cellX, self.player.cellY + target.player.facing = self.player.facing + target.player.surfing = self.player.surfing and true or false + end + local game = { data = Data, save = save, stack = stack, overworld = overworld } + stack.states[1] = overworld + local battle + if kind == "trainer" then + battle = BattleState.newTrainer(game, "OPP_FIX_YOUNGSTER", 1) + battle.checkpointOrigin = { + kind = "trainer_encounter", map = "FIX_TOWN", npcId = "TRAINER_1", + trainerClass = "OPP_FIX_YOUNGSTER", partyIndex = 1, + event = "EVENT_BEAT_TRAINER_1", + } + else + battle = BattleState.newWild(game, "FIXMON_B", 12) + battle.checkpointOrigin = { kind = "wild_encounter", map = "FIX_TOWN" } + end + battle.phase, battle.queue = "menu", {} + battle.onFinish = function() end + stack.states[2] = battle + return game, battle +end + +local game, battle = makeGame("wild") +battle.turnCount = 7 +battle.runAttempts = 2 +battle.payDay = 45 +battle.player.stages.attack = 2 +battle.player.confusedTurns = 3 +battle.player.curTypes = { "FIRE", "FLYING" } +local originalMoveId = battle.player.curMoves[1].id +battle.player.curMoves[1].id = "FIX_CUT" +battle.player.curMoves[1].mimic = true +battle.mimicRestores = { + { battler = battle.player, entry = battle.player.curMoves[1], id = originalMoveId }, +} +battle.enemy.mon.hp = battle.enemy.mon.hp - 4 +battle.enemy.shownHP = battle.enemy.mon.hp +battle.enemy.stages.defense = -1 +battle.enemy.aiLayer2 = 1 +battle.enemy.thrashMove = battle.enemy.curMoves[1] +battle.enemy.thrashTurns = 2 +battle.participants = { [game.save.party[1]] = true, + [game.save.party[2]] = true } +battle.leveledUp = { [game.save.party[2]] = true } +battle.sideToxic = { enemy = 3 } +battle.sides[1].screens.reflect = { turns = 2 } +battle.field.weather = { id = "fixture-rain", turns = 4 } + +local snapshot, code, message = Checkpoint.capture(game) +T.check(snapshot ~= nil, "settled battle captures: " .. tostring(code or message)) +T.eq(snapshot and snapshot.kind, "battle", "checkpoint kind is battle") +if snapshot and snapshot.kind == "battle" then + T.eq(snapshot.rng.love, "fixture-rng-A", "LÖVE RNG state is captured") + T.same(snapshot.runtime.overworld, + { map = "FIX_TOWN", x = 2, y = 3, facing = "left", surfing = false }, + "return overworld point is captured") + T.same(snapshot.runtime.battle.origin, + { kind = "wild_encounter", map = "FIX_TOWN" }, + "semantic continuation origin is data-only") + T.eq(snapshot.runtime.battle.turnCount, 7, "turn count is captured") + T.eq(snapshot.runtime.battle.rulesetId, "gen1_faithful", + "battle mechanics ruleset identity is captured") + T.eq(snapshot.runtime.battle.runAttempts, 2, "escape attempts are captured") + T.eq(snapshot.runtime.battle.player.stages.attack, 2, + "player stat stages are captured") + T.eq(snapshot.runtime.battle.player.confusedTurns, 3, + "player volatile status is captured") + T.same(snapshot.runtime.battle.player.curTypes, { "FIRE", "FLYING" }, + "transformed battle types are captured") + T.same(snapshot.runtime.battle.mimicRestores, + { { side = "player", slot = 1, id = originalMoveId } }, + "Mimic restore pointers normalize to side and move slot") + T.eq(snapshot.runtime.battle.enemy.stages.defense, -1, + "enemy stat stages are captured") + T.eq(snapshot.runtime.battle.enemy.aiLayer2, 1, + "enemy AI selection layer is captured") + T.eq(snapshot.runtime.battle.enemy.thrashMoveSlot, 1, + "move-instance references normalize to move slots") + T.eq(snapshot.runtime.battle.enemy.thrashMove, nil, + "live move-instance references are not serialized as detached copies") + T.same(snapshot.runtime.battle.participants, { 1, 2 }, + "Pokemon-keyed participants normalize to party indices") + T.same(snapshot.runtime.battle.leveledUp, { 2 }, + "Pokemon-keyed level-up set normalizes to party indices") + T.same(snapshot.runtime.battle.sides[1].screens.reflect, { turns = 2 }, + "data-only side extensions are captured") + T.same(snapshot.runtime.battle.field.weather, + { id = "fixture-rain", turns = 4 }, + "data-only field extensions are captured") + local encoded = SaveSerializer.encode(snapshot) + T.check(type(encoded) == "string" and #encoded > 0, + "battle checkpoint passes the canonical data-only serializer") + + snapshot.save.money = 1 + snapshot.runtime.battle.player.stages.attack = -6 + T.check(game.save.money ~= 1, "checkpoint progress is detached") + T.eq(battle.player.stages.attack, 2, "checkpoint battle state is detached") +end + +local trainerGame, trainer = makeGame("trainer") +trainer.enemyIndex = 1 +trainer.aiUses = 2 +local trainerSnapshot = Checkpoint.capture(trainerGame) +T.eq(trainerSnapshot and trainerSnapshot.kind, "battle", + "ordinary trainer battle captures") +if trainerSnapshot and trainerSnapshot.kind == "battle" then + T.eq(trainerSnapshot.runtime.battle.oppClass, "OPP_FIX_YOUNGSTER", + "trainer class is captured") + T.eq(trainerSnapshot.runtime.battle.partyIndex, 1, + "trainer roster index is captured") + T.eq(#trainerSnapshot.runtime.battle.enemyParty, #trainer.enemyParty, + "complete enemy roster is captured") +end + +local extensionGame, extensionBattle = makeGame("wild") +extensionBattle.field.tokens[1] = { id = "callback-token", onExpire = function() end } +local unsafe, unsafeCode = Checkpoint.capture(extensionGame) +T.check(unsafe == nil and unsafeCode == "battle_extension_unsafe", + "callback-bearing battle extensions are rejected, not stripped") + +love.math.getRandomState = nil +local rngGame = makeGame("wild") +local noRng, rngCode = Checkpoint.capture(rngGame) +T.check(noRng == nil and rngCode == "rng_state_unavailable", + "battle capture fails closed without serializable gameplay RNG") + +love.math.getRandomState, love.math.setRandomState = oldGet, oldSet +T.finish() diff --git a/tests/engine/battle_checkpoint_continuation.lua b/tests/engine/battle_checkpoint_continuation.lua new file mode 100644 index 00000000..3219119f --- /dev/null +++ b/tests/engine/battle_checkpoint_continuation.lua @@ -0,0 +1,102 @@ +-- Engine-owned battle continuations replace unserializable onFinish closures +-- after a persistent checkpoint reconstructs the overworld and battle. + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local T = require("tests.harness").suite("battle checkpoint continuation") +local GameMethods = require("src.core.Game") +local OverworldState = require("src.world.OverworldController") + +local function fakeOverworld() + local npc = { id = "FIX_TOWN_obj_1", frozen = true } + local ow = setmetatable({ + map = { id = "FIX_TOWN" }, + npcPool = { [npc.id] = npc }, + engaging = true, + }, { __index = OverworldState }) + ow.afterBattle = function(self, result, battle) + self.after = { result = result, battle = battle } + end + ow.checkVictoryRewards = function(self, class, party) + self.reward = { class = class, party = party } + end + return ow, npc +end + +local wildOw = fakeOverworld() +local wildGame = { save = { defeatedTrainers = {}, flags = {} } } +local wild = { game = wildGame, kind = "wild" } +T.check(wildOw:restoreBattleContinuation(wild, + { kind = "wild_encounter", map = "FIX_TOWN" }) == true, + "ordinary wild continuation binds") +wild.onFinish("run") +T.same(wildOw.after, { result = "run", battle = wild }, + "wild continuation returns through canonical afterBattle") + +local trainerOw, trainerNpc = fakeOverworld() +local trainerGame = { save = { defeatedTrainers = {}, flags = {} } } +local trainer = { + game = trainerGame, kind = "trainer", + oppClass = "OPP_FIX_YOUNGSTER", partyIndex = 1, +} +local trainerOrigin = { + kind = "trainer_encounter", map = "FIX_TOWN", + npcId = trainerNpc.id, trainerClass = trainer.oppClass, partyIndex = 1, + event = "EVENT_BEAT_FIX_TRAINER", +} +T.check(trainerOw:restoreBattleContinuation(trainer, trainerOrigin) == true, + "ordinary trainer continuation binds") +trainer.onFinish("win") +T.check(trainerGame.save.defeatedTrainers[trainerNpc.id] == true, + "trainer win stamps the stable object id") +T.check(trainerGame.save.flags.EVENT_BEAT_FIX_TRAINER == true, + "trainer win stamps the header event") +T.same(trainerOw.reward, + { class = "OPP_FIX_YOUNGSTER", party = 1 }, + "trainer win runs canonical victory rewards") +T.same(trainerOw.after, { result = "win", battle = trainer }, + "trainer win returns through canonical afterBattle") +T.check(trainerOw.engaging == false and trainerNpc.frozen == false, + "reconstructed trainer completion leaves overworld input unfrozen") + +local lossOw, lossNpc = fakeOverworld() +local lossGame = { save = { defeatedTrainers = {}, flags = {} } } +local lossBattle = { + game = lossGame, kind = "trainer", + oppClass = "OPP_FIX_YOUNGSTER", partyIndex = 1, +} +T.check(lossOw:restoreBattleContinuation(lossBattle, trainerOrigin) == true, + "trainer loss continuation binds") +lossBattle.onFinish("lose") +T.eq(lossGame.save.defeatedTrainers[lossNpc.id], nil, + "trainer loss does not stamp the trainer defeated") +T.eq(lossGame.save.flags.EVENT_BEAT_FIX_TRAINER, nil, + "trainer loss does not stamp the header event") +T.eq(lossOw.reward, nil, "trainer loss does not grant victory rewards") + +local mismatchOw = fakeOverworld() +T.check(mismatchOw:restoreBattleContinuation(trainer, { + kind = "trainer_encounter", map = "OTHER_MAP", npcId = trainerNpc.id, + trainerClass = trainer.oppClass, partyIndex = 1, +}) == false, "continuation from another map is rejected") +T.check(mismatchOw:restoreBattleContinuation(trainer, { + kind = "trainer_encounter", map = "FIX_TOWN", npcId = trainerNpc.id, + trainerClass = "OPP_OTHER", partyIndex = 1, +}) == false, "mismatched trainer identity is rejected") + +local ow = {} +local stack = { states = { ow } } +function stack:top() return self.states[#self.states] end +local game = setmetatable({ overworld = ow, stack = stack }, { __index = GameMethods }) +local entered, resumed = false, false +local battle = { + enter = function() entered = true end, + resumeCheckpoint = function() resumed = true end, +} +game:restoreCheckpointBattle(battle) +T.check(game.stack:top() == battle, "reconstructed battle is installed on stack") +T.check(resumed == true, "checkpoint-specific battle resume path runs") +T.check(entered == false, "ordinary battle intro is not replayed") + +T.finish() diff --git a/tests/engine/battle_checkpoint_restore.lua b/tests/engine/battle_checkpoint_restore.lua new file mode 100644 index 00000000..1ea08ea1 --- /dev/null +++ b/tests/engine/battle_checkpoint_restore.lua @@ -0,0 +1,315 @@ +-- A battle checkpoint reconstructs a new controller from data, rather than +-- retaining the original table/closure, and restores gameplay RNG exactly. + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local T = require("tests.harness").suite("battle checkpoint restore") +local Fixtures = require("tests.modkit").fixtures +local BattleState = require("src.battle.BattleState") +local Checkpoint = require("src.core.Checkpoint") +local Damage = require("src.battle.Damage") +local Encounter = require("src.world.Encounter") +local GameMethods = require("src.core.Game") +local Music = require("src.core.Music") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local SaveSerializer = require("src.core.SaveSerializer") +local StateStack = require("src.core.StateStack") +local TrainerAI = require("src.battle.TrainerAI") + +local Data = Fixtures.fresh() +local oldRandom = love.math.random +local oldGet, oldSet = love.math.getRandomState, love.math.setRandomState +local oldPlayBattle = Music.playBattle +Music.playBattle = function() end +local rng = 12345 +love.math.getRandomState = function() return tostring(rng) end +love.math.setRandomState = function(state) rng = assert(tonumber(state)) end +love.math.random = function(a, b) + rng = (rng * 1103515245 + 12345) % 2147483648 + local unit = rng / 2147483648 + if a == nil then return unit end + if b == nil then return math.floor(unit * a) + 1 end + return a + math.floor(unit * (b - a + 1)) +end + +local function makeGame(kind) + local save = SaveData.newGame() + save.meta.playthroughId = "battle-playthrough" + save.player.map, save.player.x, save.player.y = "FIX_TOWN", 2, 3 + save.player.facing, save.player.surfing = "left", false + save.party = { + Pokemon.new(Data, "FIXMON_A", 20), + Pokemon.new(Data, "FIXMON_C", 15), + } + -- Strip new-game defaults that are intentionally absent from the tiny + -- fixture registry, then place the sanitized save on a fixture map. + SaveData.validate(save, Data) + save.player.map, save.player.x, save.player.y = "FIX_TOWN", 2, 3 + save.player.facing, save.player.surfing = "left", false + local stack = setmetatable({ states = {} }, { __index = StateStack }) + local overworld = { + map = { id = "FIX_TOWN" }, + player = { cellX = 2, cellY = 3, facing = "left", surfing = false }, + runner = { isRunning = function() return false end }, + parallelRunners = {}, pendingScripts = {}, parallelQueue = {}, scriptMoves = {}, + } + function overworld:captureSave(target) + target.player.map = self.map.id + target.player.x, target.player.y = self.player.cellX, self.player.cellY + target.player.facing = self.player.facing + target.player.surfing = self.player.surfing and true or false + end + function overworld:restoreBattleContinuation(battle, origin) + battle.onFinish = function(result) + self.lastRestoredFinish = { result = result, origin = origin.kind } + end + return true + end + local game = setmetatable( + { data = Data, save = save, stack = stack, overworld = overworld }, + { __index = GameMethods }) + function game:restoreCheckpointSave(loaded) + self.save = loaded + self.overworld.map = { id = loaded.player.map } + self.overworld.player = { + cellX = loaded.player.x, cellY = loaded.player.y, + facing = loaded.player.facing, + surfing = loaded.player.surfing and true or false, + } + self.overworld.runner = { isRunning = function() return false end } + self.overworld.parallelRunners, self.overworld.pendingScripts = {}, {} + self.overworld.parallelQueue, self.overworld.scriptMoves = {}, {} + self.stack.states = { self.overworld } + end + stack.states[1] = overworld + local battle + if kind == "trainer" then + battle = BattleState.newTrainer(game, "OPP_FIX_YOUNGSTER", 1) + battle.checkpointOrigin = { + kind = "trainer_encounter", map = "FIX_TOWN", npcId = "TRAINER_1", + trainerClass = "OPP_FIX_YOUNGSTER", partyIndex = 1, + event = "EVENT_BEAT_TRAINER_1", + } + else + battle = BattleState.newWild(game, "FIXMON_B", 12) + battle.checkpointOrigin = { kind = "wild_encounter", map = "FIX_TOWN" } + end + battle.phase, battle.queue = "menu", {} + battle.musicKind = battle:computeMusicKind() + battle.onFinish = function() end + stack.states[2] = battle + return game, battle +end + +local function settleOverworld(game) + game.stack.states = { game.overworld } + game.save.money = 999999 + game.save.party[1].hp = 1 + game.overworld.player.cellX = 8 + game.overworld.player.facing = "up" +end + +local game, originalBattle = makeGame("wild") +originalBattle.turnCount = 4 +originalBattle.runAttempts = 1 +originalBattle.player.stages.speed = 3 +local originalMoveId = originalBattle.player.curMoves[1].id +originalBattle.player.curMoves[1].id = "FIX_CUT" +originalBattle.player.curMoves[1].mimic = true +originalBattle.mimicRestores = { + { battler = originalBattle.player, entry = originalBattle.player.curMoves[1], + id = originalMoveId }, +} +originalBattle.enemy.mon.hp = originalBattle.enemy.mon.hp - 5 +originalBattle.enemy.shownHP = originalBattle.enemy.mon.hp +originalBattle.enemy.disabledSlot = 1 +originalBattle.enemy.disabledTurns = 2 +originalBattle.enemy.aiLayer2 = 1 +originalBattle.enemy.thrashTurns = 2 +originalBattle.enemy.mon.moves = { + { id = "FIX_SCRATCH", pp = 35 }, { id = "FIX_CUT", pp = 30 }, +} +originalBattle.enemy.curMoves = originalBattle.enemy.mon.moves +originalBattle.enemy.thrashMove = originalBattle.enemy.curMoves[1] +originalBattle.participants = { [game.save.party[1]] = true } +local checkpoint = assert(Checkpoint.capture(game)) +local encounterDef = { grass = { + rate = 256, buckets = { 128, 256 }, + slots = { { species = "FIXMON_A", level = 4 }, + { species = "FIXMON_B", level = 7 } }, +} } +Encounter.load(Data) +local function randomOutcomes(battle) + local damage, detail = Damage.compute(battle.ruleset, battle.player, + battle.enemy, Data.moves.FIX_CUT, { rng = battle.rng }) + local hit = Damage.accuracyRoll(battle.ruleset, Data.moves.FIX_CUT, + battle.player, battle.enemy, battle.rng) + local ai = TrainerAI.chooseMove(battle.enemy, battle.rng, nil) + local escaped = battle:runRollVanilla(1, 100) + local encounter = Encounter.roll(encounterDef, love.math.random) + local nextValue = love.math.random(1, 1000000) + return { + damage = damage, critical = detail.crit, hit = hit, + ai = ai.id, escaped = escaped, encounter = encounter, + nextValue = nextValue, + } +end +local expectedOutcomes = randomOutcomes(originalBattle) + +settleOverworld(game) +game.save.options.ruleset = "modern_clean" +rng = 777 +local restored, code, message = Checkpoint.restore(game, checkpoint) +T.check(restored == true, "battle checkpoint restores: " .. tostring(message or code)) +local rebuilt = restored and game.stack:top() +if restored then + T.check(rebuilt ~= originalBattle, "restore creates a new battle controller") + T.eq(getmetatable(rebuilt), BattleState, "restored stack top is a BattleState") + T.eq(rebuilt.phase, "menu", "restored battle resumes at the decision menu") + T.eq(#rebuilt.queue, 0, "restored battle has no stale action queue") + T.eq(rebuilt.turnCount, 4, "turn count roundtrips") + T.eq(rebuilt.runAttempts, 1, "escape state roundtrips") + T.eq(rebuilt.player.stages.speed, 3, "player stages roundtrip") + T.check(rebuilt.mimicRestores and rebuilt.mimicRestores[1] + and rebuilt.mimicRestores[1].battler == rebuilt.player + and rebuilt.mimicRestores[1].entry == rebuilt.player.curMoves[1], + "Mimic restore references are rebuilt against the new battler") + rebuilt:restoreMimicked(rebuilt.player) + T.eq(rebuilt.player.curMoves[1].id, originalMoveId, + "restored Mimic move returns to its canonical id when battle copy leaves") + T.eq(rebuilt.player.curMoves[1].mimic, nil, + "restored Mimic marker clears with the battle copy") + -- Put the checkpointed battle state back before differential recapture. + rebuilt.player.curMoves[1].id = "FIX_CUT" + rebuilt.player.curMoves[1].mimic = true + rebuilt.mimicRestores = { + { battler = rebuilt.player, entry = rebuilt.player.curMoves[1], id = originalMoveId }, + } + T.eq(rebuilt.enemy.disabledSlot, 1, "enemy volatile state roundtrips") + T.eq(rebuilt.enemy.disabledTurns, 2, "enemy volatile duration roundtrips") + T.eq(rebuilt.enemy.aiLayer2, 1, "enemy AI selection layer roundtrips") + T.check(rebuilt.enemy.thrashMove == rebuilt.enemy.curMoves[1], + "multi-turn move references rebuild against the new move list") + T.eq(rebuilt.enemy.mon.hp, checkpoint.runtime.battle.enemyMon.hp, + "enemy Pokemon model roundtrips") + T.eq(game.save.money, checkpoint.save.money, "persistent progress roundtrips") + T.eq(game.save.party[1].hp, checkpoint.save.party[1].hp, + "party model roundtrips") + T.eq(game.save.options.ruleset, "modern_clean", + "current global ruleset option remains untouched") + T.check(rebuilt.ruleset == require("src.battle.rulesets.gen1_faithful"), + "restored battle keeps the mechanics ruleset it was captured with") + T.same(Checkpoint.capture(game), checkpoint, + "capture A, discard, restore A, capture A2 yields normalized A == A2") + local replayed = randomOutcomes(rebuilt) + T.same(replayed, expectedOutcomes, + "damage, critical, accuracy, AI, escape, encounter and next RNG replay exactly") + rebuilt.onFinish("run") + T.same(game.overworld.lastRestoredFinish, + { result = "run", origin = "wild_encounter" }, + "restored battle receives a reconstructed semantic continuation") +end + +local partyGame, switchedOriginal = makeGame("wild") +partyGame.save.party[1].hp = 0 +local activeMon = partyGame.save.party[2] +activeMon.status = "PAR" +activeMon.moves[1].pp = activeMon.moves[1].pp - 4 +switchedOriginal.player = BattleState.makeBattler( + Data, activeMon, true, partyGame.save) +switchedOriginal.sides[1].battlers = { switchedOriginal.player } +switchedOriginal.participants = { + [partyGame.save.party[1]] = true, + [partyGame.save.party[2]] = true, +} +local partyCheckpoint = assert(Checkpoint.capture(partyGame)) +settleOverworld(partyGame) +partyGame.save.party[2].status = nil +partyGame.save.party[2].moves[1].pp = 1 +restored, code, message = Checkpoint.restore(partyGame, partyCheckpoint) +T.check(restored == true, + "switched/status/PP checkpoint restores: " .. tostring(message or code)) +local partyRebuilt = partyGame.stack:top() +if restored then + T.eq(partyGame.save.party[1].hp, 0, + "fainted non-active party member roundtrips") + T.check(partyRebuilt.player.mon == partyGame.save.party[2], + "switched active Pokemon reconstructs against restored party identity") + T.eq(partyRebuilt.player.mon.status, "PAR", "active status roundtrips") + T.eq(partyRebuilt.player.mon.moves[1].pp, + partyCheckpoint.save.party[2].moves[1].pp, "reduced PP roundtrips") + T.check(partyRebuilt.participants[partyGame.save.party[1]] == true + and partyRebuilt.participants[partyGame.save.party[2]] == true, + "participant references rebuild against fainted and active party members") + T.same(Checkpoint.capture(partyGame), partyCheckpoint, + "switch, faint, status and PP differential recapture is exact") +end + +local trainerGame, trainerOriginal = makeGame("trainer") +trainerOriginal.turnCount = 6 +trainerOriginal.enemy.mon.hp = trainerOriginal.enemy.mon.hp - 3 +trainerOriginal.enemy.shownHP = trainerOriginal.enemy.mon.hp +trainerOriginal.aiUses = 1 +trainerOriginal.participants = { [trainerGame.save.party[1]] = true, + [trainerGame.save.party[2]] = true } +local trainerCheckpoint = assert(Checkpoint.capture(trainerGame)) +settleOverworld(trainerGame) +restored, code, message = Checkpoint.restore(trainerGame, trainerCheckpoint) +T.check(restored == true, "trainer checkpoint restores: " .. tostring(message or code)) +local trainerRebuilt = trainerGame.stack:top() +if restored then + T.check(trainerRebuilt ~= trainerOriginal, + "trainer restore is independent of the original controller") + T.eq(trainerRebuilt.oppClass, "OPP_FIX_YOUNGSTER", "trainer class roundtrips") + T.eq(trainerRebuilt.enemyIndex, 1, "enemy roster index roundtrips") + T.eq(trainerRebuilt.aiUses, 1, "trainer AI item budget roundtrips") + T.same(Checkpoint.capture(trainerGame), trainerCheckpoint, + "trainer differential recapture is exact") +end + +local function clone(value) + return assert(SaveSerializer.decode(SaveSerializer.encode(value))) +end + +local beforeRejected = assert(Checkpoint.capture(trainerGame)) +local missingSpecies = clone(trainerCheckpoint) +missingSpecies.runtime.battle.enemyParty[1].species = "MISSING_SPECIES" +restored, code = Checkpoint.restore(trainerGame, missingSpecies) +T.check(restored == false and code == "invalid_content", + "unknown battle content is rejected before mutation") +T.same(Checkpoint.capture(trainerGame), beforeRejected, + "rejected battle content leaves runtime and RNG unchanged") + +local badOrigin = clone(trainerCheckpoint) +badOrigin.runtime.battle.origin.npcId = nil +restored, code = Checkpoint.restore(trainerGame, badOrigin) +T.check(restored == false and code == "battle_origin_unsupported", + "incomplete semantic continuation is rejected before mutation") +T.same(Checkpoint.capture(trainerGame), beforeRejected, + "rejected continuation leaves runtime and RNG unchanged") + +-- Fail after the new battle has been installed, when its RNG is applied. +-- The transaction must reconstruct the prior battle and restore its RNG. +local workingSetRandomState = love.math.setRandomState +local setCalls = 0 +love.math.setRandomState = function(state) + setCalls = setCalls + 1 + if setCalls == 1 then error("injected RNG restore failure") end + return workingSetRandomState(state) +end +local beforeFailure = assert(Checkpoint.capture(trainerGame)) +local rngBeforeFailure = rng +restored, code = Checkpoint.restore(trainerGame, trainerCheckpoint) +T.check(restored == false and code == "restore_failed", + "post-install RNG failure is returned as a structured restore failure") +T.eq(rng, rngBeforeFailure, "failed battle restore rolls RNG back exactly") +T.same(Checkpoint.capture(trainerGame), beforeFailure, + "failed battle restore rolls the complete runtime back exactly") +love.math.setRandomState = workingSetRandomState + +love.math.random = oldRandom +love.math.getRandomState, love.math.setRandomState = oldGet, oldSet +Music.playBattle = oldPlayBattle +T.finish() diff --git a/tests/engine/battle_fit_option.lua b/tests/engine/battle_fit_option.lua index d0918446..22ca3e25 100644 --- a/tests/engine/battle_fit_option.lua +++ b/tests/engine/battle_fit_option.lua @@ -75,6 +75,19 @@ T.eq(Game.worldBgBattleDim(stack(overworld, battleBg("world"), partyMenu)), T.eq(Game.worldBgBattleDim(stack(overworld)), nil, "no battle, no dim") T.eq(Game.worldBgBattleDim(nil), nil, "and no stack is safe") +-- #773: the same walk decides whether the dark-cave shade shift may be armed +-- for this frame. A battle zeroes wMapPalOffset (init_battle_variables.asm), +-- so a world-bg battle over an un-flashed Rock Tunnel must suppress it. +T.check(Game.worldBgBattleInStack(stack(overworld, battleBg("world"))), + "a world-bg battle claims the frame, so the dark shift stays off it") +T.check(not Game.worldBgBattleInStack(stack(overworld, battleBg("white"))), + "a white-bg battle draws with no map under it and claims nothing") +T.check(Game.worldBgBattleInStack(stack(overworld, battleBg("world"), partyMenu)), + "a menu opened over the world-bg battle does not hand the shift back") +T.check(not Game.worldBgBattleInStack(stack(overworld)), + "a plain dark map still arms it") +T.check(not Game.worldBgBattleInStack(nil), "and no stack is safe") + T.check(BattleState.BG_WORLD_DIM > 0 and BattleState.BG_WORLD_DIM < 1, "the dim is a fraction, not a full blackout") diff --git a/tests/engine/build_zip_pipe_guard_bug774.lua b/tests/engine/build_zip_pipe_guard_bug774.lua new file mode 100644 index 00000000..92b34572 --- /dev/null +++ b/tests/engine/build_zip_pipe_guard_bug774.lua @@ -0,0 +1,117 @@ +-- #774: packager archive checks must never pipe an `unzip -Z1` listing +-- straight into `grep -q`. grep -q exits on the first match, unzip takes +-- SIGPIPE (141), and under the scripts' `set -o pipefail` the pipeline +-- reports 141 -- so an `if` guard reads a real match as "no match". For +-- build_android.sh's generated-data guard that failed open on exactly the +-- archive it exists to reject (one carrying the user's extracted ROM +-- data). The fix everywhere is to capture the listing once and grep the +-- captured text (build.sh, pack_love.sh, build_ios.sh, build_android.sh +-- all do); this suite keeps the class of bug from regressing. The +-- `unzip -p ... Version.lua | grep` readbacks are fine -- a 1.4 KB single +-- write fits the pipe buffer, so unzip returns before grep can close the +-- read end -- and the scan below deliberately matches only -Z1 listings. +-- Self-contained: luajit tests/engine/build_zip_pipe_guard_bug774.lua +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check + +local function readFile(path) + local f = io.open(path, "rb") + if not f then return nil end + local body = f:read("*a") + f:close() + return body +end + +local function listShellFiles() + local out = {} + local p = io.popen('find scripts -name "*.sh" -type f') + if not p then return out end + for line in p:lines() do + out[#out + 1] = line + end + p:close() + table.sort(out) + return out +end + +-- ------------------------------------------------------------- static scan +-- Join backslash continuations first: the buggy form in build_android.sh +-- spread the pipeline across two lines. +local scripts = listShellFiles() +check(#scripts > 0, "found shell scripts under scripts/ to scan") + +local violations = {} +for _, file in ipairs(scripts) do + local body = readFile(file) + if body then + local joined = body:gsub("\\\n%s*", " ") + if joined:find("unzip %-Z1[^\n|]*|%s*grep %-%a*q") then + violations[#violations + 1] = file + end + end +end + +check(#violations == 0, + "no script pipes an unzip -Z1 listing into grep -q (#774: pipefail turns" + .. " the SIGPIPE into an inverted guard)" + .. (#violations > 0 and (":\n " .. table.concat(violations, "\n ")) or "")) + +-- --------------------------------------------------------- replay the guard +-- Run build_android.sh's own forbidden-content pattern, in the captured +-- form the script now uses, over a throwaway archive that really does +-- carry a data/generated entry, and over a clean one. This pins the +-- capture-then-grep idiom's behavior rather than trusting the scan alone. +local androidBody = readFile("scripts/build_android.sh") or "" +local pattern = androidBody:match("grep %-Eq '([^']*generated[^']*)'") +check(pattern ~= nil, + "build_android.sh still greps a generated-data pattern over the listing") + +local function haveCommand(name) + local probe = io.popen("command -v " .. name .. " 2>/dev/null") + if not probe then return false end + local out = probe:read("*a") + probe:close() + return out ~= nil and out:match("%S") ~= nil +end + +if pattern and haveCommand("zip") and haveCommand("unzip") + and haveCommand("bash") then + local tmpDir = (os.getenv("TMPDIR") or "/tmp"):gsub("[/\\]+$", "") + local stage = ("%s/pokeport_bug774_%d_%d"):format( + tmpDir, os.time(), math.random(1, 999999)) + os.execute(('mkdir -p "%s/pay/data/generated"'):format(stage)) + os.execute(('touch "%s/pay/data/generated/x.lua" "%s/pay/main.lua"') + :format(stage, stage)) + os.execute(('cd "%s/pay" && zip -qr ../bad.love .'):format(stage)) + os.execute(('cd "%s/pay" && zip -qr ../clean.love main.lua'):format(stage)) + + local function guardVerdict(archive) + -- exactly the script's shape: pipefail on, listing captured once, + -- grep runs over the captured text so nothing can take a SIGPIPE + local cmd = ("bash -c 'set -euo pipefail\n" + .. 'archive_entries="$(unzip -Z1 "%s")"\n' + .. "if grep -Eq '\\''%s'\\'' <<< \"$archive_entries\"; then" + .. " echo CAUGHT; else echo CLEAN; fi' 2>/dev/null"):format( + archive, pattern) + local p = io.popen(cmd) + if not p then return nil end + local out = p:read("*a") or "" + p:close() + return out:match("%S+") + end + + check(guardVerdict(stage .. "/bad.love") == "CAUGHT", + "the captured-listing guard rejects an archive carrying data/generated" + .. " (#774: the piped form let this ship in an APK)") + check(guardVerdict(stage .. "/clean.love") == "CLEAN", + "the captured-listing guard passes an archive without generated data") + + os.execute(('rm -rf "%s"'):format(stage)) +else + print("[#774] zip/unzip/bash not all present: guard replay skipped," + .. " the static scan above still ran") +end + +T.finish() diff --git a/tests/engine/cache_fs_headless_test.lua b/tests/engine/cache_fs_headless_test.lua new file mode 100644 index 00000000..286599c4 --- /dev/null +++ b/tests/engine/cache_fs_headless_test.lua @@ -0,0 +1,20 @@ +-- CacheFs stays headless-safe: plain luajit has no love global, and the +-- modkit validate/pack driver reaches CacheFs.read through Data:load when +-- an optional generated module (audio) is missing from the checkout +-- (issue #850). With no portable root and no love there is no save +-- directory to read from, so the read is a nil miss, not a crash. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check + +check(_G.love == nil, "suite runs with no love global") + +local CacheFs = require("src.import.CacheFs") + +check(CacheFs.read("data/generated/audio.lua") == nil, + "read is a nil miss headless, not a crash") +check(CacheFs.readActive("data/generated/audio.lua") == nil, + "readActive is a nil miss headless, not a crash") + +T.finish() diff --git a/tests/engine/cache_fs_red_migration_test.lua b/tests/engine/cache_fs_red_migration_test.lua new file mode 100644 index 00000000..7ab2cd2b --- /dev/null +++ b/tests/engine/cache_fs_red_migration_test.lua @@ -0,0 +1,69 @@ +-- Issue #899: Red's extracted cache lives under red/ like blue/ and +-- yellow/, and a legacy root cache (pre-fix installs) is migrated on first +-- boot instead of reading as "never imported". +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local T = require("tests.harness") +local check = T.check +local eq = T.eq + +local CacheFs = require("src.import.CacheFs") +local GameVersion = require("src.core.GameVersion") + +eq(GameVersion.cachePrefix("red"), "red/", + "Red's cache is namespaced under red/") + +-- legacy layout: the marker and both generated trees at the save-dir root +love.filesystem.write("rom-cache.complete", "rom-cache-v9:abc") +love.filesystem.write("data/generated/maps.lua", "return {}") +love.filesystem.write("data/generated/constants.lua", "return {}") +love.filesystem.write("assets/generated/fonts/font.png", "font-bytes") + +CacheFs.migrateLegacyRedCache() + +eq(love.filesystem.read("red/rom-cache.complete"), "rom-cache-v9:abc", + "the marker moved under red/") +eq(love.filesystem.read("red/data/generated/maps.lua"), "return {}", + "the data tree moved under red/") +eq(love.filesystem.read("red/assets/generated/fonts/font.png"), "font-bytes", + "the assets tree moved under red/") +check(love.filesystem.read("rom-cache.complete") == nil, + "the root marker is gone") +check(love.filesystem.read("data/generated/maps.lua") == nil, + "the root data tree is gone") +check(love.filesystem.read("assets/generated/fonts/font.png") == nil, + "the root assets tree is gone") + +-- idempotent: a second run leaves the migrated tree alone +CacheFs.migrateLegacyRedCache() +eq(love.filesystem.read("red/rom-cache.complete"), "rom-cache-v9:abc", + "a second run keeps the migrated cache") + +-- an existing red/ cache wins over a legacy root leftover: no clobber +love.filesystem.write("rom-cache.complete", "rom-cache-v9:STALE") +CacheFs.migrateLegacyRedCache() +eq(love.filesystem.read("red/rom-cache.complete"), "rom-cache-v9:abc", + "an existing red/ cache is not clobbered") +check(love.filesystem.read("rom-cache.complete") ~= nil, + "the unmigrated leftover stays (a stale-marker re-import handles it)") +love.filesystem.remove("rom-cache.complete") + +-- mountVersion("red") overlays red/ at the un-prefixed paths, like blue/ +love.filesystem._mounts = {} +check(CacheFs.mountVersion("red") == true, "mountVersion(red) returns true") +eq(love.filesystem.read("assets/generated/fonts/font.png"), "font-bytes", + "post-mount probe reads red assets at the un-prefixed path") +eq(love.filesystem.read("data/generated/constants.lua"), "return {}", + "post-mount probe reads red data at the un-prefixed path") + +-- no legacy cache at all: migration is a no-op, not an error +love.filesystem.remove("red/rom-cache.complete") +love.filesystem.remove("red/data/generated/maps.lua") +love.filesystem.remove("red/data/generated/constants.lua") +love.filesystem.remove("red/assets/generated/fonts/font.png") +CacheFs.migrateLegacyRedCache() +check(love.filesystem.read("red/rom-cache.complete") == nil, + "nothing to migrate invents nothing") + +T.finish() diff --git a/tests/engine/chip_analog_path.lua b/tests/engine/chip_analog_path.lua new file mode 100644 index 00000000..e974c266 --- /dev/null +++ b/tests/engine/chip_analog_path.lua @@ -0,0 +1,103 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check +local eq = T.eq + +love = require("tests.love_stub") + +local ChipAsm = require("src.audio.ChipAsm") +local ChipSynth = require("src.core.ChipSynth") + +local data = { audio = {} } + +local function pulseSong() + return ChipAsm.song{ + channels = { { hw = 1, program = { + { duty = 2 }, + { notetype = { speed = 12, volume = 15, fade = 0 } }, + { octave = 4 }, + { note = "C", len = 15 }, + } } }, + } +end + +local function noiseSong() + return ChipAsm.sfx{ + channels = { { hw = 4, program = { + { noiseNote = { len = 8, volume = 15, fade = 1, parameter = 0x34 } }, + } } }, + } +end + +do + local engine = ChipSynth.newEngine(data, pulseSong(), { allowLoops = false }) + local sawNeg, sawPos = false, false + for _ = 1, 512 do + local v = engine.channels[1]:sample() + if v < -1e-12 then sawNeg = true end + if v > 1e-12 then sawPos = true end + end + check(sawPos and not sawNeg, + "pulse DAC is unipolar (high = volume, low = 0)") +end + +do + local engine = ChipSynth.newEngine(data, noiseSong(), { + sfx = true, allowLoops = false, + }) + local sawNeg, sawPos = false, false + for _ = 1, 2048 do + local v = engine.channels[1]:sample() + if v < -1e-12 then sawNeg = true end + if v > 1e-12 then sawPos = true end + end + check(sawPos and not sawNeg, + "noise DAC is unipolar (LFSR high = volume, low = 0)") +end + +local function crossingsAndSign(engine, frames) + local count, prev = 0, nil + local sawNeg, sawPos = false, false + for _ = 1, frames do + local sample = engine:sample() + if sample < -1e-12 then sawNeg = true end + if sample > 1e-12 then sawPos = true end + if prev and prev * sample < 0 then count = count + 1 end + prev = sample + end + return count, sawNeg, sawPos +end + +do + local engine = ChipSynth.newEngine(data, pulseSong(), { allowLoops = false }) + local count, sawNeg, sawPos = crossingsAndSign(engine, 4000) + check(sawNeg and sawPos, "HPF centers a unipolar pulse around analog 0") + check(count > 20, ("HPF'd pulse crosses zero (%d crossings)"):format(count)) +end + +do + local engine = ChipSynth.newEngine(data, noiseSong(), { + sfx = true, allowLoops = false, + }) + local count, sawNeg, sawPos = crossingsAndSign(engine, 8000) + check(sawNeg and sawPos, "HPF centers noise / drums around analog 0") + check(count > 50, ("HPF'd noise crosses zero (%d crossings)"):format(count)) +end + +do + local song = pulseSong() + ChipSynth.setChannelVolumes({ 1, 1, 1, 1 }) + local a = ChipSynth.newEngine(data, song, { allowLoops = false }) + local base = a.channels[1]:sample() + ChipSynth.setChannelVolume(1, 0.25) + local b = ChipSynth.newEngine(data, song, { allowLoops = false }) + local quarter = b.channels[1]:sample() + ChipSynth.setChannelVolumes({ 1, 1, 1, 1 }) + check(base > 0 and math.abs(quarter - base * 0.25) < 1e-9, + "channelVolume still quarters the unipolar DAC level") +end + +eq(type(ChipSynth.newEngine), "function", "engine factory still exported") + +T.finish("chip analog path") diff --git a/tests/engine/disable_same_turn_bug860.lua b/tests/engine/disable_same_turn_bug860.lua new file mode 100644 index 00000000..80c4ce65 --- /dev/null +++ b/tests/engine/disable_same_turn_bug860.lua @@ -0,0 +1,211 @@ +-- Disable blocks the move the slower mon ALREADY selected, on the very +-- turn the Disable lands (#860). pokered runs the test at execution +-- time, not at selection time: CheckPlayerStatusConditions +-- .TriedToUseDisabledMoveCheck (engine/battle/core.asm:3437-3447) compares +-- wPlayerDisabledMoveNumber against wPlayerSelectedMove and jumps to +-- ExecutePlayerMoveDone when they match -- "prevents a disabled move that +-- was selected before being disabled from being used", in the asm's own +-- comment. The enemy copy is .checkIfTriedToUseDisabledMove +-- (core.asm:5752+). The port only refused a disabled move at menu time, +-- so the second mover still fired the move it had latched before the +-- Disable resolved. +-- +-- The check sits after the confusion block and before the paralysis roll, +-- so this suite also pins the neighbours: the counter tick that clears an +-- expired Disable still runs first, and a move that was never disabled is +-- untouched. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +local Font = require("src.render.Font") +Font.load(Data) +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local TypeChart = require("src.battle.TypeChart") +TypeChart.load(Data) + +-- the fixture dataset has no status move; this dataset is this file's own +-- copy (fixtures.fresh), so registering one here cannot leak into another +-- case. Accuracy 100 keeps DisableEffect's MoveHitTest out of the way. +Data.moves.FIX_DISABLE = { + id = "FIX_DISABLE", index = 90, name = "FIX DISABLE", + type = "NORMAL", power = 0, accuracy = 100, pp = 20, + effect = "DISABLE_EFFECT", +} + +-- Deterministic rolls: the minimum of every range, except DisableEffect's +-- own "1-8 turns disabled" roll (effects.asm:1343-1345), which is pinned +-- at 4. A rolled 1 would be spent by the disabled mon's own counter tick +-- in the same CheckStatusConditions pass -- vanilla behaviour, but it +-- clears the disable before .TriedToUseDisabledMoveCheck can see it, so it +-- is not the case this suite is about. rng(0, 255) -> 0 makes every +-- accuracy roll hit. +local function rolls(disableTurns) + return function(a, b) + if a == 1 and b == 8 then return disableTurns end + if a then return a end + return 0 + end +end + +-- playerFirst decides who lands the Disable; the other side is the one +-- whose already-selected move has to die. Both mons get FIX_TACKLE in +-- slot 1 (the slot DisableEffect picks with the min roll) and FIX_SCRATCH +-- in slot 2 as the never-disabled control. +local function newBattle(playerFirst, disableTurns) + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "FIXMON_A", 30) } + local game = { data = Data, save = save, + stack = { top = function() return nil end, push = function() end } } + local battle = BattleState.newWild(game, "FIXMON_C", 30) + battle.rng = rolls(disableTurns or 4) + + local function loadout(battler) + battler.mon.moves = { + { id = "FIX_TACKLE", pp = 35 }, + { id = "FIX_SCRATCH", pp = 35 }, + { id = "FIX_DISABLE", pp = 20 }, + } + battler.curMoves = battler.mon.moves + end + loadout(battle.player) + loadout(battle.enemy) + + -- no speed tie to resolve: the disabler outruns its target outright + battle.player.curStats.speed = playerFirst and 200 or 1 + battle.enemy.curStats.speed = playerFirst and 1 or 200 + return battle +end + +-- the move instances the sides actually own, so PP decrements land on +-- the party copy the way DecrementPP mutates wBattleMonPP +local function slot(battler, i) return battler.curMoves[i] end + +-- consume the queue the way updateQueue does, minus the presentation +local function drain(battle) + local rows = {} + for _ = 1, 400 do + local item = table.remove(battle.queue, 1) + if not item then return rows end + if item.text then rows[#rows + 1] = { text = item.text } end + if item.fn then + battle.nextInsert = 0 + item.fn() + end + end + error("the turn queue never drained") +end + +local function saidWith(rows, needle) + for i, row in ipairs(rows) do + if row.text and row.text:find(needle, 1, true) then return i end + end + return nil +end + +-- "X's / MOVE is / disabled!" (PrintMoveIsDisabledText) versus +-- DisableEffect's own "MOVE was / disabled!" -- the two lines differ only +-- in that verb, so match on it +local function blocked(rows) return saidWith(rows, "is\ndisabled!") end +local function landed(rows) return saidWith(rows, "was\ndisabled!") end +local function usedTackle(rows) return saidWith(rows, "used FIX TACKLE!") end + +-- --------------------------------------------------------------------- +-- the player Disables first; the foe's latched FIX TACKLE dies this turn +-- --------------------------------------------------------------------- +do + local battle = newBattle(true) + battle.enemyAction = function() return slot(battle.enemy, 1) end + local hpBefore = battle.player.mon.hp + + battle:resolveTurn(slot(battle.player, 3)) + local rows = drain(battle) + + T.check(landed(rows) ~= nil, "the Disable lands") + T.eq(battle.enemy.disabledSlot, 1, "and latches onto the foe's slot 1") + T.check(blocked(rows) ~= nil, + "the foe's already-selected move reports as disabled") + T.check(landed(rows) and blocked(rows) and landed(rows) < blocked(rows), + "in that order: disabled first, then the blocked attempt") + T.check(usedTackle(rows) == nil, + "the disabled move is never announced, so it never executed") + T.eq(battle.player.mon.hp, hpBefore, "and it deals no damage") + T.eq(battle.enemy.disabledTurns, 3, + "the counter ticked once for this turn and the disable is still live") +end + +-- --------------------------------------------------------------------- +-- the same, mirrored: the foe Disables first and the player's latched +-- move dies (core.asm:5752 .checkIfTriedToUseDisabledMove) +-- --------------------------------------------------------------------- +do + local battle = newBattle(false) + battle.enemyAction = function() return slot(battle.enemy, 3) end + local hpBefore = battle.enemy.mon.hp + local ppBefore = slot(battle.player, 1).pp + + battle:resolveTurn(slot(battle.player, 1)) + local rows = drain(battle) + + T.check(landed(rows) ~= nil, "the foe's Disable lands") + T.eq(battle.player.disabledSlot, 1, "on the player's slot 1") + T.check(blocked(rows) ~= nil, "the player's latched move reports as disabled") + T.check(usedTackle(rows) == nil, "and is never announced") + T.eq(battle.enemy.mon.hp, hpBefore, "the foe takes no damage") + T.eq(slot(battle.player, 1).pp, ppBefore, + "and the move that never executed spends no PP (DecrementPP is inside " + .. "the move, past the status gauntlet)") +end + +-- --------------------------------------------------------------------- +-- no regression on the turns after: the disable keeps blocking that move +-- while its counter runs, and a different move still works +-- --------------------------------------------------------------------- +do + local battle = newBattle(true) + battle.enemyAction = function() return slot(battle.enemy, 1) end + battle:resolveTurn(slot(battle.player, 3)) + drain(battle) + T.eq(battle.enemy.disabledTurns, 3, "the disable is live going into turn 2") + + -- turn 2: the foe picks the disabled move with no Disable in flight + local hpBefore = battle.player.mon.hp + battle:resolveTurn(slot(battle.player, 2)) + local rows = drain(battle) + T.check(blocked(rows) ~= nil, "turn 2 still blocks the disabled move") + T.check(usedTackle(rows) == nil, "still no execution") + T.eq(battle.player.mon.hp, hpBefore, "still no damage") + T.eq(battle.enemy.disabledTurns, 2, "and the counter keeps ticking down") + + -- turn 3: the foe picks its OTHER move, which was never disabled + battle.enemyAction = function() return slot(battle.enemy, 2) end + hpBefore = battle.player.mon.hp + rows = (function() battle:resolveTurn(slot(battle.player, 2)); return drain(battle) end)() + T.check(blocked(rows) == nil, "an undisabled move is not blocked") + T.check(saidWith(rows, "used FIX SCRATCH!") ~= nil, "it is announced") + T.check(battle.player.mon.hp < hpBefore, "and it deals damage") +end + +-- --------------------------------------------------------------------- +-- the counter tick still runs ahead of the check: a disable that expires +-- on this turn frees the move it was holding (.DisabledCheck precedes +-- .TriedToUseDisabledMoveCheck) +-- --------------------------------------------------------------------- +do + local battle = newBattle(true) + battle.enemy.disabledSlot, battle.enemy.disabledTurns = 1, 1 + battle.enemyAction = function() return slot(battle.enemy, 1) end + local hpBefore = battle.player.mon.hp + + battle:resolveTurn(slot(battle.player, 2)) + local rows = drain(battle) + + T.check(saidWith(rows, "disabled no more!") ~= nil, "the disable expires") + T.check(blocked(rows) == nil, "so the move is not blocked") + T.check(usedTackle(rows) ~= nil, "it executes") + T.check(battle.player.mon.hp < hpBefore, "and deals damage") +end + +T.finish("disable blocks the already-selected move (#860)") diff --git a/tests/engine/drum_envelope_ring.lua b/tests/engine/drum_envelope_ring.lua new file mode 100644 index 00000000..120f55f6 --- /dev/null +++ b/tests/engine/drum_envelope_ring.lua @@ -0,0 +1,82 @@ +-- ..(audio/engine_1.asm ln 197) +-- ..(audio/sfx/noise_instrument01_1.asm ln 1) +-- luajit tests/engine/drum_envelope_ring.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check + +love = require("tests.love_stub") + +local ChipAsm = require("src.audio.ChipAsm") +local ChipSynth = require("src.core.ChipSynth") + +local snare = ChipAsm.song{ + channels = { + { hw = 4, program = { + { notetype = { speed = 12 } }, + { drum = 1, len = 2 }, + { rest = 16 }, + } }, + }, + drums = { + [1] = { + { len = 1, volume = 12, fade = 1, parameter = 0x33 }, + }, + }, +} + +local engine = ChipSynth.newEngine({ audio = {} }, snare, { allowLoops = false }) +local segs = engine:noiseInstrument(1) +local last = segs[#segs] +local ringMs = (last.endSample - last.startSample) / ChipSynth.SAMPLE_RATE * 1000 +check(ringMs > 150 and ringMs < 220, + ("snare instrument rings ~188ms, not the 17ms note (%0.1fms)"):format(ringMs)) + +local energyEarly, energyLate, energyEnd = 0, 0, 0 +local total = math.floor(ChipSynth.SAMPLE_RATE * 0.25) +for i = 1, total do + local s = engine:sample() + local e = s * s + local t = i / ChipSynth.SAMPLE_RATE + if t < 0.02 then + energyEarly = energyEarly + e + elseif t > 0.05 and t < 0.12 then + energyLate = energyLate + e + elseif t > 0.20 then + energyEnd = energyEnd + e + end +end +check(energyEarly > 0, "snare attack is audible") +check(energyLate > energyEarly * 0.05, + ("snare body still sounds at 50-120ms (early=%.4f late=%.4f)") + :format(energyEarly, energyLate)) +check(energyEnd < energyLate * 0.1, + "snare has decayed by 200ms") + +local hats = ChipAsm.song{ + channels = { + { hw = 4, program = { + { notetype = { speed = 12 } }, + { drum = 1, len = 2 }, + { drum = 1, len = 2 }, + } }, + }, + drums = { + [1] = { + { len = 1, volume = 8, fade = 1, parameter = 0x10 }, + }, + }, +} +local hatEngine = ChipSynth.newEngine({ audio = {} }, hats, { allowLoops = false }) +local hits = 0 +local prev = 0 +for _ = 1, math.floor(ChipSynth.SAMPLE_RATE * 0.3) do + local s = math.abs(hatEngine:sample()) + if prev < 0.01 and s >= 0.01 then hits = hits + 1 end + prev = s +end +check(hits >= 2, ("two rapid drum_notes both trigger (%d onsets)"):format(hits)) + +T.finish("drum envelope ring") diff --git a/tests/engine/evo_stone_cancel_bug883_test.lua b/tests/engine/evo_stone_cancel_bug883_test.lua new file mode 100644 index 00000000..6a04f155 --- /dev/null +++ b/tests/engine/evo_stone_cancel_bug883_test.lua @@ -0,0 +1,166 @@ +-- A stone evolution started from the bag must not be cancelable (#883). +-- +-- engine/items/item_effects.asm ItemUseEvoStone sets wForceEvolution before +-- `call TryEvolvingMon`, and engine/movie/evolution.asm +-- Evolution_CheckForCancel reads the joypad but throws the B press away while +-- that flag is set (#290). So the B abort is a level-up/rare-candy behavior +-- only: a stone is removed from the bag the moment it is used, and an +-- evolution the player can cancel out of would eat the stone for nothing. +-- +-- src/ui/EvolutionState.lua encodes the flag as `via`: cancelable is +-- (via ~= "TRADE" and via ~= "ITEM"). The bag's stone branch omitted the +-- argument entirely, so `via` arrived nil and the movie accepted B. The +-- assertion here is on the value that reaches the screen, which is the only +-- thing standing between the two behaviors. +-- +-- ROM-free: the fixture dataset plus a registry-supplied EvolutionState, so +-- the real Screens.push resolution runs and no sprite is ever loaded. +-- luajit tests/engine/evo_stone_cancel_bug883_test.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +-- Lazily required inside the use branches; seeding package.loaded first keeps +-- the suite silent and free of a real Font atlas. +package.loaded["src.core.Sound"] = { + play = function() end, + playCry = function() end, +} +package.loaded["src.render.TextBox"] = { + new = function(_, text, done) return { textBox = true, text = text, done = done } end, +} +-- BagMenu and PartyMenu bind TextBox at require time, so they load against the +-- stub; Screens caches its factory per id and must be told to forget. +package.loaded["src.ui.BagMenu"] = nil +package.loaded["src.ui.PartyMenu"] = nil +local BagMenu = require("src.ui.BagMenu") +local PartyMenu = require("src.ui.PartyMenu") +local Screens = require("src.ui.Screens") +Screens.invalidate() + +local Fixtures = require("tests.modkit.fixtures") +local Bag = require("src.inventory.Bag") +local Pokemon = require("src.pokemon.Pokemon") +local EvolutionState = require("src.ui.EvolutionState") + +local Data = Fixtures.fresh() +-- The fixture item table carries no stone, and ItemEffects keys its stone +-- branch on the id; BagMenu only reads name/keyItem off the def. +Data.items.THUNDER_STONE = { + id = "THUNDER_STONE", index = 33, name = "THUNDERSTONE", price = 2100, + tossable = true, +} +-- and no fixture species evolves, so give A the stone evolution the branch +-- looks for (evo.method == "ITEM" and evo.item == the stone used). +Data.pokemon.FIXMON_A.evolutions = { + { method = "ITEM", item = "THUNDER_STONE", species = "FIXMON_B" }, +} + +-- The seam: Screens resolves an id through game.data.screens before falling +-- back to the builtin module, which is the same path a mod-replaced screen +-- takes. Recording the factory here catches exactly what Evolution.evolve +-- forwards, with no monkeypatching of Screens itself. +local pushed +Data.screens = Data.screens or {} +Data.screens.EvolutionState = function(game, mon, newSpecies, onDone, via) + pushed = { game = game, mon = mon, newSpecies = newSpecies, + onDone = onDone, via = via } + return { evoRecorder = true } +end +Screens.invalidate() + +local function freshGame() + local mon = Pokemon.new(Data, "FIXMON_A", 20) + local game = { + data = Data, + save = { + party = { mon }, + player = { name = "RED", id = 1 }, + inventory = {}, + options = {}, + flags = {}, + money = 0, + }, + } + game.stack = { + states = {}, + push = function(self, s) table.insert(self.states, s) end, + pop = function(self) return table.remove(self.states) end, + top = function(self) return self.states[#self.states] end, + } + -- one button edge per update, the way Input reports a fixed step + game.input = { pressed = nil } + function game.input:wasPressed(b) return self.pressed == b end + Bag.add(game.save, "THUNDER_STONE", 1) + return game, mon +end + +local function isPicker(s) return getmetatable(s) == PartyMenu end + +local function rowFor(list, id) + for i, r in ipairs(list.items) do + if r.value == id then return i end + end + return nil +end + +-- Open the bag, put the cursor on the stone, choose it, take USE off the +-- USE/TOSS box, then press A on the party picker. +local function useStone(game) + local list = BagMenu.new(game, {}) + game.stack:push(list) + local row = rowFor(list, "THUNDER_STONE") + if not row then return nil, "no THUNDER_STONE row in the bag" end + list.index = row + list.onChoose(list.items[row], list) + local sub = game.stack:top() + if sub and sub.items and sub.items[1] and sub.items[1].onSelect then + game.stack:pop() -- the USE/TOSS Menu pops itself on select + sub.items[1].onSelect() + end + local picker = game.stack:top() + if not isPicker(picker) then return nil, "party picker never opened" end + game.input.pressed = "a" + picker:update(1 / 60) + game.input.pressed = nil + return list +end + +do + local game, mon = freshGame() + local list, why = useStone(game) + if check(list ~= nil, "the bag opened and reached the picker: " .. tostring(why)) then + if check(pushed ~= nil, "the stone use pushed the evolution screen") then + eq(pushed.newSpecies, "FIXMON_B", "and it is the stone's evolution") + eq(pushed.mon, mon, "for the mon the stone was used on") + eq(pushed.via, "ITEM", + "the evolution runs as via = \"ITEM\" (wForceEvolution), which is " + .. "what makes it non-cancelable (#883)") + end + eq(game.save.inventory.THUNDER_STONE, nil, + "the stone is already gone by then, so a cancel would cost it for " + .. "nothing") + end +end + +-- The value only matters because of what EvolutionState does with it, so +-- assert that half against the real constructor rather than trusting the +-- comment. new() loads sprites through pcall and plays music through the +-- stubbed Sound, so it is safe headless. +do + local game = freshGame() + local mon = game.save.party[1] + local stoneEvo = EvolutionState.new(game, mon, "FIXMON_B", nil, "ITEM") + check(stoneEvo.cancelable == false, + "EvolutionState refuses B for a stone evolution (evolution.asm " + .. "Evolution_CheckForCancel with wForceEvolution set)") + local levelEvo = EvolutionState.new(game, mon, "FIXMON_B", nil, "LEVEL") + check(levelEvo.cancelable == true, + "and still honours B for a level-up evolution, so the fix did not " + .. "silently disable the cancel everywhere (#290, #213)") +end + +T.finish() diff --git a/tests/engine/evolution_hold_b_bug968_test.lua b/tests/engine/evolution_hold_b_bug968_test.lua new file mode 100644 index 00000000..78174bce --- /dev/null +++ b/tests/engine/evolution_hold_b_bug968_test.lua @@ -0,0 +1,153 @@ +-- A level-up evolution survives the B held from the level-up box (#968, #1031); a fresh press still cancels (#290, #213). +-- pokered engine/movie/evolution.asm EvolveMon, Evolution_CheckForCancel. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +-- TextBox and EvolutionState both require Sound inside update, so seeding +package.loaded["src.core.Sound"] = { + play = function() end, + playCry = function() end, +} + +local Fixtures = require("tests.modkit.fixtures") +local Evolution = require("src.pokemon.Evolution") +local EvolutionState = require("src.ui.EvolutionState") +local Input = require("src.core.Input") +local Pokemon = require("src.pokemon.Pokemon") +local StateStack = require("src.core.StateStack") +local TextBox = require("src.render.TextBox") + +local Data = Fixtures.fresh() +require("src.render.Font").load(Data) + +-- FIXMON_A evolves into FIXMON_B at 16 (tests/fixture_data/pokemon.lua) +local EVO_LEVEL = 16 +-- x is the default keyboard B (src/core/Input.lua DEFAULT_BINDINGS) +local B_KEY = "x" + +local function newGame() + local game = { data = Data } + local mon = Pokemon.new(Data, "FIXMON_A", EVO_LEVEL) + game.save = { + party = { mon }, + player = { name = "RED", id = 1 }, + options = { textSpeed = 5 }, + flags = {}, + pokedex = { seen = {}, owned = {} }, + } + game.stack = setmetatable({}, { __index = StateStack }) + game.stack:init() + game.input = Input + Input:init() + return game, mon +end + +-- one fixed step, in Game:step's order: promote the queued edges, then +local function step(game) + game.input:step() + game.stack:update(1 / 60) +end + +-- the post-battle sequence: grew-to-level box, then Evolution.checkParty +local function levelUpBox(game, mon) + game.stack:push(TextBox.new(game, "FIXMON A grew\nto level 16!", + function() Evolution.checkParty(game, nil, { [mon] = true }) end)) +end + +-- one B edge on the box, still held when the movie takes over: the bug's handoff +local function dismissWithB(game, mon) + levelUpBox(game, mon) + local box = game.stack:top() + for _ = 1, 900 do + if box.done then break end + step(game) + end + if not box.done then return nil, "the level-up text never finished typing" end + Input:keypressed(B_KEY) + step(game) + local top = game.stack:top() + if getmetatable(top) ~= EvolutionState then + return nil, "the evolution screen never opened" + end + return top +end + +local function textOf(box) + local out = {} + for _, page in ipairs(box.pages) do + for _, line in ipairs(page) do out[#out + 1] = line end + end + return table.concat(out, " ") +end + +-- B held out of the text box: the movie must run to the end and evolve. +do + local game, mon = newGame() + local evo, why = dismissWithB(game, mon) + if check(evo ~= nil, "the level-up box handed off to the movie: " .. tostring(why)) then + check(Input:isDown("b"), + "B is still physically down as the movie starts, which is what " + .. "the old isDown poll cancelled on") + eq(evo.cancelable, true, + "and this is a cancelable level-up evolution, so the movie really " + .. "is reading the button (#290)") + -- never released: no second edge ever reaches the movie + for _ = 1, 400 do + if evo.done then break end + step(game) + end + check(Input:isDown("b"), "B was held for the whole movie") + eq(evo.canceled, false, "the held B did not cancel the evolution") + eq(mon.species, "FIXMON_B", "the mon actually evolved") + eq(mon.stats.hp, require("src.pokemon.Stats") + .calc(Data.pokemon.FIXMON_B, EVO_LEVEL, mon.dvs, mon.statExp).hp, + "and Evolution.apply recalculated its stats on the new species") + local top = game.stack:top() + check(getmetatable(top) == TextBox and textOf(top):find("evolved into"), + "the congratulations text is what closes the movie") + end +end + +-- A deliberate fresh press after the 80-frame delay still cancels. +do + local game, mon = newGame() + local evo = assert(dismissWithB(game, mon)) + Input:keyreleased(B_KEY) + for _ = 1, 400 do + if evo.t > 80 then break end + step(game) + end + check(evo.t > 80 and not evo.done, + "the movie is past the DelayFrames window and still running") + Input:keypressed(B_KEY) + step(game) + eq(evo.canceled, true, "a fresh B press cancels the evolution (#213)") + eq(mon.species, "FIXMON_A", "and the mon keeps its species") + local top = game.stack:top() + check(getmetatable(top) == TextBox and textOf(top):find("stopped evolving"), + "_StoppedEvolvingText prints instead of the congratulations") +end + +-- A press inside the 80 frames is not polled at all, so the mon still +do + local game, mon = newGame() + local evo = assert(dismissWithB(game, mon)) + Input:keyreleased(B_KEY) + for _ = 1, 10 do step(game) end + Input:keypressed(B_KEY) + step(game) + Input:keyreleased(B_KEY) + check(evo.t <= 80, "the press landed inside the delay window") + eq(evo.canceled, false, "a B press during the delay is never polled") + for _ = 1, 400 do + if evo.done then break end + step(game) + end + eq(mon.species, "FIXMON_B", "so the evolution still completes") +end + +T.finish() diff --git a/tests/engine/game_speed_categories_test.lua b/tests/engine/game_speed_categories_test.lua new file mode 100644 index 00000000..57bcd86f --- /dev/null +++ b/tests/engine/game_speed_categories_test.lua @@ -0,0 +1,196 @@ +-- Per-category GAME SPEED (RFC 0007): Game.speedCategoryInStack's stack +-- walk, Game:logicSpeed()'s precedence (link lock / run-argument override / +-- the core.logic_speed hook), Game:_cycleSpeed's per-category cycling, and +-- the core.logic_speed hook itself exercised through the public mod API +-- (Hooks.new() + bus:wrap, the same idiom other hooks' tests use -- not a +-- private require). +-- luajit tests/engine/game_speed_categories_test.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local check, eq = T.check, T.eq + +local Game = require("src.core.Game") +local GameSpeed = require("src.core.GameSpeed") +local Hooks = require("src.mods.Hooks") +local Runtime = require("src.mods.Runtime") + +-- ------- Game.speedCategoryInStack: the whole-stack walk + +local function stack(...) return { states = { ... } } end + +local battle = { isBattle = true } +local overworld = { isOverworld = true } +local overlay = {} -- a party menu/choice box/naming screen/text box: no marker + +eq(Game.speedCategoryInStack(nil), "menu", "a nil stack falls to menu") +eq(Game.speedCategoryInStack(stack()), "menu", "an empty stack falls to menu") +eq(Game.speedCategoryInStack(stack(overlay)), "menu", + "an unmarked state alone (title screen, credits, a standalone cutscene) is menu") + +eq(Game.speedCategoryInStack(stack(overworld)), "overworld", + "the overworld alone resolves to overworld") +eq(Game.speedCategoryInStack(stack(battle)), "battle", + "a battle alone resolves to battle") + +eq(Game.speedCategoryInStack(stack(overworld, overlay)), "overworld", + "a menu opened while walking inherits overworld") +eq(Game.speedCategoryInStack(stack(battle, overlay)), "battle", + "a menu opened mid-battle inherits battle, not menu") +eq(Game.speedCategoryInStack(stack(overworld, overlay, overlay)), "overworld", + "the inheritance walk sees through more than one stacked overlay") + +eq(Game.speedCategoryInStack(stack(overworld, battle)), "battle", + "a battle opened over the overworld reads as battle, not the overworld underneath it") +eq(Game.speedCategoryInStack(stack(overworld, battle, overlay)), "battle", + "and a menu on top of THAT still reads as battle") + +-- ------- Game:_resolveLogicSpeed: category -> save.options key -> clamp + +local unpack = table.unpack or unpack + +local function gameWith(states, options) + return setmetatable({ + save = { options = options }, + stack = stack(unpack(states or {})), + }, { __index = Game }) +end + +do + local g = gameWith({ overworld }, + { speedOverworld = 4, speedBattle = 10, speedMenu = 2 }) + eq(g:_resolveLogicSpeed(), 4, "overworld reads speedOverworld") +end +do + local g = gameWith({ battle }, + { speedOverworld = 4, speedBattle = 10, speedMenu = 2 }) + eq(g:_resolveLogicSpeed(), 10, "battle reads speedBattle") +end +do + local g = gameWith({ overlay }, + { speedOverworld = 4, speedBattle = 10, speedMenu = 2 }) + eq(g:_resolveLogicSpeed(), 2, "menu reads speedMenu") +end +do + local g = gameWith({ overworld }, { speedOverworld = 7 }) + eq(g:_resolveLogicSpeed(), GameSpeed.clamp(7), + "an odd value clamps to the nearest LEVELS entry, like the old single field") +end +do + local g = gameWith({ overworld }, nil) + eq(g:_resolveLogicSpeed(), GameSpeed.DEFAULT, + "no save.options at all defaults rather than erroring") +end + +-- ------- Game:logicSpeed(): link and speedOverride win over every category +-- and over a hook override; the hook only ever sees the ordinary case + +do + local g = gameWith({ battle }, { speedBattle = 50 }) + g.linkSession = true + eq(g:logicSpeed(), 1, "an active link session forces 1X even at 50X battle") +end +do + local g = gameWith({ battle }, { speedBattle = 50 }) + g.linkNet = { closed = false } + eq(g:logicSpeed(), 1, "an open linkNet forces 1X the same way") +end +do + local g = gameWith({ battle }, { speedBattle = 50 }) + g.linkNet = { closed = true } + eq(g:logicSpeed(), 50, "a CLOSED linkNet does not force 1X") +end +do + local g = gameWith({ overworld }, { speedOverworld = 4 }) + g.speedOverride = 20 + eq(g:logicSpeed(), 20, + "speedOverride (--speed/POKEPORT_SPEED) wins over the category option") +end +do + local g = gameWith({ battle }, { speedBattle = 50 }) + g.linkSession = true + local bus = Hooks.new() + local savedHooks = Runtime.hooks + Runtime.hooks = bus + local hookRan = false + local unsub = bus:wrap("core.logic_speed", function(next, game) + hookRan = true + return 999 + end) + eq(g:logicSpeed(), 1, + "the link lock wins even over a mod's core.logic_speed override") + check(not hookRan, "...because the hook is never called during link play") + unsub() + Runtime.hooks = savedHooks +end + +-- ------- core.logic_speed: the mod-API seam, driven through Runtime.call/ +-- Hooks.new + bus:wrap like every other hook's public-API test + +local function callLogicSpeed(g) + return Runtime.call("core.logic_speed", + function(gg) return gg:_resolveLogicSpeed() end, g) +end + +do + local g = gameWith({ battle }, { speedBattle = 4 }) + eq(callLogicSpeed(g), 4, + "with no subscriber, the hook returns the vanilla category resolution") +end + +do + local g = gameWith({ overworld }, { speedOverworld = 4 }) + local bus = Hooks.new() + local savedHooks = Runtime.hooks + Runtime.hooks = bus + + local nextArg = nil + local unsub = bus:wrap("core.logic_speed", function(next, game) + nextArg = next(game) + return nextArg + end) + eq(callLogicSpeed(g), 4, + "a subscriber calling next(game) passes the vanilla value through") + eq(nextArg, 4, "...and next(game) itself returned the vanilla resolution") + unsub() + + -- a bot mod forcing 1X for one route segment regardless of the category + unsub = bus:wrap("core.logic_speed", function(next, game) return 1 end) + eq(callLogicSpeed(g), 1, + "a subscriber may override the resolved multiplier outright") + unsub() + + Runtime.hooks = savedHooks +end + +-- ------- Game:_cycleSpeed: cycles whichever category is active, and only it + +do + local writeOptions = { calls = 0 } + local g = gameWith({ battle }, + { speedOverworld = 1, speedBattle = 1, speedMenu = 1 }) + function g:writeOptions() writeOptions.calls = writeOptions.calls + 1 end + g:_cycleSpeed(1) + eq(g.save.options.speedBattle, 2, "cycling during battle bumps speedBattle") + eq(g.save.options.speedOverworld, 1, "...and leaves speedOverworld alone") + eq(g.save.options.speedMenu, 1, "...and leaves speedMenu alone") + eq(writeOptions.calls, 1, "a successful cycle persists the option") +end +do + local g = gameWith({ overworld }, + { speedOverworld = 1, speedBattle = 1, speedMenu = 1 }) + function g:writeOptions() end + g:_cycleSpeed(1) + eq(g.save.options.speedOverworld, 2, "cycling on the overworld bumps speedOverworld") + eq(g.save.options.speedBattle, 1, "...and leaves speedBattle alone") +end +do + local g = gameWith({ overlay }, + { speedOverworld = 1, speedBattle = 1, speedMenu = 1 }) + function g:writeOptions() end + g:_cycleSpeed(1) + eq(g.save.options.speedMenu, 2, "cycling in a menu bumps speedMenu") +end + +T.finish("game_speed_categories") diff --git a/tests/engine/gate_strings_coverage.lua b/tests/engine/gate_strings_coverage.lua index e03402a3..b3828c71 100644 --- a/tests/engine/gate_strings_coverage.lua +++ b/tests/engine/gate_strings_coverage.lua @@ -41,6 +41,8 @@ local ALLOWED = { { pattern = '%.%. "\\f"', why = "page-join glue between two texts" }, { pattern = "error%(", why = "a developer error, never drawn" }, { pattern = "Logger%.", why = "a log line, never drawn" }, + { pattern = "io%.stderr", why = "a dev-harness diagnostic, never drawn " + .. "(POKEPORT_LAUNCHER_PROF's frame timings)" }, { pattern = '== "\\v"', why = "comparing against a marker, not printing it" }, { pattern = "txBuf", why = "newline-delimited wire framing, not text" }, } diff --git a/tests/engine/hit_sfx_noise_pitch_bug826.lua b/tests/engine/hit_sfx_noise_pitch_bug826.lua new file mode 100644 index 00000000..11c42b12 --- /dev/null +++ b/tests/engine/hit_sfx_noise_pitch_bug826.lua @@ -0,0 +1,145 @@ +-- The battle hit sounds must carry pokered's wFrequencyModifier onto the +-- noise channel (#826; #902 reports the same swap). PlayApplyingAttackSound +-- (engine/battle/animations.asm) picks SFX_DAMAGE / SFX_SUPER_EFFECTIVE / +-- SFX_NOT_VERY_EFFECTIVE off wDamageMultipliers and writes a frequency +-- modifier with it ($20 / $e0 / $50), and Audio2_ApplyFrequencyModifier adds +-- that to the polynomial-counter byte -- the low byte of NR43 -- with 8-bit +-- wrap (audio/engine_2.asm). The three programs are CHAN8-only, so that byte +-- IS their pitch. Dropped, the super effective hit reads as the duller of +-- the two: super effective's tail (shifts 3 then 6) sits below not very +-- effective's (5, 4, 2, 2), which is exactly the "swapped" sound #826 and +-- #902 describe. With the modifier on, super effective goes to shifts 1/4 -- +-- a bright crack -- and not very effective to 10/9/7/7 -- a dull thud. +-- +-- ROM-free: ChipAsm blobs stand in for the sfx headers, so nothing here +-- reads data/generated/. The noise-note streams below are transcribed from +-- audio/sfx/{damage,super_effective,not_very_effective}.asm, so the "sounds +-- swapped when bare" ordering is asserted against the real program shape. +-- luajit tests/engine/hit_sfx_noise_pitch_bug826.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check + +love = require("tests.love_stub") + +local ChipAsm = require("src.audio.ChipAsm") +local ChipSynth = require("src.core.ChipSynth") + +local data = { audio = {} } + +-- noise_note len, volume, fade, parameter (the asm streams, verbatim) +local HITS = { + { + name = "Damage", + pitch = 0x20, + notes = { + { len = 2, parameter = 0x44 }, + { len = 2, parameter = 0x14 }, + { len = 15, parameter = 0x32 }, + }, + want = { 0x64, 0x34, 0x52 }, + }, + { + name = "Super_Effective", + pitch = 0xe0, + notes = { + { len = 4, parameter = 0x34 }, + { len = 15, parameter = 0x64 }, + }, + want = { 0x14, 0x44 }, + }, + { + name = "Not_Very_Effective", + pitch = 0x50, + notes = { + { len = 4, parameter = 0x55 }, + { len = 2, parameter = 0x44 }, + { len = 8, parameter = 0x22 }, + { len = 15, parameter = 0x21 }, + }, + want = { 0xa5, 0x94, 0x72, 0x71 }, + }, +} + +local function hitDef(notes) + local program = {} + for _, n in ipairs(notes) do + program[#program + 1] = { + noiseNote = { len = n.len, volume = 15, fade = 1, parameter = n.parameter }, + } + end + return ChipAsm.sfx{ channels = { { hw = 4, program = program } } } +end + +-- every noise note the program emits, as { parameter, duration }, walking +-- one event at a time by marking each consumed +local function noiseNotes(def, offset) + local engine = ChipSynth.newEngine(data, def, { + sfx = true, allowLoops = false, frequencyOffset = offset, + }) + local channel = assert(engine.channels[1], "hit sfx uses exactly CHAN8") + local out = {} + while not engine:finished() do + channel:sample() + local event = channel.event + if not event then break end + if event.noiseParameter ~= nil then + out[#out + 1] = { parameter = event.noiseParameter, duration = event.duration } + end + event.sample = event.samples -- force the walk on to the next event + end + return out +end + +-- NR43 shift-clock nibble, weighted by each note's on-air duration: the +-- number #826/#902 ears actually compare (higher = duller) +local function weightedShift(notes) + local total, sum = 0, 0 + for _, n in ipairs(notes) do + total = total + n.duration + sum = sum + math.floor(n.parameter / 16) * n.duration + end + return total > 0 and sum / total or 0 +end + +local results = {} +for _, hit in ipairs(HITS) do + local def = hitDef(hit.notes) + local bare = noiseNotes(def, 0) + local pitched = noiseNotes(def, hit.pitch) + results[hit.name] = { bare = bare, pitched = pitched } + check(#bare == #hit.notes, + hit.name .. " program emits " .. #hit.notes .. " notes, not " .. #bare) + for i, n in ipairs(hit.notes) do + check(bare[i] and bare[i].parameter == n.parameter, + ("%s note %d reads NR43 $%02x unmodified"):format(hit.name, i, n.parameter)) + check(pitched[i] and pitched[i].parameter == hit.want[i], + ("%s note %d reads NR43 $%02x once $%02x is applied"):format( + hit.name, i, hit.want[i], hit.pitch)) + end +end + +-- the ordering that IS the bug: unpitched, super effective ends duller than +-- not very effective, so they sound swapped; pitched, the bright crack lands +-- on super effective and the dull thud on not very effective +local superBare = weightedShift(results.Super_Effective.bare) +local nveBare = weightedShift(results.Not_Very_Effective.bare) +check(superBare > nveBare, + ("bare, super effective (%.2f) reads duller than not very effective (%.2f)"):format( + superBare, nveBare)) +local superPitched = weightedShift(results.Super_Effective.pitched) +local nvePitched = weightedShift(results.Not_Very_Effective.pitched) +check(superPitched < nvePitched, + ("pitched, super effective (%.2f) is the brighter hit, not very effective (%.2f) the duller"):format( + superPitched, nvePitched)) + +-- the neutral hit shifts a shade duller than it used to be, per pokered +local damageBare = weightedShift(results.Damage.bare) +local damagePitched = weightedShift(results.Damage.pitched) +check(damagePitched > damageBare, + ("the neutral hit dulls a little under $20 (%.2f -> %.2f)"):format( + damageBare, damagePitched)) + +T.finish("hit sfx noise pitch (#826/#902)") diff --git a/tests/engine/host_shell_fetch_errors.lua b/tests/engine/host_shell_fetch_errors.lua new file mode 100644 index 00000000..ad5383fe --- /dev/null +++ b/tests/engine/host_shell_fetch_errors.lua @@ -0,0 +1,134 @@ +-- HostShell's HTTP error reporting. No pokered cite: host transport is +-- port-only plumbing. +-- +-- A user hit the launcher's mod index against a rate-limited GitHub and all +-- they got was one line on the terminal: +-- +-- curl: (56) The requested URL returned error: 403 +-- +-- That is curl talking to its own stderr. It names no URL, so with an index +-- feed, a releases API and a page of thumbnails all in flight there was no way +-- to tell WHICH fetch failed, and the caller upstream got a generic "empty +-- response" that said even less. HostShell now merges curl's stderr into the +-- pipe and asks for the status with --write-out, so every failure names its +-- URL and its HTTP code, and a 403 body ("API rate limit exceeded") reaches +-- the launcher's notice line where a user can act on it. +-- +-- The seam is io.popen: these cases stub it to replay exactly what curl writes +-- for each outcome, which is the only way to pin the parsing without a network +-- and a cooperating server. +-- luajit tests/engine/host_shell_fetch_errors.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check +love = love or require("tests.love_stub") + +local HostShell = require("src.core.HostShell") + +-- The marker HostShell asks curl to print before the status code. Spelled +-- here the way it arrives (a real newline), not the way it is passed to curl +-- (a backslash-n escape curl expands itself). +local MARK = "\n__gen1recomp_http__" + +-- Replay `output` as the next popen's whole stdout. `curl --version` is +-- answered separately so HostShell.haveCurl agrees a transport exists. +local realPopen = io.popen +local lastCommand +local function stubPopen(output) + io.popen = function(cmd, mode) + lastCommand = cmd + if cmd:find("--version", 1, true) then + return { read = function() return "curl 8.7.1 (test)" end, + close = function() return true end } + end + return { read = function() return output end, + close = function() return true end } + end +end +local function restorePopen() io.popen = realPopen end + +local URL = "https://api.github.com/repos/example/thing/releases" + +-- ------------------------------------------------------------------- 200 +stubPopen('{"tag_name":"v1.2.3"}' .. MARK .. "200") +local body, err = HostShell.httpGet(URL, "gen1recomp", nil, 10) +check(body == '{"tag_name":"v1.2.3"}', + "a 200 returns the body with the status marker stripped: " .. tostring(body)) +check(err == nil, "a 200 reports no error") +check(lastCommand:find("%-w ") ~= nil, + "the GET asks curl for the status code") +check(lastCommand:find("2>&1", 1, true) ~= nil, + "the GET captures curl's stderr instead of leaking it to the terminal") +check(lastCommand:find(" -f", 1, true) == nil, + "the GET does NOT pass -f: the error body is the diagnosis") + +-- ------------------------------------------------------------------- 403 +-- What GitHub actually sends when the launcher has burned its unauthenticated +-- hourly allowance, with curl's own stderr merged in ahead of it. +stubPopen('{"message":"API rate limit exceeded for 203.0.113.7."}' + .. MARK .. "403") +local body403, err403 = HostShell.httpGet(URL, "gen1recomp", nil, 10) +check(body403 == nil, "a 403 is a failure, not a body") +check(err403:find(URL, 1, true) ~= nil, + "a 403 names the URL that failed: " .. tostring(err403)) +check(err403:find("403", 1, true) ~= nil, "a 403 names the status code") +check(err403:find("rate limit", 1, true) ~= nil, + "a 403 carries the server's own explanation through to the caller") + +-- --------------------------------------------------- no response at all +-- DNS failure: curl writes its complaint and a http_code of 0. Zero is not a +-- status, and reporting "HTTP 0" would bury the only useful line there is. +stubPopen("curl: (6) Could not resolve host: nope.invalid" .. MARK .. "0") +local bodyDns, errDns = HostShell.httpGet("https://nope.invalid/x", "ua", nil, 10) +check(bodyDns == nil, "an unresolvable host is a failure") +check(errDns:find("HTTP 0", 1, true) == nil, + "a no-response failure is not reported as HTTP 0: " .. tostring(errDns)) +check(errDns:find("https://nope.invalid/x", 1, true) ~= nil, + "an unresolvable host still names the URL") +check(errDns:find("Could not resolve", 1, true) ~= nil, + "an unresolvable host reports curl's own reason") + +-- --------------------------------------------- a body containing the marker +-- The status is cut from the LAST marker only, so a payload that happens to +-- contain the token keeps every byte of its content. +local sneaky = "prefix" .. MARK .. "999" .. "suffix" +stubPopen(sneaky .. MARK .. "200") +local bodySneaky = HostShell.httpGet(URL, "gen1recomp", nil, 10) +check(bodySneaky == sneaky, + "only the trailing status marker is stripped: " .. tostring(bodySneaky)) + +-- ------------------------------------------------------------- downloads +-- The download branch keeps -f (no error body is written to the file), but it +-- must still name the URL and the code rather than "download failed". +stubPopen("curl: (56) The requested URL returned error: 403" .. MARK .. "403") +local ok, dlErr = HostShell.httpDownload(URL, "/tmp/gen1recomp-test.bin", + "gen1recomp", nil, 10) +check(ok == nil, "a 403 download fails") +check(dlErr:find(URL, 1, true) ~= nil, + "a failed download names the URL: " .. tostring(dlErr)) +check(dlErr:find("403", 1, true) ~= nil, "a failed download names the code") + +stubPopen(MARK .. "200") +local ok2, dlErr2 = HostShell.httpDownload(URL, "/tmp/gen1recomp-test.bin", + "gen1recomp", nil, 10) +check(ok2 == true, "a 200 download succeeds: " .. tostring(dlErr2)) + +restorePopen() + +-- ---------------------------------------------------------------- pclose +-- Every pipe HostShell hands out must be closed through pclose: a bare +-- pipe:close() from one thread can free a FILE while another thread's popen +-- is walking libc's stream list, and that thread never wakes up again (the +-- launcher freezing on close after a visit to the mod tabs). Nothing here can +-- exercise the race headlessly -- the test stub has no love.thread -- so this +-- pins the entry point's existence and its tolerance of junk. +check(type(HostShell.pclose) == "function", "HostShell exposes pclose") +local closed = false +HostShell.pclose({ close = function() closed = true return true end }) +check(closed, "pclose closes the pipe it is given") +local okNil = pcall(HostShell.pclose, nil) +check(okNil, "pclose on nil is a no-op rather than an error") + +T.finish("host shell fetch errors") diff --git a/tests/engine/input_hold_reconcile_test.lua b/tests/engine/input_hold_reconcile_test.lua new file mode 100644 index 00000000..7fc0fe5e --- /dev/null +++ b/tests/engine/input_hold_reconcile_test.lua @@ -0,0 +1,87 @@ +-- Held directions must survive lifecycle resets while physically held +-- (#799). Input state is event-driven, so any Input:reset (focus or +-- visibility flip, joystick add/remove, resume) wipes a hold that never +-- re-fires keypressed afterwards -- on macOS a Bluetooth controller +-- re-enumerating mid-walk fired joystickadded under a held key and parked +-- the player until the direction was released and pressed again. Game's +-- lifecycle handlers rebuild holds from device ground truth after each +-- reset; only what is physically down comes back, so the swallowed-release +-- hazards the resets guard against stay cleared. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local T = require("tests.harness") +local check = T.check + +local Input = require("src.core.Input") +local Game = require("src.core.Game") + +local realIsDown = love.keyboard.isDown +local realJoystick = love.joystick +local function restore() + love.keyboard.isDown = realIsDown + love.joystick = realJoystick +end + +Input:init() + +-- Keyboard: direction still physically held across a spurious pad +-- re-enumeration must keep walking (the #799 report). +love.keyboard.isDown = function(key) return key == "up" end +Input:reset() +Input:keypressed("up") +Input:step() +check(Input:isDown("up"), "up held before joystick re-enumeration") +Game:joystickadded({ getName = function() return "Wireless Controller" end }) +check(Input:isDown("up"), "held key survives a spurious joystickadded") + +-- Same hold across a focus bounce (another reset source). +Game:focus(false) +check(not Input:isDown("up"), "focus loss still drops the hold") +Game:focus(true) +check(Input:isDown("up"), "held key re-arms on focus regain") + +-- A key released while unfocused (keyup swallowed by the OS, the hazard +-- the resets exist for) must NOT come back. +love.keyboard.isDown = function() return false end +Game:focus(false) +Game:focus(true) +check(not Input:isDown("up"), "swallowed release still clears the hold") + +-- Controller: held d-pad across a disconnect/reconnect bounce. +local pad = { + isGamepad = function() return true end, + isGamepadDown = function(_, button) return button == "dpleft" end, + getGamepadAxis = function() return 0 end, +} +love.joystick = { getJoysticks = function() return { pad } end } +love.keyboard.isDown = function() return false end +Input:reset() +Input:gamepadpressed(pad, "dpleft") +Input:step() +check(Input:isDown("left"), "d-pad held before reconnect") +Game:joystickremoved(pad) +Game:joystickadded(pad) +check(Input:isDown("left"), "held d-pad survives a reconnect bounce") + +-- Held stick across the same bounce (axis ground truth re-derived). +pad.isGamepadDown = function() return false end +pad.getGamepadAxis = function(_, axis) return axis == "leftx" and -0.9 or 0 end +Input:reset() +Input:gamepadaxis(pad, "leftx", -0.9) +Input:step() +check(Input:isDown("left"), "stick held before re-enumeration") +Game:joystickadded(pad) +check(Input:isDown("left"), "held stick survives re-enumeration") + +-- A pad that vanished for real reports nothing held: its stale hold must +-- stay cleared (the stuck-flag hazard reset-on-remove guards against). +love.joystick = { getJoysticks = function() return {} end } +Input:reset() +Input:gamepadpressed(pad, "dpleft") +Input:step() +Game:joystickremoved(pad) +check(not Input:isDown("left"), "vanished pad's hold stays cleared") + +restore() +T.finish() diff --git a/tests/engine/launcher_delete_confirm.lua b/tests/engine/launcher_delete_confirm.lua index 7c1e31ea..f47bc507 100644 --- a/tests/engine/launcher_delete_confirm.lua +++ b/tests/engine/launcher_delete_confirm.lua @@ -1,8 +1,8 @@ -- Launcher Delete affordance (src/import/RomImporter.lua): the two-click arm -- that guards both save-slot and mod deletes (#433). Every Delete control in -- the FlexLove view routes through RomImporter:pressDelete, and every other --- queued action clears self._confirmDelete (LauncherView's queueAction), so --- the guarantees live on this seam: the first press only arms, the second +-- queued action clears self._confirmDelete (RomImporter:runActions as the +-- batch drains, #780), so the guarantees live on this seam: the first press only arms, the second -- press on the SAME target commits, any other target or a cleared arm asks -- again, and a stale arm expires instead of committing much later. -- luajit tests/engine/launcher_delete_confirm.lua diff --git a/tests/engine/launcher_mods_tests.lua b/tests/engine/launcher_mods_tests.lua index 02118482..368d6a07 100644 --- a/tests/engine/launcher_mods_tests.lua +++ b/tests/engine/launcher_mods_tests.lua @@ -328,4 +328,47 @@ do eq(bad.name, "abc", "surrogates and overlongs are dropped") end +-- ------- pre-boot translation strings (deriveStrings) +-- +-- The launcher draws before Game:load, so a translation mod's catalog has to +-- reach Strings without the loader running. These are the rules that decide +-- what it may contribute, with the filesystem read injected. +do + local manifests = { + { id = "aaa", name = "A", version = "1.0.0", path = "mods/aaa" }, + { id = "zzz", name = "Z", version = "1.0.0", path = "mods/zzz" }, + } + local catalogs = { + ["mods/aaa"] = { ["Import ROM"] = "A-rom", ["Delete"] = "A-del", + ["Cancel"] = "" }, + ["mods/zzz"] = { ["Import ROM"] = "Z-rom" }, + } + local function read(path) return catalogs[path] end + local function byIdMap(ms) + local m = {} + for _, x in ipairs(ms) do m[x.id] = x end + return m + end + + local rows = LauncherMods.deriveList(manifests, { mods = {} }) + local merged = LauncherMods.deriveStrings(rows, byIdMap(manifests), read) + eq(merged["Delete"], "A-del", "an enabled mod contributes its catalog") + eq(merged["Import ROM"], "Z-rom", + "later id wins a shared key, as it would at boot") + eq(merged["Cancel"], nil, + "an empty value is untranslated, never a blank translation") + + local offRows = LauncherMods.deriveList(manifests, { mods = { zzz = false } }) + local off = LauncherMods.deriveStrings(offRows, byIdMap(manifests), read) + eq(off["Import ROM"], "A-rom", "a disabled mod contributes nothing") + + local none = LauncherMods.deriveStrings( + LauncherMods.deriveList(manifests, { mods = { aaa = false, zzz = false } }), + byIdMap(manifests), read) + eq(none, nil, "no enabled catalog leaves the launcher on its English source") + + eq(LauncherMods.deriveStrings(rows, byIdMap(manifests), function() return nil end), + nil, "a mod that ships no catalog is skipped, not an error") +end + T.finish("launcher_mods") diff --git a/tests/engine/launcher_nx_pad_cursor_test.lua b/tests/engine/launcher_nx_pad_cursor_test.lua index 8e27bffd..3d9a8289 100644 --- a/tests/engine/launcher_nx_pad_cursor_test.lua +++ b/tests/engine/launcher_nx_pad_cursor_test.lua @@ -249,12 +249,24 @@ do "LauncherView exports applyNxPerfGuards") check(view:find("LauncherView.applyNxPerfGuards(imp)", 1, true) ~= nil, "ensureFlex calls applyNxPerfGuards") - check(view:find("FlexLove._Performance.enabled = false", 1, true) ~= nil, - "NX guard disables Performance.enabled") - check(view:find("mp.enabled = false", 1, true) ~= nil, - "NX guard disables memory profiling") - check(view:find("if not (imp and imp.isNX", 1, true) ~= nil, - "perf guard is gated on imp.isNX") + -- The perf guards these lines used to assert were FlexLove's: turning its + -- per-frame profiler off and softening its GC strategy, because the + -- immediate-mode tree rebuild allocated a full element graph every frame. + -- FlexLove is gone; the kit has no profiler and no GC strategy to tune + -- because it does not allocate per frame. What is worth guarding now is + -- that the dependency does not come back -- on NX it was the single + -- largest frame cost in the launcher. + -- Test the DEPENDENCY, not the word: the file's header comment explains + -- what it replaced, and that history is worth keeping. + check(view:find("libs.flexlove", 1, true) == nil, + "the launcher view does not require FlexLove") + check(view:find("FlexLove%.%a") == nil, + "the launcher view makes no FlexLove calls") + check(view:find('require("src.ui.kit.Kit")', 1, true) ~= nil, + "the launcher view draws with the shared UI kit") + local hasLib = io.open("libs/flexlove/FlexLove.lua", "r") + if hasLib then hasLib:close() end + check(hasLib == nil, "the FlexLove library is not vendored any more") check(view:find("parkNxPointerForHost", 1, true) ~= nil, "detach parks NX pointer before tearing down") @@ -282,8 +294,13 @@ do check(view:find("math.floor(x + 0.5)", 1, true) ~= nil, "NX pad cursor draw is pixel-snapped") - check(view:find('strategy = "periodic"', 1, true) ~= nil, - "NX softens FlexLove GC strategy") + -- Pagination replaced scrolling, which is what removes the per-frame cost + -- that the old GC tuning was compensating for: a page's row count is + -- bounded by the viewport, so a long list costs what a short one does. + check(view:find("Kit.pager(", 1, true) ~= nil, + "launcher lists paginate instead of scrolling") + check(view:find("Kit.rowsThatFit(", 1, true) ~= nil, + "page size is derived from the real viewport height") end T.finish("launcher_nx_pad_cursor") diff --git a/tests/engine/launcher_nx_version_chip_test.lua b/tests/engine/launcher_nx_version_chip_test.lua new file mode 100644 index 00000000..9fefbb1b --- /dev/null +++ b/tests/engine/launcher_nx_version_chip_test.lua @@ -0,0 +1,28 @@ +-- Switch launcher shows the running engine version in the header (NX only). +-- Desktop/Android/iOS must not paint that chip. + +local function read(path) + local f = assert(io.open(path, "r")) + local s = f:read("*a") + f:close() + return s +end + +local src = read("src/import/LauncherView.lua") + +assert(src:find('local Version = require%("src%.core%.Version"%)') + or src:find('require%("src%.core%.Version"%)'), + "LauncherView must require Version") + +assert(src:find("imp%.isNX", 1, false), "version chip must gate on isNX") + +-- The chip is inside an isNX block and prints Version.engine (layout between +-- the chip and the settings gear may change; anchor on the Switch-only comment). +local nxBlock = src:match( + "Switch%-only: show the running app version.-if imp%.isNX then(.-)end") +assert(nxBlock, "expected Switch-only version chip in the header row") +assert(nxBlock:find("Version%.engine", 1, false), "chip must show Version.engine") +assert(nxBlock:find('"v"', 1, true) or nxBlock:find('"v" %.%.', 1, false), + "chip label should be a v-prefixed version") + +print("ok — Switch launcher version chip is NX-gated and uses Version.engine") diff --git a/tests/engine/launcher_one_column_reach_bug852.lua b/tests/engine/launcher_one_column_reach_bug852.lua new file mode 100644 index 00000000..5b56d4e5 --- /dev/null +++ b/tests/engine/launcher_one_column_reach_bug852.lua @@ -0,0 +1,165 @@ +-- One-column launcher reach (#852) and the safe-area launcher anchor (#810). +-- No pokered cite: the launcher is port-only chrome. +-- +-- #852: minPanelHeight in src/import/LauncherView.lua was a flat 460*s tuned +-- for the two-column layout. A one-column window (portrait phone, squat 4:3 +-- device) stacks title + actions card + slot card + the pinned +-- Play/Reset-rebinds/Touch-Controls block, which needs more room; the flat +-- threshold read "tall enough", so the short-window page scroll never +-- engaged, buildSlotCard was cut by Kit.pushClip against the pinned block, +-- and Kit's clip-bounded hit-testing (src/ui/kit/Kit.lua) left every slot +-- row, the pager and "+ New save slot" drawn-but-inert. The fix makes the +-- threshold column-aware, so those windows scroll instead of clipping. +-- +-- The seam is LauncherView.draw itself: it publishes the page-scroll extent +-- on the importer (imp._pageScroll / imp._pageScrollMax, the values the +-- touch-drag and wheel paths feed), so a headless draw shows whether the +-- scroll engaged without reading any file-local constant. +-- +-- What is asserted here is REACHABILITY, not scrolling: "+ New save slot" is +-- the control that sits at the very bottom of the one-column pile, and the +-- bug was that it drew where no tap could land. Either it fits in the +-- window outright, or the page scrolls far enough to bring it in -- both are +-- correct, and which one a given window gets depends on how tall the panel's +-- content happens to be. Asserting "this window scrolls" instead pinned the +-- test to the size of the stack: when the pinned Touch-Controls / +-- Reset-rebinds pair moved behind the gear and the save-file buttons moved +-- into the slot card, 480x900 started fitting outright and a scroll +-- assertion failed on a window that had just got BETTER. +-- +-- #810 gets its unit-conversion pin in tests/engine/safe_area_units_test.lua; +-- here the complementary end-to-end anchor: Layout.metrics must place the +-- launcher at the corrected safe-area origin, not a DPI-inflated band down +-- the screen. +-- luajit tests/engine/launcher_one_column_reach_bug852.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +-- The launcher touches two graphics calls the shared stub does not carry +-- (focus-ring joins, the footer's BCG invert shader); both are draw-only, so +-- inert fills are enough for the layout arithmetic under test. +love.graphics.setLineJoin = love.graphics.setLineJoin or function() end +love.graphics.newShader = love.graphics.newShader or function() return {} end + +local Layout = require("src.ui.kit.Layout") +local Kit = require("src.ui.kit.Kit") +local RomImporter = require("src.import.RomImporter") +local LauncherView = require("src.import.LauncherView") + +local function window(w, h) + love.graphics.getDimensions = function() return w, h end + love.graphics.getPixelDimensions = function() return w, h end +end + +-- A fresh launcher on the Red tab; no cache exists headless, so every +-- version sits in its "ROM required" state, which still lays out the full +-- one-column pile (title, actions card, slot card, pinned block). +local function freshLauncher() + return RomImporter.new(function() end, { launcher = true }) +end + +-- Draw a frame with the layout audit on and report where "+ New save slot" +-- landed. Kit records the rect every clickable control occupies plus the +-- clip that bounds its hit test, so this sees exactly what a tap would. +local function newSlotRect(imp) + Kit.audit = {} + LauncherView.draw(imp) + local found + for _, r in ipairs(Kit.audit) do + if r.class == "control" and tostring(r.label):find("New save slot", 1, true) then + found = r + end + end + Kit.audit = nil + return found +end + +-- The control is reachable when its rect, intersected with whatever clip +-- bounds it, still has real area inside the window -- either straight away or +-- after the page is scrolled to the bottom. +local function reachable(imp, H) + local function visible() + local r = newSlotRect(imp) + if not r then return false end + local y1, y2 = r.y, r.y + r.h + if r.clip then + y1 = math.max(y1, r.clip.y) + y2 = math.min(y2, r.clip.y + r.clip.h) + end + y1, y2 = math.max(y1, 0), math.min(y2, H) + return (y2 - y1) > 1 + end + if visible() then return true end + imp._pageScroll = 1e6 -- clamps to the extent inside draw() + return visible() +end + +-- ------------------------------------------------ #852: the bottom is reachable +-- 480x900 one column: enough room for the old flat 460*s threshold, not for +-- the one-column stack. Before the fix draw() left _pageScrollMax at 0 here +-- and the slot card sat clipped inert against the pinned buttons. +window(480, 900) +local m = Layout.metrics(1200) +eq(m.twoCol, false, "480-wide window lays out one column") +local imp = freshLauncher() +LauncherView.draw(imp) +check(reachable(imp, 900), + "one-column window can reach the bottom of the slot card") +imp = freshLauncher() +LauncherView.draw(imp) +eq(imp._pageScroll, 0, "a fresh page starts at the top") + +-- The reporter's portrait phone (360x780 units) is shorter and narrower, so +-- it is the one that still engages the scroll; before the fix its slot list +-- was unreachable at any offset. +window(360, 780) +local pm = Layout.metrics(1200) +local phone = freshLauncher() +LauncherView.draw(phone) +check((phone._pageScrollMax or 0) > 0, + "portrait-phone one-column window engages the page scroll") +eq(phone._pageScroll, 0, "a fresh page starts at the top") + +-- The wheel moves the page (the same offset the touch drag feeds), and the +-- offset clamps to the extent, so the whole stack down to "+ New save slot" +-- and the footer is reachable rather than clipped away. +local extent = phone._pageScrollMax +phone._wheelY = -1 +LauncherView.draw(phone) +eq(phone._pageScroll, math.min(math.floor(48 * pm.s), extent), + "one wheel notch scrolls the page down by its step") +phone._pageScroll = 1e6 +LauncherView.draw(phone) +eq(phone._pageScroll, phone._pageScrollMax, + "an offset past the end clamps to the extent, so the bottom is reachable") +check(reachable(phone, 780), + "the scrolled portrait phone reaches the bottom of the slot card") + +-- A one-column window tall enough for the whole stack stays inert: the +-- column-aware minimum is a floor, not a permanent scroll. +window(480, 1200) +local tall = freshLauncher() +LauncherView.draw(tall) +eq(tall._pageScrollMax, 0, + "a tall one-column window does not scroll for nothing") + +-- --------------------------------------- #810: launcher anchored in units +-- Layout.metrics anchors the launcher at SafeArea.rect's origin. Feed it +-- the iOS 16 portrait frame that reported the safe rect in framebuffer +-- pixels (3x DPI): the launcher must start at the 44-unit notch inset, not +-- 132 units down with the top of the window black (the #810 report). The +-- rescale itself is pinned in tests/engine/safe_area_units_test.lua. +love.graphics.getDimensions = function() return 375, 812 end +love.graphics.getPixelDimensions = function() return 1125, 2436 end +local oldSafe = love.window.getSafeArea +love.window.getSafeArea = function() return 0, 132, 1125, 2232 end +local ios = Layout.metrics(1200) +eq(ios.top, 44, "launcher anchors at the unit-space notch inset") +eq(ios.h, 744, "launcher gets the full unit-space safe height") +love.window.getSafeArea = oldSafe + +T.finish("launcher one-column reach") diff --git a/tests/engine/launcher_panel_reflow.lua b/tests/engine/launcher_panel_reflow.lua new file mode 100644 index 00000000..4e4db52a --- /dev/null +++ b/tests/engine/launcher_panel_reflow.lua @@ -0,0 +1,208 @@ +-- Launcher panel reflow. No pokered cite: the launcher is port-only chrome. +-- +-- Three reports from the same round of testing, all of them the same root +-- cause -- a panel laying out more content than its window could hold, with +-- no scrollbar to rescue what fell off: +-- +-- * "Import failed only appears in that single line" -- the ROM card's +-- detail paragraph is elastic and got trimmed to zero lines whenever the +-- card's height budget was tight, so a failed import printed a headline +-- with no reason under it. The reporter's German ROM was rejected for a +-- specific, printable reason and the launcher swallowed it. +-- * "The settings labels are barely visible at all" -- settings rows put +-- the label and the value ladder side by side, and on a portrait phone +-- the ladder took so much of the width that every label ellipsized to +-- three characters ("TEX...", "BAT...", "BAT..."). +-- * "these buttons don't appear correctly" / Play walking off the bottom -- +-- the game panel pinned Play and a Touch-Controls/Reset-rebinds pair to +-- the bottom of a column whose height was whatever its cards needed, so +-- on a short window the pinned block left the window entirely. +-- +-- The audit sweep at the end is the general form of the third: Kit records +-- every control that could take a click (plus the clip that bounds its hit +-- test) while Kit.audit is set, so a window-size sweep can assert that no two +-- controls overlap and that nothing escapes a window which is not scrolling. +-- A window that IS scrolling legitimately draws below the fold -- reachability +-- there is pinned by tests/engine/launcher_one_column_reach_bug852.lua. +-- luajit tests/engine/launcher_panel_reflow.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +love.graphics.setLineJoin = love.graphics.setLineJoin or function() end +love.graphics.newShader = love.graphics.newShader or function() return {} end + +local Kit = require("src.ui.kit.Kit") +local RomImporter = require("src.import.RomImporter") +local LauncherView = require("src.import.LauncherView") + +local function window(w, h) + love.graphics.getDimensions = function() return w, h end + love.graphics.getPixelDimensions = function() return w, h end +end + +local function freshLauncher() + return RomImporter.new(function() end, { launcher = true }) +end + +-- Every string the frame printed. The kit falls back to love.graphics.print +-- under the stub (no newText), so recording that call captures the text the +-- panel actually put on screen -- ellipsis and all, which is the point. +local realPrint = love.graphics.print +local function drawAndCapture(imp) + local seen = {} + love.graphics.print = function(str, ...) + seen[#seen + 1] = tostring(str) + return realPrint(str, ...) + end + local ok, err = pcall(LauncherView.draw, imp) + love.graphics.print = realPrint + check(ok, "the frame draws: " .. tostring(err)) + return table.concat(seen, "\n") +end + +-- ------------------------------------- a failed import explains itself +-- setError stores the reason on imp.detail; the ROM card must print it, not +-- just the "Import failed" headline above it. Checked on the reporter's +-- phone shape, since a narrow window is exactly where the old budget +-- arithmetic trimmed the paragraph away. +local REASON = "This is a German ROM; only the English releases are supported." +window(360, 780) +local failed = freshLauncher() +failed:setError(REASON, "red") +failed.tab = "red" +local text = drawAndCapture(failed) +check(text:find("Import failed", 1, true) ~= nil, + "a failed import prints its headline") +check(text:find(REASON, 1, true) ~= nil, + "a failed import prints the REASON it failed, not just the headline") + +-- The same on a desktop window, so the detail is not an artefact of one shape. +window(1280, 720) +local failedWide = freshLauncher() +failedWide:setError(REASON, "red") +failedWide.tab = "red" +check(drawAndCapture(failedWide):find(REASON, 1, true) ~= nil, + "the failure reason survives on a desktop window too") + +-- ------------------------------- settings labels stay readable in portrait +-- Every core row's label must print in FULL. Side by side they could not, +-- so a narrow panel stacks the label on its own line above its control; the +-- assertion is on the text, not on the layout mode, because "the label is +-- readable" is the property that broke. +local function settingsText(w, h) + window(w, h) + local imp = freshLauncher() + imp:_openSettings() + check(imp._settings ~= nil, "the gear opens the settings model") + drawAndCapture(imp) -- first frame paginates + return drawAndCapture(imp) +end + +local LONG_LABELS = { "TEXT SPEED", "BATTLE ANIMATION", "BATTLE STYLE" } +local portrait = settingsText(360, 780) +for _, label in ipairs(LONG_LABELS) do + check(portrait:find(label, 1, true) ~= nil, + ("portrait settings print %q in full"):format(label)) +end +check(portrait:find("BAT...", 1, true) == nil, + "no settings label is clipped to an ellipsis on a portrait phone") + +-- A desktop window has room for the side-by-side shape and must not regress. +local desktop = settingsText(1280, 720) +for _, label in ipairs(LONG_LABELS) do + check(desktop:find(label, 1, true) ~= nil, + ("desktop settings print %q in full"):format(label)) +end + +-- ----------------------------------------------- the layout audit sweep +local function clipped(r) + local x1, y1, x2, y2 = r.x, r.y, r.x + r.w, r.y + r.h + if r.clip then + x1 = math.max(x1, r.clip.x); y1 = math.max(y1, r.clip.y) + x2 = math.min(x2, r.clip.x + r.clip.w); y2 = math.min(y2, r.clip.y + r.clip.h) + end + if x2 - x1 <= 1 or y2 - y1 <= 1 then return nil end + return x1, y1, x2, y2 +end + +local function overlap(a, b) + local ax1, ay1, ax2, ay2 = clipped(a) + if not ax1 then return false end + local bx1, by1, bx2, by2 = clipped(b) + if not bx1 then return false end + return math.min(ax2, bx2) - math.max(ax1, bx1) > 1 + and math.min(ay2, by2) - math.max(ay1, by1) > 1 +end + +-- `scrolling` windows are allowed to draw below the fold: that is the page +-- scroll doing its job, and the reach test covers it. +local function auditFrame(label, W, H, scrolling) + local controls = {} + for _, r in ipairs(Kit.audit or {}) do + if r.class == "control" then controls[#controls + 1] = r end + end + check(#controls > 0, label .. ": the frame dispatched controls at all") + local collisions, escapes = 0, 0 + for i = 1, #controls do + local a = controls[i] + local x1, y1, x2, y2 = clipped(a) + if x1 and not scrolling + and (x1 < -0.5 or y1 < -0.5 or x2 > W + 0.5 or y2 > H + 0.5) then + escapes = escapes + 1 + print((" escape: %s (%.0f,%.0f %.0fx%.0f)") + :format(a.label, a.x, a.y, a.w, a.h)) + end + for j = i + 1, #controls do + if overlap(a, controls[j]) then + collisions = collisions + 1 + print((" overlap: '%s' vs '%s' at (%.0f,%.0f) / (%.0f,%.0f)") + :format(a.label, controls[j].label, a.x, a.y, + controls[j].x, controls[j].y)) + end + end + end + check(collisions == 0, label .. ": no two controls overlap") + check(escapes == 0, label .. ": every control stays inside the window") +end + +-- The shapes the reports came from, plus the desktop ones they have to keep +-- serving: portrait phones, a 150%-scaled Linux handheld, 4:3, and widescreen. +local SIZES = { + { 360, 780 }, { 412, 915 }, { 480, 900 }, { 720, 1280 }, + { 1280, 720 }, { 1024, 768 }, { 900, 700 }, { 1920, 1080 }, +} + +for _, size in ipairs(SIZES) do + local W, H = size[1], size[2] + window(W, H) + for _, tab in ipairs({ "red", "yellow", "mods", "find" }) do + local imp = freshLauncher() + imp.tab = tab + LauncherView.draw(imp) -- warm frame: pagination settles + Kit.audit = {} + local ok, err = pcall(LauncherView.draw, imp) + Kit.audit = ok and Kit.audit or nil + check(ok, ("%dx%d %s draws: %s"):format(W, H, tab, tostring(err))) + if ok then + auditFrame(("%dx%d %s"):format(W, H, tab), W, H, + (imp._pageScrollMax or 0) > 0) + end + Kit.audit = nil + end + -- The settings panel is its own layout and its own reflow. + local imp = freshLauncher() + imp:_openSettings() + LauncherView.draw(imp) + Kit.audit = {} + local ok, err = pcall(LauncherView.draw, imp) + Kit.audit = ok and Kit.audit or nil + check(ok, ("%dx%d settings draws: %s"):format(W, H, tostring(err))) + if ok then auditFrame(("%dx%d settings"):format(W, H), W, H, false) end + Kit.audit = nil +end + +T.finish("launcher panel reflow") diff --git a/tests/engine/launcher_save_slot_overlap_bug748.lua b/tests/engine/launcher_save_slot_overlap_bug748.lua deleted file mode 100644 index 2be74fe3..00000000 --- a/tests/engine/launcher_save_slot_overlap_bug748.lua +++ /dev/null @@ -1,173 +0,0 @@ --- Regression for #748: FlexLove propagates a nested child's auto-height --- change only to its direct parent while the launcher tree is constructed. --- The two-column grid can therefore retain the shorter left-column height --- after the save-slot card makes the right column taller, placing the footer --- over the bottom of that card. Keep this test ROM- and renderer-free. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end - --- Element sizing asks the window for its current mode. FlexLove also loads --- UTF8 helpers for rendering, although this geometry test draws no text. -love.window.getMode = function() - return 1024, 768, { fullscreen = false } -end -love.window.getDesktopDimensions = function() return 1920, 1080 end -package.loaded["libs.flexlove.modules.UTF8"] = { - char = string.char, - charpattern = ".", - codepoint = string.byte, - len = string.len, - offset = function(_, n) return n end, - codes = function(s) - local i = 0 - return function() - i = i + 1 - if i <= #s then return i, s:byte(i) end - end - end, -} - -local T = require("tests.modkit") -local FlexLove = require("libs.flexlove.FlexLove") -FlexLove.init({ - immediateMode = false, - performanceMonitoring = false, - keyboardNavigation = false, -}) -local LauncherView = require("src.import.LauncherView") -local refreshAutoHeight = LauncherView._refreshAutoHeight - --- Preserve the launcher's construction order. The left card grows first; --- adding the right column then refreshes the grid to 220. Growing a card --- nested inside that right column reaches the column, but not the grid. -local function nestedColumns(leftHeight, rightHeight, gridOverrides) - local page = FlexLove.new({ - width = 800, - positioning = "flex", - flexDirection = "vertical", - gap = 12, - }) - local gridProps = { - parent = page, - width = 800, - positioning = "flex", - flexDirection = "horizontal", - alignItems = "flex-start", - } - for key, value in pairs(gridOverrides or {}) do - gridProps[key] = value - end - local grid = FlexLove.new(gridProps) - local left = FlexLove.new({ - parent = grid, - width = 390, - positioning = "flex", - flexDirection = "vertical", - }) - local leftCard = FlexLove.new({ - parent = left, - width = 390, - positioning = "flex", - flexDirection = "vertical", - }) - FlexLove.new({ parent = leftCard, width = 390, height = leftHeight }) - local right = FlexLove.new({ - parent = grid, - width = 390, - positioning = "flex", - flexDirection = "vertical", - }) - local rightCard = FlexLove.new({ - parent = right, - width = 390, - positioning = "flex", - flexDirection = "vertical", - }) - FlexLove.new({ parent = rightCard, width = 390, height = rightHeight }) - return { page = page, grid = grid, left = left, right = right } -end - --- Reported shape: real FlexLove elements finish with a 520px right column, --- but both the grid and page still reserve only the 220px left extent. -local tallRight = nestedColumns(220, 520) -local gap = 12 -T.eq(tallRight.left:getBorderBoxHeight(), 220, - "the left launcher column finishes at its content height") -T.eq(tallRight.right:getBorderBoxHeight(), 520, - "the nested save-slot column grows to its completed height") -T.eq(tallRight.grid:getBorderBoxHeight(), 220, - "FlexLove leaves the outer two-column grid stale") -T.eq(tallRight.page:getBorderBoxHeight(), 220, - "the page inherits the stale grid extent") -T.eq(tallRight.grid:calculateAutoHeight(), 520, - "the completed grid can measure the correct taller extent") -T.check(tallRight.grid:getBorderBoxHeight() + gap - < tallRight.right:getBorderBoxHeight(), - "the stale grid places the following footer over the save-slot column") - -if not T.check(type(refreshAutoHeight) == "function", - "launcher exports the auto-height reconciliation seam") then - T.finish("launcher save-slot overlap #748") -end - -tallRight.grid._dirty = false -tallRight.page._childrenDirty = false -local refreshedHeight = refreshAutoHeight(tallRight.grid) -T.eq(refreshedHeight, 520, "reconciliation returns the taller border-box height") -T.eq(tallRight.grid.height, 520, "reconciliation updates content height") -T.eq(tallRight.grid:getBorderBoxHeight(), 520, - "reconciliation updates the cached border-box height") -T.check(tallRight.grid._dirty, "reconciliation invalidates the grid") -T.check(tallRight.page._childrenDirty, - "reconciliation invalidates ancestor layout") -T.check(tallRight.grid:getBorderBoxHeight() + gap - >= tallRight.right:getBorderBoxHeight(), - "the following footer starts below the completed save-slot column") - --- Reconciliation uses the maximum completed column; it must not blindly --- copy the right side and shrink an already-taller left side. -local shortRight = nestedColumns(420, 220) -T.eq(refreshAutoHeight(shortRight.grid), 420, - "a shorter right column preserves the taller left extent") -T.eq(shortRight.grid.height, 420, - "short content keeps its correct content height") -T.eq(shortRight.grid:getBorderBoxHeight(), 420, - "short content keeps its correct border-box height") - --- Match FlexLove's resize path: clamp the padded border box, then derive and --- clamp the content box from it. -local constrained = nestedColumns(600, 500, { - padding = { top = 10, bottom = 20 }, - maxHeight = 550, -}) -T.eq(constrained.grid:calculateAutoHeight(), 600, - "the constrained grid measures its taller child before padding") -T.eq(refreshAutoHeight(constrained.grid), 550, - "max-height clamps the padded border box") -T.eq(constrained.grid.height, 520, - "content height subtracts padding from the constrained border box") -T.eq(constrained.grid:getBorderBoxHeight(), 550, - "the constrained border-box cache stays synchronized") - --- Invalid or inapplicable measurements are fail-closed and leave geometry --- untouched rather than poisoning the next layout pass. -local notANumber = nestedColumns(220, 520) -notANumber.grid.calculateAutoHeight = function() return 0 / 0 end -notANumber.grid._dirty = false -T.eq(refreshAutoHeight(notANumber.grid), false, "NaN auto height is rejected") -T.eq(notANumber.grid.height, 220, "NaN leaves content height unchanged") -T.eq(notANumber.grid:getBorderBoxHeight(), 220, - "NaN leaves border-box height unchanged") -T.eq(notANumber.grid._dirty, false, "NaN does not invalidate layout") - -local infinite = nestedColumns(220, 520) -infinite.grid.calculateAutoHeight = function() return math.huge end -T.eq(refreshAutoHeight(infinite.grid), false, "infinite auto height is rejected") -T.eq(infinite.grid.height, 220, "infinite height leaves geometry unchanged") - -local fixed = FlexLove.new({ width = 800, height = 220 }) -T.eq(refreshAutoHeight(fixed), false, "fixed-height elements are ignored") -T.eq(fixed.height, 220, "fixed-height geometry is unchanged") -T.eq(refreshAutoHeight(nil), false, "a missing element is ignored") - -T.finish("launcher save-slot overlap #748") diff --git a/tests/engine/launcher_touch_dispatch_bug780.lua b/tests/engine/launcher_touch_dispatch_bug780.lua new file mode 100644 index 00000000..5ca79bb4 --- /dev/null +++ b/tests/engine/launcher_touch_dispatch_bug780.lua @@ -0,0 +1,102 @@ +-- Launcher action drain (src/import/RomImporter.lua): on a phone one tap +-- lands on a save row AND on the chip drawn inside it, because FlexLove's +-- touch path has no topmost gate (EventHandler:processTouchEvents) while its +-- mouse path does. The drain therefore drops a row's own action when a +-- control inside that row fired in the same batch, and applies #433's disarm +-- as it runs each action rather than as the view queues them -- otherwise the +-- row's select cleared the arm the same tap had set and Delete never reached +-- its second press (#780). +-- luajit tests/engine/launcher_touch_dispatch_bug780.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local clock = 1000 +love.timer.getTime = function() return clock end + +local RomImporter = require("src.import.RomImporter") + +local function launcher() + local self = setmetatable({}, RomImporter) + self.ran = {} + return self +end + +local function tapDeleteChip(self, rowKey) + -- what one Android tap on a row's Delete chip queues: the row itself, then + -- the chip drawn on top of it + return { + { key = rowKey, keepArm = false, fn = function() + table.insert(self.ran, "select") + end }, + { key = rowKey .. "-del", keepArm = true, fn = function() + table.insert(self.ran, "delete") + self:pressDelete("slot", "slot2", "red", function() + table.insert(self.ran, "deleted") + end) + end }, + } +end + +-- ------- the chip wins the tap, and two taps delete + +do + local self = launcher() + self:runActions(tapDeleteChip(self, "slot-red-slot2")) + eq(self.ran[1], "delete", "the chip's action runs, not the row's select") + eq(#self.ran, 1, "the row behind the chip is dropped from the batch") + check(self._confirmDelete ~= nil, "the first tap leaves Delete armed") + + self:runActions(tapDeleteChip(self, "slot-red-slot2")) + eq(self.ran[3], "deleted", "the second tap on the same chip commits") + eq(self._confirmDelete, nil, "the arm is spent") +end + +-- ------- a tap on the row itself still selects, and still disarms + +do + local self = launcher() + self:pressDelete("slot", "slot2", "red", function() end) + check(self._confirmDelete ~= nil, "armed") + self:runActions({ + { key = "slot-red-slot2", keepArm = false, fn = function() + table.insert(self.ran, "select") + end }, + }) + eq(self.ran[1], "select", "a tap on empty row area selects the slot") + eq(self._confirmDelete, nil, "and disarms the pending Delete (#433)") +end + +-- ------- a sibling row's chip does not swallow another row + +do + local self = launcher() + self:runActions({ + { key = "slot-red-slot1", keepArm = false, fn = function() + table.insert(self.ran, "row1") + end }, + { key = "slot-red-slot10-del", keepArm = true, fn = function() + table.insert(self.ran, "del10") + end }, + }) + eq(self.ran[1], "row1", "slot1 is not a prefix-key parent of slot10") + eq(self.ran[2], "del10", "and slot10's chip still runs") +end + +-- ------- a failing action does not sink the rest of the batch + +do + local self = launcher() + self:runActions({ + { key = "a", keepArm = false, fn = function() error("boom") end }, + { key = "b", keepArm = false, fn = function() + table.insert(self.ran, "b") + end }, + }) + eq(self.ran[1], "b", "the queue drains past a handler that threw") +end + +print("launcher touch dispatch (#780) ok") diff --git a/tests/engine/leech_seed_timing_bug784.lua b/tests/engine/leech_seed_timing_bug784.lua new file mode 100644 index 00000000..478683d8 --- /dev/null +++ b/tests/engine/leech_seed_timing_bug784.lua @@ -0,0 +1,153 @@ +-- Gen 1 Leech Seed drains right after the SEEDED mon's move, not in an +-- end-of-round sweep (#784): MainInBattleLoop calls +-- HandlePoisonBurnLeechSeed after every Execute*Move (core.asm:426-464), +-- and the drain plays the ABSORB animation from the healing side +-- (core.asm:506-517). The port ran the whole residual sweep in +-- endOfTurn, after both sides had acted, and showed no drain animation. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.fresh() +local Font = require("src.render.Font") +Font.load(Data) +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local TypeChart = require("src.battle.TypeChart") +TypeChart.load(Data) + +local function newBattle() + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "FIXMON_A", 30) } + local game = { data = Data, save = save, + stack = { top = function() return nil end, push = function() end } } + local battle = BattleState.newWild(game, "FIXMON_C", 30) + battle.rng = function() return 0 end -- every roll lands: moves always hit + battle.enemyAction = function() return { id = "FIX_SCRATCH", pp = 35 } end + -- the seeded foe moves first, so its drain is observable mid-turn + battle.enemy.curStats.speed = 200 + battle.player.curStats.speed = 1 + return battle +end + +-- consume the queue the way updateQueue does, minus the presentation; +-- keeps the text and anim rows in order +local function drain(battle) + local rows = {} + for _ = 1, 400 do + local item = table.remove(battle.queue, 1) + if not item then return rows end + if item.text then rows[#rows + 1] = { text = item.text } end + if item.anim then + rows[#rows + 1] = { anim = item.anim, + attackerIsPlayer = item.attackerIsPlayer } + end + if item.fn then + battle.nextInsert = 0 + item.fn() + end + end + error("the turn queue never drained") +end + +local function indexOf(rows, pred) + for i, row in ipairs(rows) do + if pred(row) then return i end + end + return nil +end + +local function saidWith(rows, needle) + return indexOf(rows, function(r) + return r.text and r.text:find(needle, 1, true) ~= nil + end) +end + +-- --------------------------------------------------------------------- +-- the seeded foe is faster: its drain lands between the two moves +-- --------------------------------------------------------------------- +do + local battle = newBattle() + battle.enemy.leechSeeded = true + battle:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local rows = drain(battle) + + local scratchIdx = saidWith(rows, "FIX SCRATCH") + local seedIdx = saidWith(rows, "LEECH SEED") + local tackleIdx = saidWith(rows, "FIX TACKLE") + T.check(scratchIdx ~= nil, "the foe's move announces") + T.check(seedIdx ~= nil, "the drain announces") + T.check(tackleIdx ~= nil, "the player's move announces") + T.check(scratchIdx < seedIdx, + "the drain comes right after the seeded mon's move") + T.check(seedIdx < tackleIdx, + "and before the slower mon acts, not at end of round") + + local animIdx = indexOf(rows, function(r) return r.anim == "ABSORB" end) + T.check(animIdx ~= nil, "the drain plays the ABSORB animation") + T.check(animIdx ~= nil and rows[animIdx].attackerIsPlayer == true, + "played from the healing side (hWhoseTurn flipped)") + T.check(animIdx ~= nil and animIdx > seedIdx and animIdx < tackleIdx, + "and it rides with the drain, between the two moves") +end + +-- --------------------------------------------------------------------- +-- a seeded foe at 1 HP faints to its own drain right after moving: +-- the slower player never gets a move that turn (core.asm:426-429) +-- --------------------------------------------------------------------- +do + local battle = newBattle() + battle.enemy.leechSeeded = true + battle.enemy.mon.hp = 1 + battle:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local rows = drain(battle) + + T.check(saidWith(rows, "FIX SCRATCH") ~= nil, "the foe still moves first") + T.check(saidWith(rows, "LEECH SEED") ~= nil, "the drain still runs") + T.eq(battle.enemy.mon.hp, 0, "the drain faints the seeded foe") + T.check(saidWith(rows, "FIX TACKLE") == nil, + "the slower mon never moves after the residual faint") + T.eq(battle.result, "win", "and the battle is decided there and then") +end + +-- --------------------------------------------------------------------- +-- the modern ruleset keeps the Gen 3+ end-of-round sweep, with no +-- mid-turn drain animation +-- --------------------------------------------------------------------- +do + local battle = newBattle() + battle.ruleset = require("src.battle.rulesets.modern_clean") + battle.enemy.leechSeeded = true + battle:resolveTurn({ id = "FIX_TACKLE", pp = 35 }) + local rows = drain(battle) + + local scratchIdx = saidWith(rows, "FIX SCRATCH") + local seedIdx = saidWith(rows, "LEECH SEED") + local tackleIdx = saidWith(rows, "FIX TACKLE") + T.check(scratchIdx ~= nil and tackleIdx ~= nil and seedIdx ~= nil, + "all three beats still happen under the modern ruleset") + T.check(scratchIdx < tackleIdx and tackleIdx < seedIdx, + "but the drain waits for the end of the round") + T.check(indexOf(rows, function(r) return r.anim == "ABSORB" end) == nil, + "and no mid-turn drain animation is queued") +end + +-- --------------------------------------------------------------------- +-- an item turn spends the player's move but its residual still ticks +-- (ExecutePlayerMove rets early on wActionResultOrTookBattleTurn and +-- MainInBattleLoop calls HandlePoisonBurnLeechSeed anyway) +-- --------------------------------------------------------------------- +do + local battle = newBattle() + battle.player.mon.status = "PSN" + battle:itemUsed({}) + local rows = drain(battle) + + local scratchIdx = saidWith(rows, "FIX SCRATCH") + local poisonIdx = saidWith(rows, "hurt by poison") + T.check(scratchIdx ~= nil and poisonIdx ~= nil, + "the foe moves and the poison ticks on an item turn") + T.check(scratchIdx < poisonIdx, "the tick lands right after the foe's move") +end + +T.finish("leech seed drain timing (#784)") diff --git a/tests/engine/link_session.lua b/tests/engine/link_session.lua new file mode 100644 index 00000000..19f80fcf --- /dev/null +++ b/tests/engine/link_session.lua @@ -0,0 +1,345 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Net = require("src.link.Net") +local Json = require("src.link.Json") +local Session = require("src.link.Session") + +local function sessionPair() + local hostNet, guestNet = Net.loopbackPair() + return Session.new(hostNet, { role = "host", kind = "link" }), + Session.new(guestNet, { role = "guest", kind = "link" }) +end + +local function fakeTransport(options) + options = options or {} + local transport = { + paired = options.paired ~= false, + closed = false, + error = nil, + inbox = options.inbox or {}, + closeCount = 0, + } + function transport:update() + if options.onUpdate then options.onUpdate(self) end + if options.updateError then error(options.updateError) end + end + function transport:poll() + if options.pollError then error(options.pollError) end + local messages = self.inbox + self.inbox = {} + return messages + end + function transport:send(message) + self.sent = message + return true + end + function transport:close() + self.closeCount = self.closeCount + 1 + self.closed = true + if options.closeError then error(options.closeError) end + end + return transport +end + +local function readFile(path) + local handle = assert(io.open(path, "rb")) + local body = handle:read("*a") + handle:close() + return body +end + +do + local host, guest = sessionPair() + T.eq(host:getRole(), "host", "host role is assigned locally") + T.eq(guest:getRole(), "guest", "guest role is assigned locally") + T.eq(host:getKind(), "link", "session kind is retained") + T.eq(host:getStatus(), "paired", "wrapped loopback starts paired") + + guest:send({ + type = "hello", name = "BLUE", role = "host", kind = "tournament", + }) + host:update() + local hello = host:take("hello") + T.eq(hello.name, "BLUE", "send forwards the original payload") + T.eq(hello.session, nil, "send adds no session envelope") + T.eq(host:getRole(), "host", "peer payload cannot replace local role") + T.eq(host:getKind(), "link", "peer payload cannot replace local kind") +end + +do + local host, guest = sessionPair() + guest:send({ type = "before", sequence = 1 }) + guest:send({ type = "hello", sequence = 2 }) + guest:send({ type = "after", sequence = 3 }) + guest:send({ type = "hello", sequence = 4 }) + host:update() + + local hello = host:take("hello") + T.eq(hello.sequence, 2, "take removes the first matching packet") + T.eq(host:pollOne().sequence, 1, "pollOne removes only the FIFO head") + + local rest = host:poll() + T.eq(#rest, 2, "poll returns every remaining packet once") + T.eq(rest[1].sequence, 3, "take preserves the earlier remainder order") + T.eq(rest[2].sequence, 4, "take preserves repeated-type order") + T.eq(#host:poll(), 0, "poll clears the private FIFO") +end + +do + local sent + local transport = { + paired = false, + code = nil, + address = "192.0.2.5:7777", + target = "ROOM01", + update = function(self) + self.paired = true + self.code = "ROOM02" + end, + poll = function() return {} end, + send = function(_, message) + sent = message + return "queued", 7 + end, + close = function(self) self.closed = true end, + } + local session = Session.new(transport, { role = "guest", kind = "tournament" }) + T.eq(session:getStatus(), "connecting", "unpaired transport starts connecting") + T.eq(session.address, "192.0.2.5:7777", "address metadata is mirrored") + T.eq(session.target, "ROOM01", "target metadata is mirrored") + + local outbound = { type = "ping" } + local result, count = session:send(outbound) + T.eq(result, "queued", "send preserves the transport's first return") + T.eq(count, 7, "send preserves the transport's second return") + T.eq(sent, outbound, "send forwards the original table unchanged") + + session:update() + T.eq(session:getStatus(), "paired", "update observes transport pairing") + T.eq(session.code, "ROOM02", "update refreshes relay metadata") +end + +do + local transport = fakeTransport({ onUpdate = function(self) + self.inbox[#self.inbox + 1] = { type = "bye", final = true } + self.closed = true + end }) + local session = Session.new(transport, { role = "host", kind = "link" }) + session:update() + T.eq(session:getStatus(), "draining", "normal close drains its final packet") + T.eq(session.closed, false, "compatibility closed waits for the FIFO") + T.eq(session:take("bye").final, true, "final close packet remains observable") + T.eq(session:getStatus(), "closed", "normal drain reaches closed") + T.eq(transport.closeCount, 1, "transport cleanup runs once") +end + +do + local transport = fakeTransport({ + onUpdate = function(self) self.closed = true end, + closeError = "normal cleanup exploded", + }) + local session = Session.new(transport, { role = "host", kind = "link" }) + T.check(pcall(session.update, session), + "normal-close cleanup exception does not escape the game loop") + local reason, detail = session:getFailure() + T.eq(reason, "transport_error", + "normal-close cleanup exception becomes a transport failure") + T.check(detail:find("normal cleanup exploded", 1, true) ~= nil, + "normal-close cleanup failure keeps its diagnostic detail") + T.eq(session:getStatus(), "failed", + "normal-close cleanup exception cannot report a clean close") +end + +do + local transport = fakeTransport({ onUpdate = function(self) + self.inbox = { { type = "before", sequence = 1 } } + self.error = "socket failed" + self.closed = true + end }) + local session = Session.new(transport, { role = "guest", kind = "link" }) + session:update() + local reason, detail = session:getFailure() + T.eq(reason, "transport_error", "transport failure has a stable reason") + T.eq(detail, "socket failed", "transport failure retains original detail") + T.eq(session:getStatus(), "draining", "transport failure drains valid prefix") + T.eq(session.error, nil, "legacy error stays hidden during drain") + T.eq(session.closed, false, "legacy closed stays false during failed drain") + T.eq(session:pollOne().sequence, 1, "failed drain returns its valid prefix") + T.eq(session:getStatus(), "failed", "failed drain reaches failed") + T.eq(session.error, "socket failed", "legacy error appears at terminal failure") + transport.error = "later error" + session:update() + local _, latchedDetail = session:getFailure() + T.eq(latchedDetail, "socket failed", "first terminal failure stays latched") +end + +do + local transport = fakeTransport({ inbox = { + { type = "before", sequence = 1 }, + false, + { type = "after", sequence = 3 }, + } }) + local session = Session.new(transport, { role = "host", kind = "link" }) + session:update() + local reason = session:getFailure() + T.eq(reason, "protocol_error", "malformed packet fails as protocol_error") + T.eq(session:getStatus(), "draining", "malformed batch drains valid prefix") + local messages = session:poll() + T.eq(#messages, 1, "malformed value and untrusted tail are not exposed") + T.eq(messages[1].sequence, 1, "valid prefix survives malformed packet") + T.eq(session:getStatus(), "failed", "protocol drain reaches failed") +end + +do + local transport = fakeTransport({ inbox = { { type = 7 } } }) + local session = Session.new(transport, { role = "host", kind = "link" }) + session:update() + T.eq(session:getFailure(), "protocol_error", + "table without string type is a protocol error") +end + +do + local transport = fakeTransport({ + inbox = { { type = "future_world_packet", value = 9 } }, + }) + local session = Session.new(transport, { role = "host", kind = "link" }) + session:update() + T.eq(session:pollOne().value, 9, "unknown typed packet stays mode-owned") +end + +do + local transport = fakeTransport({ + inbox = { { type = "already_decoded", value = 4 } }, + updateError = "update exploded", + }) + local session = Session.new(transport, { role = "host", kind = "link" }) + local ok = pcall(session.update, session) + T.check(ok, "transport update exception does not escape the game loop") + T.eq(session:getStatus(), "draining", "update exception still drains prior inbox") + T.eq(session:pollOne().value, 4, "decoded packet survives update exception") + T.eq(session:getStatus(), "failed", "update exception becomes terminal failure") +end + +do + local transport = fakeTransport({ pollError = "poll exploded" }) + local session = Session.new(transport, { role = "host", kind = "link" }) + T.check(pcall(session.update, session), + "transport poll exception does not escape the game loop") + local reason, detail = session:getFailure() + T.eq(reason, "transport_error", "poll exception is a transport failure") + T.check(detail:find("poll exploded", 1, true) ~= nil, + "poll exception keeps its diagnostic detail") +end + +do + local transport = fakeTransport({ closeError = "close exploded" }) + local session = Session.new(transport, { role = "guest", kind = "link" }) + T.check(pcall(session.close, session), + "transport close exception does not escape cleanup") + T.eq(session:getFailure(), "transport_error", + "close exception is a transport failure") + session:close() + T.eq(transport.closeCount, 1, "failed close is still attempted only once") +end + +do + local transport = fakeTransport() + local session = Session.new(transport, { role = "guest", kind = "link" }) + session:close() + session:close() + session:update() + T.eq(transport.closeCount, 1, "close and post-terminal update are idempotent") + T.eq(session:getStatus(), "closed", "explicit close reaches closed") +end + +do + T.check(not pcall(Session.new, nil, { role = "host", kind = "link" }), + "constructor rejects missing transport") + local transport = fakeTransport() + T.check(not pcall(Session.new, transport, { role = "leader", kind = "link" }), + "constructor rejects unsupported role") + T.check(not pcall(Session.new, transport, { role = "host", kind = "" }), + "constructor rejects empty kind") +end + +do + local senderNet, receiverNet = Net.loopbackPair() + local receiver = Session.new(receiverNet, { role = "guest", kind = "link" }) + senderNet:send(false) + receiver:update() + T.eq(receiver:getFailure(), "protocol_error", + "loopback forwards decoded false to session validation") +end + +do + local delivered = false + local transport = Net.new() + transport.enetHost = { + service = function() + if delivered then return nil end + delivered = true + return { type = "receive", data = "false" } + end, + } + local session = Session.new(transport, { role = "guest", kind = "link" }) + session:update() + T.eq(session:getFailure(), "protocol_error", + "ENet forwards decoded false to session validation") +end + +do + local transport = Net.new() + local session = Session.new(transport, { role = "host", kind = "tournament" }) + T.check(pcall(transport.handleTCPLine, transport, "42"), + "TCP control handoff does not index a decoded scalar") + session:update() + T.eq(session:getFailure(), "protocol_error", + "decoded TCP scalar reaches session validation") +end + +do + local transport = Net.new() + transport:handleTCPLine(Json.encode({ type = "hosted", code = "ABCDEF" })) + T.eq(transport.code, "ABCDEF", "valid relay controls stay transport-owned") + transport:handleTCPLine(Json.encode({ type = "hello", name = "RED" })) + T.eq(transport:poll()[1].name, "RED", "valid application packet stays intact") +end + +do + local source = readFile("src/link/LinkState.lua") + T.check(source:find('require("src.link.Session")', 1, true) ~= nil, + "LinkState depends on the session boundary") + T.check(source:find('kind = "link"', 1, true) ~= nil, + "LinkState assigns the link session kind locally") + T.check(source:find("self.net.inbox", 1, true) == nil, + "LinkState never mutates a transport inbox") + T.check(source:find("self.net = Net.new()", 1, true) == nil, + "LinkState stores only successful session wrappers") + T.check(source:find(':take("hello")', 1, true) ~= nil, + "LinkState retrieves hello without draining unrelated packets") + T.check(source:find(':take("party")', 1, true) ~= nil, + "LinkState leaves battle handoff packets in session order") + T.check(source:find("getStatus()", 1, true) ~= nil, + "LinkState uses the session lifecycle instead of raw terminal flags") +end + +do + local source = readFile("src/link/Tournament.lua") + T.check(source:find('require("src.link.Session")', 1, true) ~= nil, + "Tournament depends on the session boundary") + T.check(source:find('kind = "tournament"', 1, true) ~= nil, + "Tournament assigns its connection role and kind locally") + T.check(source:find("self.net.inbox", 1, true) == nil, + "Tournament never mutates a transport inbox") + T.check(source:find("self.net = Net.new()", 1, true) == nil, + "Tournament stores only a successful session wrapper") + T.check(source:find(':take("hello")', 1, true) ~= nil, + "Tournament retrieves match hello without draining its tail") + T.check(source:find(":pollOne()", 1, true) ~= nil, + "Tournament processes handoff prefixes one packet at a time") + T.check(source:find("getStatus()", 1, true) ~= nil, + "Tournament uses the session lifecycle instead of raw terminal flags") +end + +T.finish("link_session") diff --git a/tests/engine/map_music_fade.lua b/tests/engine/map_music_fade.lua new file mode 100644 index 00000000..0ab6bab2 --- /dev/null +++ b/tests/engine/map_music_fade.lua @@ -0,0 +1,94 @@ +-- ..(home/audio.asm ln 9) +-- ..(home/fade_audio.asm ln 36) +-- luajit tests/engine/map_music_fade.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check +local eq = T.eq + +love = require("tests.love_stub") + +local Source = {} +Source.__index = Source +function Source:play() self.playing = true end +function Source:stop() self.playing = false end +function Source:pause() self.playing = false end +function Source:isPlaying() return self.playing end +function Source:setLooping() end +function Source:setVolume(v) self.volume = v end +function Source:setPitch() end +function Source:setFilter() end +function Source:getDuration() return 1 end + +local made = {} -- file -> the last source built for it +love.audio = { + newSource = function(file, mode) + made[file] = setmetatable({ file = file, mode = mode }, Source) + return made[file] + end, +} + +local Music = require("src.core.Music") + +local data = { audio = { + songs = { + Music_Pallet = { file = "pallet.wav" }, + Music_Routes1 = { file = "routes1.wav" }, + Music_Pewter = { file = "pewter.wav" }, + }, + mapSongs = { + PALLET_TOWN = "Music_Pallet", + ROUTE_1 = "Music_Routes1", + PEWTER_CITY = "Music_Pewter", + }, +} } + +local function frames(n) + for _ = 1, n do Music.update(data) end +end + +local function playing() + for file, src in pairs(made) do + if src.playing then return file end + end + return "(silence)" +end + +local FADE = 7 * Music.MAP_FADE -- 7 volume levels x 10 frames + +Music.stop() +Music.playMap(data, "PALLET_TOWN", false, false, Music.MAP_FADE) +eq(playing(), "pallet.wav", "the first map after boot starts at once") + +local fullVolume = made["pallet.wav"].volume + +Music.playMap(data, "ROUTE_1", false, false, Music.MAP_FADE) +eq(playing(), "pallet.wav", "the new theme waits while the old one fades") +frames(FADE - 1) +eq(playing(), "pallet.wav", "still fading one frame short of silence") +check(made["pallet.wav"].volume < fullVolume, + "the old theme has been ramped down by then") +frames(1) +eq(playing(), "routes1.wav", "the queued theme takes over after 7 * 10 frames") + +eq(made["routes1.wav"].volume, fullVolume, + "the new theme starts at full volume, not where the ramp ended") + +Music.playMap(data, "ROUTE_1", false, false, Music.MAP_FADE) +eq(playing(), "routes1.wav", "the same theme keeps playing") +frames(FADE) +eq(playing(), "routes1.wav", "and no fade was armed for it") + +Music.playMap(data, "PALLET_TOWN", false, false, Music.MAP_FADE) +frames(3 * Music.MAP_FADE) +Music.playMap(data, "PEWTER_CITY", false, false, Music.MAP_FADE) +eq(playing(), "routes1.wav", "the retargeted fade keeps ramping the old theme") +frames(4 * Music.MAP_FADE) +eq(playing(), "pewter.wav", "the ramp lands on the newest map's theme") + +Music.playMap(data, "PALLET_TOWN", false, false) +eq(playing(), "pallet.wav", "a fadeless map cue swaps immediately") + +T.finish("map_music_fade") diff --git a/tests/engine/mod_index_tests.lua b/tests/engine/mod_index_tests.lua index 82076453..87e96222 100644 --- a/tests/engine/mod_index_tests.lua +++ b/tests/engine/mod_index_tests.lua @@ -142,7 +142,11 @@ do index, err = ModIndex.parse(Json.encode({ mods = { NUZLOCKE } })) check(index == nil and err ~= nil, "a feed with no schema_version is refused") index, err = ModIndex.parse("404") - check(index == nil and err ~= nil, "an HTML error page soft-fails") + check(index == nil and tostring(err):find("HTML", 1, true) ~= nil, + "an HTML error page is named, not blamed on the parser") + index, err = ModIndex.parse("Error: upstream unavailable") + check(index == nil and tostring(err):find("not JSON", 1, true) ~= nil, + "a plain-text error names the response") index, err = ModIndex.parse('{"schema_version":1}') check(index == nil and err ~= nil, "a feed with no mods array soft-fails") end diff --git a/tests/engine/mod_update_tests.lua b/tests/engine/mod_update_tests.lua index d6ff3239..a0b3306e 100644 --- a/tests/engine/mod_update_tests.lua +++ b/tests/engine/mod_update_tests.lua @@ -76,6 +76,31 @@ do check(path == nil and dlErr ~= nil, "empty url soft-fails") end +-- the reported bug: a non-JSON answer (plain-text error, proxy/captive +-- prompt, outage message) used to leak the decoder's "unexpected character" +-- assert at the first byte of the body. The guard must name what the server +-- actually sent and never let that assert surface. +do + local list, err = ModUpdate.parseReleases("Error: API rate limit exceeded", "demo") + check(list == nil and err ~= nil, "plain-text error soft-fails") + check(tostring(err):find("not JSON", 1, true) ~= nil + and tostring(err):find("Error: API", 1, true) ~= nil, + "plain-text error names the response and previews what it said") + list, err = ModUpdate.parseReleases("502 Bad Gateway", "demo") + check(list == nil and tostring(err):find("HTML", 1, true) ~= nil, + "an HTML error page is named as such") + list, err = ModUpdate.parseReleases("", "demo") + check(list == nil and tostring(err):find("empty", 1, true) ~= nil, + "an empty response is named") + check(tostring(err):find("unexpected character", 1, true) == nil, + "the decoder's assert never leaks into the message") + list = ModUpdate.parseReleases(Json.encode({ + { tag_name = "v1.0.0", assets = { + { name = "demo-1.0.0.zip", browser_download_url = "https://x/d.zip" } } }, + }), "demo") + eq(#list, 1, "the guard lets real JSON through") +end + do local body = Json.encode({ tag_name = "v2.0.0", diff --git a/tests/engine/move_sfx_channel_gate_bug844.lua b/tests/engine/move_sfx_channel_gate_bug844.lua new file mode 100644 index 00000000..b7666e43 --- /dev/null +++ b/tests/engine/move_sfx_channel_gate_bug844.lua @@ -0,0 +1,219 @@ +-- Animation-row SFX must obey PlaySound's channel-occupancy gate (#844). +-- +-- Blizzard's animation is two rows -- `battle_anim BLIZZARD, ...` then +-- `battle_anim HYDRO_PUMP, ...` (data/moves/animations.asm, BlizzardAnim) -- +-- and PlaySubanimation issues a PlaySound for every row +-- (engine/battle/animations.asm, PlaySubanimation). The extracted data and +-- the row timing are both faithful; what the port was missing is that the +-- original never actually starts that second sound. Audio2_PlaySound's +-- .playSfx/.sfxChannelLoop (audio/engine_2.asm) walks the channels the new +-- sfx declares and, for each one already busy, does +-- `ld a,[wSoundID] / cp [hl] / jr z,.playChannel / jr c,.playChannel / ret`: +-- a channel held by a LOWER sound id aborts the whole request, while an +-- equal or lower id takes the channel over (and .playChannel resets the +-- channel, cutting the old sound off). SFX_BATTLE_29 (BLIZZARD, CHAN5+8) is +-- still sounding when the HYDRO_PUMP row starts, and SFX_BATTLE_2A wants +-- CHAN5+6+8, so on hardware it is dropped outright. Unguarded, the port +-- layered it and its watery tail outlived the animation. +-- +-- Sound ids order by header address: `DEF \1 EQUS "((\2 - SFX_Headers_1) / 3)"` +-- (constants/music_constants.asm, music_const), so a def's `address` is the +-- comparable rank inside one engine bank -- which is what Sound.playMove +-- compares and what ChipSynth.effectChannels supplies the channel set for. +-- +-- ROM-free: ChipAsm blobs stand in for the sfx headers, so nothing here +-- reads data/generated/. +-- luajit tests/engine/move_sfx_channel_gate_bug844.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq + +love = require("tests.love_stub") + +-- ------- love.audio stub +-- tests/love_stub carries no love.audio (headless suites never play), and +-- the gate reads Source:isPlaying on the previously accepted row sound. +-- These stub sources never finish on their own, which is exactly the +-- "previous sfx is still sounding" state a mid-animation row sees. +local sources = {} + +local Source = {} +Source.__index = Source +function Source:play() self.playing = true; self.plays = self.plays + 1 end +function Source:stop() self.playing = false end +function Source:isPlaying() return self.playing end +function Source:pause() self.playing = false end +function Source:setLooping(value) self.looping = value end +function Source:setVolume(value) self.volume = value end +function Source:setPitch(value) self.pitch = value end +function Source:setFilter() end +function Source:getDuration() return 1 end + +love.audio = { + newSource = function(what, mode) + local src = setmetatable({ + file = what, mode = mode, plays = 0, playing = false, + }, Source) + sources[#sources + 1] = src + return src + end, +} + +local ChipAsm = require("src.audio.ChipAsm") +local ChipSynth = require("src.core.ChipSynth") +local Sound = require("src.core.Sound") +local Runtime = require("src.mods.Runtime") + +-- ------- sfx fixtures +-- One audible note per channel. ChipAsm.sfx numbers effect channels hw+4, +-- so hw 1/2/4 assemble as CHAN5/CHAN6/CHAN8 -- the same software channels +-- the real sfx headers claim. `address` is the sound-id rank and `engine` +-- names the bank the rank is comparable within. +local function sfxDef(address, hws) + local channels = {} + for _, hw in ipairs(hws) do + local program + if hw == 4 then + program = { { noiseNote = { len = 8, volume = 15, fade = 1, + parameter = 0x11 } } } + else + program = { { squareNote = { len = 8, volume = 15, fade = 1, + frequency = 0x600 } } } + end + channels[#channels + 1] = { hw = hw, program = program } + end + local def = ChipAsm.sfx{ channels = channels } + def.address, def.engine = address, 2 + return def +end + +-- Battle_29 = CHAN5,8 at rank 16975; Battle_2A = CHAN5,6,8 at 16981. The +-- ranks below are the same ordering, scaled small for readability. +local defs = { + Blizzard_Sfx = sfxDef(100, { 1, 4 }), -- SFX_BATTLE_29 shape + HydroPump_Sfx = sfxDef(106, { 1, 2, 4 }), -- SFX_BATTLE_2A shape + Disable_Sfx = sfxDef(100, { 4 }), -- SFX_BATTLE_1B shape: CHAN8 + Leer_Sfx = sfxDef(106, { 1, 2 }), -- SFX_BATTLE_31 shape: CHAN5,6 + Loud_Sfx = sfxDef(106, { 1, 4 }), -- a high-ranked incumbent + Quiet_Sfx = sfxDef(100, { 1 }), -- a lower id that takes over + Unranked_Sfx = ChipAsm.sfx{ channels = { { hw = 1, program = { + { squareNote = { len = 8, volume = 15, fade = 1, frequency = 0x600 } }, + } } } }, -- a mod def: no header address +} + +local data = { audio = { sfx = defs, cries = {}, songs = {} } } + +-- the gate is only observable through what actually started, so watch the +-- Runtime event the mod SDK exposes for exactly that +local savedEvents, savedHooks = Runtime.events, Runtime.hooks +local events = require("src.mods.Events").new() +Runtime.install(events, require("src.mods.Hooks").new()) + +local played = {} +events:on("sound.played", function(p) played[#played + 1] = p end, nil, "test") + +local function reset() + Sound.invalidate() -- also clears the tracked row sound + for index = #sources, 1, -1 do sources[index] = nil end + for index = #played, 1, -1 do played[index] = nil end +end + +local function playMove(name) + Sound.playMove(data, { sound = name, pitch = 0, tempo = 0x80 }) +end + +local function names() + local out = {} + for _, p in ipairs(played) do out[#out + 1] = p.name end + return table.concat(out, ",") +end + +-- ------- the channel sets the gate reads +eq(table.concat(ChipSynth.effectChannels(data, defs.Blizzard_Sfx), ","), + "5,8", "effectChannels reads CHAN5+8 off the Blizzard-shaped header") +eq(table.concat(ChipSynth.effectChannels(data, defs.HydroPump_Sfx), ","), + "5,6,8", "effectChannels reads CHAN5+6+8 off the Hydro Pump-shaped header") +check(ChipSynth.effectChannels(data, "assets/beep.wav") == nil, + "a file def has no knowable channel set") + +-- ------- 1. higher id + overlapping channels is dropped (the Blizzard case) +reset() +playMove("Blizzard_Sfx") +check(#sources == 1 and sources[1].playing, "the Blizzard row sound starts") +playMove("HydroPump_Sfx") +eq(#played, 1, "the second Blizzard row is dropped, not layered (" .. names() .. ")") +eq(played[1] and played[1].name, "Blizzard_Sfx", + "the sound that survives is the Blizzard row") +eq(#sources, 1, "the dropped row never even builds a source") +check(sources[1].playing, "the incumbent keeps sounding through the drop") + +-- ------- 2. higher id + disjoint channels still plays (Disable/Leer) +-- The regression guard: SFX_BATTLE_1B is CHAN8 and SFX_BATTLE_31 is +-- CHAN5+6, so nothing is busy and both sounds are heard. +reset() +playMove("Disable_Sfx") +playMove("Leer_Sfx") +eq(#played, 2, "a higher id on disjoint channels is not gated (" .. names() .. ")") +eq(played[2] and played[2].name, "Leer_Sfx", "the second sound is the later row") +check(sources[1].playing and sources[2].playing, + "neither disjoint sound cuts the other off") + +-- ------- 3. a lower id takes the channels over +-- .playChannel zeroes the channel state, which stops whatever held it. +reset() +playMove("Loud_Sfx") +local incumbent = sources[1] +playMove("Quiet_Sfx") +eq(#played, 2, "a lower id is allowed to start (" .. names() .. ")") +check(not incumbent.playing, + "taking CHAN5 over stops the sound that held it") +check(sources[2] and sources[2].playing, "the taking-over sound is playing") + +-- ------- 4. an equal id restarts the sound +-- Repeated rows of one sound (Wrap, Metronome) must not be swallowed. +reset() +playMove("Blizzard_Sfx") +playMove("Blizzard_Sfx") +eq(#played, 2, "the same sound replayed is not gated against itself") +eq(#sources, 1, "the replay reuses the cached source") +eq(sources[1].plays, 2, "the cached source is restarted") +check(sources[1].playing, "and is sounding afterwards") + +-- ------- 5. an unrankable def is left exactly as it was +-- A mod's chip sfx has no header address, so there is no comparable sound +-- id and the gate must not invent one. +reset() +playMove("Blizzard_Sfx") +playMove("Unranked_Sfx") +playMove("Unranked_Sfx") +eq(#played, 3, "unrankable defs play regardless of what is sounding (" + .. names() .. ")") + +-- an unrankable def must also not become an incumbent that gates the next +-- ranked row +reset() +playMove("Unranked_Sfx") +playMove("HydroPump_Sfx") +eq(#played, 2, "an unrankable def gates nothing after it (" .. names() .. ")") + +-- ------- 6. a finished sound gates nothing +reset() +playMove("Blizzard_Sfx") +sources[1].playing = false -- the incumbent ran out +playMove("HydroPump_Sfx") +eq(#played, 2, "a row sound that already ended blocks nothing") + +-- ------- 7. an invalidate (hot reload / cache flush) drops the tracking +-- Sound.invalidate stops and forgets the cached sources; a stale reference +-- would gate the next row against a dead source. +reset() +playMove("Blizzard_Sfx") +Sound.invalidate() +playMove("HydroPump_Sfx") +eq(#played, 2, "invalidate clears the tracked row sound") + +Runtime.install(savedEvents, savedHooks) + +T.finish("move sfx channel gate (#844)") diff --git a/tests/engine/naming_empty_confirm_bug833.lua b/tests/engine/naming_empty_confirm_bug833.lua new file mode 100644 index 00000000..a184b62a --- /dev/null +++ b/tests/engine/naming_empty_confirm_bug833.lua @@ -0,0 +1,126 @@ +-- Empty confirm on the naming screen (#833). DisplayNamingScreen seeds +-- wStringBuffer with '@' (engine/menus/naming_screen.asm), so a name the +-- player never typed reads back as the terminator, and every caller checks +-- that first byte: AskName falls through to .declinedNickname and copies the +-- species name over the nick slot (vanilla's "un-nicknamed", which this port +-- models as mon.nickname == nil, src/save_convert/GenSave.lua), while +-- DisplayNameRaterScreen takes .playerCancelled and keeps the old nickname. +-- Nothing in the original invents a letter, so NamingScreen:confirm must hand +-- the caller "" rather than the literal "A" when nothing was typed -- both via +-- START and via the ED cell. The two fallbacks that are load bearing stay: +-- presets[1] for player/rival naming (oak_speech2.asm ChoosePlayerName never +-- accepts an empty name) and opts.default for the Name Rater cancel. +-- luajit tests/engine/naming_empty_confirm_bug833.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +-- NamingScreen reaches for Sound at the top of the module; seeding +-- package.loaded before it loads keeps the suite ROM-free and silent. +package.loaded["src.core.Sound"] = { play = function() end } + +local NamingScreen = require("src.ui.NamingScreen") + +-- The three things the screen touches: a stack it pops itself off, an input +-- queue that is exactly one fixed step of edges, and a non-nil `data` for the +-- click cue. +local function newGame() + local game = { data = {} } + game.stack = { + states = {}, + push = function(self, s) table.insert(self.states, s) end, + pop = function(self) return table.remove(self.states) end, + top = function(self) return self.states[#self.states] end, + } + game.input = { + queue = {}, + wasPressed = function(self, btn) return self.queue[btn] or false end, + isDown = function() return false end, + } + return game +end + +-- builds a pushed screen plus a `result` table the onDone writes into +local function newScreen(opts) + local game = newGame() + local result = { fired = false, name = nil } + opts = opts or {} + opts.onDone = function(n) + result.fired = true + result.name = n + end + local ns = NamingScreen.new(game, opts) + game.stack:push(ns) + return ns, game, result +end + +-- one fixed step with `btn` on its edge +local function press(ns, game, btn) + game.input.queue = { [btn] = true } + ns:update(1 / 60) + game.input.queue = {} +end + +-- the ED cell's coordinates on whatever grid the screen is showing +local function edCell(ns) + for r, row in ipairs(ns:grid()) do + for c, cell in ipairs(row) do + if cell == "ED" then return r, c end + end + end + return nil, nil +end + +-- ---------------------------------------------------------------- START, nothing typed +-- The nickname callers (BattleState caught-mon, Commands gift/starter) push +-- the screen with only title/maxLen/onDone: no presets, no default. +local ns, game, res = newScreen({ title = "NICK?", maxLen = 10 }) +press(ns, game, "start") +check(res.fired, "START confirms an untyped name") +eq(res.name, "", "START with nothing typed delivers the empty name") +check(res.name ~= "A", "an untyped confirm does not invent the letter A (#833)") +eq(#game.stack.states, 0, "confirm pops the naming screen") + +-- the caller-shaped guard both nickname sites use +local mon = {} +if res.name and #res.name > 0 then mon.nickname = res.name end +check(mon.nickname == nil, + "an empty name leaves the mon un-nicknamed, so evolution can rename it") + +-- ---------------------------------------------------------------- ED cell, nothing typed +ns, game, res = newScreen({ title = "NICK?", maxLen = 10 }) +local edRow, edCol = edCell(ns) +eq(edRow, 5, "ED sits on row 5 of the vanilla grid (data/text/alphabets.asm)") +eq(edCol, 9, "ED is the last cell of that row") +ns.row, ns.col = edRow, edCol +press(ns, game, "a") +check(res.fired, "A on the ED cell confirms") +eq(res.name, "", "ED with nothing typed delivers the empty name too") + +-- ---------------------------------------------------------------- typed names are untouched +ns, game, res = newScreen({ title = "NICK?", maxLen = 10 }) +ns.row, ns.col = 1, 1 -- "A" +press(ns, game, "a") +press(ns, game, "start") +eq(res.name, "A", "a genuinely typed A still comes back as A") + +-- ---------------------------------------------------------------- presets fallback (player / rival) +-- ChoosePlayerName / ChooseRivalName (engine/movie/oak_speech/oak_speech2.asm) +-- compare wStringBuffer to '@' and re-open rather than accept an empty name; +-- the port answers the same need with its presets fallback, which #833 must +-- not disturb. +ns, game, res = newScreen({ title = "YOUR NAME?", maxLen = 7, presets = { "RED", "ASH" } }) +press(ns, game, "start") +eq(res.name, "RED", "an empty confirm with presets still yields presets[1]") + +-- ---------------------------------------------------------------- default fallback (Name Rater) +-- DisplayNameRaterScreen jumps to .playerCancelled on '@' and keeps the +-- existing nickname; data/scripts/story4.lua passes it as opts.default. +ns, game, res = newScreen({ title = "RATTATA's name?", maxLen = 10, default = "SPLASH" }) +press(ns, game, "start") +eq(res.name, "SPLASH", "an empty confirm with a default keeps the old nickname") + +T.finish("naming_empty_confirm_bug833") diff --git a/tests/engine/nurse_bow_bug995.lua b/tests/engine/nurse_bow_bug995.lua new file mode 100644 index 00000000..d34c706e --- /dev/null +++ b/tests/engine/nurse_bow_bug995.lua @@ -0,0 +1,102 @@ +-- Nurse Joy bows between the two closing lines (#995). +-- pokered engine/events/pokecenter.asm. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.load() + +-- the ROM-extracted strings the fixture text table does not carry +Data.text._PokemonFightingFitText = "Thank you for\nwaiting.\fYour POKéMON are\nfighting fit!" +Data.text._PokemonCenterFarewellText = "We hope to see\nyou again!" + +local OW = require("src.world.OverworldController") + +local function setUpvalue(fn, name, val) + local i = 1 + while true do + local n = debug.getupvalue(fn, i) + if not n then return false end + if n == name then debug.setupvalue(fn, i, val); return true end + i = i + 1 + end +end + +local pushed = {} +local stackStub = { push = function(_, item) pushed[#pushed + 1] = item end } +local textBoxStub = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} +local fakeGame = { data = Data, stack = stackStub } +T.check(setUpvalue(OW.finishNurseHeal, "TextBox", textBoxStub), + "TextBox upvalue on finishNurseHeal") +T.check(setUpvalue(OW.finishNurseHeal, "Game", fakeGame), + "Game upvalue on finishNurseHeal") + +local FIT = Data.text._PokemonFightingFitText +local BYE = Data.text._PokemonCenterFarewellText + +local player = { cellX = 3, cellY = 5 } +local faced +local function newNurse() + faced = 0 + return { + facing = "down", + facePlayer = function(self) faced = faced + 1; self.facing = "down" end, + } +end + +local fakeSelf +local function reset() + pushed = {} + fakeSelf = setmetatable({ player = player }, { __index = OW }) +end + +-- === with the nurse on the counter: fit line, bow, farewell +reset() +local nurse = newNurse() +local finished = 0 +fakeSelf:finishNurseHeal(BYE, function() finished = finished + 1 end, nurse) +T.eq(#pushed, 1, "the fighting-fit line goes up on its own") +T.eq(pushed[1].text, FIT, "first box is exactly the fighting-fit text") +T.check(pushed[1].text:find(BYE, 1, true) == nil, + "the farewell is no longer merged into it with a page break (#995)") + +pushed[1].onDone() +T.eq(#pushed, 1, "the farewell waits for the bow") +T.eq(nurse.facing, "up", "image index $14: the nurse bows") +T.check(fakeSelf.emote ~= nil, "the bow is a world hold, not a text pause") +local hold = fakeSelf.emote or {} +T.eq(hold.npc, nurse, "the hold is anchored on the nurse") +T.eq(hold.frames, 20, "DelayFrames $14 is 20 frames") +T.eq(hold.bubble, false, "no emotion bubble is drawn over her") +T.check(not hold.skippable, "the bow cannot be skipped with A/B") +T.eq(finished, 0, "the pokecenter is still busy during the bow") + +-- OverworldState:update counts emote.frames down and then calls onDone +if hold.onDone then hold.onDone() end +T.eq(#pushed, 2, "the farewell follows the bow") +local farewell = pushed[2] or {} +T.eq(farewell.text, BYE, "second box is the farewell text") +T.eq(nurse.facing, "up", "she is still bowed while the farewell prints") + +if farewell.onDone then farewell.onDone() end +T.eq(nurse.facing, "down", "the trailing UpdateSprites faces her back") +T.eq(faced, 1, "she is turned back exactly once") +T.eq(finished, 1, "control returns to the player once, after the farewell") + +-- === no nurse sprite (the Yellow/rest-stop callers): no bow, same text +reset() +finished = 0 +fakeSelf:finishNurseHeal(BYE, function() finished = finished + 1 end) +T.eq(pushed[1].text, FIT, "npc-less caller still opens with the fit line") +pushed[1].onDone() +T.check(fakeSelf.emote == nil, "nothing to bow, so no world hold") +T.eq(#pushed, 2, "the farewell follows immediately") +farewell = pushed[2] or {} +T.eq(farewell.text, BYE, "npc-less caller still closes with the farewell") +if farewell.onDone then farewell.onDone() end +T.eq(finished, 1, "npc-less caller returns control once") + +T.finish("nurse_bow_bug995") diff --git a/tests/engine/nx_yellow_boot_test.lua b/tests/engine/nx_yellow_boot_test.lua index 764198ec..a0ac0bf5 100644 --- a/tests/engine/nx_yellow_boot_test.lua +++ b/tests/engine/nx_yellow_boot_test.lua @@ -37,7 +37,8 @@ end local Y_TITLE = { "pikachu.png", "pika_bubble.png", "eyes_half.png", "eyes_closed.png", - "player.png", "copyright.png", "yellow_version.png", + "player.png", "copyright.png", "gamefreak_inc.png", "nine.png", + "yellow_version.png", } for _, name in ipairs(Y_TITLE) do seed("yellow/assets/generated/title/" .. name) @@ -207,6 +208,11 @@ check(pre ~= nil, "the IntroMovie pre-roll was constructed") eq(pre and pre.copyright and pre.copyright.path, "yellow/assets/generated/title/copyright.png", "copyright card resolves to the yellow/ copy") +eq(pre and pre.nineImg and pre.nineImg.path, + "yellow/assets/generated/title/nine.png", + "copyright NineTile resolves to the yellow/ copy") +check(pre and pre.yellowCopy == true, + "IntroMovie selects the Yellow (c)1995-1999 tile sequence") eq(pre and pre.studioLogo and pre.studioLogo.path, "yellow/assets/generated/intro/studio_logo.png", "data-driven studio logo resolves to the yellow/ copy") @@ -266,6 +272,7 @@ GameVersion.set("blue") seed("blue/assets/generated/title/blue_version.png", "blue-version-bytes") seed("blue/assets/generated/title/player.png") seed("blue/assets/generated/title/copyright.png") +seed("blue/assets/generated/title/gamefreak_inc.png") local B_INTRO = { "gf_logo.png", "gf_text.png", "big_star.png", "falling_star.png", "falling_star_blink.png", "studio_logo.png", diff --git a/tests/engine/oaks_aide_requirement_bug1006.lua b/tests/engine/oaks_aide_requirement_bug1006.lua new file mode 100644 index 00000000..326f9a47 --- /dev/null +++ b/tests/engine/oaks_aide_requirement_bug1006.lua @@ -0,0 +1,149 @@ +-- Oak's aide quotes the REQUIREMENT, not your current count (#1006). +-- pokered engine/events/oaks_aide.asm .notEnoughOwnedMons. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.load() + +-- the ROM-extracted strings the fixture text table does not carry; labels +-- and wording match pokered data/text/text_1.asm +Data.text._OaksAideHiText = + "Hi! Remember me?\nI'm PROF.OAK's\vAIDE!\fIf you caught " .. + "{NUM:hOaksAideRequirement, 1, 3}\nkinds of POKéMON,\vI'm supposed to\v" .. + "give you an\v{RAM:wOaksAideRewardItemName}!\fSo, {PLAYER}! Have\n" .. + "you caught at\vleast {NUM:hOaksAideRequirement, 1, 3} kinds of\vPOKéMON?" +Data.text._OaksAideUhOhText = + "Let's see...\nUh-oh! You have\vcaught only " .. + "{NUM:hOaksAideNumMonsOwned, 1, 3}\vkinds of POKéMON!\fYou need " .. + "{NUM:hOaksAideRequirement, 1, 3} kinds\nif you want the\v" .. + "{RAM:wOaksAideRewardItemName}." +Data.text._OaksAideComeBackText = + "Oh. I see.\fWhen you get {NUM:hOaksAideRequirement, 1, 3}\nkinds, come " .. + "back\vfor {RAM:wOaksAideRewardItemName}." +Data.text._OaksAideHereYouGoText = + "Great! You have\ncaught {NUM:hOaksAideNumMonsOwned, 1, 3} kinds \v" .. + "of POKéMON!\vCongratulations!\fHere you go!" +Data.text._OaksAideGotItemText = + "{PLAYER} got the\n{RAM:wOaksAideRewardItemName}!" +-- the two rewards this suite drives (Route2Gate / Route11Gate2F pass them +Data.items.HM_FLASH = { id = "HM_FLASH", index = 196, name = "HM FLASH" } +Data.items.ITEMFINDER = { id = "ITEMFINDER", index = 6, name = "ITEMFINDER" } + +local SaveData = require("src.core.SaveData") + +local pushed = {} +local realTextBox = package.loaded["src.render.TextBox"] +-- story4's push/ask require TextBox lazily, so a package.loaded stub is +package.loaded["src.render.TextBox"] = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} + +local story4 = dofile("data/scripts/story4.lua") +local ROUTE_11 = story4.ROUTE_11_GATE_2F.talk.TEXT_ROUTE11GATE2F_OAKS_AIDE +local ROUTE_2 = story4.ROUTE_2_GATE.talk.TEXT_ROUTE2GATE_OAKS_AIDE +T.check(type(ROUTE_11) == "function" and type(ROUTE_2) == "function", + "both aides are wired to the shared oaksAide handler") + +local game = { + data = Data, + save = SaveData.newGame(), + stack = { push = function(_, box) pushed[#pushed + 1] = box end }, +} + +local function reset(ownedCount) + game.save = SaveData.newGame() + game.save.player.name = "RED" + local owned = {} + for i = 1, ownedCount do owned["SPECIES_" .. i] = true end + game.save.pokedex = { seen = {}, owned = owned } + pushed = {} +end +local function lastText() + return tostring(pushed[#pushed] and pushed[#pushed].text) +end +local function has(fragment) + return lastText():find(fragment, 1, true) ~= nil +end +local function held(id) + return game.save.inventory[id] or 0 +end +-- A press on the box that is up +local function dismiss() + local box = pushed[#pushed] + if box and box.onDone then box.onDone() end +end + +-- === the aide asks for his own threshold, whatever the player owns +reset(12) +local done = false +ROUTE_11(game, {}, {}, function() done = true end) +local offer = pushed[1] +T.check(offer.opts and offer.opts.choice ~= nil, + "the aide's opener is the YesNoChoice question") +T.check(has("least 30 kinds"), "opener asks for the aide's 30 kinds") +T.check(has("give you an\vITEMFINDER"), "opener names the reward item") +T.check(not has("{NUM"), "no placeholder survives into the opener") + +-- === YES with too few kinds: both decimals are filled, and differently +offer.opts.choice(true) +T.check(has("caught only 12"), "Uh-oh line reports the kinds actually owned") +T.check(has("You need 30 kinds"), "Uh-oh line then states the requirement") +T.check(not has("You need 12 kinds"), + "the requirement is not overwritten by the owned count (#1006)") +T.check(has("want the\vITEMFINDER"), "Uh-oh line still names the reward") +dismiss() +T.check(done, "the Uh-oh branch completes the talk") +T.eq(held("ITEMFINDER"), 0, "no reward below the threshold") +T.check(not game.save.flags.EVENT_GOT_ITEMFINDER, + "the aide can still be asked again") + +-- === the threshold tracks the aide, not a constant: Route 2 wants 10 +reset(3) +ROUTE_2(game, {}, {}, function() end) +pushed[1].opts.choice(true) +T.check(has("caught only 3"), "Route 2 Uh-oh reports 3 kinds owned") +T.check(has("You need 10 kinds"), "Route 2 states its own 10-kind threshold") +T.check(has("want the\vHM FLASH"), "Route 2 names the HM FLASH reward") + +-- === NO: ComeBackText quotes the requirement, nothing is given +reset(12) +done = false +ROUTE_11(game, {}, {}, function() done = true end) +pushed[1].opts.choice(false) +T.check(has("When you get 30"), "come-back line quotes the requirement") +T.check(has("back\vfor ITEMFINDER"), "come-back line names the reward") +dismiss() +T.check(done, "declining completes the talk") +T.eq(held("ITEMFINDER"), 0, "declining gives nothing") + +-- === YES at the threshold: HereYouGo carries the OWNED count, then the +reset(30) +done = false +ROUTE_11(game, {}, {}, function() done = true end) +pushed[1].opts.choice(true) +T.check(has("caught 30 kinds"), "congratulation line carries the owned count") +dismiss() +T.check(has("RED got the\nITEMFINDER!"), "the item line names player and item") +dismiss() +T.check(done, "the reward branch completes the talk") +T.eq(held("ITEMFINDER"), 1, "ITEMFINDER lands in the bag") +T.check(game.save.flags.EVENT_GOT_ITEMFINDER, "the aide's event flag is set") + +-- === repeat visit: the explanation text, no second ITEMFINDER +pushed = {} +done = false +ROUTE_11(game, {}, {}, function() done = true end) +T.eq(#pushed, 1, "a served player gets exactly one box") +T.check(pushed[1].opts == nil or pushed[1].opts.choice == nil, + "the repeat line is not a question") +T.eq(held("ITEMFINDER"), 1, "no second ITEMFINDER") + +if realTextBox ~= nil then + package.loaded["src.render.TextBox"] = realTextBox +else + package.loaded["src.render.TextBox"] = nil +end + +T.finish("oaks_aide_requirement_bug1006") diff --git a/tests/engine/oaks_lab_yellow_starter_bug1013.lua b/tests/engine/oaks_lab_yellow_starter_bug1013.lua new file mode 100644 index 00000000..33897aa4 --- /dev/null +++ b/tests/engine/oaks_lab_yellow_starter_bug1013.lua @@ -0,0 +1,113 @@ +-- Yellow's starter Pikachu gets the nickname prompt (#1013). +-- pokeyellow scripts/OaksLab.asm OaksLabPlayerReceivesPikachuScript. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.load() + +local pushed = {} +local realTextBox = package.loaded["src.render.TextBox"] +-- Commands requires TextBox at load time; stub it before the first require +package.loaded["src.render.TextBox"] = { + new = function(_, text, onDone, opts) + return { text = text, onDone = onDone, opts = opts } + end, +} +local Commands = require("src.script.Commands") +local SaveData = require("src.core.SaveData") + +-- === the lab scene: one PIKACHU gift row, and no skipNickname on it +local lab = dofile("data/scripts/oaks_lab_yellow.lua") +local ball = lab.talk.TEXT_OAKSLAB_EEVEE_POKE_BALL +T.check(type(ball) == "function", "the Eevee ball builds its rows per playthrough") + +local captured +local function buildScene(cellX, cellY) + captured = nil + local game = { data = Data, save = SaveData.newGame() } + game.save.flags.EVENT_OAK_ASKED_TO_CHOOSE_MON = true + local ow = { + player = { cellX = cellX, cellY = cellY }, + runner = { run = function(_, rows) captured = rows end }, + } + ball(game, ow, { def = {} }, function() end) + return captured or {} +end + +local function indexOf(rows, verb, arg) + for i, row in ipairs(rows) do + if row[1] == verb and (arg == nil or row[2] == arg) then return i end + end +end + +-- the shove branch (player on the table row, py == 4) and the plain +for _, spot in ipairs({ { 9, 4 }, { 5, 6 } }) do + local rows = buildScene(spot[1], spot[2]) + local where = ("from (%d,%d)"):format(spot[1], spot[2]) + local gives = 0 + for _, row in ipairs(rows) do + if row[1] == "give_pokemon" then + gives = gives + 1 + T.eq(row[2], "PIKACHU", "the gift species is PIKACHU " .. where) + T.eq(row[3], 5, "the gift is level 5 " .. where) + T.check(row[4] == nil, + "no skipNickname: AskName is left to run " .. where .. " (#1013)") + end + end + T.eq(gives, 1, "exactly one give_pokemon row " .. where) + + local give = indexOf(rows, "give_pokemon") + local received = indexOf(rows, "show_text", "_OaksLabReceivedText") + local got = indexOf(rows, "set_flag", "EVENT_GOT_STARTER") + T.check(received and give and received < give, + "the received line prints before the mon is added " .. where) + T.check(give and got and give < got, + "AddPartyMon runs before SetEvent EVENT_GOT_STARTER " .. where) +end + +-- === nothing in the Yellow lab names a Kanto starter (#1014): only a mod +local source = assert(io.open("data/scripts/oaks_lab_yellow.lua", "r")) +local text = source:read("*a") +source:close() +for _, species in ipairs({ "CHARMANDER", "SQUIRTLE", "BULBASAUR" }) do + T.check(not text:find(species, 1, true), + "the Yellow lab script never names " .. species) +end + +-- === give_pokemon offers AskName with a runner and no skipNickname +local function giveThrough(skipNickname) + pushed = {} + local game = { data = Data, save = SaveData.newGame(), + stack = { push = function(_, box) pushed[#pushed + 1] = box end } } + local runner = { + yield = function() return coroutine.yield() end, + resume = function(self, ...) coroutine.resume(self.co, ...) end, + } + local ctx = { game = game, save = game.save, runner = runner } + runner.co = coroutine.create(function() + Commands.give_pokemon(ctx, "FIXMON_A", 5, skipNickname) + end) + local ok, err = coroutine.resume(runner.co) + T.check(ok, "give_pokemon runs cleanly: " .. tostring(err)) + return game.save +end + +local save = giveThrough(nil) +T.eq(#pushed, 1, "a plain gift puts one box up") +T.check(tostring(pushed[1].text):find("nickname", 1, true) ~= nil, + "that box is AskName's question") +T.check(pushed[1].opts and pushed[1].opts.choice ~= nil, + "AskName is a YES/NO, not a plain box") +T.eq(#save.party, 1, "the gift joined the party") + +save = giveThrough(true) +T.eq(#pushed, 0, "skipNickname suppresses the prompt") +T.eq(#save.party, 1, "the gift still joined the party") + +if realTextBox ~= nil then + package.loaded["src.render.TextBox"] = realTextBox +else + package.loaded["src.render.TextBox"] = nil +end + +T.finish("oaks_lab_yellow_starter_bug1013") diff --git a/tests/engine/oaks_pc_flow.lua b/tests/engine/oaks_pc_flow.lua index ec5864d5..c786b9f4 100644 --- a/tests/engine/oaks_pc_flow.lua +++ b/tests/engine/oaks_pc_flow.lua @@ -118,8 +118,9 @@ for _, item in ipairs(menu.items) do if item.label == "PROF.OAK's PC" then oak = item end end T.check(oak ~= nil, "PROF.OAK's PC is offered once the Pokédex is had") -plays = {} -- drop the menu's Turn_On_PC; the session's jingle is what counts oak.onSelect() +-- drop the menu's Turn_On_PC and the row's Enter_PC +plays = {} T.eq(pushed[2].kind, "text", "selection opens the access text") T.check(tostring(pushed[2].text):find("Accessed", 1, true) ~= nil, "first session box is the access text") diff --git a/tests/engine/options_backup_rollforward_bug828.lua b/tests/engine/options_backup_rollforward_bug828.lua new file mode 100644 index 00000000..87168190 --- /dev/null +++ b/tests/engine/options_backup_rollforward_bug828.lua @@ -0,0 +1,109 @@ +-- #828, the revert half: after the launcher's OG -> WIDE toggle, a play +-- session rewrites options.lua with byte-identical content (play() re-stamps +-- an unchanged lastVersion, SaveData.save flushes the attached table, and +-- SaveSerializer's key-sorted encode makes equal tables equal bytes), so +-- saveOptions' conditional pre-write roll skips and options.lua.bak kept the +-- PRE-change file all session. Android and Steam Deck end sessions with a +-- hard teardown (HostShell.restart restartApp kill / AppImage execv) that can +-- eat the main file, and loadOptions then promoted that stale backup: the +-- reported "launcher-only persists, going in-game reverts". The fix rolls +-- the backup forward to the just-verified bytes after every landed write; +-- this suite pins that at-rest invariant. ROM-free (T2 engine tier), same +-- injected-fs shape as tests/engine/options_write_readback_bug828.lua, where +-- these checks should eventually fold in. +-- luajit tests/engine/options_backup_rollforward_bug828.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local SaveData = require("src.core.SaveData") + +local OPTIONS = "options.lua" +local BAK = OPTIONS .. ".bak" +local TMP = OPTIONS .. ".tmp" + +-- In-memory love.filesystem stub, the { getInfo, read, write, remove } shape +-- SaveData.persistFs accepts. `dropping` is mutable so one fs can serve a +-- healthy session and then a write that reports success without landing. +local function memfs() + local files = {} + local fs + fs = { + files = files, + dropping = false, + write = function(path, content) + if fs.dropping then return true end + files[path] = content + return true + end, + read = function(path) return files[path] end, + remove = function(path) files[path] = nil return true end, + getInfo = function(path) + if files[path] ~= nil then return { type = "file" } end + return nil + end, + } + return fs +end + +-- ---- at rest, the backup holds the newest verified bytes + +local fs = memfs() +SaveData.saveOptions({ battleLayout = "wide" }, fs) +check(fs.files[BAK] ~= nil, "the very first verified write already leaves a backup") +eq(fs.files[BAK], fs.files[OPTIONS], + "after a verified write the backup equals the main file (#828 roll-forward)") +check(fs.files[TMP] == nil, "the staged witness is still dropped after verification") + +-- ---- the reported session, write for write +-- Launcher toggles OG -> WIDE, then two rewrites whose bytes match the file +-- on disk: RomImporter:play re-stamping the same lastVersion (#835) and the +-- in-game SaveData.save flush of the attached, unchanged table. Both go +-- through loadOptions first, exactly as the shipping callers do, so the +-- encoder sees identical tables and the conditional pre-roll skips. +local live = memfs() +SaveData.saveOptions({ battleLayout = "og", lastVersion = "red" }, live) + +local toggled = SaveData.loadOptions(live) +toggled.battleLayout = "wide" +SaveData.saveOptions(toggled, live) +local wideBytes = live.files[OPTIONS] + +local stamped = SaveData.loadOptions(live) +stamped.lastVersion = "red" +SaveData.saveOptions(stamped, live) +eq(live.files[OPTIONS], wideBytes, + "the play() lastVersion re-stamp is a byte-identical rewrite (sorted encode)") +SaveData.saveOptions(SaveData.loadOptions(live), live) +eq(live.files[OPTIONS], wideBytes, "the in-game flush is byte-identical too") + +eq(live.files[BAK], wideBytes, + "identical rewrites still carry the backup forward past the skipped pre-roll") + +-- the hard teardown eats the main file; recovery must answer the toggle +live.files[OPTIONS] = nil +eq(SaveData.loadOptions(live).battleLayout, "wide", + "a lost main file recovers to WIDE, not the pre-toggle OG backup (#828)") +check(live.files[OPTIONS] ~= nil, "and the main file is healed from that copy") + +-- ---- a write that does not land must not poison the backup +-- The roll-forward has to sit AFTER the readback verification: if the bytes +-- never reached disk (the #828 external-storage failure mode) the backup +-- keeps the last state that verifiably did. +local flaky = memfs() +SaveData.saveOptions({ battleLayout = "wide" }, flaky) +local verified = flaky.files[BAK] +flaky.dropping = true +eq(SaveData.saveOptions({ battleLayout = "og" }, flaky), nil, + "the vanished write still reports failure") +flaky.dropping = false +eq(flaky.files[BAK], verified, + "a write that never landed leaves the backup at the last verified bytes") +flaky.files[OPTIONS] = nil +eq(SaveData.loadOptions(flaky).battleLayout, "wide", + "so recovery after the failed write still answers the verified state") + +T.finish("options_backup_rollforward_bug828") diff --git a/tests/engine/options_partial_write_bug932.lua b/tests/engine/options_partial_write_bug932.lua new file mode 100644 index 00000000..9f7802a9 --- /dev/null +++ b/tests/engine/options_partial_write_bug932.lua @@ -0,0 +1,100 @@ +-- #932 "Bugs reset settings": a caller that hands saveOptions a PARTIAL +-- table (only the keys it changed) used to drop every key it did not +-- mention -- launcher-only keys like lastVersion, and keys the launcher set +-- (battleBg, tilt) all fell back to defaults. saveOptions now reads the +-- on-disk file first and folds caller-absent values underneath, so a delta +-- write changes only what it names. +-- +-- This suite pins the three-way merge against injected filesystem stubs +-- (the same { getInfo, read, write, remove } shape the other engine suites +-- use). It is ROM-free (T2 engine tier). +-- luajit tests/engine/options_partial_write_bug932.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local SaveData = require("src.core.SaveData") + +local OPTIONS = "options.lua" + +local function memfs() + local files = {} + return { + files = files, + write = function(path, content) files[path] = content return true end, + read = function(path) return files[path] end, + remove = function(path) files[path] = nil return true end, + getInfo = function(path) + if files[path] ~= nil then return { type = "file" } end + return nil + end, + } +end + +-- Seed a save dir with the full snapshot a launcher would write: defaults, +-- plus the keys the issue cares about. lastVersion is launcher-only (not a +-- defaultOptions member) and must survive ANY write that does not name it. +local function seed(fs) + local seed = SaveData.defaultOptions() + seed.battleBg = "world" + seed.lastVersion = "blue" + seed.tilt = 1 + seed.mods = { foo = true } + seed.modOptions = { alpha = { keep = true, x = 1 } } + check(SaveData.saveOptions(seed, fs) ~= nil, "seeding lands") +end + +-- ---- launcher-only keys survive a delta write + +local fs = memfs() +seed(fs) + +-- loader-style partial write: only the mods bucket it manages. +SaveData.saveOptions({ mods = { foo = true } }, fs) +local opts = SaveData.loadOptions(fs) +eq(opts.battleBg, "world", "a partial write keeps battleBg the launcher set") +eq(opts.lastVersion, "blue", "a partial write keeps lastVersion (#932)") +eq(opts.tilt, 1, "a partial write keeps tilt the launcher set") + +-- ---- caller-present keys still win + +SaveData.saveOptions({ battleBg = "black" }, fs) +eq(SaveData.loadOptions(fs).battleBg, "black", + "a key the caller DOES provide wins over the on-disk value") +eq(SaveData.loadOptions(fs).lastVersion, "blue", + "...while the launcher-only key is still carried") + +-- ---- modOptions per-mod deep merge stays intact + +SaveData.saveOptions({ modOptions = { alpha = { x = 5 } } }, fs) +local after = SaveData.loadOptions(fs) +eq(after.modOptions.alpha.x, 5, "newest alpha value wins the per-mod merge") +eq(after.modOptions.alpha.keep, true, "alpha's untouched keys survive") +eq(after.modOptions.beta, nil, "no beta was invented by the merge") + +-- ---- full-table writes stay authoritative (bindings/activeProfile drops) + +-- The fold must NOT resurrect a key a full snapshot deliberately deletes: +-- the RESET REBINDS path nils bindings and the mod manager nils +-- activeProfile, always on full loadOptions tables. +fs = memfs() +seed(fs) +SaveData.saveOptions({ bindings = { a = 1 } }, fs) +eq(SaveData.loadOptions(fs).bindings.a, 1, "bindings is not a default member") + +local full = SaveData.loadOptions(fs) +full.bindings = nil +full.activeProfile = nil +SaveData.saveOptions(full, fs) +local reopened = SaveData.loadOptions(fs) +eq(reopened.bindings, nil, + "a full-snapshot deletion of bindings is NOT resurrected by the fold") +eq(reopened.activeProfile, nil, + "a full-snapshot deletion of activeProfile is NOT resurrected") +eq(reopened.battleBg, "world", + "the rest of the full snapshot is still what it was") + +T.finish("options_partial_write_bug932") diff --git a/tests/engine/options_write_readback_bug828.lua b/tests/engine/options_write_readback_bug828.lua new file mode 100644 index 00000000..b0ccbabc --- /dev/null +++ b/tests/engine/options_write_readback_bug828.lua @@ -0,0 +1,229 @@ +-- #828: launcher settings "reset" on Android and Steam Deck, with nothing in +-- the log. Every options write is a WHOLE-FILE rewrite out of the caller's +-- table (src/core/SaveData.lua saveOptions), so a filesystem that reports a +-- successful write without the bytes surviving -- an external-storage volume +-- that went away mid-session (conf.lua sets t.externalstorage on Android), a +-- read-only or full save dir -- is indistinguishable from "the launcher never +-- saved at all". saveOptions therefore reads the file back and fails loudly. +-- +-- This suite pins that contract against injected filesystem stubs, the same +-- { getInfo, read, write, remove } shape tests/engine/save_slots.lua and +-- tests/engine/save_file_io_tests.lua use. It is ROM-free (T2 engine tier). +-- +-- What it does NOT do: prove #828 is fixed. The launcher -> options.lua -> +-- bootGame chain already round-trips correctly on desktop, so the readback is +-- instrumentation for the two platforms that report the loss, and the real +-- verification is a platform run (see the issue). What is testable here is +-- that a silent no-op write is now reported instead of swallowed. +-- luajit tests/engine/options_write_readback_bug828.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local Logger = require("src.core.Logger") +local SaveData = require("src.core.SaveData") + +local OPTIONS = "options.lua" + +-- An in-memory love.filesystem stub. `mode` decides what write() does with +-- the bytes AFTER reporting success, which is the whole point of the suite: +-- "honest" -- stores them (a working save dir) +-- "drop" -- reports true, stores nothing (the volume vanished) +-- "truncate" -- reports true, stores a short prefix (a full save dir) +-- "fail" -- reports false plus an error string (the pre-existing path) +local function memfs(mode) + local files = {} + return { + files = files, + write = function(path, content) + if mode == "fail" then return false, "no space left on device" end + if mode == "drop" then return true end + if mode == "truncate" then + files[path] = tostring(content):sub(1, 16) + return true + end + files[path] = content + return true + end, + read = function(path) return files[path] end, + remove = function(path) files[path] = nil return true end, + getInfo = function(path) + if files[path] ~= nil then return { type = "file" } end + return nil + end, + } +end + +-- SaveData.persistFs hands an injected fs straight back only when it differs +-- from love.filesystem, so the suite never touches the real save directory. +local function logged(pattern) + for i = #Logger.history, 1, -1 do + if Logger.history[i]:find(pattern, 1, true) then return Logger.history[i] end + end + return nil +end + +-- ---- the write that lands: unchanged success contract + +local fs = memfs("honest") +local saved = SaveData.saveOptions({ battleLayout = "wide" }, fs) +check(saved ~= nil, "a write that lands returns the merged options table") +eq(saved and saved.battleLayout, "wide", "the caller's key survives the merge") +eq(saved and saved.textSpeed ~= nil, true, "defaults are filled in around it") +check(fs.files[OPTIONS] ~= nil, "options.lua is written to the injected fs") + +local loaded = SaveData.loadOptions(fs) +eq(loaded and loaded.battleLayout, "wide", + "loadOptions reads back what saveOptions wrote (the launcher -> game hop)") + +-- ---- the write that silently does not land: the #828 failure mode + +local dropMark = #Logger.history +local dropped = SaveData.saveOptions({ battleLayout = "wide" }, memfs("drop")) +eq(dropped, nil, "a write that reports success but stores nothing returns nil") +check(logged("options save did not land"), + "the vanished write is logged, so the next Android report can carry it") +check(#Logger.history > dropMark, "a log line was actually emitted") + +-- ---- a partial write is just as lost, and just as loud + +local truncated = SaveData.saveOptions({ battleLayout = "wide" }, memfs("truncate")) +eq(truncated, nil, "a truncated write is treated as a failed write") +check(logged("options save did not land"), "the truncated write is logged too") + +-- ---- the pre-existing honest failure still behaves exactly as before + +local failMark = #Logger.history +local failed = SaveData.saveOptions({ battleLayout = "wide" }, memfs("fail")) +eq(failed, nil, "a write that returns false still returns nil") +check(logged("options save failed"), + "the false-return path keeps its own distinct log line") +check(#Logger.history > failMark, "the false-return path still logs") + +-- A dropped write must not be reported through the false-return message: +-- the two are different diagnoses and the platform reports need to tell +-- them apart. +local last = Logger.history[#Logger.history] +check(last and last:find("options save failed", 1, true) ~= nil, + "the last failure logged is the false-return one, not the readback one") + +-- ---- an interrupted write no longer resets every setting +-- The launcher wrote WIDE and a later write dies partway through (the +-- process replaced by HostShell.restart on the way back to the launcher, an +-- external-storage flush that never happened), leaving a corrupt +-- options.lua. loadOptions must promote the staged/backup copy instead of +-- answering defaults, which is what "closing the game reset all my +-- settings" looked like. +local live = memfs("honest") +SaveData.saveOptions({ battleLayout = "wide" }, live) +SaveData.saveOptions({ battleLayout = "wide", textSpeed = 1 }, live) +check(live.files[OPTIONS .. ".bak"] ~= nil, + "the previous good options.lua is rolled aside before the rewrite") +check(live.files[OPTIONS .. ".tmp"] == nil, + "the staged witness is dropped once the main write is verified") +live.files[OPTIONS] = "return { battleLayout = " -- died mid-rewrite +local healed = SaveData.loadOptions(live) +eq(healed and healed.battleLayout, "wide", + "a corrupt options.lua is recovered from the rolled-aside copy") +check(live.files[OPTIONS] ~= "return { battleLayout = ", + "the main options file is healed from the copy that parsed") + +-- ---- a lost main file must recover to the NEWEST verified write +-- The platforms that lose options.lua do it on the hard teardown out of a +-- game session (HostShell.restart's restartApp kill on Android, execv on a +-- SteamOS AppImage), after rewrites whose bytes matched the file already on +-- disk: play()'s lastVersion stamp and the in-game save flush re-encode the +-- same table, and the key-sorted encoder makes those byte-identical, so the +-- conditional pre-write roll skips them. The backup is therefore rolled +-- forward after every verified write; otherwise recovery handed back the +-- file from BEFORE the launcher's change, which is exactly the reported +-- "set BATTLE LAYOUT to WIDE, go in game, close, and it is OG again" (#828). +local lost = memfs("honest") +SaveData.saveOptions({ battleLayout = "og", lastVersion = "red" }, lost) +local editedOpts = SaveData.loadOptions(lost) +editedOpts.battleLayout = "wide" +SaveData.saveOptions(editedOpts, lost) -- the launcher's toggle +local replay = SaveData.loadOptions(lost) +replay.lastVersion = "red" -- play() re-stamps the same value +SaveData.saveOptions(replay, lost) -- byte-identical rewrite +SaveData.saveOptions(SaveData.loadOptions(lost), lost) -- in-game save flush, identical too +lost.files[OPTIONS] = nil -- the platform ate the main file +local promoted = SaveData.loadOptions(lost) +eq(promoted.battleLayout, "wide", + "a lost main file recovers to the newest verified write, not the " + .. "pre-change backup (#828)") + +local gone = memfs("honest") +SaveData.saveOptions({ battleLayout = "wide" }, gone) +gone.files[OPTIONS] = nil +gone.files[OPTIONS .. ".bak"] = nil +gone.files[OPTIONS .. ".tmp"] = nil +eq(SaveData.loadOptions(gone).battleLayout, + SaveData.defaultOptions().battleLayout, + "with no copy left the defaults are still the answer") + +-- ---- the reported sequence end to end: launcher setting -> play -> quit +-- #828 as the reporter walks it (issue steps 2-7, and the "so its partly +-- fixed" comment): change BATTLE LAYOUT from OG to WIDE in the launcher, go +-- in game, close, reopen the launcher. Every options write is a whole-file +-- rewrite out of the caller's table (saveOptions above), so the only thing +-- keeping the launcher's key alive across a game-side write is WHEN the game +-- took its copy: SaveData.load re-attaches a fresh loadOptions() to the save +-- it just read (src/core/SaveData.lua:1108, and SaveData.newGame does the +-- same at :1458), which is after the launcher's last write because +-- RomImporter:play hands off only once the settings modal has saved +-- (src/import/LauncherSettings.lua open/save, src/import/RomImporter.lua +-- play). This pins that ordering: it is the invariant, not the merge, that +-- makes the launcher's change survive. +local hop = memfs("honest") +SaveData.saveOptions({ battleLayout = "og" }, hop) + +-- launcher: the gear menu's edited table, persisted on close +local launcherOpts = SaveData.loadOptions(hop) +launcherOpts.battleLayout = "wide" +launcherOpts.lastVersion = "blue" -- #835 rides the same file +SaveData.saveOptions(launcherOpts, hop) + +-- boot: the game's copy is taken here, never earlier +local gameOpts = SaveData.loadOptions(hop) +eq(gameOpts.battleLayout, "wide", + "the game boots on the value the launcher just wrote") + +-- play: an in-game OPTION menu change writes the whole table back +gameOpts.textSpeed = 1 +check(SaveData.saveOptions(gameOpts, hop) ~= nil, "the game-side write lands") + +local reopened = SaveData.loadOptions(hop) +eq(reopened.battleLayout, "wide", + "the launcher's BATTLE LAYOUT survives a game-side options write (#828)") +eq(reopened.textSpeed, 1, "and the in-game change is persisted alongside it") +eq(reopened.lastVersion, "blue", + "launcher-only keys the game never reads are carried through its write") + +-- The corollary, and the reason the copy has to come from loadOptions: a +-- caller that writes a partial literal instead of a loaded table would drop +-- every key it does not mention. Since #932 that drop is closed by a +-- three-way merge -- saveOptions folds on-disk values the caller's table +-- does not carry (lastVersion here), defaults-filling only what neither side +-- has -- so even a delta write keeps the launcher's key alive. Nothing on +-- the boot path writes partials today; the assertion is the guard rail if +-- someone shortcuts it. +SaveData.saveOptions({ battleLayout = "og" }, hop) +eq(SaveData.loadOptions(hop).lastVersion, "blue", + "a partial write no longer drops launcher-only keys (#932)") + +-- Known gap, deliberately not asserted: a FULL copy taken BEFORE the +-- launcher's write and flushed after it still wins -- a table holding every +-- defaultOptions key is authoritative, so its og is never folded against a +-- newer wide on disk (#932 closes the PARTIAL-write drop, not this). +-- Measured, not guessed. No shipping path holds an options table across a +-- launcher write -- HostShell.restart replaces the process on the way back +-- to the launcher (#785, #575) and LauncherSettings.open notes its own +-- cached table is only true while its modal covers the launcher -- so +-- closing that gap needs a real three-way baseline (vs caller vs disk), not +-- a straight "disk wins", which would throw away real in-game changes. + +T.finish("options_write_readback_bug828") diff --git a/tests/engine/party_fieldmove_order_bug792.lua b/tests/engine/party_fieldmove_order_bug792.lua new file mode 100644 index 00000000..6e93110f --- /dev/null +++ b/tests/engine/party_fieldmove_order_bug792.lua @@ -0,0 +1,100 @@ +-- Party-submenu field-move placement (#792, the duplicate of #768's second +-- half). In the original, DisplayFieldMoveMonMenu (engine/menus/ +-- text_box.asm) grows the box upward one row per field move and prints the +-- move names ABOVE PokemonMenuEntries ("STATS/SWITCH/CANCEL"), while +-- GetMonFieldMoves (engine/menus/text_box.asm, called from +-- start_sub_menus.asm) walks wPartyMon1Moves in slot order -- so a mon +-- with STRENGTH in slot 3 and SURF in slot 4 shows STRENGTH then SURF on +-- top, with STATS/SWITCH closing the list. The port used to build the +-- submenu as STATS/SWITCH first and tack the field moves on the bottom. +-- ROM-free: drives the real PartyMenu over stub game state. +-- luajit tests/engine/party_fieldmove_order_bug792.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq, same = T.check, T.eq, T.same +love = love or require("tests.love_stub") + +local PartyMenu = require("src.ui.PartyMenu") + +-- minimal stack/input doubles matching the StateStack and Input surfaces, +-- plus an overworld stub: PALLET_TOWN's OVERWORLD tileset passes +-- CheckIfInOutsideMap, and the badges cover the list-time HM gates +local function newGame(moves, inventory) + local game = { + data = { pokemon = { LAPRAS = { name = "LAPRAS" }, + PIKACHU = { name = "PIKACHU" } } }, + save = { + party = { { species = "LAPRAS", hp = 50, stats = { hp = 50 }, + level = 30, moves = moves } }, + inventory = inventory or {}, + options = {}, flags = {}, + }, + overworld = { map = { def = { tileset = "OVERWORLD" }, + id = "PALLET_TOWN" }, + dark = false }, + } + game.stack = { + states = {}, + push = function(self, s) table.insert(self.states, s) end, + pop = function(self) return table.remove(self.states) end, + top = function(self) return self.states[#self.states] end, + } + game.input = { + queue = {}, + wasPressed = function(self, btn) return self.queue[btn] or false end, + isDown = function() return false end, + } + return game +end + +-- one fixed step with A on its edge: opens the per-mon submenu +local function openSubmenu(pm) + pm.game.input.queue = { a = true } + pm:update(1 / 60) + pm.game.input.queue = {} +end + +local function actions(items) + local out = {} + for i, item in ipairs(items or {}) do out[i] = item.action end + return out +end + +-- The report's own example: Lapras with STRENGTH in slot 3 and SURF in +-- slot 4. HM-number order would put SURF (HM03) ahead of STRENGTH (HM04); +-- only the mon's move-list order puts STRENGTH first, and both sit above +-- STATS/SWITCH. +local game = newGame( + { { id = "WATER_GUN", pp = 25 }, { id = "BODY_SLAM", pp = 15 }, + { id = "STRENGTH", pp = 15 }, { id = "SURF", pp = 15 } }, + { RAINBOWBADGE = true, SOULBADGE = true }) +local pm = PartyMenu.new(game, {}) +game.stack:push(pm) +openSubmenu(pm) +check(pm.submenu, "A on a party mon opens the submenu") +same(actions(pm.subItems), { "strength", "surf", "stats", "switch" }, + "field moves sit above STATS/SWITCH in move-list order (#792)") +eq(pm.subItems[1].label, "STRENGTH", "slot 3's STRENGTH leads the submenu") +eq(pm.subItems[2].label, "SURF", "slot 4's SURF follows it") + +-- a mon whose moves are not field moves gets the plain STATS/SWITCH list +local plain = newGame({ { id = "TACKLE", pp = 35 } }) +local pm2 = PartyMenu.new(plain, {}) +plain.stack:push(pm2) +openSubmenu(pm2) +same(actions(pm2.subItems), { "stats", "switch" }, + "no field moves: the submenu is just STATS/SWITCH") + +-- the badge gates still filter the list: the same Lapras without the +-- badges keeps its moves but shows none of them +local noBadges = newGame( + { { id = "STRENGTH", pp = 15 }, { id = "SURF", pp = 15 } }) +local pm3 = PartyMenu.new(noBadges, {}) +noBadges.stack:push(pm3) +openSubmenu(pm3) +same(actions(pm3.subItems), { "stats", "switch" }, + "ungated badges keep the HM moves out of the submenu") + +T.finish("party_fieldmove_order_bug792") diff --git a/tests/engine/pc_list_kinds.lua b/tests/engine/pc_list_kinds.lua new file mode 100644 index 00000000..ad0238c3 --- /dev/null +++ b/tests/engine/pc_list_kinds.lua @@ -0,0 +1,48 @@ +-- Stable ListMenu identities for screen.render_visible and companion UIs. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.load() + +local SaveData = require("src.core.SaveData") +local BoxMenu = require("src.ui.BoxMenu") +local ListMenu = require("src.ui.ListMenu") +local PlayerPC = require("src.ui.PlayerPC") +local Boxes = require("src.pokemon.Boxes") + +local pushed +local game = { + data = Data, + save = SaveData.newGame(), + stack = { push = function(_, state) pushed = state end }, +} + +local species = T.fixtures.ids.species[1] +Boxes.ensure(game.save)[1][1] = { species = species, level = 5 } +game.save.party[1] = { species = species, level = 5 } +game.save.party[2] = { species = species, level = 6 } +game.save.pcItems = { FIX_POTION = 2 } +game.save.inventory.FIX_POTION = 2 + +local generic = ListMenu.new(game, "VISIBLE TITLE", {}, {}) +T.eq(generic.kind, "VISIBLE TITLE", "generic lists fall back to their title") +local explicit = ListMenu.new(game, "Localized title", {}, { kind = "stable_id" }) +T.eq(explicit.kind, "stable_id", "explicit list kind is preserved") + +local box = BoxMenu.new(game) +for i, kind in ipairs({ "pc_box_withdraw", "pc_box_deposit", + "pc_box_release", "pc_box_change" }) do + pushed = nil + box.items[i].onSelect() + T.eq(pushed and pushed.kind, kind, kind .. " is stable") +end + +local items = PlayerPC.new(game) +for i, kind in ipairs({ "pc_item_withdraw", "pc_item_deposit", + "pc_item_toss" }) do + pushed = nil + items.items[i].onSelect() + T.eq(pushed and pushed.kind, kind, kind .. " is stable") +end + +T.finish("pc_list_kinds") diff --git a/tests/engine/platform_nx_test.lua b/tests/engine/platform_nx_test.lua index af7c88aa..29973611 100644 --- a/tests/engine/platform_nx_test.lua +++ b/tests/engine/platform_nx_test.lua @@ -37,6 +37,19 @@ withOS("NX", nil, function(Platform) eq(Platform.romImportMode(), "save-directory", "romImportMode helper") end) +-- Xbox UWP: native picker, console constraints, no in-app updater +withOS("UWP", function() end, function(Platform) + local caps = Platform.detect() + eq(caps.os, "UWP", "UWP detect os") + eq(caps.uwp, true, "UWP flag") + eq(caps.console, true, "UWP console") + eq(caps.hasNativePicker, true, "UWP has pickFile") + eq(caps.romImportMode, "native-picker", "UWP romImportMode") + eq(caps.canSpawnProcess, false, "UWP cannot spawn processes") + eq(caps.networkValidated, false, "UWP network not validated") + eq(Platform.isUWP(), true, "isUWP convenience") +end) + -- Android: mobile native picker path, not NX semantics withOS("Android", function() end, function(Platform) local caps = Platform.detect() diff --git a/tests/engine/playthrough_identity.lua b/tests/engine/playthrough_identity.lua new file mode 100644 index 00000000..cb584d51 --- /dev/null +++ b/tests/engine/playthrough_identity.lua @@ -0,0 +1,154 @@ +-- Opaque playthrough identity: New Game uniqueness, save/load persistence, +-- stable legacy backfill, and version/slot isolation. No real save directory. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +love = love or require("tests.love_stub") + +local SaveData = require("src.core.SaveData") +local SaveSerializer = require("src.core.SaveSerializer") +local GameVersion = require("src.core.GameVersion") + +local realFS = love.filesystem + +local function memfs(files) + return { + write = function(path, content) files[path] = content return true end, + read = function(path) return files[path] end, + remove = function(path) files[path] = nil return true end, + createDirectory = function() return true end, + getInfo = function(path) + if files[path] then return { type = "file" } end + local prefix = path .. "/" + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then return { type = "directory" } end + end + return nil + end, + getDirectoryItems = function(path) + local prefix, seen, out = path .. "/", {}, {} + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then + local child = key:sub(#prefix + 1):match("^[^/]+") + if child and not seen[child] then + seen[child] = true + out[#out + 1] = child + end + end + end + table.sort(out) + return out + end, + } +end + +local function fresh() + local files = {} + love.filesystem = memfs(files) + SaveData.resetSlotState() + GameVersion.set("red") + return files +end + +local function legacy(version, name) + return { + version = version, + meta = { format = 4, mods = {} }, + player = { name = name, map = "PALLET_TOWN", x = 5, y = 6 }, + flags = {}, inventory = {}, pcItems = {}, party = {}, box = {}, boxes = {}, + money = 3000, defeatedTrainers = {}, pokedex = { seen = {}, owned = {} }, + } +end + +-- No-mod parity: creating/saving a vanilla playthrough allocates no tool scope. +do + fresh() + local first = SaveData.newGame({ version = "red" }) + local second = SaveData.newGame({ version = "red" }) + T.eq(first.meta.playthroughId, nil, + "New Game allocates no playthrough id before a public tool requests it") + T.check(SaveData.save(first), "unused identity fixture saves") + local untouched = SaveData.load("red") + T.eq(untouched.meta.playthroughId, nil, + "normal save/load stays identity-free when no tool uses the capability") + + local firstId = SaveData.ensurePlaythroughId(first) + local secondId = SaveData.ensurePlaythroughId(second) + T.check(type(firstId) == "string" and firstId ~= "", + "the first tool request allocates an opaque playthrough id") + T.neq(secondId, firstId, + "separate New Games receive separate requested playthrough ids") +end + +-- Dropping the id from buildMeta or save encoding must fail the roundtrip. +do + fresh() + local save = SaveData.newGame({ version = "red" }) + local expected = SaveData.ensurePlaythroughId(save) + T.check(SaveData.save(save), "identity fixture saves") + local loaded = SaveData.load("red") + T.eq(loaded and loaded.meta.playthroughId, expected, + "normal save/load preserves the playthrough id") +end + +-- Legacy identity is persisted independently: the legacy progress bytes remain +-- unchanged, yet two loads resolve the same id before a normal SAVE occurs. +do + local files = fresh() + local raw = legacy("red", "LEGACY") + files["save.lua"] = SaveSerializer.encode(raw) + + local first = SaveData.load("red") + T.eq(first and first.meta.playthroughId, nil, + "loading a legacy save alone does not allocate tool identity") + local id = SaveData.ensurePlaythroughId(first) + T.check(type(id) == "string" and id ~= "", + "a legacy save receives a playthrough id") + local mappedOptions, mappedErr = SaveSerializer.decode(files["options.lua"] or "") + T.check(mappedOptions ~= nil, + "legacy identity mapping remains decodable: " .. tostring(mappedErr)) + + local slotBytes = files["saves/red/slot1.lua"] + local onDisk = slotBytes and SaveSerializer.decode(slotBytes) + T.eq(onDisk and onDisk.meta.playthroughId, nil, + "legacy backfill does not rewrite normal progress") + + SaveData.resetSlotState() + local second = SaveData.load("red") + T.eq(SaveData.ensurePlaythroughId(second), id, + "legacy backfill is stable across reload before normal SAVE") +end + +-- Reusing names and coordinates cannot merge identities across slots or games. +do + fresh() + local redA = SaveData.createSlot("red") + local redB = SaveData.createSlot("red") + SaveData.setActiveSlot("red", redA) + T.check(SaveData.writeSlot("red", redA, legacy("red", "SAME")), + "seed red slot A") + local idA = SaveData.ensurePlaythroughId(SaveData.load("red")) + + SaveData.setActiveSlot("red", redB) + T.check(SaveData.writeSlot("red", redB, legacy("red", "SAME")), + "seed red slot B") + local idB = SaveData.ensurePlaythroughId(SaveData.load("red")) + + GameVersion.set("blue") + local blue = SaveData.createSlot("blue") + SaveData.setActiveSlot("blue", blue) + T.check(SaveData.writeSlot("blue", blue, legacy("blue", "SAME")), + "seed blue slot") + local idBlue = SaveData.ensurePlaythroughId(SaveData.load("blue")) + + T.neq(idA, idB, "two active slots do not share legacy identity") + T.neq(idA, idBlue, "Red and Blue do not share legacy identity") + T.neq(idB, idBlue, "every version/slot scope is isolated") +end + +love.filesystem = realFS +SaveData.resetSlotState() +GameVersion.set("red") + +T.finish("playthrough_identity") diff --git a/tests/engine/rare_candy_bag_open_bug796.lua b/tests/engine/rare_candy_bag_open_bug796.lua new file mode 100644 index 00000000..6aee10cf --- /dev/null +++ b/tests/engine/rare_candy_bag_open_bug796.lua @@ -0,0 +1,198 @@ +-- A RARE CANDY used from the field bag must leave the bag open (#796). +-- +-- engine/menus/start_sub_menus.asm, StartMenu_Item / .useOrTossItem sorts the +-- chosen item with IsInArray against UsableItems_CloseMenu first and +-- UsableItems_PartyMenu second. RARE_CANDY is in the party-menu array +-- (data/items/use_party.asm), so it reaches .useItem_partyMenu, which after +-- `call UseItem` -- when wActionResultOrTookBattleTurn is not $02 -- +-- restores the screen and `jp StartMenu_Item`, i.e. re-enters the item list +-- instead of CloseStartMenu. StartMenu_Item reloads wBagSavedMenuItem into +-- wCurrentMenuItem before DisplayListMenuID, so the cursor comes back on the +-- row you just used: that is what lets a stack of candies be mashed through. +-- engine/items/item_effects.asm ItemUseVitamin .useRareCandy ends with +-- RedrawPartyMenu / PrintStatsBox / WaitForTextScrollButtonPress / +-- LearnMoveFromLevelUp / TryEvolvingMon and `jp RemoveUsedItem` -- it never +-- whites out and never closes the start menu. Only .useItem_closeMenu items +-- (UsableItems_CloseMenu: bike, escape rope, rods) jump to CloseStartMenu. +-- +-- The port popped the bag ListMenu at the head of the leveledTo branch, which +-- also skipped the "xN" refresh below it, so the level text played over the +-- overworld and the player was dumped out of the menu per candy. +-- luajit tests/engine/rare_candy_bag_open_bug796.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +-- Lazily-required inside the use branches, so seeding package.loaded before +-- the UI modules load is enough to keep the suite silent and love-free. +package.loaded["src.core.Sound"] = { + play = function() end, + playCry = function() end, +} +-- Real TextBoxes want a Font atlas. This flow only cares that a message +-- opened, what it says, and what its onDone does. +package.loaded["src.render.TextBox"] = { + new = function(_, text, done) return { textBox = true, text = text, done = done } end, +} +-- BagMenu and PartyMenu bind TextBox at require time, so they load against +-- the stub; Screens caches its factory per id and must be told to forget. +package.loaded["src.ui.BagMenu"] = nil +package.loaded["src.ui.PartyMenu"] = nil +local BagMenu = require("src.ui.BagMenu") +local PartyMenu = require("src.ui.PartyMenu") +require("src.ui.Screens").invalidate() + +local Fixtures = require("tests.modkit.fixtures") +local Bag = require("src.inventory.Bag") +local Pokemon = require("src.pokemon.Pokemon") + +local Data = Fixtures.fresh() +-- The fixture item table has no candy of its own; ItemEffects keys the +-- level-up branch on the id, and BagMenu only reads name/keyItem off the def. +Data.items.RARE_CANDY = { + id = "RARE_CANDY", index = 90, name = "RARE CANDY", price = 4800, + tossable = true, +} + +-- The mon: FIXMON_C has an empty `evolutions` and a learnset that stops at +-- level 1, so the 5 -> 6 candy prints its line and nothing else follows it. +-- That keeps the assertions on the bag rather than on the stat box, which +-- would need real graphics. +local function freshGame(candies) + local mon = Pokemon.new(Data, "FIXMON_C", 5) + local game = { + data = Data, + save = { + party = { mon }, + player = { name = "RED", id = 1 }, + inventory = {}, + options = {}, + flags = {}, + money = 0, + }, + } + game.stack = { + states = {}, + push = function(self, s) table.insert(self.states, s) end, + pop = function(self) return table.remove(self.states) end, + top = function(self) return self.states[#self.states] end, + } + -- one button edge per update, the way Input reports a fixed step + game.input = { pressed = nil } + function game.input:wasPressed(b) return self.pressed == b end + -- FIX POTION first so the candy is never row 1: an index that silently + -- reset to the top would otherwise pass the cursor assertion by accident + Bag.add(game.save, "FIX_POTION", 1) + Bag.add(game.save, "RARE_CANDY", candies) + return game, mon +end + +local function isPicker(s) return getmetatable(s) == PartyMenu end +local function isBox(s) return type(s) == "table" and s.textBox == true end + +local function inStack(stack, pred) + for _, s in ipairs(stack.states) do + if pred(s) then return true end + end + return false +end + +local function rowFor(list, id) + for i, r in ipairs(list.items) do + if r.value == id then return i end + end + return nil +end + +-- Open the bag, put the cursor on `id`, choose it, take USE off the +-- USE/TOSS box, then press A on the party picker. Returns the bag list. +local function useFromBag(game, battle, id) + local list = BagMenu.new(game, { battle = battle }) + game.stack:push(list) + local row = rowFor(list, id) + if not row then return nil, "no " .. id .. " row in the bag" end + list.index = row + list.onChoose(list.items[row], list) + local sub = game.stack:top() + if not battle and sub and sub.items and sub.items[1] + and sub.items[1].onSelect then + game.stack:pop() -- the USE/TOSS Menu pops itself on select + sub.items[1].onSelect() + end + local picker = game.stack:top() + if not isPicker(picker) then return nil, "party picker never opened" end + game.input.pressed = "a" + picker:update(1 / 60) + game.input.pressed = nil + return list +end + +-- The bug: three candies in the bag, use one in the field. +do + local game, mon = freshGame(3) + local list, why = useFromBag(game, nil, "RARE_CANDY") + if check(list ~= nil, "the bag opened and reached the picker: " .. tostring(why)) then + eq(mon.level, 6, "the candy leveled the mon 5 -> 6") + check(not inStack(game.stack, isPicker), + "the pickOnly picker popped itself before onSwitch") + check(inStack(game.stack, function(s) return s == list end), + "the bag list is STILL on the stack (.useItem_partyMenu re-enters " + .. "StartMenu_Item, it does not CloseStartMenu) (#796)") + + local row = rowFor(list, "RARE_CANDY") + if check(row ~= nil, "the RARE CANDY row survived the use") then + eq(list.items[row].right, "x2", "and its count followed the inventory") + eq(list.index, row, "with the cursor left on it (wBagSavedMenuItem), " + .. "so the next candy is one A press away") + end + eq(game.save.inventory.RARE_CANDY, 2, "one candy was consumed") + + local box = game.stack:top() + if check(isBox(box), "the grew-to-level line prints over the open bag") then + check(box.text:find("level 6", 1, true) ~= nil, + "and it names the new level: " .. tostring(box.text)) + end + end +end + +-- The last candy: the row goes away (RemoveUsedItem empties the slot) and the +-- cursor clamps to a real row -- but the list itself still must not close. +do + local game = freshGame(1) + local list = useFromBag(game, nil, "RARE_CANDY") + if check(list ~= nil, "the bag reached the picker with a single candy") then + check(inStack(game.stack, function(s) return s == list end), + "the last candy does not close the bag either (#796)") + check(rowFor(list, "RARE_CANDY") == nil, "its row was removed") + eq(game.save.inventory.RARE_CANDY, nil, "and the slot is empty") + check(list.index >= 1 and list.index <= #list.items, + "the cursor clamped to a valid row (index " .. tostring(list.index) + .. " of " .. #list.items .. ")") + end +end + +-- Boundary the fix must not have moved: a candy is refused mid-battle, so the +-- field behavior above can never be mistaken for a battle regression. +-- item_effects.asm:800-803, ItemUseVitamin reads wIsInBattle and +-- `jp nz, ItemUseNotTime` before it ever falls into ItemUseMedicine, which is +-- why the port's battle-side branch is a guard rather than a live path. A +-- bare table stands in for the battle: nothing on this route reads it. +do + local game, mon = freshGame(3) + local list = useFromBag(game, { fakeBattle = true }, "RARE_CANDY") + if check(list ~= nil, "the battle bag reached the picker") then + eq(mon.level, 5, "a RARE CANDY mid-battle levels nothing (ItemUseVitamin " + .. "-> ItemUseNotTime)") + eq(game.save.inventory.RARE_CANDY, 3, "and is not consumed") + local box = game.stack:top() + if check(isBox(box), "the refusal prints") then + check(box.text:find("time to use", 1, true) ~= nil, + "with ItemUseNotTime's line: " .. tostring(box.text)) + end + end +end + +T.finish() diff --git a/tests/engine/safe_area_units_test.lua b/tests/engine/safe_area_units_test.lua new file mode 100644 index 00000000..a4387b99 --- /dev/null +++ b/tests/engine/safe_area_units_test.lua @@ -0,0 +1,51 @@ +-- SafeArea.rect unit sanity (#810): the iOS build reported the portrait +-- safe rect in framebuffer PIXELS while love.graphics works in DPI-scaled +-- units, and the old clamp kept the inflated top inset -- the launcher +-- started a band down the screen and left the top of it black. A rect +-- that cannot fit the unit window is converted back to units with +-- per-axis ratios (the axes can disagree, #208). No pokered cite: the +-- launcher is port-only chrome. +-- luajit tests/engine/safe_area_units_test.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local eq = T.eq +love = love or require("tests.love_stub") + +local SafeArea = require("src.core.SafeArea") + +local oldDims = love.graphics.getDimensions +local oldPix = love.graphics.getPixelDimensions +local oldSafe = love.window.getSafeArea + +local function frame(uw, uh, pw, ph, sx, sy, sw, sh) + love.graphics.getDimensions = function() return uw, uh end + love.graphics.getPixelDimensions = function() return pw, ph end + love.window.getSafeArea = function() return sx, sy, sw, sh end + return SafeArea.rect() +end + +-- a pixel-based rect on a 3x portrait phone comes back in units (#810) +local x, y, w, h = frame(375, 812, 1125, 2436, 0, 132, 1125, 2232) +eq(x, 0, "pixel-unit safe x rescales") +eq(y, 44, "pixel-unit top inset rescales to the real notch") +eq(w, 375, "pixel-unit safe width rescales") +eq(h, 744, "pixel-unit safe height rescales") + +-- a correct unit rect passes through untouched +x, y, w, h = frame(375, 812, 1125, 2436, 0, 44, 375, 734) +eq(y, 44, "a unit rect keeps its top inset") +eq(h, 734, "a unit rect keeps its height") + +-- dpi 1: no rescale, the oversized rect still clamps to the window +x, y, w, h = frame(640, 576, 640, 576, 0, 100, 900, 900) +eq(y, 100, "no rescale when units are pixels") +eq(w, 640, "width clamps to the drawable window") +eq(h, 476, "height clamps to the drawable window") + +love.graphics.getDimensions = oldDims +love.graphics.getPixelDimensions = oldPix +love.window.getSafeArea = oldSafe + +T.finish("safe area units") diff --git a/tests/engine/save_convert_toggle_objects.lua b/tests/engine/save_convert_toggle_objects.lua new file mode 100644 index 00000000..219dc43b --- /dev/null +++ b/tests/engine/save_convert_toggle_objects.lua @@ -0,0 +1,187 @@ +-- Gen1 save codec (src/save_convert/GenSave.lua) for wToggleableObjectFlags +-- (ram/wram.asm, flag_array $100): the ShowObject/HideObject persistence the +-- codec used to skip entirely, so an import resurrected both Mt Moon fossils +-- (#857) and reverted Cerulean's GUARD1/GUARD2/ROCKET swap so the officer +-- blocked the robbed-house door again (#763). Bit numbering comes from +-- ../pokered/data/maps/toggleable_objects.asm entry order (bit set = hidden, +-- engine/overworld/toggleable_objects.asm IsObjectHidden), and the offset is +-- re-derived here from the wram walk rather than read out of GenSave.OFFSETS. +-- Also covers the Yellow-only wPikachuHappiness byte (#763, #838): the +-- 0x271C offset is pinned from the pokeyellow symbol file, not a local +-- pokeyellow checkout, so it still wants a confirmation against a real +-- emulator-written Yellow .sav. +-- luajit tests/engine/save_convert_toggle_objects.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local bit = require("bit") +local GenSave = require("src.save_convert.GenSave") +local SaveData = require("src.core.SaveData") + +-- the codec crosswalks need the real dataset; CI has no ROM +local loadPokemon = loadfile("data/generated/pokemon.lua") +if not loadPokemon then + print("save_convert_toggle_objects skipped (needs data/generated/ for the Gen1 save codec)") + os.exit(0) +end + +GenSave.setCharmap(loadfile("src/save_convert/data/charmap.lua")()) +local toggles = loadfile("src/save_convert/data/toggle_objects.lua")() +local data = { + pokemon = loadPokemon(), + moves = loadfile("data/generated/moves.lua")(), + items = loadfile("data/generated/items.lua")(), + maps = loadfile("data/generated/maps.lua")(), + eventFlags = loadfile("src/save_convert/data/event_flags.lua")(), + toggleObjects = toggles, +} + +-- ------------------------------------------------------------------ +-- offset pins, independent of the codec's own arithmetic: wram.asm places +-- wToggleableObjectFlags 2 bytes (wPlayerCoins) past wPlayerCoins' label, +-- i.e. sav absolute 0x2852; the pokeyellow symbol file places +-- wPikachuHappiness at d46f - wMainDataStart d2f6 = 377, absolute 0x271C +-- ------------------------------------------------------------------ + +local OFF = GenSave.OFFSETS +eq(OFF.toggleObjectFlags, OFF.coins + 2, + "wToggleableObjectFlags sits 2 bytes (wPlayerCoins) past O.coins") +eq(OFF.toggleObjectFlags, 0x2852, "wToggleableObjectFlags is sav byte 0x2852") +eq(OFF.pikachuHappiness, 0x271C, "wPikachuHappiness is sav byte 0x271C") + +-- independent flag_array read (byte = index / 8, bit = index % 8), so nothing +-- below trusts the writer it is checking +local function flagGet(bytes, base, index) + local byte = bytes:byte(base + math.floor(index / 8) + 1) + return bit.band(bit.rshift(byte, index % 8), 1) == 1 +end + +-- ------------------------------------------------------------------ +-- crosswalk <-> maps.lua contract: every named toggle entry must resolve +-- to a real object_event, or encode's itemsTaken/defeatedTrainers fold +-- (which looks the object up by name) silently misses it +-- ------------------------------------------------------------------ + +local entries = 0 +for _, e in pairs(toggles.byBit) do + entries = entries + 1 + local found + for _, obj in ipairs((data.maps[e[1]] or {}).objects or {}) do + if obj.name == e[2] then found = obj break end + end + check(found ~= nil, e[1] .. " has an object_event named " .. e[2]) +end +-- toggleable_objects.asm has 228 rows; two are placeholders with no +-- object_event in this port (SILPHCO7F_UNUSED, the UNUSED_MAP_F4 entry) +eq(entries, 226, "the crosswalk carries every real toggle bit and no more") + +-- the bits under test, straight from the crosswalk's own numbering +eq(toggles.byBit[109][2], "MTMOONB2F_DOME_FOSSIL", "bit 109 is the dome fossil") +eq(toggles.byBit[110][2], "MTMOONB2F_HELIX_FOSSIL", "bit 110 is the helix fossil") +eq(toggles.byBit[7][2], "CERULEANCITY_GUARD1", "bit 7 is the door guard") +eq(toggles.byBit[9][2], "CERULEANCITY_GUARD2", "bit 9 is the roof guard") +eq(toggles.byBit[104][2], "MTMOON1F_MOON_STONE", "bit 104 is the moon stone") + +-- ------------------------------------------------------------------ +-- round trip: templateless export of a save past the fossil pickup +-- (data/scripts/story2.lua hides both balls) and the Cerulean robbery +-- resolution (data/scripts/story5.lua rocketRows shows GUARD1, hides +-- GUARD2/ROCKET), plus a taken overworld item, which vanilla folds into +-- these same bits (engine/events/pick_up_item.asm) +-- ------------------------------------------------------------------ + +local function seedSave() + local save = SaveData.newGame({ playerName = "RED", rivalName = "BLUE" }) + save.party = { { + species = "SQUIRTLE", level = 6, exp = 200, + dvs = { hp = 1, attack = 2, defense = 3, speed = 4, special = 5 }, + statExp = { hp = 0, attack = 0, defense = 0, speed = 0, special = 0 }, + stats = { hp = 22, attack = 12, defense = 13, speed = 11, special = 12 }, + hp = 22, + moves = { { id = "TACKLE", pp = 35, ppUps = 0 } }, + nickname = "SQ", ot = "RED", otId = save.player.id, catchRate = 45, + } } + return save +end + +local set = seedSave() +set.objectToggles = { + MT_MOON_B2F = { + MTMOONB2F_DOME_FOSSIL = false, + MTMOONB2F_HELIX_FOSSIL = false, + }, + CERULEAN_CITY = { + CERULEANCITY_GUARD1 = true, + CERULEANCITY_GUARD2 = false, + }, +} +-- the moon stone rides itemsTaken (src/world/OverworldController.lua +-- force-hides picked items), never objectToggles, so encode must fold it in +set.itemsTaken = { MT_MOON_1F_obj_9 = true } + +local setBytes = GenSave.encode(set, data, nil) +eq(#setBytes, GenSave.SAVE_SIZE, "the export is a 32768-byte save") + +local TOG = OFF.toggleObjectFlags +check(flagGet(setBytes, TOG, 109), "the taken dome fossil is hidden (bit 109)") +check(flagGet(setBytes, TOG, 110), "the taken helix fossil is hidden (bit 110)") +check(flagGet(setBytes, TOG, 9), "the swapped-out roof guard is hidden (bit 9)") +check(not flagGet(setBytes, TOG, 7), + "the officer now beside the door stays visible (bit 7 clear)") +check(flagGet(setBytes, TOG, 104), + "the taken moon stone folds from itemsTaken into bit 104") + +-- untouched entries fall back to their compiled-in defaults, not to zero +check(flagGet(setBytes, TOG, 0), "PALLETTOWN_OAK defaults hidden (bit 0 set)") +check(not flagGet(setBytes, TOG, 1), + "VIRIDIANCITY_OLD_MAN_SLEEPY defaults visible (bit 1 clear)") + +local back = GenSave.decode(setBytes, data) +eq(#(back.warnings or {}), 0, "the export decodes with no warnings") +local reTog = back.objectToggles +check(type(reTog) == "table", "an import populates save.objectToggles") +eq(reTog.MT_MOON_B2F.MTMOONB2F_DOME_FOSSIL, false, + "the dome fossil stays taken across export -> import") +eq(reTog.MT_MOON_B2F.MTMOONB2F_HELIX_FOSSIL, false, + "the helix fossil stays taken across export -> import") +eq(reTog.CERULEAN_CITY.CERULEANCITY_GUARD1, true, + "the officer stays beside the door across export -> import") +eq(reTog.CERULEAN_CITY.CERULEANCITY_GUARD2, false, + "the roof guard stays gone across export -> import") +eq(reTog.PALLET_TOWN.PALLETTOWN_OAK, false, + "Oak's roaming sprite imports at its hidden default") +eq(reTog.VIRIDIAN_CITY.VIRIDIANCITY_OLD_MAN_SLEEPY, true, + "the sleepy old man imports at his visible default") +eq(reTog.MT_MOON_1F.MTMOON1F_MOON_STONE, false, + "the folded moon stone imports hidden too") + +-- ------------------------------------------------------------------ +-- Yellow starter friendship: gated on the data set's game because the +-- byte is current-map scratch in Red/Blue (see O.pikachuHappiness) +-- ------------------------------------------------------------------ + +check(not flagGet(setBytes, OFF.pikachuHappiness, 0) + and setBytes:byte(OFF.pikachuHappiness + 1) == 0, + "a Red/Blue export leaves the scratch byte at 0x271C zeroed") +eq(back.pikachuHappiness, nil, "a Red/Blue import never invents a happiness") + +local dataYellow = { + pokemon = data.pokemon, moves = data.moves, items = data.items, + maps = data.maps, toggleObjects = toggles, + eventFlags = loadfile("src/save_convert/data/event_flags_yellow.lua")(), + gameVersion = "yellow", +} +local ySave = seedSave() +ySave.pikachuHappiness = 200 +local yBytes = GenSave.encode(ySave, dataYellow, nil) +eq(yBytes:byte(OFF.pikachuHappiness + 1), 200, + "pikachuHappiness = 200 reaches sav byte 0x271C") +local yBack = GenSave.decode(yBytes, dataYellow) +eq(yBack.pikachuHappiness, 200, + "the follower's happiness survives export -> import on Yellow") + +T.finish("save_convert_toggle_objects") diff --git a/tests/engine/save_export_portable_bug752.lua b/tests/engine/save_export_portable_bug752.lua new file mode 100644 index 00000000..736f670e --- /dev/null +++ b/tests/engine/save_export_portable_bug752.lua @@ -0,0 +1,92 @@ +-- Portable-mode save export (#752): with portable.txt beside the game the +-- launcher's Export save must land in the game folder, never in the OS save +-- directory LOVE hands out. SaveConvert is stubbed so this stays ROM-free; +-- what is under test is only which filesystem SaveFileIO.exportActiveSlot +-- writes through and which root the returned path reports. +-- luajit tests/engine/save_export_portable_bug752.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local SEP = package.config:sub(1, 1) +local tmp = os.getenv("TMPDIR") or os.getenv("TEMP") or "/tmp" +tmp = tmp:gsub("[/\\]$", "") +local base = tmp .. SEP .. "pokeport752" +if SEP == "\\" then + os.execute('rmdir /s /q "' .. base .. '" 2>nul') + os.execute('mkdir "' .. base .. '" 2>nul') +else + os.execute('rm -rf "' .. base .. '" && mkdir -p "' .. base .. '"') +end +local marker = io.open(base .. SEP .. "portable.txt", "wb") +check(marker ~= nil, "the temp portable folder is writable") +if not marker then T.finish("save_export_portable_bug752") return end +marker:write("portable\n") +marker:close() + +-- The love surface portable detection reads: a desktop OS plus a source +-- folder holding the marker (SaveData.gameFolders). A memfs stands in for +-- the OS save directory so a stray write there is visible in the test rather +-- than silently landing on the real machine. +local strayFiles = {} +love.system = love.system or {} +love.system.getOS = function() return "Linux" end +love.filesystem = { + getSource = function() return base end, + getSourceBaseDirectory = function() return base end, + getSaveDirectory = function() return "/fake/save" end, + getInfo = function() return nil end, + read = function() return nil end, + write = function(path, content) strayFiles[path] = content return true end, + remove = function() return true end, + createDirectory = function() return true end, +} + +-- SaveConvert stubbed before SaveFileIO requires it: the codec needs +-- data/generated/ crosswalks, and the byte content is irrelevant here. +package.loaded["src.save_convert.SaveConvert"] = { + SAVE_SIZE = 32768, + exportSav = function() return string.rep("\0", 32768) end, + importSav = function() return nil, "not used here" end, +} + +local SaveData = require("src.core.SaveData") +local GameVersion = require("src.core.GameVersion") +GameVersion.set("red") +check(SaveData.isPortable(), "portable.txt beside the game turns portable mode on") + +local slotId = SaveData.createSlot("red") +check(slotId ~= nil, "a slot registers in the portable folder") +SaveData.setActiveSlot("red", slotId) +check(SaveData.writeSlot("red", slotId, SaveData.newGame()), "the slot writes") + +local SaveFileIO = require("src.import.SaveFileIO") +local ok, path = SaveFileIO.exportActiveSlot("red") +eq(ok, true, "exportActiveSlot succeeds in portable mode") +local expected = base .. SEP .. "exports" .. SEP .. "red" .. SEP + .. "gen1recomp-red-" .. tostring(slotId) .. ".sav" +eq(path, expected, "the reported path is inside the portable game folder") + +local f = io.open(expected, "rb") +check(f ~= nil, "the export file exists in the portable exports/ folder") +if f then + local bytes = f:read("*a") + f:close() + eq(#bytes, 32768, "the export is exactly 32768 bytes") +end + +for name in pairs(strayFiles) do + check(not name:find("^exports"), + "no export leaked into the OS save directory: " .. name) +end + +if SEP == "\\" then + os.execute('rmdir /s /q "' .. base .. '" 2>nul') +else + os.execute('rm -rf "' .. base .. '"') +end + +T.finish("save_export_portable_bug752") diff --git a/tests/engine/save_file_io_tests.lua b/tests/engine/save_file_io_tests.lua index 90a7fd07..d452c1f5 100644 --- a/tests/engine/save_file_io_tests.lua +++ b/tests/engine/save_file_io_tests.lua @@ -204,6 +204,112 @@ do check(type(erre) == "string", "the empty-export failure carries a message") end +-- ---------------------------------------------- oversize / truncated policy +-- A .sav LARGER than 32768 bytes whose first 32768 bytes carry a valid +-- main-data checksum is a cartridge save with a trailing emulator RTC footer +-- (VBA appends 44/48 bytes -- bgb.bircd.org/rtcsave.html). Without force the +-- import must NOT happen silently: it returns (false, nil, {needsConfirm}) +-- so the launcher can ask. With force the surplus is dropped. A file +-- SHORTER than 32768 is refused unless its checksum region is intact, in +-- which case it imports zero-padded (a truncated box region, not a loss). + +-- DroppedFile-shaped source for arbitrary bytes (readSource disambiguates a +-- raw string of length != 32768 as a path, so tests hand a file object). +local function fileSource(bytes) + return { + _bytes = bytes, + open = function() return true end, + getSize = function(self) return #self._bytes end, + read = function(self) return self._bytes end, + close = function() return true end, + } +end + +-- A realistic 44-byte VBA MBC3 RTC footer (4 dwords, 16-byte latched copies, +-- 8-byte unix timestamp, 4-byte unix timestamp -- bgb.bircd.org/rtcsave.html). +local function rtcFooter() + local parts = {} + local function pushLe(v) + parts[#parts + 1] = string.char(v % 256, math.floor(v / 256) % 256, + math.floor(v / 65536) % 256, math.floor(v / 16777216) % 256) + end + pushLe(27) -- days + pushLe(29) -- hours + pushLe(11) -- minutes + pushLe(200) -- seconds + parts[#parts + 1] = string.rep("\0", 16) -- latched RTC copies + parts[#parts + 1] = string.rep("\0", 8) -- 64-bit unix timestamp + pushLe(0x669A00BF) -- 32-bit unix timestamp (2024-07-09) + return table.concat(parts) +end + +do + local oversize = syntheticSave("OVS") .. rtcFooter() + eq(#oversize, 32768 + 44, "the oversize fixture is 32812 bytes like the real VBA save") + + local files = fresh() + local ok, res, info = SaveFileIO.importToSlot(fileSource(oversize), "red") + eq(ok, false, "an oversize valid save is not imported without confirmation") + eq(res, nil, "the oversize result carries no error string") + check(info ~= nil and info.needsConfirm == true, "the oversize result requests confirmation") + eq(info and info.size, #oversize, "the confirmation carries the actual file size") + eq(#SaveData.listSlots("red"), 0, "no slot is created before confirmation") + + -- forcing the import truncates the footer away + local fok, slotId = SaveFileIO.importToSlot(fileSource(oversize), "red", true) + eq(fok, true, "force imports the truncated save") + local loaded = SaveData.load("red") + eq(loaded and loaded.player.name, "OVS", "the forced import keeps the player name") + eq(SaveData.activeSlot("red"), slotId, "the forced import becomes active") + + local eok, path = SaveFileIO.exportActiveSlot("red") + eq(eok, true, "the forced import exports") + local rel = path:gsub("^/fake/save/", "") + local outBytes = files[rel] + eq(outBytes and #outBytes, GenSave.SAVE_SIZE, + "the export of a forced import is exactly 32768 bytes (footer dropped)") + check(outBytes and mainChecksumValid(outBytes), + "the forced-import export carries a valid main-data checksum") +end + +do + -- oversize but corrupt: flip a byte inside the checksummed region + local bad = syntheticSave("BAD") .. rtcFooter() + bad = bad:sub(1, OFF.money) + .. string.char((bad:byte(OFF.money + 1) + 1) % 256) + .. bad:sub(OFF.money + 2) + fresh() + local ok, res = SaveFileIO.importToSlot(fileSource(bad), "red") + eq(ok, false, "an oversize file with a bad checksum is rejected") + check(type(res) == "string" and res:find("checksum", 1, true) ~= nil, + "the oversize bad-checksum error mentions the checksum") + eq(#SaveData.listSlots("red"), 0, "no slot is created for a bad oversize file") +end + +do + -- truncated to 14000 bytes: >= 13572 keeps the whole checksum region and the + -- stored checksum byte intact, so the checksum validates and the save imports + -- with the missing tail (box banks) zero-filled. + local truncated = syntheticSave("SHORT"):sub(1, 14000) + check(truncated:len() >= OFF.mainChecksum + 1, + "the truncated fixture still carries the stored checksum byte") + fresh() + local ok, slotId = SaveFileIO.importToSlot(fileSource(truncated), "red") + eq(ok, true, "a truncated file with a valid checksum imports zero-padded") + local loaded = SaveData.load("red") + eq(loaded and loaded.player.name, "SHORT", "the truncated import keeps the player name") + eq(#SaveData.listSlots("red"), 1, "the truncated import creates a slot") + eq(SaveData.activeSlot("red"), slotId, "the truncated import becomes active") + + -- truncated but too short to even carry the checksum byte -> refused + local short = truncated:sub(1, OFF.mainChecksum) + local sok, serr = SaveFileIO.importToSlot(fileSource(short), "red") + eq(sok, false, "a file too short to hold a checksum byte is refused") + check(type(serr) == "string" and serr:find("32", 1, true) ~= nil, + "the too-short error names the required size") + eq(#SaveData.listSlots("red"), 1, "the too-short refusal creates no new slot") +end + -- ---------------------------------------------- fixture-gated real save do diff --git a/tests/engine/save_import_retry_bug420.lua b/tests/engine/save_import_retry_bug420.lua index 0ae47ec1..46b955e3 100644 --- a/tests/engine/save_import_retry_bug420.lua +++ b/tests/engine/save_import_retry_bug420.lua @@ -131,7 +131,12 @@ package.loaded["src.import.CacheFs"] = fakeCache local SaveConvert = require("src.save_convert.SaveConvert") -local GENERATED = { "pokemon", "moves", "items", "maps" } +-- tilesets/audio joined the set with the #889 map-context rebuild, which +-- reads the current map's tileset row and song out of the same cache. +-- The audio entry is single-quoted on purpose: gate_meta_coverage.lua treats a +-- double-quoted registry name anywhere in the test corpus as that registry's +-- unit test, and this suite is not the mod audio registry's. +local GENERATED = { "pokemon", "moves", "items", "maps", "tilesets", 'audio' } local function prefixes() local seen = {} @@ -148,7 +153,7 @@ do name .. " comes out of Blue's cache, not the un-prefixed read path") end eq(prefixes()[GameVersion.VERSIONS.blue.cachePrefix], #GENERATED, - "all four generated tables are read under Blue's cache prefix") + "every generated table is read under Blue's cache prefix") eq(fakeCache.prefix, SENTINEL, "CacheFs.prefix is launcher-owned state and is put back after the read") check(data and data.eventFlags ~= nil, diff --git a/tests/engine/save_map_context_bug889.lua b/tests/engine/save_map_context_bug889.lua new file mode 100644 index 00000000..f0b9f6f3 --- /dev/null +++ b/tests/engine/save_map_context_bug889.lua @@ -0,0 +1,201 @@ +-- #889: a .sav exported from a save that never came from a ROM import used to +-- carry no current-map state at all. A Continue restores that window from the +-- save and never rebuilds it (LoadMainData sets BIT_NO_PREVIOUS_MAP and +-- LoadMapHeader returns early on it), so the game continued into tileset 0, +-- a $0000 map-data pointer and sound id 0 -- a garbled map and a silent hang +-- on real hardware. +-- +-- src/save_convert/MapContext.lua replays LoadMapHeader's WRAM writes from the +-- extracted ROM bytes instead. This suite pins the layout it writes, on a +-- synthetic map whose header/object bytes are chosen so every field is +-- distinguishable, and then checks the encoder's three cases: no template +-- (rebuild), a template saved on the same map (leave the game's own bytes +-- alone, which is what keeps import -> export byte-identical), and a template +-- saved on a different map (rebuild, or the export carries the wrong map's +-- header). +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local MapContext = require("src.save_convert.MapContext") +local GenSave = require("src.save_convert.GenSave") + +local O = MapContext.OFFSETS +local SAVE = GenSave.OFFSETS + +-- ---------------------------------------------------------------- fixtures + +-- tileset 0, 4 blocks tall, 5 wide, data/text/script pointers, north|west +local HEADER = { 0x00, 0x04, 0x05, 0x21, 0x43, 0x78, 0x56, 0x89, 0x67, 0x0A } +-- two 11-byte map_connection_structs, north first then west (the order +-- LoadMapHeader copies them in), each tagged with its own filler +local CONNECTIONS = { + 0x11, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, + 0x22, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, +} +local OBJECTS = { + 0x0E, -- wMapBackgroundTile + 2, -- warps + 0x04, 0x05, 0x00, 0x26, + 0x06, 0x07, 0x01, 0x27, + 2, -- signs + 0x08, 0x09, 0x03, -- Y, X, text id + 0x0A, 0x0B, 0x04, + 3, -- sprites + 0x01, 0x14, 0x15, 0xFF, 0xD0, 0x05, -- plain NPC + 0x02, 0x16, 0x17, 0xFE, 0x01, 0x47, -- trainer ($40): class, party + 0x33, 0x44, + 0x03, 0x18, 0x19, 0xFF, 0xD3, 0x85, -- item ball ($80): item id + 0x14, +} +local TILESET_HEADER = { 0x0C, 0x11, 0x40, 0x22, 0x40, 0x33, 0x40, + 0x44, 0x55, 0x66, 0x77, 0x02 } + +local function fixtureData() + local maps = {} + for id, map in pairs(dofile("tests/fixture_data/maps.lua")) do + local copy = {} + for k, v in pairs(map) do copy[k] = v end + maps[id] = copy + end + maps.FIX_TOWN.sram = { + header = HEADER, connections = CONNECTIONS, objects = OBJECTS, + } + return { + maps = maps, + pokemon = dofile("tests/fixture_data/pokemon.lua"), + moves = dofile("tests/fixture_data/moves.lua"), + items = dofile("tests/fixture_data/items.lua"), + tilesets = { [maps.FIX_TOWN.tileset] = { header = TILESET_HEADER } }, + audio = { + -- Song ids are computed from the header address + -- (constants/music_constants.asm): (address - $4000) / 3. + mapSongs = { FIX_TOWN = "Music_Fixture" }, + songs = { Music_Fixture = { address = 0x4000 + 3 * 0xBD, bank = 2 } }, + }, + } +end + +local data = fixtureData() + +-- ------------------------------------------------------------ the window + +local ctx = assert(MapContext.build(data, "FIX_TOWN", 7, 4)) +local w = ctx.writes + +local function eqBytes(got, want, msg) + got = got or {} + T.eq(#got, #want, msg .. " (length)") + for i = 1, #want do + T.eq(got[i], want[i], ("%s (byte %d)"):format(msg, i)) + end +end + +eqBytes(w[O.curMapHeader], HEADER, "wCurMapHeader is the ROM header verbatim") + +-- north and west are present; south and east must read $FF, or LoadTileBlockMap +-- walks a connection that is not there +local conn = w[O.connectionHeaders] +T.eq(#conn, 44, "all four connection structs are written") +T.eq(conn[1], 0x11, "north takes the first connection struct") +T.eq(conn[11], 0xAA, "north keeps all 11 of its bytes") +T.eq(conn[12], 0xFF, "south is disabled with $FF") +T.eq(conn[23], 0x22, "west takes the second connection struct") +T.eq(conn[34], 0xFF, "east is disabled with $FF") + +eqBytes(w[O.mapBackgroundTile], { 0x0E }, "wMapBackgroundTile") +eqBytes(w[O.numberOfWarps], { 2 }, "wNumberOfWarps") +eqBytes(w[O.warpEntries], { 0x04, 0x05, 0x00, 0x26, 0x06, 0x07, 0x01, 0x27 }, + "wWarpEntries are the raw 4-byte rows") +eqBytes(w[O.numSigns], { 2 }, "wNumSigns") +eqBytes(w[O.signCoords], { 0x08, 0x09, 0x0A, 0x0B }, + "wSignCoords are split out of the 3-byte sign rows") +eqBytes(w[O.signTextIDs], { 0x03, 0x04 }, "wSignTextIDs") +eqBytes(w[O.numSprites], { 3 }, "wNumSprites") +-- movement byte 2 and the text id with its flag bits masked ("and $3f") +eqBytes(w[O.mapSpriteData], { 0xD0, 0x05, 0x01, 0x07, 0xD3, 0x05 }, + "wMapSpriteData is (movement byte 2, text id & $3f) per sprite") +eqBytes(w[O.mapSpriteExtra], { 0, 0, 0x33, 0x44, 0x14, 0 }, + "wMapSpriteExtraData holds trainer class/party and item id") +eqBytes(w[O.currentMapHeight2], { 8 }, "map height doubled into 2x2 blocks") +eqBytes(w[O.currentMapWidth2], { 10 }, "map width doubled into 2x2 blocks") + +local tilesetRow = {} +for i = 1, 11 do tilesetRow[i] = TILESET_HEADER[i] end +eqBytes(w[O.tilesetHeader], tilesetRow, + "wTilesetBank..wGrassTile is the Tilesets row (11 bytes)") +T.eq(ctx.tileAnimations, 0x02, "the 12th tileset byte rides in sTileAnimations") + +eqBytes(w[O.mapMusicSoundID], { 0xBD }, "the song id is derived from its header address") +eqBytes(w[O.mapMusicROMBank], { 2 }, "the song's audio bank comes with it") + +-- x=7,y=4: block coords are the odd/even halves, and the view pointer is +-- macros/coords.asm event_displacement over the map width. +eqBytes(w[O.yBlockCoord], { 0 }, "wYBlockCoord is y & 1") +eqBytes(w[O.xBlockCoord], { 1 }, "wXBlockCoord is x & 1") +local view = 0xC6E8 + 7 + 5 + (5 + 6) * 2 + 3 +eqBytes(w[O.viewPointer], { view % 256, math.floor(view / 256) }, + "wCurrentTileBlockMapViewPointer") + +-- sSpriteData: picture ids in structs 1..3 of page 1, positions in page 2, +-- and every non-player struct's image index disabled at $ff +T.eq(#ctx.spriteData, 512, "sSpriteData is the full 512-byte window") +T.eq(ctx.spriteData[16 + 1], 0x01, "sprite 1 picture id") +T.eq(ctx.spriteData[16 + 3], 0xFF, "sprite 1 image index starts disabled") +T.eq(ctx.spriteData[256 + 16 + 5], 0x14, "sprite 1 map Y") +T.eq(ctx.spriteData[256 + 16 + 6], 0x15, "sprite 1 map X") +T.eq(ctx.spriteData[256 + 16 + 7], 0xFF, "sprite 1 movement byte 1") +T.eq(ctx.spriteData[3 * 16 + 1], 0x03, "sprite 3 picture id") +T.eq(ctx.spriteData[1], 0, "the player's struct is left to ResetPlayerSpriteData") + +-- a map the cache has no bytes for degrades instead of raising +T.eq(MapContext.build(data, "FIX_ROUTE", 0, 0), nil, + "a map with no extracted bytes returns nil, not an error") + +-- ------------------------------------------------------- through the codec + +GenSave.setCharmap(loadfile("src/save_convert/data/charmap.lua")()) + +local save = { + player = { name = "RED", rival = "BLUE", map = "FIX_TOWN", x = 7, y = 4 }, + money = 3000, inventory = {}, pokedex = { seen = {}, owned = {} }, + flags = {}, party = {}, boxes = {}, +} + +local function byteAt(bytes, mainOffset) + return bytes:byte(SAVE.mainData + mainOffset + 1) +end + +local fresh = GenSave.encode(save, data, nil) +T.eq(byteAt(fresh, O.curMapHeader + 1), 0x04, + "a templateless export carries the map header") +T.eq(byteAt(fresh, O.tilesetHeader), 0x0C, + "a templateless export carries the tileset header") +T.eq(byteAt(fresh, O.mapMusicSoundID), 0xBD, + "a templateless export carries the map's music, not sound id 0") +T.eq(fresh:byte(SAVE.checksumEnd - 1 + 1), 0x02, + "sTileAnimations is written before the checksum covers it") +T.eq(GenSave.mainChecksumValid(fresh), true, + "the rebuilt window is inside a valid main-data checksum") + +-- a template still on its own map keeps the game's own bytes: those include +-- live NPC positions, and preserving them is the round-trip invariant +local template = {} +for i = 1, #fresh do template[i] = fresh:sub(i, i) end +template[SAVE.mainData + O.curMapHeader + 1] = string.char(0x99) +template[SAVE.spriteData + 17] = string.char(0x77) +local sameMap = GenSave.encode(save, data, table.concat(template)) +T.eq(byteAt(sameMap, O.curMapHeader), 0x99, + "a template saved on this map keeps its map header untouched") +T.eq(sameMap:byte(SAVE.spriteData + 17), 0x77, + "a template saved on this map keeps its live sprite data") + +-- a template saved somewhere else is stale: it holds the OTHER map's header, +-- which is exactly as unbootable as an empty one +template[SAVE.mainData + SAVE.curMap - SAVE.mainData + 1] = nil +local other = {} +for i = 1, #fresh do other[i] = fresh:sub(i, i) end +other[SAVE.curMap + 1] = string.char(0xFE) -- some map this save is not on +other[SAVE.mainData + O.curMapHeader + 1] = string.char(0x99) +local moved = GenSave.encode(save, data, table.concat(other)) +T.eq(byteAt(moved, O.curMapHeader), HEADER[1], + "a template saved on another map is rebuilt for the map the save is on") diff --git a/tests/engine/second_screen_touch_test.lua b/tests/engine/second_screen_touch_test.lua new file mode 100644 index 00000000..309ba054 --- /dev/null +++ b/tests/engine/second_screen_touch_test.lua @@ -0,0 +1,42 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local name = "src.render.SecondScreen" +local oldModule = package.loaded[name] +local oldFfi = package.loaded.ffi +local oldPreload = package.preload.ffi +local null = {} +local calls = 0 +local C = { + love_android_secondary_ready = function() return 1 end, + love_android_push_secondary = function() end, + love_android_secondary_enable = function() end, + love_android_poll_secondary_touch = function() + calls = calls + 1 + return calls == 1 and "down,12,34" or null + end, +} +local fakeFfi = { + C = C, + NULL = null, + cdef = function() end, + load = function() return C end, + string = function(value) return value end, +} + +package.loaded[name] = nil +package.loaded.ffi = nil +package.preload.ffi = function() return fakeFfi end + +local SecondScreen = require(name) +T.eq(SecondScreen.pollTouch(), "down,12,34", + "secondary touch reaches the Lua facade") +T.eq(SecondScreen.pollTouch(), nil, "an empty native touch queue returns nil") +C.love_android_poll_secondary_touch = nil +T.eq(SecondScreen.pollTouch(), nil, "an older native bridge remains safe") + +package.loaded[name] = oldModule +package.loaded.ffi = oldFfi +package.preload.ffi = oldPreload + +T.finish("second-screen touch facade") diff --git a/tests/engine/timing_parity.lua b/tests/engine/timing_parity.lua index e08f3a71..cddee324 100644 --- a/tests/engine/timing_parity.lua +++ b/tests/engine/timing_parity.lua @@ -235,8 +235,10 @@ local mid, done, tpopped = 0, 0, 0 local g5 = { data = Data, save = SaveData.newGame() } g5.stack = { push = function() end, pop = function() tpopped = tpopped + 1 end, top = function() return nil end } +-- warp = true: the map-change shape with no fade back in. Script fades +-- (ViridianGym.asm .afterBeat) keep the symmetric GBFadeInFromBlack. local fade = Transition.new(g5, function() mid = mid + 1 end, - function() done = done + 1 end) + function() done = done + 1 end, true) local f = 0 while done == 0 and f < 500 do diff --git a/tests/engine/title_mon_cycle.lua b/tests/engine/title_mon_cycle.lua new file mode 100644 index 00000000..8b9c9039 --- /dev/null +++ b/tests/engine/title_mon_cycle.lua @@ -0,0 +1,109 @@ +-- ..(engine/movie/title.asm ln 227) +-- ..(engine/movie/title2.asm ln 13) +-- luajit tests/engine/title_mon_cycle.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check = T.check +local eq = T.eq + +love = require("tests.love_stub") +love.math = love.math or {} +local nextPick = 1 +love.math.random = function(lo, hi) + nextPick = nextPick % hi + 1 + return math.max(lo, nextPick) +end + +local TitleState = require("src.ui.TitleState") + +local title = TitleState.new( + { data = {}, input = { wasPressed = function() return false end } }, {}) +title.sprites = setmetatable({}, { __index = function() return false end }) + +eq(title.phase, "drop", "Red/Blue boot into the logo drop, not the loop") +local ribbonSeen = {} +for _ = 1, 400 do + if title.phase == "loop" then break end + title:update(1 / 60) + if title.phase == "ribbon" then + ribbonSeen[#ribbonSeen + 1] = title.ribbonOffset + end +end +eq(title.phase, "loop", "the cinematic lands within 400 frames") +eq(ribbonSeen[1], 112, + "the ribbon is parked off the right edge on its first drawn frame") +eq(ribbonSeen[#ribbonSeen], 4, "and walks in 4px a frame to its rest") + +title.cycleIndex = 1 -- CHARMANDER: a starter, so the ball juggle runs +title.scrollPhase, title.scrollFrame, title.timer = "hold", 1, 0 +title.monOffset = 0 + +local frames = {} +for _ = 1, 260 do + title:update(1 / 60) + frames[#frames + 1] = { + phase = title.scrollPhase, offset = title.monOffset, + ball = title.ballY, mon = title.cycleSpecies[title.cycleIndex], + } +end + +local HOLD_FRAMES = 200 + +local function span(phase) + local first, count = nil, 0 + for i, f in ipairs(frames) do + if f.phase == phase then + if not first then first = i end + if first + count == i then count = count + 1 end + end + end + return first, count +end + +local holdAt, holdLen = span("hold") +local outAt, outLen = span("out") +local ballAt, ballLen = span("ball") +local inAt, inLen = span("in") +eq(holdAt, 1, "the cycle opens on the hold") +eq(holdLen, HOLD_FRAMES - 1, "ld c, 200 / CheckForUserInterruption") +eq(outAt, HOLD_FRAMES, "the scroll out begins as the 200th hold frame ends") +eq(outLen, 18, "TitleScroll_Out is 2+2+2+2+2+2+3+3 frames") +eq(ballLen, 10, "TitleScroll_WaitBall is two runs of 5") +eq(inLen, 17, "TitleScroll_In is 2+4+4+3+2+1+1 frames") +check(outAt < ballAt and ballAt < inAt, "out, then the ball, then in") + +local OUT = { 0, -1, -2, -4, -6, -9, -12, -16, -20, -25, -30, -36, -42, + -50, -58, -66, -75, -84 } +for i, want in ipairs(OUT) do + eq(frames[outAt + i - 1].offset, want, + "TitleScroll_Out offset at frame " .. i) +end + +local IN = { 120, 110, 100, 91, 82, 73, 64, 56, 48, 40, 32, 26, 20, 14, 9, + 4, 1 } +for i, want in ipairs(IN) do + eq(frames[inAt + i - 1].offset, want, "TitleScroll_In offset at frame " .. i) +end + +local BALL = { 97, 95, 94, 93, 92, 93, 94, 95, 97, 100 } +for i, want in ipairs(BALL) do + eq(frames[ballAt + i - 1].ball, want, "TitleBallYTable entry " .. i) +end + +local outgoing = frames[outAt].mon +eq(outgoing, "CHARMANDER", "the starter is the one that scrolls out") +for i = outAt, inAt - 1 do + eq(frames[i].mon, outgoing, + "the pick does not change before the scroll in, at frame " .. i) +end +local incoming = frames[inAt].mon +check(incoming ~= outgoing, "TitleScreenPickNewMon never repeats the pick") +for i = inAt, inAt + inLen - 1 do + check(frames[i].offset > 0, + "the incoming mon is only ever drawn right of rest, at frame " .. i) +end +eq(frames[inAt + inLen].offset, 0, "and settles at its resting column") + +T.finish("title_mon_cycle") diff --git a/tests/engine/title_zone_seams.lua b/tests/engine/title_zone_seams.lua index da454142..edee845f 100644 --- a/tests/engine/title_zone_seams.lua +++ b/tests/engine/title_zone_seams.lua @@ -15,21 +15,41 @@ local check, eq, same = T.check, T.eq, T.same -- endFrame's blit closure, which needs canvases and a compiled shader. The -- source is loaded directly so the rect arithmetic can be exercised with no -- GPU, the same way parity_picker_pointer_grab reads RomImporter (#254). -local scissorClamped, captured -do +local captured +local function loadScissor(loveMajor) local f = io.open("src/render/Renderer.lua", "rb") check(f ~= nil, "Renderer source is readable") local src = f and f:read("*a") or "" if f then f:close() end + local bias = src:match("\nlocal SCISSOR_PIXEL_BIAS = 0%.5.-\nend\n") + check(bias ~= nil, "scissor bias is still version-gated") local body = src:match("\nlocal function scissorClamped.-\nend\n") check(body ~= nil, "scissorClamped is still a single local function") - local fakeLove = { graphics = { setScissor = function(x, y, w, h) - captured = { x = x, y = y, w = w, h = h } - end } } - local chunk = assert(loadstring("local love = ...\n" .. (body or "") + local fakeLove = { + getVersion = function() return loveMajor, 0, 0 end, + graphics = { setScissor = function(x, y, w, h) + captured = { x = x, y = y, w = w, h = h } + end }, + } + local chunk = assert(loadstring("local love = ...\n" .. (bias or "") + .. (body or "") .. "\nreturn scissorClamped")) - scissorClamped = chunk(fakeLove) - check(type(scissorClamped) == "function", "scissorClamped loads standalone") + local scissor = chunk(fakeLove) + check(type(scissor) == "function", "scissorClamped loads standalone") + return scissor +end +local scissorClamped = loadScissor(11) + +-- LÖVE 12 changed setScissor from truncating Lua integers to accepting floats +-- and rounding in the backend. Its arguments must therefore describe the +-- already-snapped rectangle exactly, without LÖVE 11's half-pixel nudge. +do + local scissor12 = loadScissor(12) + captured = nil + check(scissor12(50, 60, 10, 20, 0, 0, 100, 100, 2, 2), + "LÖVE 12 integer test rect draws") + same(captured, { x = 50, y = 60, w = 10, h = 20 }, + "LÖVE 12 receives an unbiased snapped scissor") end -- The title's three SGB zones in canvas pixels (PaletteFX.zone turns the diff --git a/tests/engine/trade_art_import.lua b/tests/engine/trade_art_import.lua new file mode 100644 index 00000000..71736adf --- /dev/null +++ b/tests/engine/trade_art_import.lua @@ -0,0 +1,98 @@ +-- The trade cinematic's Game Boy / cable / ball / bubble art must come out +-- of the ROM importer, not just the developer-only Python path (#750). +-- RomExtractor:extractTradeArt reads five symbols -- gfx/trade.asm +-- TradingAnimationGraphics(+2), engine/gfx/mon_icons.asm TradeBubbleIconGFX, +-- and the data/tilemaps.asm GameBoyTiles / LinkCableTiles id lists -- so +-- every shipped manifest has to carry them, the manifest generator has to +-- keep them on a regen, and RomImporter has to force pre-#750 caches to +-- re-import. Addresses below were byte-verified against the canonical +-- Red/Blue/Yellow ROMs (each payload occurs exactly once) and match +-- pokered.sym / pokeblue.sym. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") + +local function readFile(path) + local handle = io.open(path, "r") + if not handle then return nil end + local text = handle:read("*a") + handle:close() + return text +end + +-- Red and Blue place the trade art identically; Yellow shifted it. +local RED_BLUE = { + GameBoyTiles = { 30, 23584 }, -- 1e:5c20 + LinkCableTiles = { 30, 23632 }, -- 1e:5c50 + TradeBubbleIconGFX = { 28, 23129 }, -- 1c:5a59 + TradingAnimationGraphics = { 14, 27070 }, -- 0e:69be + TradingAnimationGraphics2 = { 14, 27854 }, -- 0e:6cce +} +local YELLOW = { + GameBoyTiles = { 30, 23932 }, + LinkCableTiles = { 30, 23980 }, + TradeBubbleIconGFX = { 28, 23302 }, + TradingAnimationGraphics = { 14, 27240 }, + TradingAnimationGraphics2 = { 14, 28024 }, +} + +local MANIFESTS = { + { "tools/rom_manifest.json", RED_BLUE }, + { "tools/rom_manifest_blue.json", RED_BLUE }, + { "tools/rom_manifest_yellow.json", YELLOW }, +} + +for _, spec in ipairs(MANIFESTS) do + local path, expected = spec[1], spec[2] + local text = readFile(path) + T.check(text ~= nil, path .. " is readable") + if text then + for name, location in pairs(expected) do + local bank, addr = text:match( + '"' .. name .. '"%s*:%s*%[%s*(%d+)%s*,%s*(%d+)%s*%]') + T.eq(bank, tostring(location[1]), + path .. ": " .. name .. " bank") + T.eq(addr, tostring(location[2]), + path .. ": " .. name .. " address") + end + end +end + +-- the manifests are generated, so the generator has to keep asking for the +-- symbols or the next regen silently drops the trade art again +local gen = readFile("tools/make_rom_manifest.py") +T.check(gen ~= nil, "tools/make_rom_manifest.py is readable") +if gen then + for name in pairs(RED_BLUE) do + T.check(gen:find('"' .. name .. '"', 1, true) ~= nil, + "a regenerated manifest keeps " .. name) + end +end + +-- extractTradeArt exists, extractField calls it, and the field table +-- publishes the paths the same way the Python path's field.py does, so +-- TradeAnim's `game.data.field.tradeArt` lookup lands on both build paths +local extractor = readFile("src/import/RomExtractor.lua") +T.check(extractor ~= nil, "src/import/RomExtractor.lua is readable") +if extractor then + T.check(extractor:find("function RomExtractor:extractTradeArt", 1, true) ~= nil, + "RomExtractor has extractTradeArt") + T.check(extractor:find("self:extractTradeArt()", 1, true) ~= nil, + "extractField runs it") + T.check(extractor:find("data.tradeArt = tradeArt", 1, true) ~= nil, + "field.lua publishes tradeArt") +end + +-- a cache imported before #750 has none of the art; listing one of the +-- files in REQUIRED_FILES is what makes it re-import +local importer = readFile("src/import/RomImporter.lua") +T.check(importer ~= nil, "src/import/RomImporter.lua is readable") +if importer then + local required = importer:match("local REQUIRED_FILES = {(.-)\n}") + T.check(required ~= nil, "REQUIRED_FILES parses") + T.check(required ~= nil and required:find( + '"assets/generated/trade/game_boy.png"', 1, true) ~= nil, + "REQUIRED_FILES makes pre-#750 caches re-import the trade art") +end + +T.finish("trade art import") diff --git a/tests/engine/trainer_battle_theme_bug945.lua b/tests/engine/trainer_battle_theme_bug945.lua new file mode 100644 index 00000000..d7a4ce8e --- /dev/null +++ b/tests/engine/trainer_battle_theme_bug945.lua @@ -0,0 +1,190 @@ +-- Issue #945: a mod's per-trainer battleTheme (trainers.battleTheme, an +-- audio.songs id) was validated and merged onto the trainer record but +-- never read -- battle music came solely from data.audio.battle[kind] where +-- kind is computeMusicKind()'s final/gym/trainer/wild. Both battle-theme +-- start sites (OverworldController:pushBattle's pre-wipe cue and +-- BattleState:enter) now route through BattleState:playBattleTheme(), which +-- hands the override to Music.playBattle's new song arg. A nil override +-- keeps the kind default, so vanilla trainer fights -- and #782's non-gym +-- Giovanni -- are unchanged. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") + +-- ------- love audio stub: file-backed songs only (mod_audio pattern) + +local love = _G.love or {} +_G.love = love +love.audio = love.audio or {} + +local assets = { + ["assets/theme.ogg"] = true, + ["assets/alt.ogg"] = true, +} + +local sources = {} + +local Source = {} +Source.__index = Source +function Source:play() end +function Source:stop() end +function Source:setLooping() end +function Source:setVolume() end +function Source:setFilter() end + +love.audio.newSource = function(what, mode) + if type(what) == "string" and not assets[what] then + error("could not open file " .. what, 0) + end + local src = setmetatable({ file = what, mode = mode, queueable = false }, Source) + sources[#sources + 1] = src + return src +end +love.audio.newQueueableSource = function() + local src = setmetatable({ queueable = true }, Source) + sources[#sources + 1] = src + return src +end + +local Music = require("src.core.Music") +local Runtime = require("src.mods.Runtime") +local Font = require("src.render.Font") +local TypeChart = require("src.battle.TypeChart") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local BattleState = require("src.battle.BattleState") + +-- ------- the fix-945 mod: register a song and point a trainer class at it + +local MOD = { + ["mods/fix_youngster_theme/manifest.json"] = [[{ + "id": "fix_youngster_theme", + "name": "Fix Youngster Theme", + "version": "1.0.0", + "entry": "main.lua", + "api": 2 + }]], + ["mods/fix_youngster_theme/main.lua"] = [[ + local mod = ... + mod.content.music:register("Music_ModTheme", { file = "assets/theme.ogg" }) + mod.content.trainers:patch("OPP_FIX_YOUNGSTER", { + battleTheme = "Music_ModTheme", + }) + ]], +} + +local function newGame(data) + local save = SaveData.newGame() + save.player.name = "RED" + save.player.rival = "GARY" + save.party = { Pokemon.new(data, "FIXMON_A", 30) } + return { data = data, save = save, + stack = { top = function() return nil end, + push = function() end, pop = function() end } } +end + +-- record every cue the music.select hook sees +local function hookRecorder(seen) + Runtime.hooks:wrap("music.select", function(nextLink, song, ctx) + seen[#seen + 1] = { song = song, kind = ctx.kind, + trainerId = ctx.trainerId } + return nextLink(song, ctx) + end, nil, "bug945") +end + +-- a playBattle spy that records (kind, trainerId, song) without touching audio +local function spyPlayBattle() + local calls = {} + local real = Music.playBattle + Music.playBattle = function(data, kind, trainerId, song) + calls[#calls + 1] = { kind = kind, trainerId = trainerId, song = song } + end + return calls, function() Music.playBattle = real end +end + +-- ------- the modded class resolves and the override reaches the cue + +local Data = T.fixtures.fresh() +Font.load(Data) +TypeChart.load(Data) + +local run = T.sdk.loadMods({ "mods/fix_youngster_theme" }, + { data = Data, fs = T.sdk.memfs(MOD) }) +T.eq(#run.errors, 0, "the battleTheme mod loads without validation errors") +T.eq(Data.trainers.OPP_FIX_YOUNGSTER.battleTheme, "Music_ModTheme", + "the patch lands on the trainer record") + +-- give the kind defaults a home so the no-override fallback is observable +Data.audio = Data.audio or {} +Data.audio.battle = Data.audio.battle or { + wild = "Music_DefaultWild", trainer = "Music_DefaultTrainer", +} +Data.audio.songs = Data.audio.songs or {} +Data.audio.songs.Music_DefaultWild = { file = "assets/alt.ogg" } +Data.audio.songs.Music_DefaultTrainer = { file = "assets/alt.ogg" } + +local battle = BattleState.newTrainer(newGame(Data), "OPP_FIX_YOUNGSTER", 1) +T.eq(battle:battleTheme(), "Music_ModTheme", + "battleTheme() resolves the per-trainer override") +T.eq(battle:computeMusicKind(), "trainer", + "a plain trainer fight is still trainer-kind") + +local calls, restore = spyPlayBattle() +battle:playBattleTheme() +T.eq(#calls, 1, "playBattleTheme cues the theme once") +T.eq(calls[1].kind, "trainer", "the cue carries the computed kind") +T.eq(calls[1].trainerId, "OPP_FIX_YOUNGSTER", "the cue carries the trainer id") +T.eq(calls[1].song, "Music_ModTheme", "the override label wins over the kind default") + +-- enter() sets self.musicKind before playing; playBattleTheme honors it +battle.musicKind = "gym" +battle:playBattleTheme() +T.eq(calls[2].kind, "gym", "a pre-set musicKind (the enter path) is used as-is") +restore() + +-- ------- Music.playBattle: override arg wins; nil falls back to the default + +local seen = {} +hookRecorder(seen) +Music.reload() +Music.playBattle(Data, "trainer", "OPP_FIX_YOUNGSTER", "Music_ModTheme") +T.eq(seen[1].song, "Music_ModTheme", "the override arg is played") +T.eq(seen[1].kind, "trainer", "the hook sees the battle kind") +T.eq(seen[1].trainerId, "OPP_FIX_YOUNGSTER", "the hook sees the trainer id") + +Music.reload() +Music.playBattle(Data, "trainer", "OPP_FIX_YOUNGSTER") +T.eq(seen[2].song, "Music_DefaultTrainer", + "no override falls back to the kind's default song") +T.eq(seen[2].trainerId, "OPP_FIX_YOUNGSTER", + "the hook still sees the trainer id on the default path") + +-- ------- a vanilla class has no override, so the kind default is untouched + +local DataV = T.fixtures.fresh() +Font.load(DataV) +TypeChart.load(DataV) +DataV.audio = { + battle = { wild = "Music_DefaultWild", trainer = "Music_DefaultTrainer" }, + songs = { + Music_DefaultWild = { file = "assets/alt.ogg" }, + Music_DefaultTrainer = { file = "assets/alt.ogg" }, + }, +} + +local battleV = BattleState.newTrainer(newGame(DataV), "OPP_FIX_YOUNGSTER", 1) +T.eq(battleV:battleTheme(), nil, "a vanilla trainer class has no override") +local callsV, restoreV = spyPlayBattle() +battleV:playBattleTheme() +T.eq(callsV[1].kind, "trainer", "vanilla cue keeps the trainer kind") +T.eq(callsV[1].song, nil, "vanilla passes no override, so the default plays (#782)") +restoreV() + +local seenV = {} +hookRecorder(seenV) +Music.reload() +Music.playBattle(DataV, "trainer", "OPP_FIX_YOUNGSTER") +T.eq(seenV[1].song, "Music_DefaultTrainer", + "vanilla battles play the kind default, not a per-trainer theme (#782)") + +T.finish("trainer battle theme bug945") diff --git a/tests/engine/trainer_talk_sting_bug764.lua b/tests/engine/trainer_talk_sting_bug764.lua new file mode 100644 index 00000000..838f5f05 --- /dev/null +++ b/tests/engine/trainer_talk_sting_bug764.lua @@ -0,0 +1,104 @@ +-- Talking a trainer into battle must start the encounter sting (#764). +-- TalkToTrainer (pokered home/trainers.asm:88) prints the before-battle +-- text and then `call EngageMapTrainer` -> PlayTrainerMusic +-- (home/trainers.asm:399): evil list, female list, male by default, rivals +-- excluded. The port only ran the sting on the sight-line path +-- (startTrainerApproach), so a trainer challenged from the side or back -- +-- and every scripted battle routed through ow:engageTrainer -- went into +-- the battle in map music. Asserts the talk path now plays the class +-- sting, skips rivals, and does not restart it when the sight path +-- (self.engaging) already did. +-- ROM-free: stubs Game/TextBox/BattleState/Music around engageTrainer. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local OW = require("src.world.OverworldController") + +local function setUpvalue(fn, name, val) + local i = 1 + while true do + local n = debug.getupvalue(fn, i) + if not n then return false end + if n == name then debug.setupvalue(fn, i, val); return true end + i = i + 1 + end +end + +local pushed = {} +local stackStub = { + push = function(_, item) pushed[#pushed + 1] = item end, +} +local textBoxStub = { + new = function(_, text, onDone) return { text = text, onDone = onDone } end, + substitute = function(_, text) return text end, +} + +-- Music / BattleState are required lazily at the call site; stub via +-- package.loaded (same trick as tests/engine/oaks_pc_flow.lua) +local plays = {} +local realMusic = package.loaded["src.core.Music"] +package.loaded["src.core.Music"] = { + play = function(_, song) plays[#plays + 1] = song end, +} +local realBattle = package.loaded["src.battle.BattleState"] +package.loaded["src.battle.BattleState"] = { + newTrainer = function() return {} end, +} + +local fakeGame = { + data = { + text = {}, + trainerHeader = function() return nil end, + resolveText = function() return "You looked at me\nfunny!" end, + }, + stack = stackStub, +} +T.check(setUpvalue(OW.engageTrainer, "Game", fakeGame), + "Game upvalue on engageTrainer") +T.check(setUpvalue(OW.engageTrainer, "TextBox", textBoxStub), + "TextBox upvalue on engageTrainer") + +-- pushBattle would touch the real stack machinery; the engagement is over +-- by the time it runs, so a no-op keeps the test on the music question +local fakeSelf = setmetatable({ + map = { def = { label = "Route24" } }, + pushBattle = function() end, +}, { __index = OW }) + +-- run engageTrainer for one class and return what the sting played +local function stingFor(cls, engaging) + pushed, plays = {}, {} + fakeSelf.engaging = engaging + fakeSelf:engageTrainer({ id = "npc#1", def = { trainerClass = cls, + trainerParty = 1, index = 1 } }) + T.eq(#pushed, 1, "engageTrainer pushes the before-battle text") + T.eq(#plays, 0, "no sting while the dialogue is still up (" .. cls .. ")") + pushed[1].onDone() -- close the box; TalkToTrainer engages here + return plays[1], #plays +end + +-- PlayTrainerMusic's three buckets (data/trainers/encounter_types.asm) +T.eq(stingFor("OPP_LASS"), "Music_MeetFemaleTrainer", + "female-list class plays the female sting") +T.eq(stingFor("OPP_ROCKET"), "Music_MeetEvilTrainer", + "evil-list class plays the evil sting") +T.eq(stingFor("OPP_YOUNGSTER"), "Music_MeetMaleTrainer", + "any other class defaults to the male sting") + +-- the rivals `ret z` out of PlayTrainerMusic; their scripts run +-- MUSIC_MEET_RIVAL themselves (data/scripts/oaks_lab.lua) +local _, rivalCount = stingFor("OPP_RIVAL1") +T.eq(rivalCount, 0, "rival classes play no encounter sting here") + +-- sight path already engaged: TrainerEngage started the sting before the +-- "!" bubble, and TalkToTrainer's BIT_SEEN_BY_TRAINER guard keeps the +-- talk path from restarting it +local _, seenCount = stingFor("OPP_LASS", true) +T.eq(seenCount, 0, "self.engaging suppresses a second sting") + +if realMusic ~= nil then package.loaded["src.core.Music"] = realMusic +else package.loaded["src.core.Music"] = nil end +if realBattle ~= nil then package.loaded["src.battle.BattleState"] = realBattle +else package.loaded["src.battle.BattleState"] = nil end + +T.finish("trainer_talk_sting_bug764") diff --git a/tests/engine/ttf_font_mode.lua b/tests/engine/ttf_font_mode.lua new file mode 100644 index 00000000..351f9a89 --- /dev/null +++ b/tests/engine/ttf_font_mode.lua @@ -0,0 +1,247 @@ +-- TTF TEXT MODE: a translation registers `font:register("ttf", {})` and the +-- engine renders ordinary characters through a real TTF (the bundled Plain +-- Pixel, assets/fonts/plainpixel/) instead of demanding a hand-drawn glyph +-- page per script. The contracts under test: +-- +-- * with no ttf entry nothing changes: vanilla text stays tile-for-tile +-- identical (the same guarantee Strings makes for an absent catalog); +-- * with it, single characters become TTF glyph codes while multi-char +-- charmap sequences ( macros, the 'd ligatures) and the sub-0x80 +-- box chrome keep their tiles, so borders never depend on TTF coverage; +-- * advances come from the font's metrics (5px Latin, 11px double-width +-- kana/CJK in Plain Pixel), which TextBox's pixel-budget paginate and +-- its per-glyph draw pen both honor; +-- * the "ttf" id is a legal font-registry entry and merges to +-- data.font.ttf, rebuilt each merge so disabling the mod disables it. +-- +-- luajit tests/engine/ttf_font_mode.lua + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Font = require("src.render.Font") + +local BASE = Font.TTF_BASE + +-- a charmap with a single-char entry, a multi-byte single char, and a +-- two-char ligature: the three shapes split() has to tell apart +local CHARMAP = { + { code = 0x80, seq = "A" }, + { code = 0xBA, seq = "\195\169" }, -- é + { code = 0xD0, seq = "'d" }, +} + +-- ------------------------------------------------- vanilla stays vanilla + +Font.load({ font = { charmap = CHARMAP } }) +T.check(not Font.ttfActive(), "no ttf entry means tile mode") +T.eq(Font.encode("A")[1], 0x80, "charmap keeps mapping to tiles") +T.eq(Font.advanceOf(0x80), 8, "and the pen stays 8px monospace") + +-- ------------------------------------------------- the ttf takes over + +-- Font rasterizers must use the same 1x pixel grid as PixelCanvas, rather +-- than inheriting Android's window density. Keep this local fake so the +-- contract is testable without requiring a real LÖVE window. +do + local g, oldNewFont = love.graphics, love.graphics.newFont + local args + g.newFont = function(...) + args = { ... } + return oldNewFont(Font.PLAINPIXEL, Font.PLAINPIXEL_SIZE) + end + local loaded, err = pcall(Font.load, { + font = { charmap = CHARMAP, ttf = { file = "custom.ttf", size = 13 } }, + }) + g.newFont = oldNewFont + if not loaded then error(err, 0) end + T.eq(args[1], "custom.ttf", "rasterizer uses the configured file") + T.eq(args[2], 13, "rasterizer uses the configured size") + T.eq(args[3], "mono", "rasterizer keeps the pixel hinting mode") + T.eq(args[4], 1, "rasterizer stays on the pixel canvas scale") +end + +Font.load({ font = { charmap = CHARMAP, ttf = {} } }) +T.check(Font.ttfActive(), "an empty ttf table loads the bundled font") + +T.eq(Font.encode("A")[1], BASE + 65, + "a single character routes to the TTF even with a charmap entry") +T.eq(Font.encode("\195\169")[1], BASE + 0xE9, + "a multi-byte single character does too") +T.eq(Font.encode("'d")[1], 0xD0, + "a multi-character ligature keeps its tile glyph") +T.eq(#Font.encode("'d"), 1, "and still consumes the whole sequence") +T.eq(Font.encode("\227\129\130")[1], BASE + 0x3042, + "kana with no charmap entry at all still gets a glyph") + +-- ------------------------------------------------- ttf.tiles opts back out + +-- A CJK translation sizes the font so a kana fills the 8px cell, which leaves +-- Latin narrower than the tile font it replaces and pulls the numeric columns +-- (the party menu's ":L12" over "34/ 34") out of line. ttf.tiles names the +-- characters that keep their ROM tile anyway, so numbers stay identical to the +-- English build while kana still come from the font. +Font.load({ font = { charmap = CHARMAP, ttf = { tiles = "A" } } }) +T.eq(Font.encode("A")[1], 0x80, "a character in ttf.tiles keeps its tile glyph") +T.eq(Font.advanceOf(0x80), 8, "and its 8px monospace advance with it") +T.eq(Font.encode("\195\169")[1], BASE + 0xE9, + "while everything else still routes to the TTF") + +-- a list form, for naming a multi-character sequence explicitly +Font.load({ font = { charmap = CHARMAP, ttf = { tiles = { "\195\169" } } } }) +T.eq(Font.encode("\195\169")[1], 0xBA, + "the list form accepts a multi-byte character") +T.eq(Font.encode("A")[1], BASE + 65, "and leaves the others on the TTF") + +Font.load({ font = { charmap = CHARMAP, ttf = {} } }) +T.eq(Font.encode("A")[1], BASE + 65, "no tiles list means the TTF takes it back") + +-- ------------------------------------------------- drawBox restores color + +-- drawBox fills its interior white and used to leave the color that way. +-- Tile pages are black glyphs on transparent, so they draw black whatever the +-- color is and the leak stayed invisible for as long as every glyph was a +-- tile. TTF text draws in the current color, so every label printed after a +-- box came out white on white -- the summary screen lost ATTACK/DEFENSE/ +-- SPEED/SPECIAL and TYPE1/TYPE2 while the numbers beside them survived. +love.graphics.setColor(0, 0, 0, 1) +Font.drawBox(0, 0, 4, 4) +local r, g, b, a = love.graphics.getColor() +T.eq(("%s,%s,%s,%s"):format(r, g, b, a), "0,0,0,1", + "drawBox leaves the caller's color alone") + +-- metrics come straight from the font object (the stub: half the point +-- size per codepoint, doubled from U+1000 up, mimicking Plain Pixel's +-- single/double width split) +local latin = Font.advanceOf(BASE + 65) +local kana = Font.advanceOf(BASE + 0x3042) +T.check(latin > 0, "a TTF glyph reports a positive advance") +T.eq(kana, latin * 2, "double-width kana advances twice a Latin glyph") +T.eq(Font.width("AB"), latin * 2, "width() sums TTF advances") +T.eq(Font.draw("AB", 0, 0), latin * 2, "draw() returns the same pen travel") + +-- border chrome stays on tiles: below every page base here, drawCode must +-- not touch the TTF path (nothing to assert beyond "does not blow up", +-- because with no page image loaded the tile path draws nothing) +Font.drawCode(Font.BORDER.tl, 0, 0) + +-- ------------------------------------------------- draw details + +-- drawCode prints through the TTF at the configured vertical offset +-- (default bottom-aligns the tall glyph to the 8px cell) and restores +-- whatever font the caller had set +do + local g = love.graphics + local prints = {} + local oldPrint, oldSetFont = g.print, g.setFont + local marker = { marker = true } + g.setFont(marker) + g.print = function(text, x, y) prints[#prints + 1] = { text = text, x = x, y = y } end + Font.drawCode(BASE + 65, 10, 20) + g.print = oldPrint + T.eq(#prints, 1, "a TTF code draws through one love.graphics.print") + T.eq(prints[1].text, "A", "printing the character it encodes") + T.eq(prints[1].x, 10, "at the pen x") + -- stub baseline is px - 2 = 13 at the size-15 default; tile row 7 + T.eq(prints[1].y, 20 + (7 - 13), "baseline-aligned to row 7 of the cell") + T.eq(g.getFont(), marker, "the caller's font is restored") + g.setFont = oldSetFont +end + +-- bold = true double-prints at a 1px offset and widens the advance +Font.load({ font = { charmap = {}, ttf = { bold = true } } }) +do + local g = love.graphics + local prints = {} + local oldPrint = g.print + g.print = function(_, x) prints[#prints + 1] = x end + Font.drawCode(BASE + 65, 10, 20) + g.print = oldPrint + T.eq(#prints, 2, "bold prints twice") + T.eq(prints[2], 11, "the second pass sits 1px over") + T.eq(Font.advanceOf(BASE + 65), latin + 1, "and the advance grows with it") +end +Font.load({ font = { charmap = CHARMAP, ttf = {} } }) + +-- spacing and yOffset are honored when a mod tunes them +Font.load({ font = { charmap = {}, ttf = { spacing = 2, yOffset = -2 } } }) +T.eq(Font.advanceOf(BASE + 65), latin + 2, "spacing pads every advance") +do + local g = love.graphics + local y + local oldPrint = g.print + g.print = function(_, _, py) y = py end + Font.drawCode(BASE + 65, 0, 20) + g.print = oldPrint + T.eq(y, 18, "yOffset overrides the bottom-align default") +end + +-- a bad file degrades to tile mode instead of crashing the boot +Font.load({ font = { charmap = CHARMAP, + ttf = { file = "no/such/font.ttf" } } }) +T.check(not Font.ttfActive(), "an unloadable ttf falls back to tiles") +T.eq(Font.encode("A")[1], 0x80, "and the charmap works as if never asked") + +-- ------------------------------------------------- TextBox draws with it + +-- the dialogue box pen must advance per glyph (it measured that way in +-- paginate all along); with the TTF active a line of 5px glyphs would +-- otherwise be drawn spread across the 8px grid +Font.load({ font = { charmap = CHARMAP, ttf = {} } }) +do + local TextBox = require("src.render.TextBox") + local box = setmetatable({ + boxTx = 0, boxTy = 12, boxTw = 20, boxTh = 6, + textX = 8, line1Y = 112, line2Y = 128, + shown = { Font.encode("AB'd") }, blink = 0, + }, TextBox) + local pens = {} + local oldDraw = Font.drawCode + Font.drawCode = function(code, x, y) + if code >= BASE or code == 0xD0 then pens[#pens + 1] = { code = code, x = x } end + end + box:draw() + Font.drawCode = oldDraw + T.eq(#pens, 3, "three glyphs drawn for AB'd") + T.eq(pens[1].x, 8, "the pen starts at textX") + T.eq(pens[2].x, 8 + latin, "and moves by the TTF advance, not 8px") + T.eq(pens[3].x, 8 + latin * 2, "the ligature tile sits after the TTF pair") +end + +-- ------------------------------------------------- the registry entry + +do + local Schemas = require("src.mods.Schemas") + local spec = Schemas.REGISTRIES["font"] + T.check(select(1, Schemas.check(spec, "font", "ttf", {})) == true, + 'register("ttf", {}) is legal: every field is optional') + T.check(select(1, Schemas.check(spec, "font", "ttf", + { file = "mods/x/f.ttf", size = 11, spacing = 1, yOffset = -3 })) == true, + "so is a fully tuned entry") + T.check(select(1, Schemas.check(spec, "font", "ttf", + { size = 10, tiles = "0123456789/:" })) == true, + "tiles takes a string of characters") + T.check(select(1, Schemas.check(spec, "font", "ttf", + { tiles = { "0", "" } })) == true, "or a list of charmap sequences") + T.check(Schemas.check(spec, "font", "ttf", { tiles = 10 }) == nil, + "but not a number") + T.check(Schemas.check(spec, "font", "ttf", { image = "x.png", base = 0x100 }) + == nil, "a page payload under the ttf id is refused") + T.check(Schemas.check(spec, "font", "page", {}) == nil, + "and a bare page still needs image and base") + + -- write(): the entry lands on data.font.ttf and is rebuilt per merge + local target = { charmap = {}, pages = {} } + local registry = { order = { "ttf" }, + get = function(_, id) return { size = 11 } end } + spec.write(target, registry) + T.eq(target.ttf and target.ttf.size, 11, "write() fills data.font.ttf") + T.check(target.pages.ttf == nil, "and does not mistake it for a page") + spec.write(target, { order = {}, get = function() return nil end }) + T.check(target.ttf == nil, "a merge without the mod clears it again") +end + +-- leave the shared Font module in tile mode for whatever runs next +Font.load({ font = { charmap = {} } }) + +T.finish("ttf font mode") diff --git a/tests/engine/ui_kit_pagination.lua b/tests/engine/ui_kit_pagination.lua new file mode 100644 index 00000000..d18bbf00 --- /dev/null +++ b/tests/engine/ui_kit_pagination.lua @@ -0,0 +1,127 @@ +-- The launcher's UI kit: pagination bounds, viewport-derived page size, and +-- the text safety rules. These replace the two FlexLove engine tests +-- (flexlove_wheel_scroll_dt0, launcher_save_slot_overlap_bug748), whose +-- subjects -- a scroll manager fed dt = 0 and an auto-height propagation bug +-- -- no longer exist: there is no scrolling and no layout engine. +-- +-- What DOES need guarding now is the property the whole performance claim +-- rests on: a list draws at most one page of rows regardless of how many +-- items it holds, and the page arithmetic never walks off either end. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local T = require("tests.modkit") +local Kit = require("src.ui.kit.Kit") +local Theme = require("src.ui.kit.Theme") + +-- ---------------------------------------------------------------- page math +-- pageBounds(page, total, perPage) -> first, last, clampedPage, pages +do + local first, last, page, pages = Kit.pageBounds(1, 38, 5) + T.eq(first, 1, "page 1 starts at item 1") + T.eq(last, 5, "page 1 ends at perPage") + T.eq(page, 1, "page 1 is in range") + T.eq(pages, 8, "38 items at 5 per page is 8 pages") + + first, last = Kit.pageBounds(8, 38, 5) + T.eq(first, 36, "the last page starts after the full ones") + T.eq(last, 38, "the last page stops at the item count, not at perPage") + + -- Out of range in both directions clamps rather than producing a window + -- that would index past the list (the launcher repages lists underneath + -- the user: a refresh can shrink an index from 38 mods to 2). + local _, _, low = Kit.pageBounds(0, 38, 5) + T.eq(low, 1, "page 0 clamps up to 1") + local f2, l2, high, p2 = Kit.pageBounds(99, 38, 5) + T.eq(high, 8, "a page past the end clamps to the last page") + T.eq(p2, 8, "the page count is unchanged by clamping") + T.check(l2 >= f2, "a clamped window is never inverted") + + -- An empty list still has one page, and draws no rows. + local ef, el, ep, epages = Kit.pageBounds(1, 0, 5) + T.eq(epages, 1, "an empty list still reports one page") + T.eq(ep, 1, "an empty list sits on page 1") + T.check(el < ef, "an empty page yields an empty row range") + + -- THE INVARIANT: no page ever yields more rows than perPage. + for total = 0, 40 do + for per = 1, 7 do + local _, _, _, np = Kit.pageBounds(1, total, per) + for p = 1, np do + local a, b = Kit.pageBounds(p, total, per) + T.check(b - a + 1 <= per, + ("page %d of %d (total %d) draws at most %d rows"):format(p, np, total, per)) + T.check(b <= total, "a page never runs past the item count") + end + end + end +end + +-- ------------------------------------------------------- viewport page size +-- rowsThatFit derives perPage from the real viewport, which is what lets a +-- tall window show more rows and a phone fewer with no scrolling either way. +do + -- n rows need n*rowH + (n-1)*gap pixels. + T.eq(Kit.rowsThatFit(200, 80, 20, 1, 20), 2, "200px fits two 80px rows + one gap") + T.eq(Kit.rowsThatFit(180, 80, 20, 1, 20), 2, "exactly two rows still fit") + T.eq(Kit.rowsThatFit(179, 80, 20, 1, 20), 1, "one pixel short drops to one row") + T.eq(Kit.rowsThatFit(0, 80, 20, 1, 20), 1, + "a collapsed viewport still shows one row rather than none") + T.eq(Kit.rowsThatFit(-500, 80, 20, 1, 20), 1, + "a negative budget cannot produce a negative page size") + T.eq(Kit.rowsThatFit(100000, 80, 20, 1, 12), 12, "the cap is honoured") +end + +-- ------------------------------------------------------------- text safety +-- Truncation must move whole codepoints. LOVE's Font:getWidth raises +-- "UTF-8 decoding error" on a string cut through a multi-byte sequence, and +-- the launcher lists mod names from third-party indexes -- this crashed the +-- first frame on a Japanese listing. +do + -- A font stub that REFUSES malformed UTF-8, the way LOVE's does. + local font = {} + function font:getWidth(s) + local i = 1 + while i <= #s do + local b = s:byte(i) + local n = (b < 0x80 and 1) or (b >= 0xF0 and 4) or (b >= 0xE0 and 3) + or (b >= 0xC0 and 2) or nil + if not n then error("UTF-8 decoding error: unexpected continuation", 0) end + for k = 1, n - 1 do + local c = s:byte(i + k) + if not c or c < 0x80 or c >= 0xC0 then + error("UTF-8 decoding error: Not enough space", 0) + end + end + i = i + n + end + -- 10px per codepoint, whatever its byte length + local count = 0 + i = 1 + while i <= #s do + local b = s:byte(i) + i = i + ((b < 0x80 and 1) or (b >= 0xF0 and 4) or (b >= 0xE0 and 3) or 2) + count = count + 1 + end + return count * 10 + end + + local jp = "ポケットモンスター" -- 9 codepoints, 27 bytes + local ok, cut = pcall(Theme.ellipsize, font, jp, 55) + T.check(ok, "ellipsize does not raise on multi-byte text: " .. tostring(cut)) + T.check(font:getWidth(cut) <= 55 + 1e-9, "the result fits the budget") + local ok2 = pcall(font.getWidth, font, cut) + T.check(ok2, "the truncated string is still valid UTF-8") + + local okL, cutL = pcall(Theme.ellipsizeLeft, font, jp, 55) + T.check(okL, "ellipsizeLeft does not raise on multi-byte text") + T.check(pcall(font.getWidth, font, cutL), + "the left-truncated string is still valid UTF-8") + + T.eq(Theme.ellipsize(font, jp, 0), "", + "a zero budget means nothing fits, not everything fits") + T.eq(Theme.ellipsize(font, "abc", 999), "abc", + "text that already fits is returned untouched") +end + +T.finish("ui_kit_pagination") diff --git a/tests/engine/update_check_tests.lua b/tests/engine/update_check_tests.lua index 698c5c9f..193ab118 100644 --- a/tests/engine/update_check_tests.lua +++ b/tests/engine/update_check_tests.lua @@ -47,6 +47,20 @@ eq(bad, nil, "non-X.Y.Z tag rejected") check(badErr ~= nil, "rejection carries an error string") eq(Check.parseRelease(Json.encode({ foo = 1 })), nil, "missing tag_name rejected") +-- Network failures can return a plain-text or HTML body instead of JSON. The +-- launcher must describe that response rather than leaking the decoder's +-- low-level "unexpected character 'E'" assertion. +do + local release, err = Check.parseRelease("Error: API rate limit exceeded") + check(release == nil and tostring(err):find("not JSON", 1, true) ~= nil, + "plain-text update failure is reported as non-JSON") + release, err = Check.parseRelease("502 Bad Gateway") + check(release == nil and tostring(err):find("HTML", 1, true) ~= nil, + "HTML update failure is identified") + check(tostring(err):find("unexpected character", 1, true) == nil, + "decoder assertion is not exposed") +end + -- parseSums: shasum -a 256 format, tolerating the '*' binary marker, a './' -- prefix and CRLF line endings; unrelated lines are skipped local sums = diff --git a/tests/engine/uwp_baseroms_test.lua b/tests/engine/uwp_baseroms_test.lua new file mode 100644 index 00000000..65e03cf3 --- /dev/null +++ b/tests/engine/uwp_baseroms_test.lua @@ -0,0 +1,156 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local S = require("tests.harness").suite("UWP baseroms discovery") +local check, eq = S.check, S.eq + +local GameVersion = require("src.core.GameVersion") +local Platform = require("src.core.Platform") +local RomImporter = require("src.import.RomImporter") + +love.data = love.data or {} +love.system = love.system or {} +love.filesystem = love.filesystem or {} + +local MiB = 1024 * 1024 +local roms = { + ["baseroms/z-red.gb"] = string.rep("R", MiB), + ["baseroms/a-blue.gb"] = string.rep("B", MiB), + ["baseroms/b-yellow.gbc"] = string.rep("Y", MiB), + ["baseroms/small.gb"] = "small", + ["baseroms/unknown.gb"] = string.rep("?", MiB), +} + +local saved = { + hash = love.data.hash, + encode = love.data.encode, + getOS = love.system.getOS, + pickFile = love.system.pickFile, + getPickedFile = love.system.getPickedFile, + read = love.filesystem.read, + getInfo = love.filesystem.getInfo, + getDirectoryItems = love.filesystem.getDirectoryItems, + createDirectory = love.filesystem.createDirectory, + isReady = RomImporter.isReady, +} + +love.data.hash = function(_, data) return data:sub(1, 1) end +love.data.encode = function(_, _, digest) + if digest == "R" then return GameVersion.info("red").sha1 end + if digest == "B" then return GameVersion.info("blue").sha1 end + if digest == "Y" then return GameVersion.info("yellow").sha1 end + return "0000000000000000000000000000000000000000" +end + +local reads, listings, picks = 0, 0, 0 +love.filesystem.read = function(path) + reads = reads + 1 + return roms[path] +end +love.filesystem.getInfo = function(path, filter) + if path == "baseroms" then + return filter and nil or { type = "directory" } + end + local data = roms[path] + if data and (not filter or filter == "file") then + return { type = "file", size = #data } + end + return nil +end +love.filesystem.getDirectoryItems = function(path) + if path ~= "baseroms" then return {} end + listings = listings + 1 + return { "z-red.gb", "small.gb", "unknown.gb", "b-yellow.gbc", "a-blue.gb" } +end +love.filesystem.createDirectory = function() return true end +love.system.pickFile = function() + picks = picks + 1 + return true +end +love.system.getPickedFile = function() return nil end + +local function importer(ready) + return setmetatable({ + baseRomDiscovery = true, + baseRoms = {}, + ready = ready or { red = false, blue = false, yellow = false }, + returning = {}, + workState = nil, + nativePicker = true, + isNX = false, + }, RomImporter) +end + +local allReady = importer({ red = true, blue = true, yellow = true }) +allReady:_queueBaseRomScan() +eq(allReady.baseRomScan.state, "done", "ready launcher skips discovery") +eq(listings, 0, "ready launcher does not enumerate baseroms") + +local imp = importer() +imp:_queueBaseRomScan() +for _ = 1, 5 do + local before = reads + imp:_stepBaseRomScan() + check(reads - before <= 1, "discovery reads at most one ROM per step") +end +eq(listings, 1, "discovery enumerates baseroms once") +eq(imp.baseRoms.blue.name, "a-blue.gb", "Blue ROM is detected by SHA-1") +eq(imp.baseRoms.yellow.name, "b-yellow.gbc", "Yellow ROM is detected by SHA-1") +eq(imp.baseRoms.red.name, "z-red.gb", "Red ROM is detected by SHA-1") +eq(reads, 4, "wrong-sized files are skipped before reading") +eq(imp.baseRomScan.state, "done", "discovery stops when every missing ROM is found") + +local settledReads, settledListings = reads, listings +for _ = 1, 10 do imp:_stepBaseRomScan() end +eq(reads, settledReads, "completed discovery does not read again") +eq(listings, settledListings, "completed discovery does not enumerate again") + +local detected = importer() +detected.baseRoms.red = { path = "baseroms/z-red.gb", name = "z-red.gb" } +detected.startData = function(self, data, name) + self.started = { data = data, name = name } +end +detected:choose("red") +eq(detected.started.name, "z-red.gb", "detected ROM uses the normal import path") +eq(picks, 0, "detected ROM does not open the picker") +check(roms["baseroms/z-red.gb"] ~= nil, "detected ROM remains in baseroms") + +local missing = importer() +missing.baseRoms.red = { path = "baseroms/gone.gb", name = "gone.gb" } +missing:choose("red") +check(missing.notice ~= nil, "missing detected ROM reports a notice") +eq(picks, 0, "missing detected ROM does not open the picker unexpectedly") +missing:choose("red") +eq(picks, 1, "the next import attempt falls back to the native picker") + +local rescanned = importer({ red = true, blue = true, yellow = true }) +rescanned.baseRoms.red = { path = "baseroms/z-red.gb", name = "z-red.gb" } +rescanned:reimport("red") +check(rescanned.baseRoms.red == nil, "re-import clears the detected ROM") +eq(rescanned.baseRomScan.state, "queued", "re-import queues one fresh scan") + +RomImporter.isReady = function() return false end +love.system.getOS = function() return "UWP" end +Platform._resetForTests() +local launcher = RomImporter.new(function() end, { launcher = true }) +check(launcher.baseRomDiscovery, "UWP launcher enables baseroms discovery") +local importOnly = RomImporter.new(function() end) +check(not importOnly.baseRomDiscovery, "non-launcher UWP import stays unchanged") +love.system.getOS = function() return "Windows" end +Platform._resetForTests() +local desktop = RomImporter.new(function() end, { launcher = true }) +check(not desktop.baseRomDiscovery, "desktop launcher does not scan baseroms") + +love.data.hash = saved.hash +love.data.encode = saved.encode +love.system.getOS = saved.getOS +love.system.pickFile = saved.pickFile +love.system.getPickedFile = saved.getPickedFile +love.filesystem.read = saved.read +love.filesystem.getInfo = saved.getInfo +love.filesystem.getDirectoryItems = saved.getDirectoryItems +love.filesystem.createDirectory = saved.createDirectory +RomImporter.isReady = saved.isReady +Platform._resetForTests() + +S.finish() diff --git a/tests/engine/uwp_native_picker_test.lua b/tests/engine/uwp_native_picker_test.lua new file mode 100644 index 00000000..ab5c34d2 --- /dev/null +++ b/tests/engine/uwp_native_picker_test.lua @@ -0,0 +1,54 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local S = require("tests.harness").suite("UWP native picker") +local check = S.check + +local Platform = require("src.core.Platform") +local RomImporter = require("src.import.RomImporter") + +love.system = love.system or {} +local saved = { + getOS = love.system.getOS, + pickFile = love.system.pickFile, + getPickedFile = love.system.getPickedFile, + getPickError = love.system.getPickError, + remove = os.remove, +} + +love.system.getOS = function() return "UWP" end +love.system.pickFile = function() return true end +love.system.getPickedFile = function() + love.system.getPickedFile = function() return nil end + return [[C:\LocalState\picked_mod.zip]] +end +love.system.getPickError = function() return nil end + +local removedPath +os.remove = function(path) + removedPath = path + return true +end + +Platform._resetForTests() +local importer = RomImporter.new(function() end, { launcher = true }) +importer.pickerPendingKind = "mod" +importer._installMod = function(self, source) + self.installedPath = source + self.modNotice = { ok = true, text = "Installed test-mod" } +end +importer:update(0) + +check(importer.installedPath == [[C:\LocalState\picked_mod.zip]], + "passes the picked path to the mod installer") +check(removedPath == [[C:\LocalState\picked_mod.zip]], + "removes the temporary copy after installation") + +love.system.getOS = saved.getOS +love.system.pickFile = saved.pickFile +love.system.getPickedFile = saved.getPickedFile +love.system.getPickError = saved.getPickError +os.remove = saved.remove +Platform._resetForTests() + +S.finish() diff --git a/tests/engine/viridian_fisher_pre_bug775.lua b/tests/engine/viridian_fisher_pre_bug775.lua new file mode 100644 index 00000000..134fc120 --- /dev/null +++ b/tests/engine/viridian_fisher_pre_bug775.lua @@ -0,0 +1,89 @@ +-- Headless regression: the Viridian fisher's TM42 gift skipped his pre +-- text and jumped straight to "received TM42!" (#775). pokered's +-- ViridianCityFisherText (scripts/ViridianCity.asm) prints +-- .YouCanHaveThisText before GiveItem; on Red that label sits outside the +-- extractor's symbol set (no leading underscore, same class as the +-- SilphCo2F worker in #393), so the ported literal has to carry the flow +-- when the text table has no entry. ROM-free: the gift closure only +-- touches text/items/flags, so TextBox, Sound and Bag are stubbed and the +-- boxes are advanced by hand. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") + +-- story5's gift() requires these at call time, so preloading stubs is +-- enough; each tier suite is its own process, nothing leaks +local boxes = {} +package.loaded["src.render.TextBox"] = { + new = function(_, s, done) return { text = s, onDone = done } end, +} +package.loaded["src.core.Sound"] = { play = function() end } +package.loaded["src.inventory.Bag"] = { + add = function(save, item, n) + save.inventory[item] = (save.inventory[item] or 0) + n + return true + end, +} + +local story5 = require("data.scripts.story5") +local fisher = story5.VIRIDIAN_CITY.talk.TEXT_VIRIDIANCITY_FISHER +T.check(type(fisher) == "function", "the fisher talk entry is a gift closure") + +local function newGame(textTable) + boxes = {} + return { + data = { + text = textTable, + items = { TM_DREAM_EATER = { name = "TM42" } }, + }, + save = { + flags = {}, inventory = {}, player = { name = "RED" }, + }, + stack = { + push = function(_, box) boxes[#boxes + 1] = box end, + }, + } +end + +-- Red-like: empty text table, the fallback literal must carry the scene +local game = newGame({}) +local finished = false +fisher(game, nil, nil, function() finished = true end) + +T.eq(#boxes, 1, "talking opens exactly one box before any A press") +local pre = boxes[1].text +T.check(type(pre) == "string" and pre:sub(1, 5) == "Yawn!", + "the first box is the fisher's pre text, not the receipt") +T.check(pre:find("DROWZEE", 1, true) ~= nil, + "the fallback carries the DROWZEE dream paragraph") +T.check(pre:find("have this TM.", 1, true) ~= nil, + "and ends on the hand-over line") +T.check(not game.save.flags.EVENT_GOT_TM42, + "the flag stays unset until the pre text is dismissed") + +boxes[1].onDone() +T.eq(#boxes, 2, "dismissing the pre text opens the received box") +T.eq(boxes[2].text, "RED received\nTM42!", + "the received fallback is filled with player and item") +T.eq(game.save.inventory.TM_DREAM_EATER, 1, "TM42 reached the bag") +T.check(game.save.flags.EVENT_GOT_TM42 == true, "the event flag is set") +boxes[2].onDone() +T.eq(#boxes, 3, "the explanation box follows the receipt") +boxes[3].onDone() +T.check(finished, "the talk chain hands control back") + +-- Yellow-like: the extracted string exists, so it wins over the fallback +game = newGame({ ViridianCityFisherYouCanHaveThisText = "ROM STRING" }) +fisher(game, nil, nil, function() end) +T.eq(boxes[1].text, "ROM STRING", + "an extracted ViridianCityFisherYouCanHaveThisText beats the fallback") + +-- repeat visit: the flag routes straight to the explanation, no re-gift +game = newGame({ _ViridianCityFisherTM42ExplanationText = "EXPLAIN" }) +game.save.flags.EVENT_GOT_TM42 = true +fisher(game, nil, nil, function() end) +T.eq(#boxes, 1, "a second talk opens a single box") +T.eq(boxes[1].text, "EXPLAIN", "and it is the TM42 explanation") +T.eq(game.save.inventory.TM_DREAM_EATER, nil, "no duplicate TM42") + +T.finish("viridian_fisher_pre_bug775") diff --git a/tests/engine/warp_sprite_hidden_bug916.lua b/tests/engine/warp_sprite_hidden_bug916.lua new file mode 100644 index 00000000..5e6cd885 --- /dev/null +++ b/tests/engine/warp_sprite_hidden_bug916.lua @@ -0,0 +1,151 @@ +-- Engine invariant (#916): after the Fly / Dig departure animation ends, the +-- trainer sprite must stay hidden through the warp fade-out and only become +-- visible again when the arrival animation (flyArrive / teleport spin-down) +-- plays on the new map. +-- +-- Root cause: the player-hide guard only held while a departure animation +-- was live. flyAnim was nil'd the instant the bird finished path2, and the +-- teleportOut countdown cleared the spin fields at 0, but startWarpTo's +-- 32-frame fade keeps the overworld drawing beneath the veil (the Transition +-- is not isOpaque), so with the departure guard gone and the arrival not yet +-- armed, the standing sprite popped back in at the old cell for the whole +-- fade. +-- +-- The fix is a playerHidden flag on OverworldState: set when the departure +-- completes (flyAnim path2 / teleportOut countdown), cleared in startWarpTo's +-- midpoint the same tick the arrival arms, and folded into both player-draw +-- guards. This suite runs the REAL Transition + setMap headlessly and +-- asserts there is no fade frame where the player would draw bare. +-- +-- ROM-free (fixture dataset, no ROM boot): lives in tests/engine so the CI +-- headless tier runs it; also runnable standalone via +-- `luajit tests/engine/warp_sprite_hidden_bug916.lua`. + +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local T = require("tests.modkit") +local check, eq = T.check, T.eq + +local Data = T.fixtures.fresh() +-- fixture patches that let the overworld boot and run headlessly +Data.tilesets.FIX_OUT.tilesPerRow = 16 +Data.field.flyWarps = Data.field.flyWarps or {} +Data.field.playerSprites = { walk = "SPRITE_FIX_PLAYER" } +Data.field.waterTilesets = {} +Data.field.forcedMovement = { tiles = {} } + +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local StateStack = require("src.core.StateStack") +local Renderer = require("src.render.Renderer") +local SaveData = require("src.core.SaveData") +local Pokemon = require("src.pokemon.Pokemon") +local OW = require("src.world.OverworldController") + +Game.data = Data +Game.input = Input; Input:init() +Game.renderer = Renderer; Renderer:init() +Game.stack = StateStack; StateStack:init() +Game.save = SaveData.newGame() +Game.save.party = { Pokemon.new(Data, "FIXMON_A", 20) } +local stack = Game.stack + +-- The draw guard both entity passes use: the player sprite is skipped while +-- any of flyAnim / flyArrive / playerHidden is set. +local function playerHidden(ow) + return ow.flyAnim ~= nil or ow.flyArrive ~= nil or ow.playerHidden == true +end + +local function newOW() + stack:push(OW, "FIX_TOWN", 5, 6, "down") + local ow = stack:top() + Game.overworld = ow + return ow +end + +-- Drive `ow` until its departure + warp + arrival all complete, tracking the +-- fade window. Returns counters: fadeFrames / fadeFramesHidden (frames the +-- Transition was on top; of those, frames the player was hidden), gapFrames +-- (fade frames where NO arrival was active AND the player was NOT hidden -- +-- the regression this suite guards), arrivalFrame (first frame an arrival +-- animation armed), warpFrame (first frame a fade is up). +-- +-- Breaks once an arrival armed and then fully finished (no stale departure +-- or arrival animation, OW back on top); `maxFrames` is the safety net. +local function drive(ow, maxFrames) + local st = { fadeFrames = 0, fadeFramesHidden = 0, gapFrames = 0, + arrivalFrame = nil, warpFrame = nil } + for i = 1, maxFrames or 260 do + local fading = stack:top() ~= ow + stack:update() + if fading then + st.fadeFrames = st.fadeFrames + 1 + if playerHidden(ow) then st.fadeFramesHidden = st.fadeFramesHidden + 1 end + local arrivalActive = ow.flyArrive ~= nil or ow.player.spinDrop == true + if not arrivalActive and not playerHidden(ow) then + st.gapFrames = st.gapFrames + 1 + end + if st.warpFrame == nil then st.warpFrame = i end + end + if st.arrivalFrame == nil + and (ow.flyArrive ~= nil or ow.player.spinDrop == true) then + st.arrivalFrame = i + end + if st.arrivalFrame and stack:top() == ow + and ow.flyArrive == nil and ow.player.spinDrop ~= true + and not ow.player.inputLocked then + break -- departure + fade + arrival all finished + end + end + return st +end + +-- ------------------------------------------------------------------ dig/teleport +-- Departure spin (48) -> warp fade -> arrival spin-down. From the moment +-- the spin ends until the arrival arms, the sprite must never draw bare. +local ow = newOW() +local doneFired = false +ow:beginTeleportOut(function() + doneFired = true + ow.player.inputLocked = false -- the party-menu caller unlocks after the warp +end) +local st = drive(ow, 260) +check(st.warpFrame ~= nil, "dig departure ends and the warp fade begins") +check(st.fadeFrames > 0, "dig warp fade ran (" .. st.fadeFrames .. " frames)") +eq(st.gapFrames, 0, + "no dig fade frame leaves the player standing bare (#916)") +check(st.fadeFramesHidden >= st.fadeFrames - 1, + "dig fade hidden on every frame but the arrival-arming midpoint (" + .. st.fadeFramesHidden .. "/" .. st.fadeFrames .. ")") +check(st.arrivalFrame ~= nil, "dig arrival spin-down arms") +check(ow.playerHidden == false, "dig hide cleared on the new map") +check(doneFired, "dig onDone fires after the warp") +check(ow.player.spinDrop ~= true and ow.player.spinning == false, + "dig arrival spin-down completes") +check(not playerHidden(ow), "player drawable again after the dig landing") + +-- ------------------------------------------------------------------ fly +-- flap (24) + path1 (36) + hold (40) + path2 (33) = 133 frames of flyAnim, +-- then the fade, then the bird swoops in (flyArrive). Same invariant. +Data.field.flyWarps.FIX_ROUTE = { x = 4, y = 6 } +ow = newOW() +ow:flyTo("FIX_ROUTE") +st = drive(ow, 260) +check(st.warpFrame ~= nil, "fly departure ends and the warp fade begins") +-- flap (8*3) + path1 (12*3) + hold (40) + path2 (11*3) = 133 frames; the +-- warp fires on frame 133's update, so the fade is on top from loop frame 134 +eq(st.warpFrame, 134, "fly fade begins right after the bird''s exit path") +check(st.fadeFrames > 0, "fly warp fade ran (" .. st.fadeFrames .. " frames)") +eq(st.gapFrames, 0, + "no fly fade frame leaves the player standing bare (#916)") +check(st.fadeFramesHidden >= st.fadeFrames - 1, + "fly fade hidden on every frame but the arrival-arming midpoint (" + .. st.fadeFramesHidden .. "/" .. st.fadeFrames .. ")") +check(st.arrivalFrame ~= nil, "fly arrival swoop arms") +check(ow.playerHidden == false, "fly hide cleared on the new map") +check(ow.flyArrive == nil, "fly arrival swoop completes") +check(not ow.player.inputLocked, "fly landing releases player input") +check(not playerHidden(ow), "player drawable again after the fly landing") + +T.finish("warp_sprite_hidden_bug916") diff --git a/tests/engine/wide_battle_shake_bug562.lua b/tests/engine/wide_battle_shake_bug562.lua index 4f367a12..b445748c 100644 --- a/tests/engine/wide_battle_shake_bug562.lua +++ b/tests/engine/wide_battle_shake_bug562.lua @@ -74,6 +74,9 @@ local function battleWith(fx, sprites) growInScale = function() return nil end, drawBallRow = function() end, statusLabel = function() return "" end, + statusHUDVisible = function() return true end, + bottomUIVisible = function() return true end, + caughtMarkerVisible = function() return false end, } end diff --git a/tests/engine/world_map_overview_test.lua b/tests/engine/world_map_overview_test.lua new file mode 100644 index 00000000..32c540a2 --- /dev/null +++ b/tests/engine/world_map_overview_test.lua @@ -0,0 +1,78 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local Assets = require("src.render.Assets") +local WorldAPI = require("src.world.WorldAPI") + +Assets.imageData = function() + return { getPixel = function(_, x, y) + local shade = x >= 8 and 0 or ({ 1, 0, 2 / 3, 1 / 3 })[ + math.floor(y / 4) * 2 + math.floor(x / 4) + 1] + return shade, shade, shade, 1 + end } +end + +local api = WorldAPI.new({ stack = { states = {} } }, "tester") +local overview, err = api:mapOverview() +T.eq(overview, nil, "map overview is unavailable outside the overworld") +T.eq(err, "no overworld", "map overview reports why it is unavailable") + +local map = { + id = "TEST_MAP", widthCells = 2, heightCells = 2, + def = { + warps = { { x = 1, y = 0 } }, + objects = { { index = 1, x = 0, y = 1, item = "POTION" } }, + }, +} +function map:isWarpTileCell(x, y) return x == 1 and y == 0 end +function map:isWaterCell(x, y) return x == 0 and y == 1 end +function map:isWalkableCell(x, y) return x == 0 and y == 0 end +function map:tileAt(x) return x % 2 end + +local save = {} +local world = { + isOverworld = true, + map = map, + objectVisible = function(s, mapId, obj) + return not (s.itemsTaken and s.itemsTaken[mapId .. "_obj_" .. obj.index]) + end, +} +local game = { + save = save, + data = { field = { hiddenItems = { + TEST_MAP = { { x = 1, y = 1, item = "NUGGET" } }, + } } }, + stack = { states = { world } }, +} +api = WorldAPI.new(game, "tester") +overview = api:mapOverview() +T.eq(overview.mapId, "TEST_MAP", "map overview identifies the active map") +T.eq(overview.width, 2, "map overview reports its width") +T.eq(overview.height, 2, "map overview reports its height") +T.eq(overview.rows[1], ".+", "walkable land and warps are distinct") +T.eq(overview.rows[2], "~ ", "water and blocked terrain are distinct") +T.eq(overview.tileRows, nil, "tile overview is optional") +T.eq(#overview.markers, 3, "active exits and untaken items are marked") +T.eq(overview.markers[1].kind, "warp", "warp marker is semantic") +T.eq(overview.markers[2].kind, "item", "visible item marker is semantic") +T.eq(overview.markers[3].kind, "hidden", "hidden item marker is semantic") + +save.itemsTaken = { TEST_MAP_obj_1 = true } +save.hiddenTaken = { TEST_MAP_1_1 = true } +overview = api:mapOverview() +T.eq(#overview.markers, 1, "collected items disappear from the overview") +T.eq(overview.markers[1].kind, "warp", "exits remain after collecting items") + +map.tileset = { image = "test.png", tilesPerRow = 2 } +overview = api:mapOverview() +T.eq(overview.tileWidth, 4, "tile overview reports its width") +T.eq(overview.tileHeight, 4, "tile overview reports its height") +T.eq(overview.tileRows[1], "2323", "tile overview preserves average shading") +T.eq(overview.tileDetailWidth, 8, "detail overview reports its width") +T.eq(overview.tileDetailHeight, 8, "detail overview reports its height") +T.eq(overview.tileDetailRows[1], "03330333", + "detail overview preserves top tile quadrants") +T.eq(overview.tileDetailRows[2], "12331233", + "detail overview preserves bottom tile quadrants") + +T.finish("world map overview") diff --git a/tests/launcher_mods_install_zip_test.lua b/tests/launcher_mods_install_zip_test.lua index 0fb9d1d0..e7b117f9 100644 --- a/tests/launcher_mods_install_zip_test.lua +++ b/tests/launcher_mods_install_zip_test.lua @@ -188,6 +188,43 @@ local leftover = 0 for _ in pairs(stagedTemps) do leftover = leftover + 1 end eq(leftover, 0, "fallback cleans staged temp after install") +-- #801: a same-id copy under a different folder name is replaced too, so the +-- update cannot leave a shadow copy for discover()'s first-id-wins race +resetFs() +files["mods/WildsOfKanto-1.5.0/manifest.json"] = + ('{"id":"%s","name":"Old Copy","version":"0.9.0","entry":"main.lua"}') + :format(MOD_ID) +files["mods/WildsOfKanto-1.5.0/main.lua"] = "return function() end\n" +files["imports/mods/update.zip"] = "PK\3\4update" +ok, err = LauncherMods.installZip("imports/mods/update.zip", + { replace = true, expectId = MOD_ID }) +check(ok == true, "replace install succeeds over an odd-named copy (" + .. tostring(err) .. ")") +check(files["mods/WildsOfKanto-1.5.0/manifest.json"] == nil, + "odd-named same-id folder is removed by the replace") +check(files["mods/" .. MOD_ID .. "/manifest.json"] ~= nil, + "replace still lands in mods/") + +-- #834: a manifest-less mods/ tree (interrupted copy debris) must not +-- block a plain re-import as "already installed" +resetFs() +files["mods/" .. MOD_ID .. "/gfx/a.bin"] = "x" +files["imports/mods/again.zip"] = "PK\3\4again" +ok, err = LauncherMods.installZip("imports/mods/again.zip") +check(ok == true, "debris tree does not block re-import (" + .. tostring(err) .. ")") +check(files["mods/" .. MOD_ID .. "/gfx/a.bin"] == nil, + "debris is cleared by the re-import") + +-- a real installed copy still refuses a plain duplicate import +resetFs() +files["mods/" .. MOD_ID .. "/manifest.json"] = ARCHIVE[MOD_ID .. "/manifest.json"] +files["imports/mods/dup.zip"] = "PK\3\4dup" +ok, err = LauncherMods.installZip("imports/mods/dup.zip") +check(not ok, "a listed install still refuses a plain duplicate import") +check(tostring(err):find("already installed", 1, true), + "duplicate refusal still names already installed") + -- Restore love.filesystem = savedFs SaveData.portableBaseDir = savedSaveDataPortable diff --git a/tests/launcher_mods_shadow_copy_bug801_834_test.lua b/tests/launcher_mods_shadow_copy_bug801_834_test.lua new file mode 100644 index 00000000..65ee4ba7 --- /dev/null +++ b/tests/launcher_mods_shadow_copy_bug801_834_test.lua @@ -0,0 +1,225 @@ +-- #801 / #834: what the PLAYER sees after an update over a shadow copy. +-- The tier file (launcher_mods_install_zip_test.lua) asserts which folders +-- survive installZip; this one asserts the panel-facing contracts built on +-- top of them: LauncherMods.list() must report the NEW version after a +-- replace even when a same-id copy sits under an archive-named folder that +-- enumerates first (#801, "Updated ... to X" yet the old version kept +-- loading), and uninstall()/re-import must both recover a manifest-less +-- mods/ debris tree left by an interrupted copy (#834, "already +-- installed" with nothing showing in the panel). +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local S = require("tests.harness").suite("launcher mods shadow copy #801/#834") +local eq = S.eq +local check = S.check + +-- The real-world shape from the #801 report: a hand-unzipped copy kept the +-- archive's folder name. "W" sorts before "o" in the stub's sorted +-- enumeration, so the shadow folder enumerates first -- the exact ordering +-- that made discover()'s first-id-wins dedupe resolve the stale copy. +local MOD_ID = "overworld_wild_spawns" +local SHADOW = "mods/WildsOfKanto-1.5.0" +local NEW_VERSION = "1.7.1" + +local ARCHIVE = { + [MOD_ID .. "/manifest.json"] = + ('{"id":"%s","name":"Wilds of Kanto","version":"%s","entry":"main.lua"}') + :format(MOD_ID, NEW_VERSION), + [MOD_ID .. "/main.lua"] = "return function() end\n", +} + +local files, dirs, arch = {}, {}, {} + +local function resetFs() + for k in pairs(files) do files[k] = nil end + for k in pairs(dirs) do dirs[k] = nil end + for k in pairs(arch) do arch[k] = nil end +end + +local function dirChild(key, name) + if name == nil or name == "" then return key:match("^[^/]+") end + local prefix = name .. "/" + if key:sub(1, #prefix) ~= prefix then return nil end + return key:sub(#prefix + 1):match("^[^/]+") +end + +local function mapInfo(map, name, kind) + if map[name] ~= nil then return { type = kind or "file" } end + for key in pairs(map) do + if dirChild(key, name) then return { type = "directory" } end + end + return nil +end + +local vfs = {} + +function vfs.write(name, data) + files[name] = data + return true +end + +function vfs.read(name) + if arch[name] ~= nil then return arch[name] end + return files[name] +end + +function vfs.remove(name) + files[name] = nil + dirs[name] = nil + return true +end + +function vfs.createDirectory(name) + dirs[name] = true + return true +end + +function vfs.getInfo(name, kind) + local info = mapInfo(arch, name) + or mapInfo(files, name) + or mapInfo(dirs, name, "directory") + if info and kind and info.type ~= kind then return nil end + return info +end + +function vfs.getDirectoryItems(name) + local seen, items = {}, {} + local function add(child) + if child and not seen[child] then + seen[child] = true + items[#items + 1] = child + end + end + for key in pairs(arch) do add(dirChild(key, name)) end + for key in pairs(files) do add(dirChild(key, name)) end + for key in pairs(dirs) do add(dirChild(key, name)) end + table.sort(items) + return items +end + +function vfs.mount(_, point) + for rel, body in pairs(ARCHIVE) do + arch[point .. "/" .. rel] = body + end + return true +end + +function vfs.unmount() + for k in pairs(arch) do arch[k] = nil end + return true +end + +function vfs.newFileData(data, name) + return { __filedata = true, data = data, name = name } +end + +function vfs.getSaveDirectory() + return "/tmp/pokeport-shadow-copy-test" +end + +function vfs.getSource() + return nil +end + +local savedFs = love.filesystem +local savedCacheFs = package.loaded["src.import.CacheFs"] +local savedLauncherMods = package.loaded["src.mods.LauncherMods"] + +local SaveData = require("src.core.SaveData") +local savedPortableBase = SaveData.portableBaseDir +local savedPortableFs = SaveData.portableFs + +love.filesystem = vfs +package.loaded["src.import.CacheFs"] = nil +package.loaded["src.mods.LauncherMods"] = nil +-- Portable mode off: loadOptions/uninstall must stay on the stub vfs, or the +-- checkout's real save directory would leak into the test. +SaveData.portableBaseDir = function() return nil end +SaveData.portableFs = function() return nil end +local LauncherMods = require("src.mods.LauncherMods") + +local function versionOf(id) + for _, row in ipairs(LauncherMods.list()) do + if row.id == id then return row.version end + end + return nil +end + +-- #801: the shadow copy alone resolves as the mod (sanity for the setup), +-- and after a replace-install the panel row flips to the zip's version. +-- Pre-fix, installZip returned success but only rewrote mods/; the +-- shadow folder enumerated first and list() kept answering 1.5.0 forever. +resetFs() +files[SHADOW .. "/manifest.json"] = + ('{"id":"%s","name":"Wilds of Kanto","version":"1.5.0","entry":"main.lua"}') + :format(MOD_ID) +files[SHADOW .. "/main.lua"] = "return function() end\n" +eq(versionOf(MOD_ID), "1.5.0", "shadow copy resolves before the update") + +files["imports/mods/update.zip"] = "PK\3\4update" +local ok, err = LauncherMods.installZip("imports/mods/update.zip", + { replace = true, expectId = MOD_ID }) +check(ok == true, "replace over a shadow copy succeeds (" .. tostring(err) .. ")") +eq(err, MOD_ID, "replace reports the manifest id") +eq(versionOf(MOD_ID), NEW_VERSION, + "list() reports the zip's version after the replace") +check(files[SHADOW .. "/manifest.json"] == nil, + "shadow folder is gone, so no stale copy can win first-id-wins later") +eq(#LauncherMods.list(), 1, "the update leaves exactly one panel row") + +-- #801: Delete from the panel must take the shadow copy with it, or the +-- next boot resurrects the mod from the odd-named folder. +resetFs() +files[SHADOW .. "/manifest.json"] = ARCHIVE[MOD_ID .. "/manifest.json"] +files["mods/" .. MOD_ID .. "/manifest.json"] = ARCHIVE[MOD_ID .. "/manifest.json"] +ok, err = LauncherMods.uninstall(MOD_ID) +check(ok == true, "uninstall succeeds with a shadow copy present (" + .. tostring(err) .. ")") +check(files[SHADOW .. "/manifest.json"] == nil, + "uninstall removes the same-id shadow folder too") +check(files["mods/" .. MOD_ID .. "/manifest.json"] == nil, + "uninstall removes mods/") +eq(#LauncherMods.list(), 0, "nothing is left for the panel to show") + +-- #834: interrupted-copy debris (mods/ with no manifest.json) is +-- invisible to list() yet used to hard-block every plain re-import with +-- "a mod named '' is already installed". Both recovery paths the +-- player can reach must work: plain re-import, and Delete. +resetFs() +files["mods/" .. MOD_ID .. "/gfx/a.bin"] = "x" +eq(#LauncherMods.list(), 0, "debris tree shows no panel row") +files["imports/mods/again.zip"] = "PK\3\4again" +ok, err = LauncherMods.installZip("imports/mods/again.zip") +check(ok == true, "plain re-import over debris succeeds (" + .. tostring(err) .. ")") +check(files["mods/" .. MOD_ID .. "/gfx/a.bin"] == nil, + "re-import clears the debris file") +eq(versionOf(MOD_ID), NEW_VERSION, "re-import yields a listable mod") + +resetFs() +files["mods/" .. MOD_ID .. "/gfx/a.bin"] = "x" +ok, err = LauncherMods.uninstall(MOD_ID) +check(ok == true, "uninstall clears a debris-only tree (" + .. tostring(err) .. ")") +check(files["mods/" .. MOD_ID .. "/gfx/a.bin"] == nil, + "debris file is gone after uninstall") + +-- guard rail: with a healthy install and NO debris, the duplicate gate +-- still refuses a plain import with the same wording the panel shows +resetFs() +files["mods/" .. MOD_ID .. "/manifest.json"] = ARCHIVE[MOD_ID .. "/manifest.json"] +files["imports/mods/dup.zip"] = "PK\3\4dup" +ok, err = LauncherMods.installZip("imports/mods/dup.zip") +check(not ok, "healthy duplicate import is still refused") +check(tostring(err):find("already installed", 1, true), + "refusal keeps the already installed wording") + +-- Restore +love.filesystem = savedFs +SaveData.portableBaseDir = savedPortableBase +SaveData.portableFs = savedPortableFs +package.loaded["src.import.CacheFs"] = savedCacheFs +package.loaded["src.mods.LauncherMods"] = savedLauncherMods + +S.finish() diff --git a/tests/love_stub.lua b/tests/love_stub.lua index 06fb2a91..0e35f8af 100644 --- a/tests/love_stub.lua +++ b/tests/love_stub.lua @@ -74,11 +74,36 @@ stub.graphics = { -- newMesh / stencil stay absent on purpose: tools/save-editor/Theme.lua -- probes for them and falls back to flat fills, which is the path a -- headless run should take. - newFont = function(size) - local px = size or 12 + -- Accepts both real signatures: newFont(size) and + -- newFont(filename, size, hinting), the latter for the TTF text mode + -- (src/render/Font.lua). Width counts codepoints, not bytes, and CJK / + -- kana measure double, so tests can assert the wide-glyph metrics a real + -- pixel font (5px base, 11px double-width) exhibits without rasterizing. + newFont = function(a, b) + if type(a) == "string" then + -- real LÖVE raises on a missing file; callers pcall and fall back + local handle = io.open(a, "rb") + if not handle then error("Could not open file " .. a) end + handle:close() + end + local px = (type(a) == "number" and a) or b or 12 + local unit = math.max(1, px * 0.5) return { - getWidth = function(_, text) return #tostring(text) * math.max(1, px * 0.5) end, + getWidth = function(_, text) + text = tostring(text) + local w, i, n = 0, 1, #text + while i <= n do + local byte = text:byte(i) + local len = byte >= 0xF0 and 4 or byte >= 0xE0 and 3 + or byte >= 0xC0 and 2 or 1 + w = w + (byte >= 0xE1 and 2 or 1) * unit -- U+1000+: double width + i = i + len + end + return w + end, getHeight = function() return px end, + getBaseline = function() return px - 2 end, + setFilter = noop, } end, setFont = function(f) gstate.font = f end, @@ -323,6 +348,7 @@ function ImageData:getDimensions() return self.w, self.h end function ImageData:getPixel() return 0, 0, 0, 1 end function ImageData:setPixel() end function ImageData:mapPixel() end +function ImageData:paste() end function ImageData:encode() return { getString = function() return "" end } end stub.image = { @@ -337,6 +363,12 @@ stub.image = { end, } +-- Headless runs report the desktop OS so platform gates (GamepadMap's NX +-- check, the touch-overlay filter) take their desktop branch. +stub.system = { + getOS = function() return "OS X" end, +} + -- Desktop / headless: full-window safe area (matches LÖVE's fallback). stub.window = { getSafeArea = function() diff --git a/tests/mod_battle_tests.lua b/tests/mod_battle_tests.lua index 34f696c5..e42df2e4 100644 --- a/tests/mod_battle_tests.lua +++ b/tests/mod_battle_tests.lua @@ -52,7 +52,8 @@ local function makeGame(party) function stack:pop() return table.remove(self.states) end function stack:top() return self.states[#self.states] end return { data = Data, save = save, stack = stack, - input = { wasPressed = function() return true end } } + input = { wasPressed = function() return true end, + isDown = function() return true end } } end local function pump(battle, limit) diff --git a/tests/mod_graphics_tests.lua b/tests/mod_graphics_tests.lua index 8100a907..8e310e63 100644 --- a/tests/mod_graphics_tests.lua +++ b/tests/mod_graphics_tests.lua @@ -428,16 +428,42 @@ local spriteReg = Registry.new("sprites", Schemas.REGISTRIES.sprites) spriteReg:register("SPRITE_TITLE_LOGO", { image = "mods/logo/logo.png", frames = 1, trueColor = true }, "logo_mod") +spriteReg:register("SPRITE_LARGE_ACTOR", + { image = "mods/actor/actor.png", frames = 6, + walker = true, frameWidth = 32, frameHeight = 24, + anchorX = 16, anchorY = 24, trueColor = true }, + "actor_mod") local logoDef = spriteReg:get("SPRITE_TITLE_LOGO") check(Schemas.check(Schemas.REGISTRIES.sprites, "sprites", "SPRITE_TITLE_LOGO", logoDef, "register"), "a trueColor sprites record validates against the catalog schema") check(logoDef.trueColor == true, "and keeps the flag through the merge") +local largeDef = spriteReg:get("SPRITE_LARGE_ACTOR") +check(Schemas.check(Schemas.REGISTRIES.sprites, "sprites", "SPRITE_LARGE_ACTOR", + largeDef, "register"), + "a variable-size sprites record validates against the catalog schema") Renderer:init() local plainSprite = SpriteRenderer.new( { image = "assets/generated/sprites/red.png", frames = 1 }) local litSprite = SpriteRenderer.new(logoDef) +local largeSprite = SpriteRenderer.new(largeDef) +check(plainSprite.frameWidth == 16 and plainSprite.frameHeight == 16 + and plainSprite.anchorX == 8 and plainSprite.anchorY == 16, + "legacy sprite definitions keep the vanilla frame geometry") +local frameGeometry = largeSprite:getFrameGeometry(5) +check(frameGeometry.frame == 5 and frameGeometry.x == 0 + and frameGeometry.y == 120 and frameGeometry.width == 32 + and frameGeometry.height == 24 and frameGeometry.anchorX == 16 + and frameGeometry.anchorY == 24, + "frame geometry exposes a larger sheet rectangle and anchor") +local poseGeometry = largeSprite:getPoseGeometry("right", 1, true) +check(poseGeometry.frame == 5 and poseGeometry.mirror == true + and poseGeometry.quad == largeSprite.frames[5], + "pose geometry follows walker frame selection and right mirroring") +local originX, originY = largeSprite:getScreenOrigin(32, 32, 0, 0) +check(originX == 24 and originY == 20, + "a custom anchor keeps a larger sprite grounded at its cell") Renderer:beginFrame(true) check(#PaletteFX.trueColorRects("ui") == 0 @@ -471,6 +497,27 @@ check(#worldDrawn == 2, "the reported zone joins the world list endFrame blits") check(worldDrawn[1].shader and worldDrawn[2].shader == false, "the colorized pass runs first, then the sprite's rect with no shader") +-- Larger true-color frames claim their actual extent, and fishing's top-half +-- path reserves only the bottom 8-pixel tile for the overlay. +Renderer:beginFrame(true) +Renderer:beginWorldPass() +largeSprite:draw(32, 32, 0, 0, "down", 0, false) +local largeRects = PaletteFX.trueColorRects("world") +check(#largeRects == 1 and largeRects[1].x == 24 and largeRects[1].y == 20 + and largeRects[1].w == 32 and largeRects[1].h == 24, + "a larger trueColor sprite reports its full anchored extent") +Renderer:endWorldPass() + +Renderer:beginFrame(true) +Renderer:beginWorldPass() +largeSprite:draw(32, 32, 0, 0, "down", 0, false, true) +local topRects = PaletteFX.trueColorRects("world") +check(#topRects == 1 and topRects[1].h == 16 + and largeSprite.halfFrames[0].y == 0 + and largeSprite.halfFrames[0].h == 16, + "the fishing overlay keeps a larger frame's bottom tile clear") +Renderer:endWorldPass() + -- the same path on the UI canvas, which is where a full-color title logo -- or menu portrait lands Renderer:beginFrame(false) @@ -612,9 +659,13 @@ check(PaletteFX.usesGbcPack(), "redpp mode selects the gbc pack") local gbc = PaletteFX.gbcPack() check(gbc ~= nil and gbc.palettes.BULBASAUR ~= nil, "data/palettes_gbc.lua ships per-species pals") -check(PaletteFX.monPalName({ palettes = nil }, "BULBASAUR") == "BULBASAUR", +-- the pack's species map follows pokered-gbc's Gen 1 (non-GEN_2_GRAPHICS) +-- palette assignments -- data/pokemon/palettes.asm ELSE branch -- so +-- Bulbasaur wears GREENMON, not a per-species PAL_BULBASAUR authored for +-- Gen 2 sprite art (see the pokemon table comment in data/palettes_gbc.lua) +check(PaletteFX.monPalName({ palettes = nil }, "BULBASAUR") == "GREENMON", "RED++ monPalName resolves to the species palette id") -check(PaletteFX.monPal({ palettes = nil }, "BULBASAUR") == gbc.palettes.BULBASAUR, +check(PaletteFX.monPal({ palettes = nil }, "BULBASAUR") == gbc.palettes.GREENMON, "RED++ monPal reads the species colors without a ROM pack") check(PaletteFX.pal({ palettes = nil }, "ROUTE") == gbc.palettes.ROUTE, "RED++ still has ROUTE (aliased from VIRIDIAN)") @@ -813,9 +864,9 @@ do Renderer:beginWorldPass() Renderer:endWorldPass() wipe:draw() - check(Renderer.battleCascadeProg ~= nil - and Renderer.battleCascadeProg > 0 - and Renderer.battleCascadeProg < 1, + check(Renderer.battleWipe ~= nil + and Renderer.battleWipe.prog > 0 + and Renderer.battleWipe.prog < 1, "battle wipe publishes mid-progress cascade to the renderer") rects = {} Renderer:endFrame(nil, fullWorldZones()) @@ -911,7 +962,8 @@ local vanilla = BattleTransition.new({ stack = stack }, nil, { trainer = true, stronger = true }) check(vanilla.style == "spiralout", "the vanilla 3-bit select is the hook's default (trainer+stronger)") -check(vanilla.wipeLen == 40, "the selected wipe brings its own length") +check(vanilla.wipeLen == BattleTransition.STYLES.spiralout.frames, + "the selected wipe brings its own length") local savedRuntime = { events = Runtime.events, hooks = Runtime.hooks, errors = Runtime.errors } @@ -924,7 +976,8 @@ hooks:wrap("transition.style", function(nextLink, ctx) end, 0, "test") local hooked = BattleTransition.new({ stack = stack }, nil, { trainer = true }) check(hooked.style == "hstripes", "a transition.style hook picks the wipe") -check(hooked.wipeLen == 24, "the hooked style brings its own length") +check(hooked.wipeLen == BattleTransition.STYLES.hstripes.frames, + "the hooked style brings its own length") check(seenCtx.trainer == true and seenCtx.stronger == nil, "the hook receives the selection bits as context") diff --git a/tests/mod_loader_tests.lua b/tests/mod_loader_tests.lua index 4b4633a6..e14d2b78 100644 --- a/tests/mod_loader_tests.lua +++ b/tests/mod_loader_tests.lua @@ -271,6 +271,38 @@ do "and nothing is disabled off a failed migration") end +-- ------- force_enable_env: an env var can override a saved disable +-- (src/mods/Loader.lua's enable-resolution block, added for a mod that +-- cannot function disabled on the one build where its env var is set -- +-- e.g. a platform-launcher bridge mod). +do + local forceFiles = { + ["options.lua"] = "return { mods = { forced = false } }", + ["mods/forced/manifest.json"] = + [[{"id":"forced","name":"forced","version":"1.0.0","entry":"main.lua",]] + .. [["force_enable_env":"SOME_TEST_ENV"}]], + ["mods/forced/main.lua"] = "return function(mod) end", + } + + local realGetenv = os.getenv + os.getenv = function(name) + if name == "SOME_TEST_ENV" then return "1" end + return realGetenv(name) + end + local onLoader = Loader.new({ fs = memfs(forceFiles) }) + check(onLoader:load({ pokemon = {} }) == true, + "force_enable_env: load succeeds with the env var set") + check(onLoader.mods.forced.enabled == true, + "a matching force_enable_env re-enables a mod saved as disabled") + os.getenv = realGetenv + + local offLoader = Loader.new({ fs = memfs(forceFiles) }) + check(offLoader:load({ pokemon = {} }) == true, + "force_enable_env: load succeeds with the env var unset") + check(offLoader.mods.forced.enabled == false, + "with the env var unset, the saved disable is left alone") +end + -- leave shared singletons the way we found them for later chained tests local StateStack = require("src.core.StateStack") while StateStack:top() do StateStack:pop() end diff --git a/tests/mod_manifest_tests.lua b/tests/mod_manifest_tests.lua index d6c615e9..569479d2 100644 --- a/tests/mod_manifest_tests.lua +++ b/tests/mod_manifest_tests.lua @@ -105,6 +105,7 @@ local full = Manifest.validate({ api = 2, profile = "overhaul", permissions = { "network" }, dependencies = { "colorlib@^1.2" }, conflicts = { "always_noon" }, options_schema = "options.lua", assets_transforms = "transforms.lua", + force_enable_env = "SOME_ENV", }, "mods/full") check(full.api == 2 and full.profile == "overhaul", "api and profile parse") check(full.affects_link == true, "overhaul defaults to affecting link play") @@ -115,6 +116,7 @@ check(full.conflictSpecs[1].id == "always_noon" and full.conflictSpecs[1].range "a bare conflict entry has no range") check(full.options_schema == "options.lua" and full.assets_transforms == "transforms.lua", "declared files are kept") +check(full.force_enable_env == "SOME_ENV", "force_enable_env is kept") -- ------- github / experimental / incompatible local gh = Manifest.validate({ diff --git a/tests/mod_qol_hooks_tests.lua b/tests/mod_qol_hooks_tests.lua index 2d4157ef..dfa425b2 100644 --- a/tests/mod_qol_hooks_tests.lua +++ b/tests/mod_qol_hooks_tests.lua @@ -11,6 +11,8 @@ local Stats = require("src.pokemon.Stats") local Zoom = require("src.render.Zoom") local ListMenu = require("src.ui.ListMenu") local NamingScreen = require("src.ui.NamingScreen") +local TextBox = require("src.render.TextBox") +local PartyMenu = require("src.ui.PartyMenu") local Player = require("src.world.Player") local Music = require("src.core.Music") @@ -124,6 +126,12 @@ end do Zoom.reset() + -- Zoom.reset() clears only the offset: allowSurvey is the performance + -- tier's clamp (Game:applyOptions), and an earlier suite in the same + -- process may have applied a LOW tier. Pin the vanilla precondition and + -- restore whatever the run had afterwards. + local savedSurvey = Zoom.allowSurvey + Zoom.allowSurvey = true local lo, hi = Zoom.offsetRange(4) check(lo == -3 and hi == 4, "vanilla zoom.range is (1-S, S)") local unsub = wrap("zoom.range", function(next, a, b, S) @@ -139,6 +147,7 @@ do unsub() Zoom.reset() check(Zoom.scale(4) == 4, "unwrapped zoom returns to FIT") + Zoom.allowSurvey = savedSurvey end -- ------- battle.overlay (shiny sparkles / HUD chrome) @@ -154,6 +163,71 @@ do unsub() end +-- ------- battle UI visibility (companion / alternate renderers) + +do + local BattleState = require("src.battle.BattleState") + check(BattleState.bottomUIVisible({ phase = "menu" }), + "battle bottom UI is visible without a mod") + local seen + local unsub = wrap("battle.bottom_ui_visible", function(_, state) + seen = state + return false + end) + check(not BattleState.bottomUIVisible({ phase = "messages" }), + "a mod can hide the battle text and menu layer") + local text = setmetatable({}, TextBox) + text:draw() + check(seen == text, "pushed text boxes use the same visibility hook") + unsub() + check(BattleState.bottomUIVisible({ phase = "moveSelect" }), + "battle bottom UI returns when the hook is removed") + + check(BattleState.statusHUDVisible({}), + "battle status HUD is visible without a mod") + unsub = wrap("battle.status_hud_visible", function() return false end) + check(not BattleState.statusHUDVisible({}), + "a mod can hide the battle status HUD") + unsub() + check(BattleState.statusHUDVisible({}), + "battle status HUD returns when the hook is removed") +end + +-- ------- grid navigation ownership (alternate menu renderers) + +do + local BattleState = require("src.battle.BattleState") + local battle = { wideLayout = function() return false end } + check(not BattleState.moveGridNavigation(battle), + "classic move navigation stays a list without a mod") + local unsub = wrap("battle.move_grid_navigation", function() return true end) + check(BattleState.moveGridNavigation(battle), + "a mod can opt the classic move menu into grid navigation") + unsub() + battle.wideLayout = function() return true end + check(BattleState.moveGridNavigation(battle), + "the native wide move grid remains enabled without a mod") + + local game = { + save = { party = { {}, {}, {}, {}, {}, {} } }, + input = { wasPressed = function(_, key) return key == "down" end }, + } + local field = PartyMenu.new(game) + unsub = wrap("ui.party.grid_navigation", function() return true end) + field:update(0) + check(field.index == 2, + "field party navigation remains a native list when the hook is active") + game.partyMenuSavedIndex = nil + local menu = PartyMenu.new(game, { battle = {} }) + menu:update(0) + check(menu.index == 3 and game.partyMenuSavedIndex == 3, + "a battle party can follow and remember a companion grid") + unsub() + menu:update(0) + check(menu.index == 4, + "removing the hook restores native list navigation immediately") +end + -- ------- music.volume (distance / indoor muffling) do diff --git a/tests/mod_ui_tests.lua b/tests/mod_ui_tests.lua index 49169fb4..51bde011 100644 --- a/tests/mod_ui_tests.lua +++ b/tests/mod_ui_tests.lua @@ -282,10 +282,13 @@ local function optGame() end local om = OptionsMenu.new(optGame()) local WANT_IDS = { "textSpeed", "animations", "battleStyle", "battleLayout", + "battleFit", "battleBg", "uiLayout", "ruleset", "musicVol", "sfxVol", "musicFilter", "performance", "colors", - "tilt", "gbcfx", "zoom", "voidFill", "videoMode", "fpsCap", - "speed", "mods", "controls" } + "tilt", "gbcfx", "zoom", "voidFill", "videoMode", + "faithfulRes", "fpsCap", + "speedOverworld", "speedBattle", "speedMenu", + "mods", "controls" } check(#om.rows == #WANT_IDS, "vanilla options row count (plus MODS/CONTROLS)") for i, id in ipairs(WANT_IDS) do check(om.rows[i].id == id, "options row order: " .. id) @@ -293,11 +296,11 @@ end -- ruleset row cycles the sorted non-hidden registry ids showing name om.game.save.options.ruleset = "gen1_faithful" -check(om.rows[5].value(om.game) == "GEN 1", "ruleset row shows record.name") -om.rows[5].step(om.game, 1) +check(om.rows[8].value(om.game) == "GEN 1", "ruleset row shows record.name") +om.rows[8].step(om.game, 1) check(om.game.save.options.ruleset == "modern_clean", "ruleset row cycles sorted registry ids") -om.rows[5].step(om.game, 1) +om.rows[8].step(om.game, 1) check(om.game.save.options.ruleset == "gen1_faithful", "hidden rulesets are excluded from the cycle") @@ -316,41 +319,42 @@ check(om.game.save.options.battleLayout == "wide", "battle layout flips to WIDE" check(om.rows[4].value(om.game) == "WIDE", "the WIDE layout renders its label") om.rows[4].step(om.game, 1) check(om.game.save.options.battleLayout == "og", "battle layout flips back") -om.rows[6].step(om.game, -1) +om.rows[9].step(om.game, -1) check(om.game.save.options.musicVol == 6, "music volume steps down") -for _ = 1, 10 do om.rows[6].step(om.game, -1) end +for _ = 1, 10 do om.rows[9].step(om.game, -1) end check(om.game.save.options.musicVol == 0, "music volume clamps at 0") --- ZOOM / VOID FILL rows (indices shifted +1 by the PERFORMANCE row spliced --- in ahead of COLORS) +-- ZOOM / VOID FILL rows (indices track WANT_IDS above; the battle +-- composition rows -- BATTLE SIZE / BATTLE BG / UI LAYOUT -- sit ahead of +-- RULESET, and FAITHFUL RATIO lands between VIDEO MODE and MAX FPS) local Zoom = require("src.render.Zoom") local TileRenderer = require("src.render.TileRenderer") om.game.save.options.zoom = 0 Zoom.offset = 0 -check(om.rows[13].value(om.game) == "FIT", "ZOOM row shows FIT at offset 0") -om.rows[13].step(om.game, 1) +check(om.rows[16].value(om.game) == "FIT", "ZOOM row shows FIT at offset 0") +om.rows[16].step(om.game, 1) check(om.game.save.options.zoom == 1 and Zoom.offset == 1, "ZOOM row steps to IN1") -om.rows[14].step(om.game, 1) +om.rows[17].step(om.game, 1) check(om.game.save.options.voidFill == "water" and TileRenderer.voidFill == "water", "VOID FILL row cycles TREES → WATER") -om.rows[14].step(om.game, 1) +om.rows[17].step(om.game, 1) check(om.game.save.options.voidFill == "black", "VOID FILL steps to BLACK") -om.rows[14].step(om.game, 1) +om.rows[17].step(om.game, 1) check(om.game.save.options.voidFill == "trees", "VOID FILL wraps to TREES") -- the MAX FPS row cycles the render-cap steps and shows the value plain om.game.save.options.fpsCap = nil -check(om.rows[16].value(om.game) == "60", +check(om.rows[20].value(om.game) == "60", "MAX FPS row defaults to 60 with no saved cap") -om.rows[16].step(om.game, 1) +om.rows[20].step(om.game, 1) check(om.game.save.options.fpsCap == 75, "MAX FPS steps up from 60 to 75") -check(om.rows[16].value(om.game) == "75", "the MAX FPS row renders the cap") +check(om.rows[20].value(om.game) == "75", "the MAX FPS row renders the cap") om.game.save.options.fpsCap = 160 -om.rows[16].step(om.game, 1) +om.rows[20].step(om.game, 1) check(om.game.save.options.fpsCap == 30, "MAX FPS wraps past the ceiling to 30") -om.rows[16].step(om.game, -1) +om.rows[20].step(om.game, -1) check(om.game.save.options.fpsCap == 160, "MAX FPS wraps back down to the ceiling") -- ------- FrameCap normalize / cycle (issue #88) @@ -380,7 +384,7 @@ check(FrameCap.current == 60, "FrameCap.applyOptions defaults a missing key to 6 -- the MODS row is the manager's discoverable home local mgGame = optGame() om = OptionsMenu.new(mgGame) -om.rows[18].activate(mgGame) +om.rows[24].activate(mgGame) check(getmetatable(mgGame.stack:top()) == ManagerState, "the MODS row opens the manager") check(mgGame.stack:top().screenId == "ManagerState", @@ -390,7 +394,7 @@ check(mgGame.stack:top().screenId == "ManagerState", local BindingsMenu = require("src.ui.BindingsMenu") local cbGame = optGame() om = OptionsMenu.new(cbGame) -om.rows[19].activate(cbGame) +om.rows[25].activate(cbGame) local bm = cbGame.stack:top() check(getmetatable(bm) == BindingsMenu, "the CONTROLS row opens the rebind list") @@ -435,9 +439,12 @@ local Input = require("src.core.Input") local gpGame = { stack = newStack() } local sawPad gpGame.stack:push({ onGamepadPressed = function(_, b) sawPad = b end }) +-- gamepadpressed reads Input:isDown("select") for the display-chord and +-- shoulder-hotkey gates before it routes to the capturing state, so the +-- button state table must exist first +Input:init() Game.gamepadpressed(gpGame, nil, "y") check(sawPad == "y", "pad buttons reach a capturing top state") -Input:init() gpGame.stack:pop() Game.gamepadpressed(gpGame, nil, "a") Input:step() @@ -510,6 +517,21 @@ check(#pm.subItems == 2, "a non-table submenu result keeps the vanilla list") hooks:removeOwner("bad") pm.submenu = nil +-- ------- #768: the party cursor persists until a battle +-- (PartyMenuInit reads wPartyAndBillsPCSavedMenuItem, HandlePartyMenuInput +-- writes it back; InitBattleVariables / end_of_battle.asm zero it) +pgame.save.party[2] = { species = "PIKACHU", hp = 10, stats = { hp = 10 }, + level = 5, moves = { { id = "TACKLE" } } } +press(pm, "down") +check(pgame.partyMenuSavedIndex == 2, "the party cursor is saved on move") +local pm2 = PartyMenu.new(pgame) +check(pm2.index == 2, "reopening the party menu keeps the cursor (#768)") +pgame.save.party[2] = nil +check(PartyMenu.new(pgame).index == 1, + "a shrunken party clamps the saved cursor back into range") +pgame.partyMenuSavedIndex = nil -- a battle clears it (InitBattleVariables) +check(PartyMenu.new(pgame).index == 1, "a battle resets the party cursor") + -- ------- battle PKMN: SWITCH / STATS / CANCEL (#180) local switched local bgame = partyGame() @@ -555,9 +577,9 @@ do pm.game = sgame sgame.stack:push(pm) press(pm, "a") -- open the submenu - check(pm.subItems[#pm.subItems].action == "strength", - "the strength row is listed with badge + move") - pm.subIndex = #pm.subItems + check(pm.subItems[1].action == "strength", + "the strength row is listed with badge + move, above STATS/SWITCH (#768)") + pm.subIndex = 1 press(pm, "a") -- run STRENGTH local states = sgame.stack.states check(#states == 2 and states[1] == pm and states[2].pages ~= nil, @@ -679,8 +701,14 @@ do end -- issue #133: title menu / continue overlays must not inherit LOGO2/LOGO1 --- (blue/red UI ink). A trailing trueColor zone covers the overlay box. +-- (blue/red UI ink). A trailing GRAYS zone covers the overlay box: through +-- the shade-remap shader it is the identity for the box's DMG shades, so +-- pass-through modes keep #133's white paper / black ink, while the mono +-- and inverted display modes still recolor it with the rest of the screen +-- (a trueColor rect skipped the shader and left a raw white hole over a +-- CLASSIC pea-green title, #870). do + local PaletteFX = require("src.render.PaletteFX") local logo2 = { { 255, 255, 255 }, { 230, 197, 0 }, { 148, 156, 148 }, { 41, 99, 181 }, } @@ -709,8 +737,8 @@ do menu.titleUiBox = { 0, 0, 12, 3 } game.stack:push(menu) local withMenu = TitleState.sgbPalettes(title, game) - check(withMenu and #withMenu == 4 and withMenu[4].colors == false, - "title menu adds a trueColor overlay zone") + check(withMenu and #withMenu == 4 and withMenu[4].colors == PaletteFX.GRAYS, + "title menu adds a DMG-grays overlay zone (#870)") check(withMenu[4].x == 0 and withMenu[4].y == 0 and withMenu[4].w == 13 * 8 and withMenu[4].h == 4 * 8, "menu overlay covers the CONTINUE/NEW GAME box") @@ -718,8 +746,8 @@ do game.stack:pop() game.stack:push({ titleUiBox = { 4, 7, 19, 16 } }) local withCont = TitleState.sgbPalettes(title, game) - check(withCont and #withCont == 4 and withCont[4].colors == false, - "continue-info overlay adds a trueColor zone") + check(withCont and #withCont == 4 and withCont[4].colors == PaletteFX.GRAYS, + "continue-info overlay adds a DMG-grays zone (#870)") check(withCont[4].x == 4 * 8 and withCont[4].y == 7 * 8 and withCont[4].w == 16 * 8 and withCont[4].h == 10 * 8, "continue overlay matches DisplayContinueGameInfo's box") diff --git a/tests/mod_world_tests.lua b/tests/mod_world_tests.lua index 60aefb97..4deb2640 100644 --- a/tests/mod_world_tests.lua +++ b/tests/mod_world_tests.lua @@ -823,6 +823,29 @@ do and caught.enemy.mon.species == "TANGELA", "and it is the species the authored encounter table names") + -- Trainer battles do not arm the wild-encounter cooldown. + walker.wildEncounterGraceSteps = 0 + walker:afterBattle("win", { kind = "trainer" }) + check(walker.wildEncounterGraceSteps == 0, + "trainer battles do not start the wild encounter grace period") + + -- pokered grants three completed steps after a wild battle before the + -- next random battle can start (end_of_battle.asm + home/overworld.asm). + local finishedWild = caught + walker:afterBattle("run", finishedWild) + caught = nil + withBuses(function(_, hooks) + hooks:wrap("encounter.roll", function() + return { species = "TANGELA", level = 5 } + end, 0, "grace-period") + for step = 1, 3 do + pcall(walker.onStepComplete, walker) + check(caught == nil, "wild encounter grace period blocks step " .. step) + end + pcall(walker.onStepComplete, walker) + check(caught ~= nil, "wild encounter is eligible on step 4") + end) + -- the same walk with an encounter.roll wrapper never starts a battle withBuses(function(_, hooks) hooks:wrap("encounter.roll", function() return nil end, 0, "nuzlocke") @@ -893,6 +916,76 @@ do check(value == nil and err == "no overworld", "npc() off the world") value, err = api:queueScript({}) check(value == nil and err == "no overworld", "queueScript() off the world") + value, err = api:startWildBattle("PIDGEY", 5) + check(value == nil and err == "no overworld", "startWildBattle() off the world") +end + +-- startWildBattle: what regresses is the handoff, not the battle. A mod that +-- builds a BattleState and pushes it itself still fights and still levels; it +-- silently loses onFinish -> afterBattle (evolutions, blackout-on-loss) and +-- pushBattle (entry wipe, battle theme). Shipped mods have hit exactly this. +do + -- the real dataset, not fixture(): a battle reaches for type_chart, items, + -- battle_anims and more, and this block only reads + local data = Data + local state, game = liveWorld(data) + -- the handoff runs through these three, so each needs the live game + for _, fn in ipairs({ "pushBattle", "isDungeonTransitionMap", "afterBattle" }) do + check(bindGame(OW[fn], game), fn .. " binds Game") + end + state:setMap("PALLET_TOWN", 5, 6, "down", { via = "boot" }) + + local Pokemon = require("src.pokemon.Pokemon") + local api = WorldAPI.new(game, "tester") + + local value, err = api:startWildBattle("NOT_A_MON", 5) + check(value == nil and err:find("unknown species", 1, true), + "an unknown species refuses and names it") + -- Pokemon.new writes the level through into the stat calc and the exp curve + -- verbatim, so a fraction has to be refused rather than rounded downstream + for _, lv in ipairs({ 0, 101, "nope", 5.5 }) do + check(api:startWildBattle("PIDGEY", lv) == nil, + "level " .. tostring(lv) .. " refuses") + end + + local caterpie = Pokemon.new(data, "CATERPIE", 6) + game.save.party = { caterpie } + check(api:startWildBattle("PIDGEY", 25) == true, "a wild battle starts") + + -- pushBattle pushes the entry transition, which pushes the battle from its + -- own callback; awardExp is the BattleState marker (screenId would not work, + -- only Screens.push stamps that and pushBattle pushes the battle directly) + check(game.stack:top() ~= nil and game.stack:top().awardExp == nil, + "the entry transition goes on first") + -- overworld() resolves the world from UNDER the battle, so a second call + -- while one is up has to refuse rather than stack another + check(api:startWildBattle("PIDGEY", 5) == nil, + "a battle already running refuses") + + local battle + for _ = 1, 400 do + local t = game.stack:top() + if t and t.awardExp then battle = t break end + if t and t.update then t:update(1 / 60) else break end + end + check(battle ~= nil, "the transition hands off to the battle") + + battle.participants = { [caterpie] = true } + battle:awardExp() + check(caterpie.level >= 7, "the mon levels past its evolution threshold") + check(battle.leveledUp and battle.leveledUp[caterpie], + "awardExp records the level-up for EvolveAfterBattle") + + game.stack:pop() + battle.onFinish("win") + for _ = 1, 12 do + local t = game.stack:top() + if not t or t.screenId == "EvolutionState" then break end + game.stack:pop() + if t.onDone then t.onDone() end + end + check(game.stack:top() and game.stack:top().screenId == "EvolutionState", + "the win reaches the evolution screen") end do diff --git a/tests/modkit/cases/checkpoint_cross_mod.lua b/tests/modkit/cases/checkpoint_cross_mod.lua new file mode 100644 index 00000000..91e1a504 --- /dev/null +++ b/tests/modkit/cases/checkpoint_cross_mod.lua @@ -0,0 +1,342 @@ +-- Cross-mod checkpoint ownership and lifecycle contract through public API only. +-- The Pokemon metadata case models masterwebx/SHINY_POKEMON 1.0.8 at 2141b2e: +-- shiny identity is data on the plain Pokemon record (`dvs` plus `shiny`). + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local T = require("tests.harness").suite("checkpoint cross-mod compatibility") +local BattleState = require("src.battle.BattleState") +local Fixtures = require("tests.modkit").fixtures +local GameMethods = require("src.core.Game") +local Loader = require("src.mods.Loader") +local Pokemon = require("src.pokemon.Pokemon") +local Runtime = require("src.mods.Runtime") +local SaveData = require("src.core.SaveData") +local StateStack = require("src.core.StateStack") +local Stats = require("src.pokemon.Stats") + +local savedEvents, savedHooks = Runtime.events, Runtime.hooks +local oldGetRandomState = love.math.getRandomState +local oldSetRandomState = love.math.setRandomState +local rngState = "cross-mod-rng-A" +love.math.getRandomState = function() return rngState end +love.math.setRandomState = function(state) rngState = state end + +local function memfs(files) + return { + read = function(path) return files[path] end, + write = function(path, body) files[path] = body return true end, + remove = function(path) files[path] = nil return true end, + createDirectory = function() return true end, + getInfo = function(path) + if files[path] then return { type = "file" } end + local prefix = path .. "/" + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then return { type = "directory" } end + end + return nil + end, + load = function(path) + if not files[path] then return nil, "no file: " .. path end + return load(files[path], path) + end, + getDirectoryItems = function(path) + local prefix, seen, out = path .. "/", {}, {} + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then + local child = key:sub(#prefix + 1):match("^[^/]+") + if child and not seen[child] then + seen[child] = true + out[#out + 1] = child + end + end + end + table.sort(out) + return out + end, + } +end + +local function shinyDvs() + return { attack = 2, defense = 10, speed = 10, special = 10, hp = 0 } +end + +local function ordinaryDvs() + return { attack = 1, defense = 1, speed = 1, special = 1, hp = 15 } +end + +local function setPokemonIdentity(data, mon, shiny) + mon.dvs = shiny and shinyDvs() or ordinaryDvs() + mon.shiny = shiny and true or false + mon.stats = Stats.calc(data.pokemon[mon.species], mon.level, mon.dvs, mon.statExp) + mon.hp = math.min(mon.hp or mon.stats.hp, mon.stats.hp) +end + +local function setBattlerIdentity(data, battler, shiny) + setPokemonIdentity(data, battler.mon, shiny) + battler.curStats = battler.mon.stats + battler.shownHP = battler.mon.hp + battler.shownStatus = battler.mon.status +end + +local function makeGame() + local data = Fixtures.fresh() + local save = SaveData.newGame() + save.meta.playthroughId = "cross-mod-playthrough" + save.party = { Pokemon.new(data, "FIXMON_A", 20) } + SaveData.validate(save, data) + save.player.map, save.player.x, save.player.y = "FIX_TOWN", 2, 3 + save.player.facing, save.player.surfing = "left", false + save.options.modOptions = {} + + local stack = setmetatable({ states = {} }, { __index = StateStack }) + local game + local ow = { + map = { id = "FIX_TOWN" }, + player = { cellX = 2, cellY = 3, facing = "left", surfing = false }, + runner = { isRunning = function() return false end }, + parallelRunners = {}, pendingScripts = {}, parallelQueue = {}, scriptMoves = {}, + } + function ow:captureSave(target) + target.player.map = self.map.id + target.player.x, target.player.y = self.player.cellX, self.player.cellY + target.player.facing = self.player.facing + target.player.surfing = self.player.surfing and true or false + end + function ow:enter(mapId, x, y, facing, opts) + if game.failNextEnter then + game.failNextEnter = false + error("injected cross-mod reconstruction failure") + end + self.map = { id = mapId } + self.player = { + cellX = x, cellY = y, facing = facing, + surfing = game.save.player.surfing and true or false, + } + self.runner = { isRunning = function() return false end } + self.parallelRunners, self.pendingScripts = {}, {} + self.parallelQueue, self.scriptMoves = {}, {} + game.lastCheckpointEnter = opts + end + function ow:restoreBattleContinuation(battle, origin) + if origin.kind ~= "wild_encounter" or origin.map ~= self.map.id then + return false + end + battle.onFinish = function() end + return true + end + + game = setmetatable({ + data = data, save = save, stack = stack, overworld = ow, + }, { __index = GameMethods }) + stack.states[1] = ow + return game, ow +end + +local function manifest(id) + return ('{"id":"%s","name":"%s","version":"1.0.0",') + :format(id, id) .. '"entry":"main.lua","api":2,"profile":"content"}' +end + +local files = { + ["mods/cooperator/manifest.json"] = manifest("cooperator"), + ["mods/cooperator/main.lua"] = [[ +return function(mod) + local cachedStage = mod.save:get("stage", "unset") + local restoreCount = 0 + mod.options:define({ + { key = "mode", type = "choice", default = "default", + choices = { { "A", "A" }, { "B", "B" }, { "C", "C" } } }, + }) + mod.exports.checkpoints = mod.checkpoints + mod.exports.storage = mod.storage + mod.exports.setStage = function(stage) + mod.save:set("stage", stage) + cachedStage = stage + end + mod.exports.stage = function() return mod.save:get("stage", "unset") end + mod.exports.cachedStage = function() return cachedStage end + mod.exports.restoreCount = function() return restoreCount end + mod.events:on("checkpoint.restored", function(ev) + restoreCount = restoreCount + 1 + cachedStage = mod.save:get("stage", "unset") + mod.exports.lastRestore = { + game = ev.game, + kind = ev.kind, + top = ev.game.stack:top(), + } + end) +end +]], + ["mods/passive/manifest.json"] = manifest("passive"), + ["mods/passive/main.lua"] = [[ +return function(mod) + local cachedStage = mod.save:get("stage", "unset") + mod.exports.setStage = function(stage) + mod.save:set("stage", stage) + cachedStage = stage + end + mod.exports.stage = function() return mod.save:get("stage", "unset") end + mod.exports.cachedStage = function() return cachedStage end +end +]], +} + +local game, ow = makeGame() +local loader = Loader.new({ fs = memfs(files) }) +loader.game, game.mods = game, loader +T.check(loader:load({}) == true, "cooperating fixture mods load") +local cooperator = loader.exports.cooperator +local passive = loader.exports.passive +T.check(type(cooperator) == "table" and type(passive) == "table", + "fixture exposes only public mod exports") +if type(cooperator) ~= "table" or type(passive) ~= "table" then + Runtime.events, Runtime.hooks = savedEvents, savedHooks + love.math.getRandomState = oldGetRandomState + love.math.setRandomState = oldSetRandomState + T.finish() +end + +cooperator.setStage("A") +passive.setStage("A") +game:adoptSave(game.save, true) +local optionBucket = { mode = "A" } +game.save.options.modOptions.cooperator = optionBucket +loader.modOptions.cooperator = optionBucket +setPokemonIdentity(game.data, game.save.party[1], true) +T.check(Stats.isShiny(game.save.party[1].dvs), + "condition A uses the real Gen 2 DV shiny predicate") +T.check(cooperator.storage:write(game, "history", { generation = "A" }), + "independent history condition A writes through mod.storage") + +local overworldA, captureCode = cooperator.checkpoints:capture(game) +T.check(overworldA ~= nil, "condition A overworld captures: " .. tostring(captureCode)) + +-- Mutate canonical progress, both mod.save buckets, independent storage, +-- options, and runtime caches to condition B. +game.save.money = 999999 +setPokemonIdentity(game.data, game.save.party[1], false) +cooperator.setStage("B") +passive.setStage("B") +optionBucket.mode = "B" +rngState = "cross-mod-rng-B" +T.check(cooperator.storage:write(game, "history", { generation = "B" }), + "independent history advances to condition B") + +local bad = cooperator.checkpoints:capture(game) +bad.format = 99 +local rejected, rejectCode = cooperator.checkpoints:restore(game, bad) +T.check(rejected == false and rejectCode == "unsupported_format", + "failed checkpoint validation is reported") +T.eq(cooperator.restoreCount(), 0, "failed restore emits no lifecycle event") +T.eq(cooperator.cachedStage(), "B", "failed restore leaves runtime cache at B") +T.eq(cooperator.stage(), "B", "failed restore leaves mod.save at B") + +local failedTarget = cooperator.checkpoints:capture(game) +game.failNextEnter = true +local failed, failedCode = cooperator.checkpoints:restore(game, failedTarget) +T.check(failed == false and failedCode == "restore_failed", + "failed reconstruction rolls back without committing") +T.eq(cooperator.restoreCount(), 0, + "failed reconstruction and successful rollback emit no lifecycle event") +T.eq(cooperator.cachedStage(), "B", + "failed reconstruction leaves cooperating runtime cache at B") +T.eq(cooperator.stage(), "B", + "failed reconstruction rollback leaves mod.save at B") + +local restored, restoreCode, restoreMessage = + cooperator.checkpoints:restore(game, overworldA) +T.check(restored == true, + "condition A overworld restores: " .. tostring(restoreCode or restoreMessage)) +T.eq(game.save.money, overworldA.save.money, "core game progress rewinds to A") +T.eq(game.save.party[1].shiny, true, + "shiny marker rewinds with its Pokemon record") +T.check(Stats.isShiny(game.save.party[1].dvs), + "authoritative shiny DVs rewind with the Pokemon record") +T.eq(cooperator.stage(), "A", "cooperating mod.save progress rewinds to A") +T.eq(passive.stage(), "A", "all mods' mod.save progress rewinds generically") +T.eq(passive.cachedStage(), "B", + "runtime-only state is not serialized for a non-cooperating mod") +T.eq(cooperator.cachedStage(), "A", + "checkpoint lifecycle lets a cooperating mod rebuild its runtime cache") +T.same(cooperator.storage:read(game, "history"), { generation = "B" }, + "independent mod.storage history does not rewind") +T.eq(cooperator.restoreCount(), 1, "successful overworld restore emits once") +local overworldEvent = cooperator.lastRestore or {} +T.eq(overworldEvent.game, game, "restore event carries the final live game") +T.eq(overworldEvent.kind, "overworld", "restore event identifies overworld") +T.eq(overworldEvent.top, ow, + "restore event runs after the reconstructed overworld is installed") +T.eq(loader.modOptions.cooperator.mode, "B", + "per-mod global options stay at condition B") +T.eq(game.save.options.modOptions.cooperator.mode, "B", + "checkpoint reattaches the current global options table") + +-- Repeat the same ownership rules at a supported ordinary wild battle safe point. +cooperator.setStage("battle-A") +passive.setStage("battle-A") +setPokemonIdentity(game.data, game.save.party[1], true) +local battle = BattleState.newWild(game, "FIXMON_B", 12) +battle.phase, battle.queue = "menu", {} +battle.checkpointOrigin = { kind = "wild_encounter", map = "FIX_TOWN" } +battle.musicKind = battle:computeMusicKind() +battle.onFinish = function() end +setBattlerIdentity(game.data, battle.enemy, true) +game.stack.states[2] = battle +rngState = "cross-mod-battle-rng-A" + +local battleA, battleCaptureCode = cooperator.checkpoints:capture(game) +T.check(battleA and battleA.kind == "battle", + "condition A battle captures: " .. tostring(battleCaptureCode)) +if battleA then + setBattlerIdentity(game.data, battle.player, false) + setBattlerIdentity(game.data, battle.enemy, false) + cooperator.setStage("battle-B") + passive.setStage("battle-B") + optionBucket.mode = "C" + rngState = "cross-mod-battle-rng-B" + T.check(cooperator.storage:write(game, "history", { generation = "battle-B" }), + "independent history advances during battle") + + local battleRestored, battleRestoreCode, battleRestoreMessage = + cooperator.checkpoints:restore(game, battleA) + T.check(battleRestored == true, + "condition A battle restores: " + .. tostring(battleRestoreCode or battleRestoreMessage)) + local restoredBattle = game.stack:top() + T.eq(restoredBattle.player.mon, game.save.party[1], + "restored player battler rebinds to canonical party Pokemon") + T.eq(restoredBattle.player.mon.shiny, true, + "player shiny metadata rewinds through battle reconstruction") + T.check(Stats.isShiny(restoredBattle.player.mon.dvs), + "player shiny DVs rewind through battle reconstruction") + T.eq(restoredBattle.enemy.mon.shiny, true, + "enemy shiny metadata rewinds with copied battle Pokemon") + T.check(Stats.isShiny(restoredBattle.enemy.mon.dvs), + "enemy shiny DVs rewind through battle reconstruction") + T.eq(cooperator.stage(), "battle-A", "battle restore rewinds mod.save progress") + T.eq(cooperator.cachedStage(), "battle-A", + "battle restore event rebuilds cooperating runtime cache") + T.eq(passive.cachedStage(), "battle-B", + "battle restore still does not serialize arbitrary mod runtime") + T.same(cooperator.storage:read(game, "history"), { generation = "battle-B" }, + "battle restore leaves independent history current") + T.eq(loader.modOptions.cooperator.mode, "C", + "battle restore leaves per-mod global options current") + T.eq(cooperator.restoreCount(), 2, "successful battle restore emits once") + local battleEvent = cooperator.lastRestore or {} + T.eq(battleEvent.kind, "battle", "restore event identifies battle") + T.eq(battleEvent.top, restoredBattle, + "battle restore event runs after final battle installation") + T.same(cooperator.checkpoints:capture(game), battleA, + "combined battle and mod progress is a differential roundtrip") +end + +Runtime.events, Runtime.hooks = savedEvents, savedHooks +Runtime.currentMod = nil +love.math.getRandomState = oldGetRandomState +love.math.setRandomState = oldSetRandomState +_G.CROSS_MOD_CHECKPOINT = nil + +T.finish() diff --git a/tests/modkit/cases/checkpoints.lua b/tests/modkit/cases/checkpoints.lua new file mode 100644 index 00000000..adea5f90 --- /dev/null +++ b/tests/modkit/cases/checkpoints.lua @@ -0,0 +1,442 @@ +-- Public mod.checkpoints contract over a semantic Game/StateStack fixture. +-- The mod entry chunk sees no private module; the harness builds the engine side. + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local oldGetRandomState = love.math.getRandomState +local oldSetRandomState = love.math.setRandomState +local checkpointRngState = "overworld-rng-A" +love.math.getRandomState = function() return checkpointRngState end +love.math.setRandomState = function(state) checkpointRngState = state end + +local T = require("tests.harness").suite("mod checkpoints") +local Loader = require("src.mods.Loader") +local Runtime = require("src.mods.Runtime") +local GameMethods = require("src.core.Game") +local BattleState = require("src.battle.BattleState") +local Fixtures = require("tests.modkit").fixtures +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local StateStack = require("src.core.StateStack") +local Version = require("src.core.Version") + +local savedEvents, savedHooks = Runtime.events, Runtime.hooks + +local function memfs(files) + return { + read = function(path) return files[path] end, + write = function(path, body) files[path] = body return true end, + remove = function(path) files[path] = nil return true end, + createDirectory = function() return true end, + getInfo = function(path) + if files[path] then return { type = "file" } end + local prefix = path .. "/" + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then return { type = "directory" } end + end + return nil + end, + load = function(path) + if not files[path] then return nil, "no file: " .. path end + return load(files[path], path) + end, + getDirectoryItems = function(path) + local prefix, seen, out = path .. "/", {}, {} + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then + local child = key:sub(#prefix + 1):match("^[^/]+") + if child and not seen[child] then + seen[child] = true + out[#out + 1] = child + end + end + end + table.sort(out) + return out + end, + } +end + +local function baseSave() + return { + version = "red", + meta = { format = 4, mods = {}, playthroughId = "play-a" }, + player = { + map = "PALLET_TOWN", x = 5, y = 6, facing = "down", surfing = false, + name = "RED", rival = "BLUE", id = 7, + }, + money = 3000, + party = { { species = "BULBASAUR", level = 5, hp = 19, + moves = { "TACKLE" } } }, + flags = { GOT_STARTER = true }, + inventory = { POTION = 1 }, + pcItems = { POTION = 2 }, + box = { { species = "BULBASAUR", level = 4, hp = 16, + moves = { "TACKLE" } } }, + boxes = { [2] = { { species = "BULBASAUR", level = 3, hp = 14, + moves = { "TACKLE" } } } }, + defeatedTrainers = { PALLET_RIVAL = true }, + objectToggles = { PALLET_TOWN = { OAK = false } }, + itemsTaken = { PALLET_TOWN_POTION = true }, + pokedex = { seen = { BULBASAUR = true }, owned = { BULBASAUR = true } }, + modData = {}, + options = { volume = 4, bindings = {} }, + } +end + +local function makeGame() + local stack = setmetatable({ states = {} }, { __index = StateStack }) + local game + local ow = { + map = { id = "PALLET_TOWN" }, + player = { cellX = 5, cellY = 6, facing = "down", surfing = false }, + scriptMoves = {}, pendingScripts = {}, parallelRunners = {}, parallelQueue = {}, + runner = { isRunning = function() return false end }, + } + function ow:captureSave(save) + save.player.map = self.map.id + save.player.x = self.player.cellX + save.player.y = self.player.cellY + save.player.facing = self.player.facing + save.player.surfing = self.player.surfing and true or false + end + function ow:enter(mapId, x, y, facing, opts) + game.lastEnterOpts = opts + if game.failNextEnter then + game.failNextEnter = false + error("injected reconstruction failure") + end + self.map = { id = mapId } + self.player = { + cellX = x, cellY = y, facing = facing, + surfing = game.save.player.surfing and true or false, + } + self.scriptMoves, self.pendingScripts = {}, {} + self.parallelRunners, self.parallelQueue = {}, {} + self.runner = { isRunning = function() return false end } + end + game = setmetatable({ + save = baseSave(), stack = stack, overworld = ow, + data = { + pokemon = { BULBASAUR = { dex = 1 } }, + moves = { TACKLE = { pp = 35 } }, + items = { POTION = {} }, + constants = { fallbackMove = "TACKLE" }, + field = { boot = { startMap = "PALLET_TOWN", startX = 5, startY = 6 } }, + maps = { + PALLET_TOWN = { id = "PALLET_TOWN", width = 10, height = 9 }, + ROUTE_1 = { id = "ROUTE_1", width = 10, height = 18 }, + BROKEN = { id = "BROKEN", width = 10, height = 9 }, + }, + }, + }, { __index = GameMethods }) + stack.states[1] = ow + return game, ow +end + +local files = { + ["mods/probe/manifest.json"] = + '{"id":"probe","name":"probe","version":"1.0.0",' + .. '"entry":"main.lua","api":2,"profile":"content"}', + ["mods/probe/main.lua"] = [[ +return function(mod) _G.MOD_CHECKPOINTS = mod.checkpoints end +]], +} +local game, ow = makeGame() +local loader = Loader.new({ fs = memfs(files) }) +loader.game = game +T.check(loader:load({}) == true, "checkpoint fixture mod loads") +local checkpoints = _G.MOD_CHECKPOINTS +T.check(type(checkpoints) == "table", + "Loader exposes mod.checkpoints through the public mod object") +if type(checkpoints) ~= "table" then + Runtime.events, Runtime.hooks = savedEvents, savedHooks + _G.MOD_CHECKPOINTS = nil + T.finish() +end + +local capability = checkpoints:inspect(game) +T.same(capability, { canCapture = true, canRestore = true, kind = "overworld" }, + "plain overworld control is a stable checkpoint boundary") + +local function refused(mutator, expectedCode, message) + local undo = mutator() + local result = checkpoints:inspect(game) + T.check(result.canCapture == false and result.reason == expectedCode, message) + undo() +end + +refused(function() + ow.transitioning = true + return function() ow.transitioning = nil end +end, "transition_busy", "transition frames are rejected") + +refused(function() + ow.runner = { isRunning = function() return true end } + return function() ow.runner = { isRunning = function() return false end } end +end, "script_busy", "foreground suspended scripts are rejected") + +refused(function() + ow.parallelRunners = { { isRunning = function() return true end } } + return function() ow.parallelRunners = {} end +end, "script_busy", "parallel suspended scripts are rejected") + +refused(function() + ow.pendingScripts = { { rows = {} } } + return function() ow.pendingScripts = {} end +end, "script_busy", "queued scripts are rejected") + +refused(function() + ow.scriptMoves = { { entity = ow.player } } + return function() ow.scriptMoves = {} end +end, "script_busy", "scripted movement is rejected") + +refused(function() + game.stack.states[2] = { screenId = "StartMenu" } + return function() game.stack.states[2] = nil end +end, "screen_busy", "modal screens over the overworld are rejected") + +refused(function() + ow.emote = { frames = 1 } + return function() ow.emote = nil end +end, "animation_busy", "partial overworld animations are rejected") + +refused(function() + ow.player.moving = true + return function() ow.player.moving = nil end +end, "movement_busy", "partial player movement is rejected") + +local titleGame = { save = game.save, stack = { + top = function() return { screenId = "TitleState" } end, +} } +local titleCapability = checkpoints:inspect(titleGame) +T.check(titleCapability.canCapture == false + and titleCapability.reason == "not_overworld", + "title and non-playthrough runtime is rejected") + +-- Capture synchronizes semantic position into a detached data-only record. +ow.map.id, ow.player.cellX, ow.player.cellY = "ROUTE_1", 7, 8 +ow.player.facing, ow.player.surfing = "left", true +local snapshot, code, message = checkpoints:capture(game) +T.check(snapshot ~= nil, "stable overworld captures: " .. tostring(code or message)) +T.eq(snapshot.format, 1, "checkpoint format is explicit") +T.eq(snapshot.kind, "overworld", "checkpoint runtime kind is explicit") +T.same(snapshot.identity, { + engineVersion = Version.engine, + gameVersion = "red", + playthroughId = "play-a", + }, + "checkpoint carries compatibility identity") +T.same(snapshot.runtime.overworld, + { map = "ROUTE_1", x = 7, y = 8, facing = "left", surfing = true }, + "checkpoint carries exact semantic overworld position") +T.eq(snapshot.save.player.map, "ROUTE_1", + "captured progress is synchronized from the live controller") +T.eq(snapshot.save.options, nil, "global settings are excluded from progress rewind") +T.same(snapshot.rng, { love = "overworld-rng-A" }, + "overworld checkpoint carries deterministic gameplay RNG") + +local legacy = checkpoints:capture(game) +legacy.rng = nil +checkpointRngState = "legacy-runtime-rng" +local legacyRestored, legacyCode = checkpoints:restore(game, legacy) +T.check(legacyRestored == true, + "legacy format-1 overworld checkpoint without RNG remains loadable: " + .. tostring(legacyCode)) +T.eq(checkpointRngState, "legacy-runtime-rng", + "legacy checkpoint leaves the current RNG stream untouched") +checkpointRngState = "overworld-rng-A" + +snapshot.save.money = 1 +snapshot.runtime.overworld.x = 1 +T.eq(game.save.money, 3000, "mutating a checkpoint cannot mutate live progress") +T.eq(ow.player.cellX, 7, "mutating a checkpoint cannot move the live player") + +-- Recapture the unmodified canonical A used for the differential roundtrip. +snapshot = checkpoints:capture(game) +local original = snapshot + +game.save.money = 999999 +game.save.flags.GOT_STARTER = nil +game.save.party[1].hp = 1 +game.save.inventory.POTION = 99 +game.save.pcItems.POTION = nil +game.save.box = {} +game.save.boxes = {} +game.save.defeatedTrainers.PALLET_RIVAL = nil +game.save.objectToggles.PALLET_TOWN.OAK = true +game.save.itemsTaken.PALLET_TOWN_POTION = nil +game.save.pokedex.seen.BULBASAUR = nil +game.save.pokedex.owned.BULBASAUR = nil +game.save.options.volume = 9 +checkpointRngState = "overworld-rng-B" +ow.map.id, ow.player.cellX, ow.player.cellY = "PALLET_TOWN", 2, 3 +ow.player.facing, ow.player.surfing = "up", false + +local restored, restoreCode, restoreMessage = checkpoints:restore(game, original) +T.check(restored == true, + "valid checkpoint restores: " .. tostring(restoreCode or restoreMessage)) +local recaptured = checkpoints:capture(game) +T.same(recaptured, original, + "capture A, mutate B, restore A, capture A2 yields normalized A == A2") +T.eq(game.save.options.volume, 9, + "checkpoint restoration preserves current global settings") +T.eq(checkpointRngState, "overworld-rng-A", + "overworld checkpoint restores gameplay RNG") +T.eq(game.save.inventory.POTION, 1, "inventory progress roundtrips") +T.eq(game.save.pcItems.POTION, 2, "PC item progress roundtrips") +T.eq(game.save.box[1].hp, 16, "current box Pokemon roundtrips") +T.eq(game.save.boxes[2][1].hp, 14, "stored box collection roundtrips") +T.eq(game.save.defeatedTrainers.PALLET_RIVAL, true, + "defeated trainer progress roundtrips") +T.eq(game.save.objectToggles.PALLET_TOWN.OAK, false, + "map object toggle progress roundtrips") +T.eq(game.save.itemsTaken.PALLET_TOWN_POTION, true, + "taken-object progress roundtrips") +T.eq(game.save.pokedex.owned.BULBASAUR, true, "Pokedex progress roundtrips") +T.check(game.lastEnterOpts and game.lastEnterOpts.checkpoint == true, + "engine reconstruction is marked to suppress map-entry side effects") + +-- Compatibility and schema failures occur before any mutation. +local beforeRejected = checkpoints:capture(game) +local wrongFormat = checkpoints:capture(game) +wrongFormat.format = 99 +restored, restoreCode = checkpoints:restore(game, wrongFormat) +T.check(not restored and restoreCode == "unsupported_format", + "unknown checkpoint format is rejected") + +local wrongGame = checkpoints:capture(game) +wrongGame.identity.gameVersion = "blue" +restored, restoreCode = checkpoints:restore(game, wrongGame) +T.check(not restored and restoreCode == "wrong_game", + "another game version is rejected") + +local wrongProfile = checkpoints:capture(game) +wrongProfile.identity.playthroughId = "play-b" +restored, restoreCode = checkpoints:restore(game, wrongProfile) +T.check(not restored and restoreCode == "wrong_playthrough", + "another playthrough is rejected") + +local badMap = checkpoints:capture(game) +badMap.runtime.overworld.map = "MISSING_MAP" +badMap.save.player.map = "MISSING_MAP" +restored, restoreCode = checkpoints:restore(game, badMap) +T.check(not restored and restoreCode == "invalid_map", + "unknown content reference is rejected") +T.same(checkpoints:capture(game), beforeRejected, + "validation failures leave the live state unchanged") + +local invalidGame = makeGame() +local badSpecies = checkpoints:capture(invalidGame) +badSpecies.save.party[1].species = "MISSING_SPECIES" +restored, restoreCode = checkpoints:restore(invalidGame, badSpecies) +T.check(not restored and restoreCode == "invalid_content", + "unknown Pokemon content is rejected before reconstruction") +T.eq(invalidGame.save.party[1].species, "BULBASAUR", + "invalid Pokemon content leaves the live party unchanged") + +-- A reconstruction exception rolls back to the exact pre-operation state. +local target = checkpoints:capture(game) +target.runtime.overworld.map = "BROKEN" +target.runtime.overworld.x, target.runtime.overworld.y = 1, 1 +target.save.player.map = "BROKEN" +target.save.player.x, target.save.player.y = 1, 1 +target.save.money = 42 +local beforeFailure = checkpoints:capture(game) +game.failNextEnter = true +restored, restoreCode = checkpoints:restore(game, target) +T.check(not restored and restoreCode == "restore_failed", + "reconstruction exception is returned as a structured failure") +T.same(checkpoints:capture(game), beforeFailure, + "failed reconstruction rolls back the complete pre-operation checkpoint") + +-- The same public facade must carry a real battle checkpoint end to end. The +-- engine-side fixture is deliberately constructed outside the probe mod; the +-- mod sees and calls only mod.checkpoints. +local function makeBattleGame() + local data = Fixtures.fresh() + local save = SaveData.newGame() + save.meta.playthroughId = "public-battle-playthrough" + save.party = { Pokemon.new(data, "FIXMON_A", 20) } + -- The tiny fixture registry intentionally omits several full-game defaults. + -- Normalize those once, then place the save on its fixture map. + SaveData.validate(save, data) + save.player.map, save.player.x, save.player.y = "FIX_TOWN", 2, 3 + save.player.facing, save.player.surfing = "left", false + local stack = setmetatable({ states = {} }, { __index = StateStack }) + local battleGame + local battleOw = { + map = { id = "FIX_TOWN" }, + player = { cellX = 2, cellY = 3, facing = "left", surfing = false }, + runner = { isRunning = function() return false end }, + parallelRunners = {}, pendingScripts = {}, parallelQueue = {}, scriptMoves = {}, + } + function battleOw:captureSave(target) + target.player.map = self.map.id + target.player.x, target.player.y = self.player.cellX, self.player.cellY + target.player.facing = self.player.facing + target.player.surfing = self.player.surfing and true or false + end + function battleOw:enter(mapId, x, y, facing) + self.map = { id = mapId } + self.player = { cellX = x, cellY = y, facing = facing, surfing = false } + end + function battleOw:restoreBattleContinuation(restoredBattle, origin) + if origin.kind ~= "wild_encounter" or origin.map ~= self.map.id then + return false + end + restoredBattle.onFinish = function() end + return true + end + battleGame = setmetatable({ + data = data, save = save, stack = stack, overworld = battleOw, + }, { __index = GameMethods }) + stack.states[1] = battleOw + local battle = BattleState.newWild(battleGame, "FIXMON_B", 12) + battle.phase, battle.queue = "menu", {} + battle.checkpointOrigin = { kind = "wild_encounter", map = "FIX_TOWN" } + battle.musicKind = battle:computeMusicKind() + battle.onFinish = function() end + stack.states[2] = battle + return battleGame, battle +end + +checkpointRngState = "public-battle-rng-A" +local battleGame, liveBattle = makeBattleGame() +T.same(checkpoints:inspect(battleGame), { + canCapture = true, canRestore = true, kind = "battle", +}, "public mod.checkpoints reports a settled battle boundary") +liveBattle.turnCount = 4 +liveBattle.player.stages.attack = 2 +local battleSnapshot, battleCaptureCode = checkpoints:capture(battleGame) +T.check(battleSnapshot and battleSnapshot.kind == "battle", + "public mod.checkpoints captures a data-only battle: " + .. tostring(battleCaptureCode)) +if battleSnapshot then + battleGame.save.money = 1 + liveBattle.turnCount = 99 + checkpointRngState = "public-battle-rng-B" + local battleRestored, battleRestoreCode, battleRestoreMessage = checkpoints:restore( + battleGame, battleSnapshot) + T.check(battleRestored == true, + "public mod.checkpoints reconstructs a battle: " + .. tostring(battleRestoreCode) .. " / " .. tostring(battleRestoreMessage)) + local restoredBattle = battleGame.stack:top() + T.eq(restoredBattle.turnCount, 4, + "public battle reconstruction restores the exact turn") + T.eq(restoredBattle.player.stages.attack, 2, + "public battle reconstruction restores battler stages") + T.eq(checkpointRngState, "public-battle-rng-A", + "public battle reconstruction restores gameplay RNG") + T.same(checkpoints:capture(battleGame), battleSnapshot, + "public battle capture/restore/capture is a normalized differential roundtrip") +end + +Runtime.events, Runtime.hooks = savedEvents, savedHooks +Runtime.currentMod = nil +_G.MOD_CHECKPOINTS = nil +love.math.getRandomState = oldGetRandomState +love.math.setRandomState = oldSetRandomState + +T.finish() diff --git a/tests/modkit/cases/platform_lifecycle_hooks.lua b/tests/modkit/cases/platform_lifecycle_hooks.lua new file mode 100644 index 00000000..88d95336 --- /dev/null +++ b/tests/modkit/cases/platform_lifecycle_hooks.lua @@ -0,0 +1,93 @@ +-- core.update / core.quit_to_launcher through the public mod API: a +-- platform-launcher integration can pause the simulation and veto the +-- return-to-launcher decision from a mod, with no main.lua patch. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local PlatformHooks = require("src.core.PlatformHooks") + +local FIXTURE = { + ["mods/fix_platform_bridge/manifest.json"] = [[{ + "id": "fix_platform_bridge", + "name": "Fixture Platform Bridge", + "version": "1.0.0", + "entry": "main.lua", + "api": 2 + }]], + ["mods/fix_platform_bridge/main.lua"] = [[ + local mod = ... + local paused = false + local extraPolls = 0 + mod.hooks:wrap("core.update", function(nextFn, game, dt) + extraPolls = extraPolls + 1 + if not paused then nextFn(game, dt) end + end) + mod.hooks:wrap("core.quit_to_launcher", function(nextFn) + if os.getenv("FIXTURE_VETO_QUIT") == "1" then return false end + return nextFn() + end) + -- test-only knobs, read back through mod.storage-free globals since + -- this fixture never leaves the process + _G.__fixturePlatformBridge = { + setPaused = function(v) paused = v end, + extraPolls = function() return extraPolls end, + } + ]], +} + +-- core.update: a subscriber can pause (skip vanilla) and still run every frame +do + local run = T.sdk.loadMods({ "mods/fix_platform_bridge" }, + { fs = T.sdk.memfs(FIXTURE) }) + T.eq(#run.errors, 0, + "the fixture mod loads clean (" .. tostring(run.errors[1]) .. ")") + + local calls = 0 + local fakeGame = { update = function(self, dt) calls = calls + 1 end } + + _G.__fixturePlatformBridge.setPaused(false) + PlatformHooks.update(fakeGame, 1 / 60) + T.eq(calls, 1, "unpaused: vanilla Game:update runs") + T.eq(_G.__fixturePlatformBridge.extraPolls(), 1, + "the subscriber's wrapper runs every frame") + + _G.__fixturePlatformBridge.setPaused(true) + PlatformHooks.update(fakeGame, 1 / 60) + T.eq(calls, 1, "paused: vanilla Game:update is skipped") + T.eq(_G.__fixturePlatformBridge.extraPolls(), 2, + "the subscriber keeps polling every frame while paused") + + run.release() + _G.__fixturePlatformBridge = nil +end + +-- core.quit_to_launcher: a subscriber can veto without the vanilla +-- condition ever running, or pass it through unchanged +do + local run = T.sdk.loadMods({ "mods/fix_platform_bridge" }, + { fs = T.sdk.memfs(FIXTURE) }) + T.eq(#run.errors, 0, + "the fixture mod loads clean (" .. tostring(run.errors[1]) .. ")") + + local realGetenv = os.getenv + os.getenv = function(name) + if name == "FIXTURE_VETO_QUIT" then return "1" end + return realGetenv(name) + end + local vanillaCalls = 0 + local vetoed = PlatformHooks.quitToLauncher(function() + vanillaCalls = vanillaCalls + 1 + return true + end) + T.eq(vetoed, false, "a subscriber can veto the return-to-launcher decision") + T.eq(vanillaCalls, 0, "a veto never evaluates the vanilla condition") + os.getenv = realGetenv + + local passed = PlatformHooks.quitToLauncher(function() return true end) + T.eq(passed, true, "with no veto, the vanilla decision passes through unchanged") + + run.release() +end + +T.finish("platform_lifecycle_hooks") diff --git a/tests/modkit/cases/pointer_input.lua b/tests/modkit/cases/pointer_input.lua new file mode 100644 index 00000000..b4e9d33d --- /dev/null +++ b/tests/modkit/cases/pointer_input.lua @@ -0,0 +1,327 @@ +-- T4: the gameplay pointer seam and source-safe mod input (#807), through +-- the public mod API. +-- +-- Two seams under test. "input.pointer" delivers uncaptured gameplay +-- touch and mouse events to hook subscribers, with the on-screen touch +-- controls keeping first refusal. mod.input taps and holds GB buttons as +-- per-mod Input sources, so no mod can clear a hold it does not own. +-- Both are driven the way main.lua drives them -- through Game's own +-- handlers, against the real Input and TouchControls singletons -- so a +-- green run means the wiring, not just the buses. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") + +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local TouchControls = require("src.core.TouchControls") +local Runtime = require("src.mods.Runtime") + +-- a Game stand-in that resolves the real methods (Game is the boot +-- singleton, so its handlers expect to be their own self) +local function fakeGame(loader, data) + return setmetatable({ input = Input, mods = loader, data = data }, + { __index = Game }) +end + +-- ------- fixture mods + +-- the watcher journals every pointer event it sees and exports its own +-- mod.input facade, so the suite drives exactly the surface a mod +-- compiles against; the peer exists to prove cross-mod token refusal +local FIXTURES = { + ["mods/fix_pointer_watcher/manifest.json"] = [[{ + "id": "fix_pointer_watcher", + "name": "Fixture Pointer Watcher", + "version": "1.0.0", + "entry": "main.lua", + "api": 2 + }]], + ["mods/fix_pointer_watcher/main.lua"] = [[ + local mod = ... + local seen = {} + mod.exports.seen = seen + mod.hooks:wrap("input.pointer", function(nextFn, game, ev) + seen[#seen + 1] = { phase = ev.phase, source = ev.source, id = ev.id, + x = ev.x, y = ev.y, dx = ev.dx, dy = ev.dy, + pressure = ev.pressure, button = ev.button } + return nextFn(game, ev) + end) + mod.exports.input = mod.input + ]], + ["mods/fix_pointer_peer/manifest.json"] = [[{ + "id": "fix_pointer_peer", + "name": "Fixture Pointer Peer", + "version": "1.0.0", + "entry": "main.lua", + "api": 2 + }]], + ["mods/fix_pointer_peer/main.lua"] = [[ + local mod = ... + mod.exports.input = mod.input + ]], +} + +-- ------- no-mod parity: the pointer path costs nothing unsubscribed + +do + local run = T.sdk.loadNone({}) + Input:init() + TouchControls:init() + local game = fakeGame(run.loader, run.data) + T.eq(Runtime.wantsHook("input.pointer"), false, + "no subscriber: wantsHook(\"input.pointer\") is false") + Game.touchpressed(game, 1, 50, 60, 0, 0, 1) + Game.touchmoved(game, 1, 55, 66, 5, 6, 1) + Game.touchreleased(game, 1, 55, 66, 0, 0, 1) + Game.mousepressed(game, 10, 10, 1, false) + Game.mousemoved(game, 12, 12, 2, 2, false) + Game.mousereleased(game, 12, 12, 1, false) + Game.cancelPointers(game) + T.eq(game.modPointers, nil, + "no subscriber: the pointer path allocates no tracking state") + run.release() +end + +-- ------- the subscribed path, through the loader-created mod API + +local run = T.sdk.loadMods( + { "mods/fix_pointer_watcher", "mods/fix_pointer_peer" }, + { fs = T.sdk.memfs(FIXTURES) }) +T.eq(#run.errors, 0, + "the fixture mods load clean (" .. tostring(run.errors[1]) .. ")") + +local watcher = run.loader.exports.fix_pointer_watcher +local peer = run.loader.exports.fix_pointer_peer +local seen = watcher.seen +local game = fakeGame(run.loader, run.data) + +Input:init() +TouchControls:init() + +local function wipe() + for i = #seen, 1, -1 do seen[i] = nil end +end + +-- one pressed/moved/released sequence per outside touch +do + wipe() + Game.touchpressed(game, 7, 100, 120, 0, 0, 0.5) + Game.touchmoved(game, 7, 110, 125, 10, 5, 0.5) + Game.touchreleased(game, 7, 110, 125, 0, 0, 0.5) + T.eq(#seen, 3, "an outside touch is one pressed/moved/released sequence") + T.eq(seen[1].phase, "pressed", "the sequence begins with pressed") + T.eq(seen[1].source, "touch", "a finger reports source touch") + T.eq(seen[1].id, 7, "the touch id rides the payload") + T.eq(seen[1].x, 100, "coordinates are the LOVE window units handed in") + T.eq(seen[1].pressure, 0.5, "pressure rides the payload when present") + T.eq(seen[2].phase, "moved", "then moved") + T.eq(seen[2].dx, 10, "moved carries the event deltas") + T.eq(seen[3].phase, "released", "then released") + T.eq(game.modPointers[7], nil, "a released pointer leaves no record") + + -- press+release inside one logic tick still yields both events: the + -- seam is event-driven, not sampled at the step boundary + wipe() + Game.touchpressed(game, 8, 10, 10, 0, 0, 1) + Game.touchreleased(game, 8, 10, 10, 0, 0, 1) + T.eq(#seen, 2, "press+release inside one tick delivers both events") + T.eq(seen[1].phase, "pressed", "...pressed first") + T.eq(seen[2].phase, "released", "...released second") +end + +-- multi-id drags stay per-pointer +do + wipe() + Game.touchpressed(game, "t1", 40, 40, 0, 0, 1) + Game.touchpressed(game, "t2", 200, 90, 0, 0, 1) + Game.touchmoved(game, "t1", 45, 44, 5, 4, 1) + Game.touchmoved(game, "t2", 210, 80, 10, -10, 1) + Game.touchreleased(game, "t2", 210, 80, 0, 0, 1) + Game.touchreleased(game, "t1", 45, 44, 0, 0, 1) + T.eq(#seen, 6, "two concurrent touches interleave without cross-talk") + T.eq(seen[3].id, "t1", "each moved names its own pointer") + T.eq(seen[4].id, "t2", "...and only its own pointer") + T.eq(seen[5].id, "t2", "release order follows the fingers, not the presses") + T.eq(seen[5].phase, "released", "t2 released first") + T.eq(seen[6].id, "t1", "t1 released second") + T.eq(next(game.modPointers), nil, "all records are gone after both lifts") +end + +-- the virtual pad keeps first refusal +do + -- force the overlay live the way a phone would have it; img is only + -- tested for truthiness on the input path (soft_reset_bug563 idiom) + TouchControls.active, TouchControls.enabled = true, true + TouchControls.img = { stub = true } + local L = TouchControls:layout() + + wipe() + Game.touchpressed(game, 21, L.a.cx, L.a.cy) + T.eq(Input:isDown("a"), true, "the overlay captured the touch and holds A") + Game.touchmoved(game, 21, L.a.cx + 4, L.a.cy + 4) + Game.touchreleased(game, 21, L.a.cx + 4, L.a.cy + 4) + T.eq(Input:isDown("a"), false, "lifting the finger releases A") + T.eq(#seen, 0, "a touch that begins on a virtual control never reaches mods") + + -- begins outside, wanders across A: stays mod-visible, never presses A + wipe() + Game.touchpressed(game, 22, 5, 5) + Game.touchmoved(game, 22, L.a.cx, L.a.cy) + T.eq(Input:isDown("a"), false, "crossing a control mid-drag never presses it") + Game.touchreleased(game, 22, L.a.cx, L.a.cy) + T.eq(#seen, 3, "a touch that begins outside stays mod-visible throughout") + T.eq(seen[2].dx, L.a.cx - 5, "deltas derive from the last seen position " + .. "when the event carries none") + + TouchControls.active = false + TouchControls.img = nil + TouchControls:reset() +end + +-- real mouse events, and the synthesized istouch twins that must not fire +do + wipe() + Game.mousepressed(game, 30, 30, 1, true) + Game.mousemoved(game, 31, 31, 1, 1, true) + Game.mousereleased(game, 31, 31, 1, true) + T.eq(#seen, 0, "synthesized istouch mouse twins never reach the hook") + + Game.mousepressed(game, 30, 30, 1, false) + Game.mousemoved(game, 34, 32, 4, 2, false) + Game.mousereleased(game, 34, 32, 1, false) + T.eq(#seen, 3, "a real mouse reaches the hook without POKEPORT_TOUCH") + T.eq(seen[1].source, "mouse", "the mouse reports source mouse") + T.eq(seen[1].id, "mouse", "...under the fixed id mouse") + T.eq(seen[1].button, 1, "pressed carries the button") + T.eq(seen[2].dx, 4, "moved carries the event deltas") + T.eq(seen[3].phase, "released", "the lifecycle closes with released") + T.eq(game.modPointers.mouse, nil, "the mouse record is gone after release") +end + +-- focus loss cancels every live mod-visible pointer +do + wipe() + Game.touchpressed(game, 40, 70, 80, 0, 0, 1) + Game.focus(game, false) + T.eq(#seen, 2, "focus loss follows the pressed with exactly one more event") + T.eq(seen[2].phase, "cancelled", "...a cancelled") + T.eq(seen[2].id, 40, "...for the pointer that was alive") + T.eq(seen[2].x, 70, "...at its last seen position") + T.eq(game.modPointers, nil, "cancel clears the tracking state") +end + +-- ------- mod.input: tap is one edge, no held state + +do + Input:init() + watcher.input:tap(game, "a") + T.eq(Input:isDown("a"), false, "tap holds nothing before the step") + Input:step() + T.eq(Input:wasPressed("a"), true, "tap queues exactly one wasPressed edge") + T.eq(Input:isDown("a"), false, "the edge carries no held state") + Input:step() + T.eq(Input:wasPressed("a"), false, "the edge lasts exactly one step") + T.eq(Input:isDown("a"), false, "and still nothing is held") +end + +-- mod.input: a press token holds across steps and respects other sources + +do + Input:init() + local token = watcher.input:press(game, "b") + Input:step() + T.eq(Input:wasPressed("b"), true, "press queues the edge") + T.eq(Input:isDown("b"), true, "and holds the button") + Input:step() + T.eq(Input:isDown("b"), true, "the hold survives steps until release") + -- the keyboard joins on the same button (X is a default B binding) + Input:keypressed("x") + Input:step() + T.eq(watcher.input:release(token), true, "release honors this mod's token") + T.eq(Input:isDown("b"), true, + "releasing the mod token leaves the keyboard's hold alone") + T.eq(watcher.input:release(token), false, "release is idempotent") + T.eq(Input:isDown("b"), true, "a second release changes nothing") + Input:keyreleased("x") + T.eq(Input:isDown("b"), false, "the keyboard's own release ends the hold") +end + +-- mod.input: one mod cannot release another mod's press + +do + Input:init() + local token = watcher.input:press(game, "start") + T.eq(peer.input:release(token), false, + "a mod cannot release another mod's token") + T.eq(Input:isDown("start"), true, "the refused release drops nothing") + T.eq(watcher.input:release(token), true, "the owning mod still can") + T.eq(Input:isDown("start"), false, "and then the button is up") + T.raises(function() watcher.input:tap(game, "quit") end, + "unknown GB button", "unknown buttons are refused") +end + +-- mod.input: input recovery retires outstanding tokens + +do + Input:init() + local token = watcher.input:press(game, "a") + T.eq(Input:isDown("a"), true, "the hold is live before recovery") + Game.recoverInput(game, "joystickadded") + T.eq(Input:isDown("a"), false, + "input recovery drops mod holds with every other source") + T.eq(watcher.input:release(token), false, + "recovery retires the outstanding token") +end + +-- ------- cleanup: entry-chunk rollback releases what the chunk pressed + +do + local BROKEN = { + ["mods/fix_pointer_broken/manifest.json"] = [[{ + "id": "fix_pointer_broken", + "name": "Fixture Pointer Broken", + "version": "1.0.0", + "entry": "main.lua", + "api": 2 + }]], + ["mods/fix_pointer_broken/main.lua"] = [[ + local mod = ... + mod.input:press(FIX_POINTER_GAME, "select") + error("entry chunk exploded") + ]], + } + Input:init() + _G.FIX_POINTER_GAME = { input = Input } + local broken = T.sdk.loadMods({ "mods/fix_pointer_broken" }, + { fs = T.sdk.memfs(BROKEN) }) + _G.FIX_POINTER_GAME = nil + T.check(#broken.errors > 0, "the throwing entry chunk is reported") + T.eq(Input:isDown("select"), false, + "entry-chunk rollback releases the mod's outstanding holds") + broken.release() +end + +-- ------- cleanup: hot reload retires the old loader's holds (LAST: it +-- replaces the buses, so the watcher's subscription dies with it) + +do + Input:init() + local token = watcher.input:press(game, "up") + T.eq(Input:isDown("up"), true, "the hold is live before the reload") + -- a fresh fixture dataset for the reload's re-merge, with the inherited + -- Data:reloadGenerated shadowed out: it would read data/generated, and + -- this tier is ROM-free + local rdata = T.fixtures.fresh() + rawset(rdata, "reloadGenerated", function() end) + local rgame = fakeGame(run.loader, rdata) + require("src.dev.HotReload").run(rgame, { fs = T.sdk.memfs({}) }) + T.eq(Input:isDown("up"), false, "hot reload retires the old loader's holds") + T.eq(watcher.input:release(token), false, + "a token from before the reload is refused, not re-released") +end + +run.release() + +T.finish("pointer_input") diff --git a/tests/modkit/cases/screen_render_visible.lua b/tests/modkit/cases/screen_render_visible.lua new file mode 100644 index 00000000..eb8d246a --- /dev/null +++ b/tests/modkit/cases/screen_render_visible.lua @@ -0,0 +1,101 @@ +-- screen.render_visible through the public mod API: a mirrored native screen +-- may leave the main render without leaving the active state stack. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Game = require("src.core.Game") +local Runtime = require("src.mods.Runtime") +local StateStack = require("src.core.StateStack") +local Renderer = require("src.render.Renderer") +local TouchControls = require("src.core.TouchControls") + +local FIXTURE = { + ["mods/fix_screen_mirror/manifest.json"] = [[{ + "id": "fix_screen_mirror", + "name": "Fixture Screen Mirror", + "version": "1.0.0", + "entry": "main.lua", + "api": 2 + }]], + ["mods/fix_screen_mirror/main.lua"] = [[ + local mod = ... + mod.hooks:wrap("screen.render_visible", function(nextFn, state) + if state.screenId == "BagMenu" then return false end + return nextFn(state) + end) + ]], +} + +local savedSetUISize, savedBegin, savedEnd, savedTouch = + Renderer.setUISize, Renderer.beginFrame, Renderer.endFrame, + TouchControls.draw +local presentedZones +Renderer.setUISize = function() end +Renderer.beginFrame = function() end +Renderer.endFrame = function(_, zones) + presentedZones = zones + return {} +end +TouchControls.draw = function() end + +local function scene() + local stack = setmetatable({}, { __index = StateStack }) + stack:init() + local base = { + isOpaque = true, + draws = 0, + draw = function(self) self.draws = self.draws + 1 end, + sgbPalettes = function() return "base zones" end, + } + local menu = { + screenId = "BagMenu", + isOpaque = true, + draws = 0, + updates = 0, + draw = function(self) self.draws = self.draws + 1 end, + update = function(self) self.updates = self.updates + 1 end, + sgbPalettes = function() return "menu zones" end, + } + stack:push(base) + stack:push(menu) + return { stack = stack, overworld = base, save = { options = {} } }, + base, menu +end + +-- no-mod parity +do + local run = T.sdk.loadNone({}) + local game, base, menu = scene() + T.eq(Runtime.wantsHook("screen.render_visible"), false, + "no subscriber leaves the render hook cold") + Game.draw(game) + T.eq(base.draws, 0, "the opaque menu still covers the state beneath") + T.eq(menu.draws, 1, "the opaque menu still draws") + T.eq(presentedZones, "menu zones", "the visible menu still owns palettes") + run.release() +end + +-- subscribed path, registered by a real fixture mod +do + local run = T.sdk.loadMods({ "mods/fix_screen_mirror" }, + { fs = T.sdk.memfs(FIXTURE) }) + T.eq(#run.errors, 0, + "the fixture mod loads clean (" .. tostring(run.errors[1]) .. ")") + local game, base, menu = scene() + Game.draw(game) + T.eq(base.draws, 1, "the state beneath the hidden menu draws") + T.eq(menu.draws, 0, "the mirrored menu is omitted from the main draw") + T.eq(presentedZones, "base zones", + "a hidden state cannot own the main-screen palette") + T.check(game.stack:top() == menu, + "the hidden menu remains the active top state") + game.stack:update(1 / 60) + T.eq(menu.updates, 1, "the hidden menu keeps its update ownership") + run.release() +end + +Renderer.setUISize, Renderer.beginFrame, Renderer.endFrame, + TouchControls.draw = savedSetUISize, savedBegin, savedEnd, savedTouch + +T.finish("screen_render_visible") diff --git a/tests/modkit/cases/storage.lua b/tests/modkit/cases/storage.lua new file mode 100644 index 00000000..56564c31 --- /dev/null +++ b/tests/modkit/cases/storage.lua @@ -0,0 +1,181 @@ +-- Public mod.storage contract: data-only transactions, namespace isolation, +-- deterministic listing, recovery, and failure retention. + +package.path = "./?.lua;./?/init.lua;" .. package.path +love = love or require("tests.love_stub") + +local T = require("tests.harness").suite("mod storage") +local Loader = require("src.mods.Loader") +local Runtime = require("src.mods.Runtime") +local Version = require("src.core.Version") + +local savedEvents, savedHooks = Runtime.events, Runtime.hooks + +local function manifest(id) + return ('{"id":"%s","name":"%s","version":"1.0.0",') + :format(id, id) .. '"entry":"main.lua","api":2,"profile":"content"}' +end + +local function memfs(files) + local fs = { files = files, failTmp = false, failMain = false } + + function fs.read(path) return files[path] end + function fs.write(path, body) + if fs.failTmp and path:sub(-4) == ".tmp" then return false, "tmp denied" end + if fs.failMain and path:sub(-4) == ".lua" then return false, "main denied" end + files[path] = body + return true + end + function fs.remove(path) files[path] = nil return true end + function fs.createDirectory() return true end + function fs.getInfo(path) + if files[path] then return { type = "file" } end + local prefix = path .. "/" + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then return { type = "directory" } end + end + return nil + end + function fs.load(path) + if not files[path] then return nil, "no file: " .. path end + return load(files[path], path) + end + function fs.getDirectoryItems(path) + local prefix, seen, out = path .. "/", {}, {} + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then + local child = key:sub(#prefix + 1):match("^[^/]+") + if child and not seen[child] then + seen[child] = true + out[#out + 1] = child + end + end + end + table.sort(out) + return out + end + return fs +end + +local function game(version, playthroughId) + return { save = { + version = version, + meta = { format = 4, mods = {}, playthroughId = playthroughId }, + } } +end + +local files = { + ["mods/alpha/manifest.json"] = manifest("alpha"), + ["mods/alpha/main.lua"] = [[ +return function(mod) _G.MOD_STORAGE_ALPHA = mod.storage end +]], + ["mods/beta/manifest.json"] = manifest("beta"), + ["mods/beta/main.lua"] = [[ +return function(mod) _G.MOD_STORAGE_BETA = mod.storage end +]], +} +local fs = memfs(files) +local loader = Loader.new({ fs = fs }) +local current = game("red", "play-a") +loader.game = current +T.check(loader:load({}) == true, "storage fixture mods load") + +local alpha, beta = _G.MOD_STORAGE_ALPHA, _G.MOD_STORAGE_BETA +T.check(type(alpha) == "table" and type(beta) == "table", + "Loader exposes mod.storage through the public mod object") +if type(alpha) ~= "table" or type(beta) ~= "table" then + Runtime.events, Runtime.hooks = savedEvents, savedHooks + _G.MOD_STORAGE_ALPHA, _G.MOD_STORAGE_BETA = nil, nil + T.finish() +end + +-- Removing scope identity or exposing a mutable private slot id breaks this. +local context = alpha:context(current) +T.same(context, { + engineVersion = Version.engine, + gameVersion = "red", + playthroughId = "play-a", +}, "context exposes stable engine/game/playthrough compatibility identity") + +-- Data-only write/read. The literal expected table is independent of storage. +local payload = { format = 1, nested = { money = 1234 }, flags = { a = true } } +local ok, code, message = alpha:write(current, "states/quick/q1", payload) +T.check(ok == true, "data-only payload writes: " .. tostring(code or message)) +local loaded = alpha:read(current, "states/quick/q1") +T.same(loaded, payload, "stored payload roundtrips as data") +T.check(loaded ~= payload and loaded.nested ~= payload.nested, + "read returns decoded data rather than the caller's live table") + +local bad, badCode = alpha:write(current, "states/bad", { callback = function() end }) +T.check(not bad and badCode == "encode_failed", + "functions are rejected with a stable data-only error") + +local escaped, escapedCode = alpha:write(current, "../escape", {}) +T.check(not escaped and escapedCode == "invalid_key", + "path traversal is rejected before persistence") + +-- Logical enumeration is deterministic and prefix-scoped. +T.check(alpha:write(current, "states/quick/zeta", { n = 2 }), "write zeta") +T.check(alpha:write(current, "states/quick/alpha", { n = 1 }), "write alpha") +T.check(alpha:write(current, "settings", { enabled = true }), "write settings") +local keys = alpha:list(current, "states/quick") +T.same(keys, { "states/quick/alpha", "states/quick/q1", "states/quick/zeta" }, + "list returns sorted logical keys under the requested prefix") + +-- Mod, playthrough, and game namespaces cannot observe each other. +local missing, missingCode = beta:read(current, "states/quick/q1") +T.check(missing == nil and missingCode == "not_found", + "another mod cannot read the first mod's payload") +missing, missingCode = alpha:read(game("red", "play-b"), "states/quick/q1") +T.check(missing == nil and missingCode == "not_found", + "another playthrough cannot read the payload") +missing, missingCode = alpha:read(game("blue", "play-a"), "states/quick/q1") +T.check(missing == nil and missingCode == "not_found", + "another game version cannot read the payload") + +-- Find the implementation-owned file only to inject corruption; assertions stay +-- on public read behavior, not the path shape. +local function mainFor(fragment) + for path in pairs(files) do + if path:find(fragment, 1, true) and path:sub(-4) == ".lua" then return path end + end +end + +local q1Main = mainFor("q1") +T.check(type(q1Main) == "string", "failure fixture locates the persisted q1") +files[q1Main] = "not a serialized table" +loaded, code = alpha:read(current, "states/quick/q1") +T.same(loaded, payload, "corrupt main recovers the last verified payload") +T.eq(code, nil, "successful recovery is a normal read") + +-- A failed replacement cannot destroy the prior verified value. +T.check(alpha:write(current, "replace", { version = 1 }), "seed replace value") +fs.failTmp = true +ok, code = alpha:write(current, "replace", { version = 2 }) +fs.failTmp = false +T.check(not ok and code == "write_failed", "staging failure is reported") +T.same(alpha:read(current, "replace"), { version = 1 }, + "staging failure leaves the prior value readable") + +-- Delete is exact and idempotent-not-found is explicit. +T.check(alpha:write(current, "delete/me", { yes = true }), "seed delete target") +T.check(alpha:write(current, "delete/keep", { yes = true }), "seed delete neighbor") +T.check(alpha:delete(current, "delete/me") == true, "delete removes its target") +missing, missingCode = alpha:read(current, "delete/me") +T.check(missing == nil and missingCode == "not_found", "deleted key is unavailable") +T.same(alpha:read(current, "delete/keep"), { yes = true }, + "delete leaves neighboring keys untouched") + +-- No-mod parity: constructing/loading an empty loader creates no storage bytes. +local emptyFiles, emptyFs = {}, nil +emptyFs = memfs(emptyFiles) +local emptyLoader = Loader.new({ fs = emptyFs }) +emptyLoader.game = current +T.check(emptyLoader:load({}) == true, "no-mod loader still boots") +T.eq(next(emptyFiles), nil, "no-mod boot creates no storage paths or files") + +Runtime.events, Runtime.hooks = savedEvents, savedHooks +Runtime.currentMod = nil +_G.MOD_STORAGE_ALPHA, _G.MOD_STORAGE_BETA = nil, nil + +T.finish() diff --git a/tests/modkit_tests.lua b/tests/modkit_tests.lua index 3a688793..90b795f9 100644 --- a/tests/modkit_tests.lua +++ b/tests/modkit_tests.lua @@ -636,6 +636,52 @@ local packed = io.open(cleanPkg, "rb") check(packed ~= nil, "pack writes the package") if packed then packed:close() end +-- Reproducible-build callers pin the informational pack timestamp through the +-- standard SOURCE_DATE_EPOCH contract. Two clean invocations over the same +-- input must then produce identical archive bytes and metadata. +local epoch = "1234567890" +local envPrefix = isWindows + and ('set "SOURCE_DATE_EPOCH=%s" && '):format(epoch) + or ("SOURCE_DATE_EPOCH=%s "):format(epoch) +local deterministicA = root .. "/declared-a.modpkg" +local deterministicB = root .. "/declared-b.modpkg" +out, code = run(envPrefix .. + ("%s tools/modkit.py pack %q -o %q --base fixture") + :format(python, declared, deterministicA)) +check(code == 0, "SOURCE_DATE_EPOCH package A succeeds: " .. out) +out, code = run(envPrefix .. + ("%s tools/modkit.py pack %q -o %q --base fixture") + :format(python, declared, deterministicB)) +check(code == 0, "SOURCE_DATE_EPOCH package B succeeds: " .. out) +local archiveA = assert(io.open(deterministicA, "rb")) +local bytesA = archiveA:read("*a") +archiveA:close() +local archiveB = assert(io.open(deterministicB, "rb")) +local bytesB = archiveB:read("*a") +archiveB:close() +check(bytesA == bytesB, "SOURCE_DATE_EPOCH makes package bytes reproducible") +local inspectPack = root .. "/inspect_pack.py" +write(inspectPack, [[ +import json, sys, zipfile +with zipfile.ZipFile(sys.argv[1]) as archive: + meta = json.loads(archive.read(".modkit/pack.json")) +assert meta["packed_at"] == "2009-02-13T23:31:30Z", meta["packed_at"] +]]) +out, code = run(("%s %q %q"):format(python, inspectPack, deterministicA)) +check(code == 0, "pack metadata honors SOURCE_DATE_EPOCH: " .. out) +local invalidEpochPrefix = isWindows + and 'set "SOURCE_DATE_EPOCH=not-a-time" && ' + or "SOURCE_DATE_EPOCH=not-a-time " +local invalidEpochPkg = root .. "/declared-invalid-epoch.modpkg" +out, code = run(invalidEpochPrefix .. + ("%s tools/modkit.py pack %q -o %q --base fixture") + :format(python, declared, invalidEpochPkg)) +check(code == 2, "invalid SOURCE_DATE_EPOCH is a usage failure: " .. out) +check(out:find("SOURCE_DATE_EPOCH", 1, true) ~= nil, + "invalid source epoch names the failed contract") +check(io.open(invalidEpochPkg, "rb") == nil, + "invalid source epoch writes no package") + -- MK305 diffs shipped tables against the imported dataset; fake one under -- a scratch repo root so the check exercises the same on ROM-less machines local fake = root .. "/fakerepo" diff --git a/tests/parity_A.lua b/tests/parity_A.lua index 7c8bd473..7c15caa0 100644 --- a/tests/parity_A.lua +++ b/tests/parity_A.lua @@ -183,7 +183,7 @@ do new = function(game, text, done) return { text = text, onDone = done } end, } - local function driveLeader(mapId, textConst, beatFlag) + local function driveLeader(mapId, textConst, beatFlag, gotFlag) local pushed, engaged local game = { save = { flags = {} }, @@ -198,6 +198,9 @@ do mapId .. " leader talk (no badge) engages the leader battle") engaged, pushed = nil, nil game.save.flags[beatFlag] = true + -- the advice/farewell branch is pokered's .afterBeat, reached only + -- once EVENT_GOT_TM* is set (the TM went into the bag) + if gotFlag then game.save.flags[gotFlag] = true end local state = {} script(game, ow, { def = {} }, function() state.doneCalled = true end) check(pushed and not engaged, @@ -206,17 +209,17 @@ do end local _, box = driveLeader("CERULEAN_GYM", "TEXT_CERULEANGYM_MISTY", - "EVENT_BEAT_MISTY") + "EVENT_BEAT_MISTY", "EVENT_GOT_TM11") eq(box and box.text, Data.text._CeruleanGymMistyTM11ExplanationText, "Misty (beaten) shows the TM11 explanation text") _, box = driveLeader("CINNABAR_GYM", "TEXT_CINNABARGYM_BLAINE", - "EVENT_BEAT_BLAINE") + "EVENT_BEAT_BLAINE", "EVENT_GOT_TM38") eq(box and box.text, Data.text._CinnabarGymBlainePostBattleAdviceText, "Blaine (beaten) shows his post-battle advice text") local game, gbox, state = driveLeader("VIRIDIAN_GYM", "TEXT_VIRIDIANGYM_GIOVANNI", - "EVENT_BEAT_GIOVANNI") + "EVENT_BEAT_GIOVANNI", "EVENT_GOT_TM27") eq(gbox and gbox.text, Data.text._ViridianGymGiovanniPostBattleAdviceText, "Giovanni (beaten) shows his farewell text") @@ -392,6 +395,59 @@ do check(cinnabarNpc and ow:trainerDefeated(cinnabarNpc), "unfought Cinnabar trainer is defeated via seeded header event") + -- #797: a full bag at the victory skips the TM hand-over (pokered's + -- `call GiveItem` / `jr nc, .BagFull`): badge and beat flag still land, + -- but EVENT_GOT_TM34 stays unset and the "make room" line replaces the + -- received/explanation texts. Talking to Brock afterwards re-runs the + -- ReceiveTM script and grants the TM once there is room. + while Game.stack:top() do Game.stack:pop() end + Game.save = SaveData.newGame() + Game.save.flags = {} + Game.save.inventory = {} + Game.save.defeatedTrainers = {} + local Bag = require("src.inventory.Bag") + for i = 1, Bag.capacity(Data) do + Bag.add(Game.save, "FULLBAG_" .. i, 1, Data) + end + eq(Bag.slots(Game.save), Bag.capacity(Data), "bag is full before Brock") + Game.stack:push(OW, "PEWTER_GYM", 4, 13, "up") + ow = Game.stack:top() + ow:checkVictoryRewards("OPP_BROCK", 1) + local fullBagText = stackedDialogue() + check(Game.save.flags.EVENT_BEAT_BROCK, + "full bag: Brock victory still sets EVENT_BEAT_BROCK") + check(Game.save.inventory.BOULDERBADGE == 1, + "full bag: Brock victory still awards BOULDERBADGE") + check(not Game.save.flags.EVENT_GOT_TM34, + "full bag: EVENT_GOT_TM34 stays unset (bag_full branch)") + check(Game.save.inventory.TM_BIDE == nil, + "full bag: TM34 is not forced into the bag") + check(fullBagText:find("room", 1, true) ~= nil, + "full bag: victory dialogue shows Brock's make-room line") + check(fullBagText:find("BIDE", 1, true) == nil, + "full bag: received/explanation texts are skipped") + + -- make room, then talk to Brock: the middle branch re-runs ReceiveTM34 + while Game.stack:top() do Game.stack:pop() end + Bag.remove(Game.save, "FULLBAG_1", 1) + local brockTalk = init.talkScript("PEWTER_GYM", "TEXT_PEWTERGYM_BROCK") + check(brockTalk ~= nil, "Brock's talk script is registered") + brockTalk(Game, ow, { def = {} }, function() end) + local retryText = stackedDialogue() + check(retryText:find("Wait!", 1, true) ~= nil, + "retry: ReceiveTM34 lead-in (Wait! Take this!) shows again") + check(retryText:find("BIDE", 1, true) ~= nil, + "retry: received/explanation texts show once the TM fits") + eq(Game.save.inventory.TM_BIDE, 1, "retry: TM34 goes into the bag") + check(Game.save.flags.EVENT_GOT_TM34, "retry: EVENT_GOT_TM34 is set") + + -- once the TM is handed over, Brock falls back to his advice text + while Game.stack:top() do Game.stack:pop() end + brockTalk(Game, ow, { def = {} }, function() end) + local adviceText = stackedDialogue() + check(adviceText:find("CERULEAN", 1, true) ~= nil, + "after the TM: Brock shows his post-battle advice text") + while Game.stack:top() do Game.stack:pop() end end diff --git a/tests/parity_B.lua b/tests/parity_B.lua index 3a0291a9..4c5876c0 100644 --- a/tests/parity_B.lua +++ b/tests/parity_B.lua @@ -60,6 +60,17 @@ for _, r in ipairs(rows) do end check(not hasRecord, "CHAMPIONS_ROOM rival script no longer calls record_hall_of_fame") +-- The post-battle walk takes the right-hand detour before heading north, so +-- the player does not visibly pass through the rival at (4,2). +local route = {} +for _, r in ipairs(rows) do + if r[1] == "move_player" then route[#route + 1] = r end +end +eq(route[#route - 1] and route[#route - 1][2], "right", + "walk-out route first moves right around the rival") +eq(route[#route] and route[#route][2], "up", + "walk-out route then heads north to Hall of Fame") + -- (3) Commands.face_player_dir sets the player's facing local Commands = require("src.script.Commands") check(type(Commands.face_player_dir) == "function", "Commands.face_player_dir is a function") diff --git a/tests/parity_I_M.lua b/tests/parity_I_M.lua index 980b37b0..6213e265 100644 --- a/tests/parity_I_M.lua +++ b/tests/parity_I_M.lua @@ -114,10 +114,10 @@ eq(ow:checkBoulderPush("right"), false, "no push before activation (bump 1)") eq(ow:checkBoulderPush("right"), false, "no push before activation (bump 2)") eq(boulder.cellX, 18, "boulder unmoved while STRENGTH is inactive") --- activate via the party menu STRENGTH action (submenu {STATS,SWITCH,STRENGTH}) +-- activate via the party menu STRENGTH action (submenu {STRENGTH,STATS,SWITCH}) clearCaptured() local pmStr = PartyMenu.new(Game) -selectSubItem(pmStr, 3) +selectSubItem(pmStr, 1) eq(Game.overworld.strengthActive, true, "party-menu STRENGTH sets strengthActive") check(onStack(pmStr), "party menu stays under the STRENGTH texts (#385)") check(sawText("used") and sawText("STRENGTH"), "_UsedStrengthText shown") @@ -156,7 +156,7 @@ Game.save.inventory.SOULBADGE = true ow.player.facing = "up"; ow.player.surfing = false clearCaptured() local pmSurfFail = PartyMenu.new(Game) -selectSubItem(pmSurfFail, 3) +selectSubItem(pmSurfFail, 1) check(sawText("No SURFing"), "_NoSurfingHereText when not facing water") check(pmSurfFail.submenu == true, "party menu stays open after a failed SURF") eq(ow.player.surfing, false, "no mount when SURF fails") @@ -166,7 +166,7 @@ popToOW() ow.player.facing = "down"; ow.player.surfing = false clearCaptured() local pmSurf = PartyMenu.new(Game) -selectSubItem(pmSurf, 3) +selectSubItem(pmSurf, 1) -- the got-on text prints over the menu (#385); dismissing it closes the -- menu and mounts, and the blink that follows carries the step check(onStack(pmSurf), "party menu stays under the got-on text") @@ -228,7 +228,7 @@ ow = pushOW("CERULEAN_CITY", 19, 27, "down") -- success path: facing the tree -> _UsedCutText, menu closes, tree replaced clearCaptured() local pmCut = PartyMenu.new(Game) -selectSubItem(pmCut, 3) +selectSubItem(pmCut, 1) check(not onStack(pmCut), "party menu closes after a successful CUT") check(sawText("CUT"), "_UsedCutText shown on a successful CUT") drainText() -- the tree swap is deferred until the message is dismissed @@ -239,7 +239,7 @@ popToOW() ow.player.facing = "up" clearCaptured() local pmCutFail = PartyMenu.new(Game) -selectSubItem(pmCutFail, 3) +selectSubItem(pmCutFail, 1) check(sawText("anything to CUT"), "_NothingToCutText when not facing a tree") check(pmCutFail.submenu == true, "party menu stays open after a failed CUT") ow.player.facing = "right" @@ -276,7 +276,7 @@ Game.save.forcedBike = true eq(ow:useSurfFieldMove(), "forced_bike", "forced bike refuses SURF (even facing water)") clearCaptured() local pmBike = PartyMenu.new(Game) -selectSubItem(pmBike, 3) +selectSubItem(pmBike, 1) check(sawText("Cycling is fun!\nForget SURFing!"), "_CyclingIsFunText verbatim") check(pmBike.submenu == true, "party menu stays open (.loop) after the bike refusal") eq(ow.player.surfing, false, "no mount on the Cycling Road") @@ -320,7 +320,7 @@ check(ow.map:isWaterCell(7, 12), "water south of the B4F stairs square") eq(ow:useSurfFieldMove(), "current", "B4F stairs square refuses SURF pre-boulders") clearCaptured() local pmCur = PartyMenu.new(Game) -selectSubItem(pmCur, 3) +selectSubItem(pmCur, 1) check(sawText("The current is\nmuch too fast!"), "_CurrentTooFastText verbatim") check(pmCur.submenu == true, "party menu stays open (.loop) after the current refusal") eq(ow.player.surfing, false, "no mount against the current") @@ -356,7 +356,7 @@ table.remove(ow.entities) -- .goBackToMap) and the simulated pad press steps the player ashore clearCaptured() local pmOff = PartyMenu.new(Game) -selectSubItem(pmOff, 3) +selectSubItem(pmOff, 1) check(not onStack(pmOff), "party menu closes on dismount") eq(ow.player.surfing, false, ".stopSurfing returns to walking before the step") eq(#captured, 0, "no message on a successful dismount") @@ -375,7 +375,7 @@ ow.player.px, ow.player.py = 4 * 16, 15 * 16 ow.player.facing = "down" clearCaptured() local pmNoOff = PartyMenu.new(Game) -selectSubItem(pmNoOff, 3) +selectSubItem(pmNoOff, 1) check(sawText("There's no place\nto get off!"), "_SurfingNoPlaceToGetOffText verbatim") check(onStack(pmNoOff), "the menu stays under the message (#385)") eq(ow.player.surfing, true, "still surfing after a blocked dismount") @@ -394,7 +394,7 @@ Game.save.inventory = { RAINBOWBADGE = true } ow = pushOW("SEAFOAM_ISLANDS_1F", 17, 10, "right") clearCaptured() local pmStr2 = PartyMenu.new(Game) -selectSubItem(pmStr2, 3) +selectSubItem(pmStr2, 1) local page1 = Game.stack:top() check(page1 ~= nil and page1.pages ~= nil and page1.auto ~= nil, "_UsedStrengthText box is a no-prompt (auto) page") @@ -486,8 +486,8 @@ Game.save.inventory = { ow = pushOW("SEAFOAM_ISLANDS_1F", 17, 10, "right") clearCaptured() local pmFaintStr = PartyMenu.new(Game) --- Seafoam is not OVERWORLD, so FLY is omitted: STATS, SWITCH, CUT, STRENGTH, SURF -selectSubItem(pmFaintStr, 4) +-- Seafoam is not OVERWORLD, so FLY is omitted: CUT, STRENGTH, SURF, STATS, SWITCH +selectSubItem(pmFaintStr, 2) eq(Game.overworld.strengthActive, true, "fainted mon can activate STRENGTH from the party menu") check(sawText("used") and sawText("STRENGTH"), @@ -501,9 +501,9 @@ ow = pushOW("PALLET_TOWN", 4, 13, "down") ow.player.surfing = false eq(ow:useSurfFieldMove(), "ok", "useSurfFieldMove ok with only a fainted SURF mon") clearCaptured() --- submenu order: STATS, SWITCH, FLY, CUT, STRENGTH, SURF (move order on mon) +-- submenu order: FLY, CUT, STRENGTH, SURF (move order on mon), then STATS, SWITCH local pmFaintSurf = PartyMenu.new(Game) -selectSubItem(pmFaintSurf, 6) +selectSubItem(pmFaintSurf, 4) Game.stack:pop().onDone() -- dismiss the text: menu closes, mount (#320, #385) eq(ow.player.surfing, true, "fainted mon can SURF from the party menu") check(not onStack(pmFaintSurf), "party menu closes after fainted SURF") diff --git a/tests/parity_J.lua b/tests/parity_J.lua index 67b61d8c..5316ca07 100644 --- a/tests/parity_J.lua +++ b/tests/parity_J.lua @@ -283,7 +283,8 @@ end do local pressed = {} local tb = freshBattle() - tb.game = { input = { wasPressed = function(_, k) return pressed[k] or false end }, + tb.game = { input = { wasPressed = function(_, k) return pressed[k] or false end, + isDown = function(_, k) return pressed[k] or false end }, stack = { top = function() return tb end }, save = Game.save } tb.kind = "wild" @@ -359,7 +360,8 @@ do local fg = { data = Data, save = require("src.core.SaveData").newGame(), - input = { wasPressed = function(_, k) return pressed[k] or false end }, + input = { wasPressed = function(_, k) return pressed[k] or false end, + isDown = function(_, k) return pressed[k] or false end }, stack = stack, } fg.save.party = { Pokemon.new(Data, "BULBASAUR", 20) } @@ -426,10 +428,15 @@ do bag:update(1 / 60) end check(stack:top() ~= bag, "the ball is thrown without input") + -- Battle exit now rides Transition.battleReturn (MapEntryAfterBattle's + -- GBFadeInFromWhite, home/overworld.asm:749-753): the battle pops itself + -- and pushes the fade, which fires onFinish only once ITS update counts + -- down -- so pump whatever sits on top of the stack, not the demo state. for _ = 1, 2000 do if finished then break end pressed.a = true - demo:update(1 / 60) + local top = stack:top() + if top then top:update(1 / 60) else break end end pressed.a = false check(finished, "the throw ends the demo battle") diff --git a/tests/parity_L.lua b/tests/parity_L.lua index 817886d8..11c8ae37 100644 --- a/tests/parity_L.lua +++ b/tests/parity_L.lua @@ -59,7 +59,11 @@ local t = battler{ disabledSlot = 1, disabledTurns = 2, xAccuracy = true, mon = { status = "SLP" }, name = "TARGET", } -local msg = MoveEffects.primary.HAZE_EFFECT(nil, u, t) +-- Effect rows take the battle handle first so romText can serve the ROM's +-- own wording; a data-only stub is all these pure rows dereference. +local B = { data = Data } + +local msg = MoveEffects.primary.HAZE_EFFECT(B, u, t) check(next(u.stages) == nil, "Haze clears all of the user's stat stages") check(next(t.stages) == nil, "Haze clears all of the target's stat stages") @@ -89,13 +93,13 @@ eq(msg[1], "All STATUS changes\nare eliminated!", "Haze prints the elimination t -- FRZ target also forfeits its move. local frz = battler{ mon = { status = "FRZ" }, name = "FROZEN" } -MoveEffects.primary.HAZE_EFFECT(nil, battler{ mon = {} }, frz) +MoveEffects.primary.HAZE_EFFECT(B, battler{ mon = {} }, frz) eq(frz.mon.status, nil, "target's freeze is cured") check(frz.skipMove == true, "curing target's freeze forfeits its move") -- Badly-poisoned TARGET: status cured, no forfeit, toxic counter gone. local psnT = battler{ mon = { status = "PSN" }, toxicCounter = 4, name = "PSN_T" } -MoveEffects.primary.HAZE_EFFECT(nil, battler{ mon = {} }, psnT) +MoveEffects.primary.HAZE_EFFECT(B, battler{ mon = {} }, psnT) eq(psnT.mon.status, nil, "badly-poisoned target is fully cured of poison") check(psnT.toxicCounter == nil, "badly-poisoned target's toxic counter cleared") check(not psnT.skipMove, "curing poison does NOT forfeit the target's move") @@ -103,14 +107,14 @@ check(not psnT.skipMove, "curing poison does NOT forfeit the target's move") -- BRN / PAR targets: cured, no forfeit. for _, st in ipairs({ "BRN", "PAR" }) do local tb = battler{ mon = { status = st }, name = st } - MoveEffects.primary.HAZE_EFFECT(nil, battler{ mon = {} }, tb) + MoveEffects.primary.HAZE_EFFECT(B, battler{ mon = {} }, tb) eq(tb.mon.status, nil, "target's " .. st .. " is cured") check(not tb.skipMove, st .. " target keeps its move (no sleep/freeze forfeit)") end -- A burned USER keeps its own burn (status not the one Haze cures). local burnedUser = battler{ mon = { status = "BRN" }, name = "BURNER" } -MoveEffects.primary.HAZE_EFFECT(nil, burnedUser, battler{ mon = {} }) +MoveEffects.primary.HAZE_EFFECT(B, burnedUser, battler{ mon = {} }) eq(burnedUser.mon.status, "BRN", "user's own burn is not cured by Haze") -- ===================================================================== @@ -145,7 +149,7 @@ eq(dBurnedHaze, dHealthy, "Haze lifts the burn Attack-halving (damage == unburne -- A stat-stage change re-bakes the penalty (effects.asm:505-506). Bump the -- attacker's DEFENSE (irrelevant to its own offense) so only hazeStatReset flips. -MoveEffects.primary.DEFENSE_UP1_EFFECT(nil, hazedAtk, nil) +MoveEffects.primary.DEFENSE_UP1_EFFECT(B, hazedAtk, nil) check(hazedAtk.hazeStatReset == nil, "a stat-stage change re-arms the burn penalty") local dAfter = Damage.compute(ruleset, hazedAtk, defender, move, opts) eq(dAfter, dBurnedRaw, "burn Attack-halving returns after the stage change") @@ -159,10 +163,10 @@ local para = battler{ mon = { status = "PAR", level = 50, stats = { hp = 100 } }, name = "PARA", } eq(TurnOrder.effectiveSpeed(para), 25, "paralysis quarters speed before Haze (100 -> 25)") -MoveEffects.primary.HAZE_EFFECT(nil, para, battler{ mon = {} }) +MoveEffects.primary.HAZE_EFFECT(B, para, battler{ mon = {} }) eq(TurnOrder.effectiveSpeed(para), 100, "Haze lifts paralysis Speed-quartering") -- Re-arm via an ATTACK stage change (irrelevant to the speed calc). -MoveEffects.primary.ATTACK_UP1_EFFECT(nil, para, nil) +MoveEffects.primary.ATTACK_UP1_EFFECT(B, para, nil) check(para.hazeStatReset == nil, "stage change re-arms the paralysis penalty") eq(TurnOrder.effectiveSpeed(para), 25, "Speed-quartering resumes after the stage change") diff --git a/tests/parity_ai_switch_rate.lua b/tests/parity_ai_switch_rate.lua new file mode 100644 index 00000000..d792a3e7 --- /dev/null +++ b/tests/parity_ai_switch_rate.lua @@ -0,0 +1,123 @@ +-- Parity test: the per-class trainer switch rolls (#890). +-- +-- Reports keep landing that Jugglers and Agatha "never switch". The rolls +-- are exact byte compares in pokered, so they are machine-assertable: sweep +-- every one of the 256 random bytes through TrainerAI.classAction and count +-- the switch outcomes. +-- +-- JugglerAI (engine/battle/trainer_ai.asm:324-327) +-- cp 25 percent + 1 / ret nc / jp AISwitchIfEnoughMons +-- `percent` is `* $ff / 100` (macros/data.asm:3), so the threshold is +-- 25 * 255 / 100 + 1 = 64 and the switch fires on rolls 0..63. +-- AgathaAI (engine/battle/trainer_ai.asm:429-437) +-- cp 8 percent / jp c, AISwitchIfEnoughMons -> 8 * 255 / 100 = 20, so +-- rolls 0..19 switch; the SAME byte then feeds cp 50 percent + 1 = 128 +-- for the SUPER POTION branch, which is why the two outcomes partition +-- the byte range instead of rolling twice. +-- +-- Self-contained; run via `luajit tests/parity_ai_switch_rate.lua`. +-- Also picked up by tests/run_tests.lua's parity_* glob. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not (Data.pokemon and Data.pokemon.RATTATA) then Data:load() end + +local Pokemon = require("src.pokemon.Pokemon") +local TrainerAI = require("src.battle.TrainerAI") +local BattleState = require("src.battle.BattleState") +local S = require("tests.harness").suite("parity ai switch rate") +local check, eq = S.check, S.eq + +-- Just the fields classAction reads: the class lookup goes through +-- trainer.id, the HP fraction through enemy.mon, the reserve scan through +-- enemyParty/enemyIndex. hpFrac is current/max for the item branches. +local function stubBattle(id, roll, hpFrac) + local maxHp = 100 + return { + kind = "trainer", trainer = { id = id, name = id }, data = Data, + aiUses = 3, + enemy = { mon = { hp = math.floor(maxHp * hpFrac), stats = { hp = maxHp } }, + stages = {}, name = "MON" }, + enemyParty = { { hp = maxHp }, { hp = maxHp }, { hp = maxHp } }, + enemyIndex = 1, + rng = function() return roll end, + } +end + +-- Sweep the whole byte range: the counts ARE the thresholds. +local function sweep(id, hpFrac) + local switches, items = 0, 0 + for roll = 0, 255 do + local act = TrainerAI.classAction(stubBattle(id, roll, hpFrac)) + if act and act.special == "aiSwitch" then switches = switches + 1 + elseif act and act.special == "aiItem" then items = items + 1 end + end + return switches, items +end + +do + local sw, it = sweep("OPP_JUGGLER", 1.0) + eq(sw, 64, "Juggler switches on 64 of 256 rolls (cp 25 percent + 1)") + eq(it, 0, "Juggler never reaches for an item") + local swLow = sweep("OPP_JUGGLER", 0.05) + eq(swLow, 64, "the Juggler roll does not depend on the enemy's HP") +end + +do + -- above 1/4 max HP the item branch is refused, so only the switch fires + local sw, it = sweep("OPP_AGATHA", 1.0) + eq(sw, 20, "Agatha switches on 20 of 256 rolls (cp 8 percent)") + eq(it, 0, "Agatha holds the SUPER POTION above 1/4 HP") + -- below 1/4 the shared byte splits: 0..19 switch, 20..127 potion + local swLow, itLow = sweep("OPP_AGATHA", 0.1) + eq(swLow, 20, "the switch roll still wins the low rolls at low HP") + eq(itLow, 108, "the same byte leaves 20..127 for the SUPER POTION") +end + +-- AISwitchIfEnoughMons (engine/battle/trainer_ai.asm:554-582) counts every +-- unfainted party mon including the active one and needs 2 or more, so a +-- one-mon roster never switches however low the roll lands. +do + local b = stubBattle("OPP_JUGGLER", 0, 1.0) + b.enemyParty = { { hp = 100 } } + check(TrainerAI.classAction(b) == nil, + "a lone enemy mon never switches (cp 2 / jp nc)") + local b2 = stubBattle("OPP_JUGGLER", 0, 1.0) + b2.enemyParty = { { hp = 100 }, { hp = 0 }, { hp = 100 } } + local act = TrainerAI.classAction(b2) + check(act and act.index == 3, + "the switch takes the first living reserve, skipping the fainted slot") +end + +-- End to end through the real battle: the action a Juggler picks has to +-- reach executeAction and actually swap the active mon plus print +-- _AIBattleWithdrawText, otherwise a correct roll is invisible in play. +do + local Game = { + data = Data, + save = { party = { Pokemon.new(Data, "BULBASAUR", 50) }, + player = { name = "RED" }, inventory = {}, + options = { battleStyle = "set" }, + pokedex = { seen = {}, owned = {} }, flags = {}, money = 0 }, + stack = { push = function() end, pop = function() end, top = function() end }, + } + -- Juggler party 2 is the four-mon Victory Road roster + local b = BattleState.newTrainer(Game, "OPP_JUGGLER", 2) + eq(b.aiUses, 3, "wAICount seeded from the class record on send-out") + b.rng = function(lo) return lo end -- roll 0: inside every threshold + local act = b:enemyAction() + check(act and act.special == "aiSwitch", "the enemy turn resolves to a switch") + local outgoing = b.enemy.name + b:executeAction(b.enemy, b.player, act) + eq(b.enemyIndex, 2, "the active enemy slot moved to the reserve") + check(b.enemy.name ~= outgoing, "a different mon is out") + eq(b.aiUses, 3, "EnemySendOutFirstMon reseeds wAICount (core.asm:1305-1307)") + local withdrew = false + for _, item in ipairs(b.queue) do + if item.text and item.text:find("with%-\ndrew") then withdrew = true end + end + check(withdrew, "_AIBattleWithdrawText is queued for the player to read") +end + +S.finish() diff --git a/tests/parity_android_permissions.lua b/tests/parity_android_permissions.lua index ebe72ee6..8db14fde 100644 --- a/tests/parity_android_permissions.lua +++ b/tests/parity_android_permissions.lua @@ -39,7 +39,16 @@ check(type(script) == "string", SCRIPT .. " is readable") -- The trim is only dangerous because it edits the tracked manifest in place; if -- that stops being true, everything below tests a file the build never touches. if script then - local target = script:match('local manifest="([^"]+)"') + -- The script has grown other `local manifest=` locals (the Yellow ROM + -- import manifest recovery), so scan every assignment for the one that + -- names the Android manifest instead of trusting the first match. + local target + for candidate in script:gmatch('local manifest="([^"]+)"') do + if candidate:find("AndroidManifest.xml", 1, true) then + target = candidate + break + end + end check(target ~= nil, "build_android.sh names the manifest it rewrites") check(target ~= nil and target:find("app/src/main/AndroidManifest.xml", 1, true) ~= nil, diff --git a/tests/parity_applying_attack_anim.lua b/tests/parity_applying_attack_anim.lua index 6b167580..4cb22bed 100644 --- a/tests/parity_applying_attack_anim.lua +++ b/tests/parity_applying_attack_anim.lua @@ -90,7 +90,14 @@ end -- type-4 turn can still use it do local _, _, rows = typeOf("BUBBLEBEAM", true) - eq(rows[1].sfx, "Damage", "the row carries the damage sound") + -- PlayApplyingAttackSound sets wFrequencyModifier alongside the sound + -- ($20 for SFX_DAMAGE), and the noise channel's polynomial counter IS + -- that modifier, so the row carries both now (#826) + eq(type(rows[1].sfx) == "table" and rows[1].sfx.sound, "Damage", + "the row carries the damage sound") + eq(rows[1].sfx.pitch, 0x20, "with its PlayApplyingAttackSound pitch byte") + eq(rows[1].sfx.tempo, nil, + "and no tempo byte: Audio2_note_length skips the sfx tempo on CHAN8") local _, _, plain = typeOf("TACKLE", true) check(plain[1].blink ~= nil, "a type-4 row carries the pic to blink") end @@ -190,7 +197,10 @@ do eq(tb.fx.shakeProg, nil, "type 4 arms no shake (#354 must not regress it)") check(tb.fx.blink ~= nil and tb.fx.blink.target == tb.enemy, "type 4 blinks the enemy pic") - eq(tb.waitFrames, 20, "for the 20 frames AnimationBlinkEnemyMon takes") + -- AnimationBlinkMon (animations.asm:1360-1376) is `ld c, 6` iterations + -- of hide + DelayFrames 5 + show + DelayFrames 5 = 60 frames; the port + -- once ran it in 20, a third of its length (Timing.BLINK_MON). + eq(tb.waitFrames, 60, "for the 60 frames AnimationBlinkEnemyMon takes") end -- the OPTIONS animation toggle still gates the whole thing; the sound does not diff --git a/tests/parity_battle_auto_text_bug765.lua b/tests/parity_battle_auto_text_bug765.lua new file mode 100644 index 00000000..278fc448 --- /dev/null +++ b/tests/parity_battle_auto_text_bug765.lua @@ -0,0 +1,115 @@ +-- Parity test: battle pages whose ROM tail is `text_end` / `done` hand off +-- with no button press (#765). Only TX_PROMPT_BUTTON writes the '▼' and +-- runs ManualTextScroll (home/text.asm:434-446); a TX_END tail returns +-- straight out of PrintText (home/text.asm:328-334). The used-move line +-- (engine/battle/used_move_text.asm EndUsedMove1Text..EndUsedMove5Text) and +-- the item-use line (ItemUseText00, engine/items/item_effects.asm) are both +-- of that kind, so a sayAuto row must flow into the next queue row untouched +-- while a plain say page still waits on A/B like PromptText. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local S = require("tests.harness").suite("parity battle auto text (#765)") +local check, eq = S.check, S.eq + +local Data = require("src.core.Data") +if not Data.maps then Data:load() end +local Font = require("src.render.Font") +Font.load(Data) + +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") +local Sound = require("src.core.Sound") +local Music = require("src.core.Music") +local Timing = require("src.core.Timing") + +-- Silence audio: BattleState reaches both modules through require() at the +-- call site, so patching the fields here is what the battle ends up calling. +Sound.playCry = function() end +Sound.play = function() end +Sound.playMove = function() end +Sound.playMoveCry = function() end +Sound.stopLoop = function() end +Music.playBattle = function() end +Music.play = function() end + +-- stub stack + input, like the other headless battle probes +local press = {} +local function makeGame(party) + local save = SaveData.newGame() + save.party = party + local stack = { states = {} } + function stack:push(state) self.states[#self.states + 1] = state end + function stack:pop() return table.remove(self.states) end + function stack:top() return self.states[#self.states] end + return { data = Data, save = save, stack = stack, + input = { wasPressed = function(_, b) return press[b] == true end, + isDown = function(_, b) return press[b] == true end } } +end + +local game = makeGame({ Pokemon.new(Data, "BULBASAUR", 50) }) +local battle = BattleState.newWild(game, "RATTATA", 2) +battle.onFinish = function() end +battle:enter() + +-- strip the intro so the queue under test is exactly what gets inserted; +-- afterQueue is cleared so a drained queue between probes cannot flip the +-- phase to "menu" and stop update() from pumping messages +battle.queue = {} +battle.current = nil +battle.introSlide = 0 +battle.phase = "messages" +battle.afterQueue = nil + +-- ------------------------------------------------- auto page, no delay +local ran = false +battle:sayAuto("AUTO PAGE") +battle:act(function() ran = true end) + +local promptedDuringAuto = false +for _ = 1, 300 do + if ran then break end + if battle.msgPrompt then promptedDuringAuto = true end + battle:update(1 / 60) +end +check(ran, "an auto page hands off to the next row with no button") +check(not promptedDuringAuto, "the prompt flag never rises on an auto page") +eq(battle.msgHold, true, + "the finished auto page stays held for drawTextArea (#296)") + +-- ------------------------------------------------- auto page, autoDelay +local ran2, typedFrame, ranFrame = false, nil, nil +battle:sayAuto("HELD PAGE", 30) +battle:act(function() ran2 = true end) +for f = 1, 600 do + battle:update(1 / 60) + if not typedFrame and battle.current + and battle.charIndex >= battle.total then + typedFrame = f + end + if ran2 then ranFrame = f break end +end +check(ran2, "the delayed auto page still hands off by itself") +check(typedFrame ~= nil and ranFrame ~= nil + and ranFrame - typedFrame >= 30, + "autoDelay holds the finished page for its frame count first") + +-- ------------------------------------------------- plain page still prompts +battle:say("PROMPT PAGE") +local prompted = false +for _ = 1, 300 do + battle:update(1 / 60) + if battle.msgPrompt then prompted = true break end +end +check(prompted, "a plain page still raises the blinking prompt (#317)") +-- PromptText runs ProtectedDelay3 before ManualTextScroll watches the +-- joypad (home/text.asm:213-217), so pay that hold before pressing +for _ = 1, Timing.TEXT_PRE_ADVANCE do battle:update(1 / 60) end +check(battle.current ~= nil, "and the page holds on screen with no button") +press.a = true +battle:update(1 / 60) +press.a = false +eq(battle.msgPrompt, nil, "the A press clears the prompt") +eq(battle.current, nil, "and dismisses the page") + +S.finish() diff --git a/tests/parity_battle_blackout_pals.lua b/tests/parity_battle_blackout_pals.lua index ea398041..42637f51 100644 --- a/tests/parity_battle_blackout_pals.lua +++ b/tests/parity_battle_blackout_pals.lua @@ -38,7 +38,8 @@ local function makeGame(party) function stack:pop() return table.remove(self.states) end function stack:top() return self.states[#self.states] end return { data = Data, save = save, stack = stack, - input = { wasPressed = function(_, b) return press[b] == true end } } + input = { wasPressed = function(_, b) return press[b] == true end, + isDown = function(_, b) return press[b] == true end } } end local function step(battle) diff --git a/tests/parity_battle_intro_cry.lua b/tests/parity_battle_intro_cry.lua index 641dd1b1..cd7dcd11 100644 --- a/tests/parity_battle_intro_cry.lua +++ b/tests/parity_battle_intro_cry.lua @@ -44,7 +44,8 @@ local function makeGame(party) function stack:pop() return table.remove(self.states) end function stack:top() return self.states[#self.states] end return { data = Data, save = save, stack = stack, - input = { wasPressed = function() return false end } } + input = { wasPressed = function() return false end, + isDown = function() return false end } } end local game = makeGame({ Pokemon.new(Data, "BULBASAUR", 50) }) diff --git a/tests/parity_battle_item_turn.lua b/tests/parity_battle_item_turn.lua index 508d88d1..cf54bedf 100644 --- a/tests/parity_battle_item_turn.lua +++ b/tests/parity_battle_item_turn.lua @@ -172,7 +172,11 @@ do local box = game.stack:top() check(isBox(box), "the restored-HP message opened (#379)") if isBox(box) then - check(box.text:find("was restored", 1, true) ~= nil, + -- the line is _PotionText itself when the cache carries it (" + -- recovered by !") and the engine's "was restored" wording on a + -- dataset without the label (src/core/RomText.lua) + check((box.text:find("recovered by", 1, true) + or box.text:find("was restored", 1, true)) ~= nil, "and it is the restored-HP line: " .. tostring(box.text)) dismiss(game.stack, box) end diff --git a/tests/parity_battle_music_bug782.lua b/tests/parity_battle_music_bug782.lua new file mode 100644 index 00000000..9af8eb55 --- /dev/null +++ b/tests/parity_battle_music_bug782.lua @@ -0,0 +1,72 @@ +-- Parity: which trainers get the gym-leader battle theme (#782). +-- PlayBattleMusic (audio/play_battle_music.asm) picks MUSIC_GYM_LEADER_BATTLE +-- only when wGymLeaderNo is set, and the eight gym scripts +-- (scripts/PewterGym.asm .. ViridianGym.asm) are its only writers; Lance +-- shares the theme by opponent class and the Champion (OPP_RIVAL3) takes +-- MUSIC_FINAL_BATTLE. Giovanni's Rocket Hideout (OPP_GIOVANNI#1) and Silph +-- Co (OPP_GIOVANNI#2) fights never touch the byte, so they must play +-- MUSIC_TRAINER_BATTLE. The port keyed the boss check on the trainer CLASS +-- alone, so every Giovanni battle borrowed the Earth Badge roster's theme, +-- the gym victory jingle, and the Pikachu GYMLEADER happiness bump. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local S = require("tests.harness").suite("parity battle music bug782") +local check, eq = S.check, S.eq + +local Data = require("src.core.Data") +if not Data.maps then Data:load() end +local Font = require("src.render.Font") +Font.load(Data) + +local BattleState = require("src.battle.BattleState") +local Pokemon = require("src.pokemon.Pokemon") +local SaveData = require("src.core.SaveData") + +local function makeGame() + local save = SaveData.newGame() + save.party = { Pokemon.new(Data, "BULBASAUR", 60) } + return { data = Data, save = save, + input = { wasPressed = function() return false end, + isDown = function() return false end }, + stack = { top = function() return nil end, + push = function() end, pop = function() end } } +end + +local function kindOf(oppClass, partyIndex) + local battle = BattleState.newTrainer(makeGame(), oppClass, partyIndex) + return battle:computeMusicKind(), battle.isGymLeader +end + +-- the two non-gym Giovanni fights: plain trainer theme, no gym-leader flag +do + local kind, gym = kindOf("OPP_GIOVANNI", 1) -- Rocket Hideout B4F + eq(kind, "trainer", "Rocket Hideout Giovanni plays the trainer theme") + check(not gym, "Rocket Hideout Giovanni is not a gym leader") + kind, gym = kindOf("OPP_GIOVANNI", 2) -- Silph Co 11F + eq(kind, "trainer", "Silph Co Giovanni plays the trainer theme") + check(not gym, "Silph Co Giovanni is not a gym leader") +end + +-- the badge fight itself keeps the gym theme and the happiness bump +do + local kind, gym = kindOf("OPP_GIOVANNI", 3) -- Viridian Gym + eq(kind, "gym", "Viridian Gym Giovanni plays the gym-leader theme") + check(gym, "Viridian Gym Giovanni sets isGymLeader") +end + +-- regression guards around the branch below the badge lookup +do + local kind, gym = kindOf("OPP_BROCK", 1) + eq(kind, "gym", "Brock plays the gym-leader theme") + check(gym, "Brock sets isGymLeader") + kind, gym = kindOf("OPP_LANCE", 1) + eq(kind, "gym", "Lance shares the gym-leader theme") + check(not gym, "Lance is not a wGymLeaderNo writer (no happiness bump)") + kind = kindOf("OPP_RIVAL3", 1) + eq(kind, "final", "the Champion plays the final-battle theme") + kind, gym = kindOf("OPP_YOUNGSTER", 1) + eq(kind, "trainer", "an ordinary trainer plays the trainer theme") + check(not gym, "an ordinary trainer is not a gym leader") +end + +S.finish() diff --git a/tests/parity_dig_pic.lua b/tests/parity_dig_pic.lua index cc9edd87..cd7cce9b 100644 --- a/tests/parity_dig_pic.lua +++ b/tests/parity_dig_pic.lua @@ -28,7 +28,8 @@ local function makeGame(species, level, moves) function stack:pop() return table.remove(self.states) end function stack:top() return self.states[#self.states] end return { data = Data, save = save, stack = stack, - input = { wasPressed = function() return true end } } + input = { wasPressed = function() return true end, + isDown = function() return true end } } end local function pumpToMenu(battle) diff --git a/tests/parity_escape_rope_bug805.lua b/tests/parity_escape_rope_bug805.lua new file mode 100644 index 00000000..f1025b1c --- /dev/null +++ b/tests/parity_escape_rope_bug805.lua @@ -0,0 +1,131 @@ +-- Parity test (#805): ESCAPE ROPE / DIG / TELEPORT must land on an outdoor +-- fly-warp cell, and must re-point the LAST_MAP memory at it. +-- +-- pret: ItemUseEscapeRope (engine/items/item_effects.asm) sets BIT_FLY_WARP +-- and BIT_ESCAPE_WARP, and LoadSpecialWarpData's .usedFlyWarp path +-- (engine/overworld/special_warps.asm) warps to wLastBlackoutMap with the +-- landing cell read from FlyWarpDataPtr. wLastBlackoutMap is ALWAYS an +-- outdoor map: SetLastBlackoutMap (engine/events/set_blackout_map.asm) +-- copies wLastMap, and WarpFound2 (home/overworld.asm) only writes wLastMap +-- when CheckIfInOutsideMap passes. PrepareForSpecialWarp +-- (engine/overworld/special_warps.asm) then does `ld [wLastMap], a` with +-- that destination for every fly/escape warp that is not a dungeon warp. +-- +-- The port broke both halves. A .sav import stamps lastHeal from wherever +-- the cartridge was saved (src/save_convert/SaveConvert.lua mergeDefaults, +-- which records no outdoor), so a save made inside Seafoam Islands made +-- ESCAPE ROPE warp the player back into that cave; and the teleport branch +-- skipped rememberOutdoor, so the first LAST_MAP exit after the rope still +-- resolved against the dungeon door walked in through. +-- +-- Self-contained; run via `luajit tests/parity_escape_rope_bug805.lua`. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end +local S = require("tests.harness").suite("parity escape rope #805") +local check, eq = S.check, S.eq + +require("src.render.Font").load(Data) +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local StateStack = require("src.core.StateStack") +local Renderer = require("src.render.Renderer") +local SaveData = require("src.core.SaveData") +local Pokemon = require("src.pokemon.Pokemon") +local Map = require("src.world.Map") +local FieldDefaults = require("src.world.FieldDefaults") +local OW = require("src.world.OverworldController") + +Game.data = Data +Game.input = Input; Input:init() +Game.renderer = Renderer; Renderer:init() +Game.stack = StateStack; StateStack:init() +Game.save = SaveData.newGame() +Game.save.party = { Pokemon.new(Data, "SQUIRTLE", 20) } + +-- The player is deep in a cave, having walked in from the outdoor door +-- cell the LAST_MAP exits still point at. +Game.stack:push(OW, "SEAFOAM_ISLANDS_B2F", 5, 5, "down") +local ow = Game.stack:top() +Game.overworld = ow + +-- Capture the warp target instead of running the real Transition. +local dest +local realStart = ow.startWarpTo +ow.startWarpTo = function(self, mapId, x, y, facing, onDone, opts) + dest = { map = mapId, x = x, y = y } + self.arriveWarp = nil + self.transitioning = false +end + +local outsideTilesets = FieldDefaults.field(Data, "outsideTilesets") +local function isOutside(mapId) + local def = Data.maps[mapId] + return def ~= nil and Map.isOutside(def, outsideTilesets) +end + +local flyWarps = Data.field.flyWarps or {} +local bootHeal = SaveData.defaultHeal(Data.field.boot) + +-- --------------------------------------------------------------- 1. healthy +-- A save healed by a nurse records the outdoor town alongside the interior +-- heal cell; the rope lands on that town's FlyWarpDataPtr cell. +Game.save.lastHeal = { map = "VIRIDIAN_POKECENTER", x = 3, y = 3, + outdoor = { id = "VIRIDIAN_CITY", x = 23, y = 27 } } +ow:rememberOutdoor("ROUTE_23", 8, 60) -- the Victory Road door walked in from +dest = nil +ow:warpToHealPoint(nil, { arrive = "teleport" }) + +check(flyWarps.VIRIDIAN_CITY ~= nil, "Viridian City has a fly warp cell") +eq(dest.map, "VIRIDIAN_CITY", "healthy heal record: rope lands on the town") +eq(dest.x, flyWarps.VIRIDIAN_CITY.x, "rope lands on the FlyWarpDataPtr x") +eq(dest.y, flyWarps.VIRIDIAN_CITY.y, "rope lands on the FlyWarpDataPtr y") + +-- 3. PrepareForSpecialWarp: the destination becomes the new wLastMap, so a +-- LAST_MAP exit taken after the rope resolves against the town just landed +-- in, not the dungeon door from before. +eq(Game.save.lastOutdoor.id, "VIRIDIAN_CITY", + "teleport warp re-points wLastMap at the destination (#805)") +eq(Game.save.lastOutdoor.x, dest.x, "wLastMap x follows the landing cell") +eq(Game.save.lastOutdoor.y, dest.y, "wLastMap y follows the landing cell") + +-- --------------------------------------------------------------- 2. imported +-- Exactly what SaveConvert stamps for a cartridge save made in a cave: the +-- player's own cell, no outdoor town. wLastBlackoutMap can never name an +-- indoor map, so this record is unusable and falls back to the boot heal +-- town (vanilla's zero-filled wLastBlackoutMap is map 0, Pallet Town). +Game.save.lastHeal = { map = "SEAFOAM_ISLANDS_B2F", x = 5, y = 5 } +ow:rememberOutdoor("ROUTE_23", 8, 60) +dest = nil +ow:warpToHealPoint(nil, { arrive = "teleport" }) + +check(not isOutside("SEAFOAM_ISLANDS_B2F"), + "Seafoam Islands B2F is not an outside map") +check(dest.map ~= "SEAFOAM_ISLANDS_B2F", + "imported heal record does not dump the rope back in the cave (#805)") +check(isOutside(dest.map), "escape-warp destination is always an outside map") +eq(dest.map, bootHeal.map, "unusable heal record falls back to the boot town") +eq(dest.x, bootHeal.x, "boot-town fallback keeps its landing x") +eq(dest.y, bootHeal.y, "boot-town fallback keeps its landing y") +eq(Game.save.lastOutdoor.id, bootHeal.map, + "fallback landing is remembered as wLastMap too") + +-- --------------------------------------------------------------- 3. blackout +-- A blackout (no opts) still lands on the interior heal cell and re-points +-- LAST_MAP exits at the remembered town door: HandleBlackOut never sets +-- BIT_FLY_WARP, so it is not a special warp destination of its own. +Game.save.lastHeal = { map = "VIRIDIAN_POKECENTER", x = 3, y = 3, + outdoor = { id = "VIRIDIAN_CITY", x = 23, y = 27 } } +ow:rememberOutdoor("ROUTE_23", 8, 60) +dest = nil +ow:warpToHealPoint() + +eq(dest.map, "VIRIDIAN_POKECENTER", "blackout still lands at the heal cell") +eq(Game.save.lastOutdoor.id, "VIRIDIAN_CITY", + "blackout re-points wLastMap at the remembered town door") +eq(Game.save.lastOutdoor.x, 23, "blackout keeps the recorded door x") +eq(Game.save.lastOutdoor.y, 27, "blackout keeps the recorded door y") + +ow.startWarpTo = realStart +S.finish() diff --git a/tests/parity_faint_cry_bug709.lua b/tests/parity_faint_cry_bug709.lua index 45f7e3b0..bf61f36c 100644 --- a/tests/parity_faint_cry_bug709.lua +++ b/tests/parity_faint_cry_bug709.lua @@ -14,6 +14,15 @@ package.path = "./?.lua;./?/init.lua;" .. package.path local T = require("tests.modkit") + +-- Scoped suite, not the module-level counters: run_tests.lua dofiles this +-- file in its own process, and T.finish ends in os.exit, which took the +-- parent runner down with it. The run still exited 0, so it read as a pass +-- while every alphabetically later parity file and the three tiers chained +-- after them silently never ran. S.finish raises instead, which is what the +-- rest of the parity files do. modkit does not re-export suite, so it comes +-- off the shared harness it wraps. +local S = T.harness.suite("parity faint cry bug709") local Data = T.fixtures.fresh() local Font = require("src.render.Font") Font.load(Data) @@ -70,10 +79,10 @@ do battle.playVictoryMusic = function() end battle:onFaint(battle.player) pump(battle, 1) - T.eq(cries[1], "FIXMON_A", "the player mon's faint plays its species cry") - T.eq(#cries, 1, "no other cry on the player faint") + S.eq(cries[1], "FIXMON_A", "the player mon's faint plays its species cry") + S.eq(#cries, 1, "no other cry on the player faint") for _, name in ipairs(sfx) do - T.check(name ~= "Faint_Fall", + S.check(name ~= "Faint_Fall", "the player faint never plays Faint_Fall (#709)") end end @@ -87,18 +96,18 @@ do battle.playVictoryMusic = function() end battle:onFaint(battle.enemy) pump(battle, 2) - T.eq(#cries, 0, "the enemy faint plays no species cry") + S.eq(#cries, 0, "the enemy faint plays no species cry") local fall, thud = false, false for i, name in ipairs(sfx) do if name == "Faint_Fall" then - T.check(not fall, "Faint_Fall plays once") + S.check(not fall, "Faint_Fall plays once") fall = true - T.check(not thud, "Faint_Fall precedes Faint_Thud") + S.check(not thud, "Faint_Fall precedes Faint_Thud") elseif name == "Faint_Thud" then thud = true end end - T.check(fall and thud, "trainer enemy faint plays Faint_Fall and Faint_Thud") + S.check(fall and thud, "trainer enemy faint plays Faint_Fall and Faint_Thud") end -- enemy faint, wild battle: no faint sfx at all (victory music only) @@ -110,11 +119,11 @@ do battle.playVictoryMusic = function() end battle:onFaint(battle.enemy) pump(battle) - T.eq(#cries, 0, "the wild enemy faint plays no species cry") + S.eq(#cries, 0, "the wild enemy faint plays no species cry") for _, name in ipairs(sfx) do - T.check(name ~= "Faint_Fall" and name ~= "Faint_Thud", + S.check(name ~= "Faint_Fall" and name ~= "Faint_Thud", "the wild enemy faint plays no faint sfx (.wild_win)") end end -T.finish("parity faint cry bug709") +S.finish() diff --git a/tests/parity_fan_club_chairman_bug1050.lua b/tests/parity_fan_club_chairman_bug1050.lua new file mode 100644 index 00000000..1743100e --- /dev/null +++ b/tests/parity_fan_club_chairman_bug1050.lua @@ -0,0 +1,121 @@ +-- Parity: the Fan Club Chairman asks before telling his story (#1050). +-- pokered scripts/PokemonFanClub.asm PokemonFanClubChairmanText. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end + +local S = require("tests.harness").suite("parity Fan Club chairman") +local check, eq = S.check, S.eq + +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local StateStack = require("src.core.StateStack") +local SaveData = require("src.core.SaveData") +local ScriptRunner = require("src.script.ScriptRunner") +local Commands = require("src.script.Commands") +local Flags = require("src.script.Flags") +local ChoiceBox = require("src.ui.ChoiceBox") +local mapScripts = require("data.scripts.init") + +Game.data = Data +Game.input = Input; Input:init() +Game.stack = StateStack; StateStack:init() +Game.save = SaveData.newGame() +require("src.render.Font").load(Data) + +local MAP, TEXT = "POKEMON_FAN_CLUB", "TEXT_POKEMONFANCLUB_CHAIRMAN" +local script = mapScripts.talkScript(MAP, TEXT) +check(type(script) == "table", "the chairman is a row list") +eq(#ScriptRunner.validate(script), 0, + "the rows validate cleanly (labels resolve after the renumbering)") + +-- === the intro is the question, not a plain box === +local asks = 0 +for _, row in ipairs(type(script) == "table" and script or {}) do + if row[1] == "ask" then + asks = asks + 1 + eq(row[2], "_PokemonFanClubChairmanIntroText", + "the YesNoChoice rides the intro text (#1050)") + end +end +eq(asks, 1, "exactly one question, right after the intro") + +-- === harness: run the talk script headless, recording show_text ids === +local shown = {} +local origShow = Commands.show_text +-- forward extraOpts: Commands.ask rides show_text's 4th argument +Commands.show_text = function(ctx, textId, subs, ...) + table.insert(shown, textId) + return origShow(ctx, textId, subs, ...) +end + +-- pressFn returns the Input.pressed table for this frame (default: A) +local function runScript(pressFn) + shown = {} + local ow = { map = { id = MAP, def = { label = MAP } }, + npcs = {}, entities = {} } + local r = ScriptRunner.new(Game, ow) + r:run(script, { npc = { def = {}, facePlayer = function() end }, + overworld = ow }) + local guard = 0 + while r:isRunning() and guard < 3000 do + guard = guard + 1 + Input.pressed = pressFn and pressFn() or { a = true } + StateStack:update(1 / 60) + r:update() + end + Input.pressed = {} + return not r:isRunning() +end + +local function shownIs(want, msg) + eq(table.concat(shown, ","), table.concat(want, ","), msg) +end + +-- press B while the YES/NO box is up, A otherwise: the NO answer +local function declines() + if getmetatable(StateStack:top()) == ChoiceBox then return { b = true } end + return { a = true } +end + +local function held(id) return Game.save.inventory[id] or 0 end + +-- === 1) YES: story, voucher, received line, explanation === +Game.save = SaveData.newGame() +check(runScript(), "chairman script completes on YES") +shownIs({ "_PokemonFanClubChairmanIntroText", + "_PokemonFanClubChairmanStoryText", + "_PokemonFanClubReceivedBikeVoucherText", + "_PokemonFanClubExplainBikeVoucherText" }, + "YES hears the RAPIDASH story out and collects the voucher") +eq(held("BIKE_VOUCHER"), 1, "the BIKE VOUCHER is in the bag") +check(Flags.get(Game.save, "EVENT_RECEIVED_BIKE_VOUCHER"), + "EVENT_GOT_BIKE_VOUCHER is set") + +-- === 2) NO: the brush-off, and the voucher stays with the chairman === +Game.save = SaveData.newGame() +check(runScript(declines), "chairman script completes on NO") +shownIs({ "_PokemonFanClubChairmanIntroText", "_PokemonFanClubNoStoryText" }, + "NO skips the story and the gift (#1050)") +eq(held("BIKE_VOUCHER"), 0, "declining leaves the voucher unclaimed") +check(not Flags.get(Game.save, "EVENT_RECEIVED_BIKE_VOUCHER"), + "declining leaves the event clear, so he can be asked again") + +-- === 3) asking again after NO still works, and YES then pays out === +check(runScript(), "second visit completes") +shownIs({ "_PokemonFanClubChairmanIntroText", + "_PokemonFanClubChairmanStoryText", + "_PokemonFanClubReceivedBikeVoucherText", + "_PokemonFanClubExplainBikeVoucherText" }, + "a player who said NO can come back for the voucher") +eq(held("BIKE_VOUCHER"), 1, "the voucher arrives on the second visit") + +-- === 4) served player: .nothingleft, no question at all === +check(runScript(), "post-voucher script completes") +shownIs({ "_PokemonFanClubChairFinalText" }, + "with the voucher collected he only reminisces") +eq(held("BIKE_VOUCHER"), 1, "no second voucher") + +Commands.show_text = origShow +S.finish() diff --git a/tests/parity_field_move_layering.lua b/tests/parity_field_move_layering.lua index d5c3bbf3..083cfcda 100644 --- a/tests/parity_field_move_layering.lua +++ b/tests/parity_field_move_layering.lua @@ -111,7 +111,7 @@ Game.save.party = { mkMon("MACHOP", "STRENGTH") } Game.save.inventory = { RAINBOWBADGE = true } local ow = pushOW("SEAFOAM_ISLANDS_1F", 17, 10, "right") local pmStr = PartyMenu.new(Game) -selectSubItem(pmStr, 3) +selectSubItem(pmStr, 1) check(isText(Game.stack:top()), "STRENGTH opens _UsedStrengthText") eq(backdrop(), pmStr, "the party menu is the backdrop of _UsedStrengthText") drainOne() @@ -133,7 +133,7 @@ Game.save.inventory = { SOULBADGE = true } ow = pushOW("PALLET_TOWN", 4, 13, "down") ow.player.surfing = false local pmSurf = PartyMenu.new(Game) -selectSubItem(pmSurf, 3) +selectSubItem(pmSurf, 1) check(isText(Game.stack:top()), "SURF opens _SurfingGotOnText") eq(backdrop(), pmSurf, "the party menu is the backdrop of _SurfingGotOnText") drainOne() @@ -148,7 +148,7 @@ eq(Game.stack:top(), ow, "SURF ends on the map") ow = pushOW("PALLET_TOWN", 4, 15, "down") ow.player.surfing = true local pmNoOff = PartyMenu.new(Game) -selectSubItem(pmNoOff, 3) +selectSubItem(pmNoOff, 1) check(isText(Game.stack:top()), "a blocked dismount opens _SurfingNoPlaceToGetOffText") eq(backdrop(), pmNoOff, "the party menu is the backdrop of the no-place message") drainOne() @@ -172,7 +172,7 @@ Game.save.inventory = { BOULDERBADGE = true } ow = pushOW("ROCK_TUNNEL_1F", 15, 4, "down") eq(ow.dark, true, "ROCK_TUNNEL_1F loads dark before FLASH") local pmFlash = PartyMenu.new(Game) -selectSubItem(pmFlash, 3) +selectSubItem(pmFlash, 1) check(isText(Game.stack:top()), "FLASH opens _FlashLightsAreaText") eq(backdrop(), pmFlash, "the party menu is the backdrop of _FlashLightsAreaText") eq(ow.dark, true, "the tunnel is still dark while the message is up") diff --git a/tests/parity_fighting_dojo_dex.lua b/tests/parity_fighting_dojo_dex.lua new file mode 100644 index 00000000..448ac2db --- /dev/null +++ b/tests/parity_fighting_dojo_dex.lua @@ -0,0 +1,89 @@ +-- Parity: the Fighting Dojo prize balls open the Pokédex entry before the +-- take-it prompt (#853). FightingDojo.asm runs DisplayPokedex on the +-- ball's species (marking it seen) and only then prints the yes/no ask. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not Data.maps then Data:load() end + +local S = require("tests.harness").suite("parity Fighting Dojo dex entry") +local check, eq = S.check, S.eq + +local Font = require("src.render.Font") +Font.load(Data) + +local TextBox = require("src.render.TextBox") +local DexEntryMenu = require("src.ui.DexEntryMenu") +local SaveData = require("src.core.SaveData") +local dojo = require("data.scripts.story4").FIGHTING_DOJO + +local function fakeGame() + local states = {} + local save = SaveData.newGame() + save.pokedex = { seen = {}, owned = {} } + save.flags = { EVENT_BEAT_KARATE_MASTER = true } + local game = { + data = Data, + save = save, + pressed = false, + stack = { + states = states, + push = function(_, s) states[#states + 1] = s end, + pop = function(_) states[#states] = nil end, + top = function(_) return states[#states] end, + }, + } + game.input = { wasPressed = function(_, btn) + local p = game.pressed + game.pressed = false + return p and btn == "a" + end } + return game +end + +local function pageText(box) + local out = {} + for _, page in ipairs(box.pages or {}) do + out[#out + 1] = table.concat(page, "\n") + end + return table.concat(out, "\n") +end + +-- each ball: dex entry first (seen, not owned), then the ask prompt +for _, c in ipairs({ + { textId = "TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL", species = "HITMONLEE" }, + { textId = "TEXT_FIGHTINGDOJO_HITMONCHAN_POKE_BALL", species = "HITMONCHAN" }, +}) do + local game = fakeGame() + dojo.talk[c.textId](game, {}, nil, function() end) + local top = game.stack:top() + check(getmetatable(top) == DexEntryMenu, + c.textId .. " opens the Pokédex entry first") + eq(top and top.def and top.def.id, c.species, + "the entry shows " .. c.species) + check(game.save.pokedex.seen[c.species] == true, + "the preview marks " .. c.species .. " seen") + check(not game.save.pokedex.owned[c.species], + "the preview does not mark " .. c.species .. " owned") + game.pressed = true + top:update(0) + local ask = game.stack:top() + check(getmetatable(ask) == TextBox, + "closing the entry shows the take-it prompt") + check(ask and pageText(ask):find(c.species, 1, true) ~= nil, + "the prompt names " .. c.species) +end + +-- before the Karate Master is beaten the ball still refuses, no dex entry +do + local game = fakeGame() + game.save.flags.EVENT_BEAT_KARATE_MASTER = nil + dojo.talk.TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL(game, {}, nil, + function() end) + check(getmetatable(game.stack:top()) == TextBox, + "an unbeaten master keeps the refusal text, not the dex entry") + check(not game.save.pokedex.seen.HITMONLEE, + "the refusal does not mark Hitmonlee seen") +end + +S.finish() diff --git a/tests/parity_flash_blink_bug610.lua b/tests/parity_flash_blink_bug610.lua index 161d52ae..bbcbd156 100644 --- a/tests/parity_flash_blink_bug610.lua +++ b/tests/parity_flash_blink_bug610.lua @@ -66,8 +66,7 @@ eq(ow.dark, true, "ROCK_TUNNEL_1F loads dark before FLASH") local pm = PartyMenu.new(Game) Game.stack:push(pm) frame({ "a" }) -- open the field-move submenu on PIKACHU -for _ = 2, 3 do frame({ "down" }) end -frame({ "a" }) -- FLASH +frame({ "a" }) -- FLASH is the top row now (#768) drainOne() -- dismiss _FlashLightsAreaText local blink = Game.stack:top() diff --git a/tests/parity_fly_anim.lua b/tests/parity_fly_anim.lua deleted file mode 100644 index e3e14ef1..00000000 --- a/tests/parity_fly_anim.lua +++ /dev/null @@ -1,91 +0,0 @@ --- Parity: the Fly overworld animation (#702). --- --- Oracle: engine/overworld/player_animations.asm. Departure --- (_LeaveMapAnim .flyAnimation) flaps the bird in place for 8 x Delay3, --- plays SFX_FLY, flies FlyAnimationScreenCoords1 up and off to the right --- (12 pairs, 3 frames each), waits 40 frames, then exits over the --- top-left along FlyAnimationScreenCoords2 (11 pairs). Arrival --- (EnterMapAnim .flyAnimation) plays SFX_FLY again and swoops in along --- FlyAnimationEnterScreenCoords (12 pairs), and only then does --- LoadPlayerSpriteGraphics bring the player back. --- --- Self-contained: `luajit tests/parity_fly_anim.lua`; also globbed by --- tests/run_tests.lua. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local Data = require("src.core.Data") -if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end -local S = require("tests.harness").suite("parity fly anim (#702)") -local check, eq = S.check, S.eq - -require("src.render.Font").load(Data) -local Game = require("src.core.Game") -local Input = require("src.core.Input") -local StateStack = require("src.core.StateStack") -local Renderer = require("src.render.Renderer") -local SaveData = require("src.core.SaveData") -local OW = require("src.world.OverworldController") - -Game.data = Data -Game.input = Input; Input:init() -Game.renderer = Renderer; Renderer:init() -Game.stack = StateStack; StateStack:init() -Game.save = SaveData.newGame() - --- record SFX without touching the audio backend -local plays = {} -local Sound = require("src.core.Sound") -local realPlay = Sound.play -Sound.play = function(_, key) plays[#plays + 1] = key end - -local function popAll() while Game.stack:top() do Game.stack:pop() end end -local function frame() - Input.pressed = {} - StateStack:update(1 / 60) -end -local function frames(n) for _ = 1, n do frame() end end - -Game.stack:push(OW, "ROUTE_17", 4, 10, "down") -local ow = Game.stack:top() - -ow:flyTo("PALLET_TOWN") -check(ow.flyAnim ~= nil, "the bird lead-in starts on FLY") -eq(ow.flyAnim and ow.flyAnim.phase, "flap", "the bird flaps in place first") -eq(ow.player.inputLocked, true, "input is locked for the flight") -eq(#plays, 0, "no SFX during the in-place flap") - -frames(23) -eq(ow.flyAnim and ow.flyAnim.phase, "flap", "still flapping 23 frames in") -frame() -eq(ow.flyAnim and ow.flyAnim.phase, "path1", - "the up-right path starts after 8 x Delay3") -eq(plays[#plays], "Fly", "SFX_FLY plays as the bird takes off") - -frames(36) -eq(ow.flyAnim and ow.flyAnim.phase, "hold", - "the bird parks off screen after the 12-pair path") -frames(40) -eq(ow.flyAnim and ow.flyAnim.phase, "path2", - "the top-left exit follows the 40-frame beat") -frames(33) -check(ow.flyAnim == nil, "the departure ends after the 11-pair exit") - --- the warp transition runs its fade out/in; the map switches inside it -local guard = 0 -while ow.map.id == "ROUTE_17" and guard < 400 do - guard = guard + 1 - frame() -end -eq(ow.map.id, "PALLET_TOWN", "the warp lands in Pallet Town") -check(ow.flyArrive ~= nil, "the landing swoop starts on arrival") -eq(plays[#plays], "Fly", "SFX_FLY plays again for the landing") -eq(ow.player.inputLocked, true, "input stays locked for the swoop") - -frames(35) -check(ow.flyArrive ~= nil, "the swoop is still flying 35 frames in") -frame() -check(ow.flyArrive == nil, "the swoop ends after the 12-pair path") -eq(ow.player.inputLocked, false, "and hands input back") - -Sound.play = realPlay -S.finish() diff --git a/tests/parity_fly_cursor_order.lua b/tests/parity_fly_cursor_order.lua new file mode 100644 index 00000000..1a1bb518 --- /dev/null +++ b/tests/parity_fly_cursor_order.lua @@ -0,0 +1,102 @@ +-- Parity test: the FLY town map opens on PALLET_TOWN and Up walks the +-- towns forward, Down backward (#795). +-- +-- pokered's LoadTownMap_Fly (engine/items/town_map.asm) enters its loop +-- with hl on wFlyLocationsList[0] -- the cursor ALWAYS starts on the first +-- fly destination, PALLET_TOWN, never the town the player is standing in. +-- .pressedUp does `inc hl` (next town, skipping NOT_VISITED entries, +-- wrapping at the $ff terminator back to the start) and .pressedDown does +-- `dec hl` (previous town, wrapping off the top to the last visited town). +-- The port started the cursor on the player's current town and had Up and +-- Down swapped, so the menu felt like it cycled in a random order. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end + +local S = require("tests.harness").suite("parity fly cursor order") +local check, eq = S.check, S.eq + +local TownMap = require("src.ui.TownMap") + +local function makeGame(visited, currentMap) + local pressed = {} + local game = { + data = Data, + save = { visited = visited }, + overworld = currentMap and { map = { id = currentMap } } or nil, + input = { wasPressed = function(_, name) + local p = pressed[name] + pressed[name] = nil + return p + end }, + stack = { pop = function() end }, + } + return game, function(name) pressed[name] = true end +end + +local function selection(tm) + return tm.flyMapIds[tm.sel] +end + +local ALL = { + PALLET_TOWN = true, VIRIDIAN_CITY = true, PEWTER_CITY = true, + CERULEAN_CITY = true, LAVENDER_TOWN = true, VERMILION_CITY = true, + CELADON_CITY = true, FUCHSIA_CITY = true, CINNABAR_ISLAND = true, + INDIGO_PLATEAU = true, SAFFRON_CITY = true, +} + +-- ------------------------------------------------------------------ +-- 1) the cursor opens on PALLET_TOWN, not the player's town +-- ------------------------------------------------------------------ +local game, tap = makeGame(ALL, "CELADON_CITY") +local tm = TownMap.new(game, { fly = true }) +check(tm.fly == true, "the town map opens in fly mode") +eq(selection(tm), "PALLET_TOWN", + "the fly cursor opens on PALLET_TOWN while the player stands in Celadon") + +-- ------------------------------------------------------------------ +-- 2) Up walks the towns forward, Down backward, both wrapping +-- ------------------------------------------------------------------ +tap("up") tm:update(0) +eq(selection(tm), "VIRIDIAN_CITY", "Up from PALLET_TOWN selects VIRIDIAN_CITY") +tap("up") tm:update(0) +eq(selection(tm), "PEWTER_CITY", "Up again selects PEWTER_CITY") +tap("down") tm:update(0) +eq(selection(tm), "VIRIDIAN_CITY", "Down steps back to VIRIDIAN_CITY") +tap("down") tm:update(0) +eq(selection(tm), "PALLET_TOWN", "Down again returns to PALLET_TOWN") +tap("down") tm:update(0) +eq(selection(tm), "SAFFRON_CITY", + "Down off the top wraps to the last visited town (SAFFRON_CITY)") +tap("up") tm:update(0) +eq(selection(tm), "PALLET_TOWN", "Up off the bottom wraps back to PALLET_TOWN") + +-- ------------------------------------------------------------------ +-- 3) unvisited towns are skipped, in list order +-- ------------------------------------------------------------------ +local PARTIAL = { PALLET_TOWN = true, VIRIDIAN_CITY = true, CELADON_CITY = true } +local game2, tap2 = makeGame(PARTIAL, "VIRIDIAN_CITY") +local tm2 = TownMap.new(game2, { fly = true }) +eq(selection(tm2), "PALLET_TOWN", + "the fly cursor opens on PALLET_TOWN on a partial save too") +tap2("up") tm2:update(0) +eq(selection(tm2), "VIRIDIAN_CITY", "Up selects VIRIDIAN_CITY") +tap2("up") tm2:update(0) +eq(selection(tm2), "CELADON_CITY", "Up skips every unvisited town to CELADON") +tap2("up") tm2:update(0) +eq(selection(tm2), "PALLET_TOWN", "Up off the end wraps to PALLET_TOWN") +tap2("down") tm2:update(0) +eq(selection(tm2), "CELADON_CITY", + "Down off the top wraps to CELADON, the last visited town") + +-- ------------------------------------------------------------------ +-- 4) the plain town map viewer still opens on the player's town +-- ------------------------------------------------------------------ +local game3 = makeGame(ALL, "CERULEAN_CITY") +local viewer = TownMap.new(game3) +check(not viewer.fly, "the plain viewer is not in fly mode") +check(viewer.locs[viewer.sel] == viewer.playerLoc, + "the plain viewer still opens on the player's current location") + +S.finish() diff --git a/tests/parity_fly_route_centers.lua b/tests/parity_fly_route_centers.lua new file mode 100644 index 00000000..6906190d --- /dev/null +++ b/tests/parity_fly_route_centers.lua @@ -0,0 +1,77 @@ +-- Parity test: the Route 4 / Route 10 Pokemon Centers are not FLY +-- destinations (#788). +-- +-- pokered keeps fly-warp landing spots for ROUTE_4 and ROUTE_10 in +-- data/maps/special_warps.asm FlyWarpDataPtr, but the fly picker never +-- offers them: BuildFlyLocationsList (engine/items/town_map.asm) walks map +-- ids 0..NUM_CITY_MAPS-1 only, and MarkTownVisitedAndLoadToggleableObjects +-- (engine/overworld/toggleable_objects.asm) sets a wTownVisitedFlag bit +-- only for maps below FIRST_ROUTE_MAP. The port walked the whole fly-warp +-- table and gated on "outdoor", so both route centers showed up as fly +-- targets the moment the player had set foot on those routes. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end + +local S = require("tests.harness").suite("parity fly route centers") +local check, eq = S.check, S.eq + +local Map = require("src.world.Map") +local FlyMenu = require("src.ui.FlyMenu") +local TownMap = require("src.ui.TownMap") + +local TOWNS = { + "PALLET_TOWN", "VIRIDIAN_CITY", "PEWTER_CITY", "CERULEAN_CITY", + "LAVENDER_TOWN", "VERMILION_CITY", "CELADON_CITY", "FUCHSIA_CITY", + "CINNABAR_ISLAND", "INDIGO_PLATEAU", "SAFFRON_CITY", +} + +-- ------------------------------------------------------------------ +-- 1) the gate itself, straight off generated map records +-- ------------------------------------------------------------------ +-- map indices 0..10 are exactly the eleven towns (data/generated/maps.lua +-- carries pokered's map constant order), and Map.isFlyTown keys off them +check(Map.isFlyTown(Data.maps.PALLET_TOWN), "PALLET_TOWN is a fly town") +check(Map.isFlyTown(Data.maps.INDIGO_PLATEAU), + "INDIGO_PLATEAU is a fly town (index 9, past the PLATEAU tileset)") +check(not Map.isFlyTown(Data.maps.ROUTE_4), "ROUTE_4 is not a fly town") +check(not Map.isFlyTown(Data.maps.ROUTE_10), "ROUTE_10 is not a fly town") +check(not Map.isFlyTown(Data.maps.POKEMON_MANSION_1F), + "POKEMON_MANSION_1F (dungeon escape spot) is not a fly town") +-- a mod-authored town has no vanilla index and keeps the outdoor test +check(Map.isFlyTown({ tileset = "OVERWORLD" }), + "an index-less outdoor map (mod town) is a fly town") +check(not Map.isFlyTown({ tileset = "CAVERN" }), + "an index-less cave map is not a fly town") + +-- ------------------------------------------------------------------ +-- 2) both pickers exclude the route centers, even on a polluted save +-- ------------------------------------------------------------------ +-- Saves written before this fix already carry visited.ROUTE_4 / +-- visited.ROUTE_10 (any map with a fly warp was marked on entry), so the +-- menu side has to filter, not just the writer. +local visited = { ROUTE_4 = true, ROUTE_10 = true } +for _, id in ipairs(TOWNS) do visited[id] = true end +local save = { visited = visited } + +local menu = FlyMenu.new({ data = Data, save = save }) +local menuIds = {} +for _, item in ipairs(menu.items or {}) do menuIds[#menuIds + 1] = item.value end +eq(#menuIds, #TOWNS, "FLY lists exactly the eleven towns") +for i, id in ipairs(TOWNS) do + eq(menuIds[i], id, ("FLY entry %d is %s"):format(i, id)) +end + +local tm = TownMap.new({ data = Data, save = save }, { fly = true }) +check(tm.fly == true, "the town map opens in fly mode") +local listed = {} +for _, id in ipairs(tm.flyMapIds or {}) do listed[id] = true end +eq(#tm.flyMapIds, #TOWNS, "the fly town map cycles exactly the eleven towns") +check(not listed.ROUTE_4, "ROUTE_4 is not on the fly town map") +check(not listed.ROUTE_10, "ROUTE_10 is not on the fly town map") +for _, id in ipairs(TOWNS) do + check(listed[id], id .. " is on the fly town map") +end + +S.finish() diff --git a/tests/parity_gift_atomicity.lua b/tests/parity_gift_atomicity.lua deleted file mode 100644 index 96e6e394..00000000 --- a/tests/parity_gift_atomicity.lua +++ /dev/null @@ -1,185 +0,0 @@ --- Parity test, gift atomicity: a mon handed over by give_pokemon and the --- event that closes its offer must land in the same script step, so a --- script torn down between the two cannot hand the gift out twice (#426). --- --- asm sources: --- pokeyellow scripts/Route24.asm (Route24CooltrainerM4Text: CheckEvent --- EVENT_54F -> YesNoChoice -> GivePokemon -> `jp nc, TextScriptEnd` --- (party + box full leaves the event clear so the offer repeats) -> --- PrintText Route24Text_515e3 -> SetEvent EVENT_54F) --- pokeyellow scripts/CeruleanMelaniesHouse.asm (same shape plus predef --- HideObject TOGGLE_CERULEAN_BULBASAUR, then SetEvent --- EVENT_GOT_BULBASAUR_IN_CERULEAN) --- pokeyellow scripts/VermilionCity_2.asm (CheckEvent / SetEvent --- EVENT_GOT_SQUIRTLE_FROM_OFFICER_JENNY) --- scripts/CeladonMansionRoofHouse.asm (Eevee ball: GivePokemon with no --- confirm, HideObject on success) --- On hardware the event write trails the received text because no step in --- between can abort. The port yields there (AskName, NamingScreen, the --- text box) and wraps every row in the script.command mod hook, so the --- write is hoisted ahead of the text: the event is only read at script --- entry, and the failed-give path still leaves it clear. --- --- Self-contained: run via `luajit tests/parity_gift_atomicity.lua`; also --- dofile'd by tests/run_tests.lua's aggregator. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local Data = require("src.core.Data") -if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end - -local S = require("tests.harness").suite("parity gift atomicity") -local check, eq = S.check, S.eq - -local Commands = require("src.script.Commands") -local Events = require("src.mods.Events") -local Flags = require("src.script.Flags") -local Game = require("src.core.Game") -local Hooks = require("src.mods.Hooks") -local Input = require("src.core.Input") -local Logger = require("src.core.Logger") -local Runtime = require("src.mods.Runtime") -local SaveData = require("src.core.SaveData") -local ScriptRunner = require("src.script.ScriptRunner") -local StateStack = require("src.core.StateStack") - -Game.data = Data -Game.input = Input; Input:init() -Game.stack = StateStack; StateStack:init() -Game.save = SaveData.newGame() -require("src.render.Font").load(Data) - -local gifts = require("data.scripts.yellow_gifts") -local eevee = require("data.scripts.celadon_eevee") - --- === 1) row-order audit: on every gift site the carry guard follows --- give_pokemon immediately and the bookkeeping (event, and the --- HideObject that clears a ball or a pen mon) comes before any --- received text === -local function audit(label, rows) - local give - for i, row in ipairs(rows) do - if row[1] == "give_pokemon" then give = i break end - end - if not give then - check(false, label .. ": has a give_pokemon row") - return - end - eq(rows[give + 1] and rows[give + 1][1], "jump_if_false", - label .. ": carry guard sits right after give_pokemon") - local flag, text, hide - for i = give + 2, #rows do - local name = rows[i][1] - if name == "set_flag" and not flag then flag = i end - if name == "hide_object" and not hide then hide = i end - if (name == "show_text" or name == "ask") and not text then text = i end - if name == "jump" and rows[i][2] ~= nil and text then break end - end - eq(flag, give + 2, label .. ": event write is the first row past the guard") - check(text and flag < text, - label .. ": event write precedes the received text") - if hide then - check(hide < text, label .. ": HideObject precedes the received text") - end -end - --- the two function-form scripts build their rows per talk; run them with --- the gift branch's preconditions and keep what they hand the runner -local function capture(fn, save) - local rows - local ow = { runner = { run = function(_, r) rows = r end } } - fn({ save = save }, ow, { def = {}, facePlayer = function() end }, - function() end) - return rows or {} -end - -audit("Route 24 Damian", - gifts.ROUTE_24.talk.TEXT_ROUTE24_COOLTRAINER_M4) -audit("Melanie's BULBASAUR", - capture(gifts.CERULEAN_MELANIES_HOUSE.talk - .TEXT_CERULEANMELANIESHOUSE_MELANIE, - { flags = {}, pikachuHappiness = 200 })) -audit("Officer Jenny's SQUIRTLE", - capture(gifts.VERMILION_CITY.talk.TEXT_VERMILIONCITY_OFFICER_JENNY, - { flags = {}, inventory = { THUNDERBADGE = 1 } })) -audit("Celadon EEVEE ball", - eevee.talk.TEXT_CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL) - --- === harness: run a row list headless, A-mashing through the yes/no, --- the nickname prompt and every text box, recording show_text ids --- (Yellow's gift text is not in a Red cache, so show_text takes --- its literal-id fallback: the ids are still what we assert on) === -local shown = {} -local origShow = Commands.show_text -Commands.show_text = function(ctx, textId, subs) - shown[#shown + 1] = textId - return origShow(ctx, textId, subs) -end - -local function runRows(rows) - shown = {} - StateStack:init() - local ow = { map = { id = "ROUTE_24", def = { label = "ROUTE_24" } }, - npcs = {}, entities = {} } - local r = ScriptRunner.new(Game, ow) - r:run(rows, { npc = { def = {}, facePlayer = function() end }, - overworld = ow }) - local guard = 0 - while r:isRunning() and guard < 3000 do - guard = guard + 1 - Input.pressed = { a = true } - StateStack:update(1 / 60) - r:update() - end - Input.pressed = {} - return not r:isRunning() -end - -local DAMIAN = gifts.ROUTE_24.talk.TEXT_ROUTE24_COOLTRAINER_M4 - --- === 2) plain accept: one CHARMANDER, EVENT_54F set, and the next talk --- is Damian's after-text only === -Game.save = SaveData.newGame() -check(runRows(DAMIAN), "Damian gift script completes") -eq(#Game.save.party, 1, "CHARMANDER joins the party") -eq(Game.save.party[1].species, "CHARMANDER", "gift species is CHARMANDER") -check(Flags.get(Game.save, "EVENT_54F"), "accepting sets EVENT_54F") -check(runRows(DAMIAN), "post-gift talk completes") -eq(table.concat(shown, ","), "_Route24DamianText4", - "a closed offer shows only the after-text") -eq(#Game.save.party, 1, "no second CHARMANDER") - --- === 3) the regression itself: every row runs inside the script.command --- hook, and a mod that mishandles the row after the give (the --- reporter was running a third-party UI mod) tears the coroutine --- down mid-gift -- here by sending the pc at a label that is not --- there. The mon is already in the party, so EVENT_54F has to be --- set by then or the next talk re-runs the whole offer === -local savedEvents, savedHooks, savedErrors = - Runtime.events, Runtime.hooks, Runtime.errors -local hooks = Hooks.new() -Runtime.install(Events.new(), hooks, {}) -local remove = hooks:wrap("script.command", function(nextFn, _, name, args) - if name == "show_text" and args[1] == "_Route24DamianText2" then - return "no_such_label" - end - return nextFn() -end, 0, "t") - -Game.save = SaveData.newGame() -local origError = Logger.error -- the tear-down logs; the test expects it -Logger.error = function() end -runRows(DAMIAN) -Logger.error = origError -eq(#Game.save.party, 1, "the killed script still handed the CHARMANDER over") -check(Flags.get(Game.save, "EVENT_54F"), - "EVENT_54F survives a tear-down after the give") - -remove() -Runtime.install(savedEvents, savedHooks, savedErrors) - -check(runRows(DAMIAN), "talk after the tear-down completes") -eq(table.concat(shown, ","), "_Route24DamianText4", - "the interrupted gift is not offered again") -eq(#Game.save.party, 1, "still exactly one CHARMANDER") - -S.finish() diff --git a/tests/parity_gym_tm_bag_full_bug797.lua b/tests/parity_gym_tm_bag_full_bug797.lua new file mode 100644 index 00000000..2117fe87 --- /dev/null +++ b/tests/parity_gym_tm_bag_full_bug797.lua @@ -0,0 +1,161 @@ +-- Parity test: gym leader TM award respects the bag cap (#797). +-- +-- scripts/PewterGym.asm, PewterGymScriptReceiveTM34: after +-- SetEvent EVENT_BEAT_BROCK it runs `lb bc, TM_BIDE, 1` / `call GiveItem` +-- / `jr nc, .BagFull`. On success it prints TEXT_PEWTERGYM_RECEIVED_TM34 +-- (and the TM34 explanation); on carry-clear it prints +-- TEXT_PEWTERGYM_TM34_NO_ROOM instead. Both paths fall through to +-- .gymVictory, so the badge lands either way and the TM is simply lost. +-- The same `jr nc, .BagFull` shape is in CeruleanGym.asm, VermilionGym.asm, +-- CeladonGym.asm, FuchsiaGym.asm, SaffronGym.asm, CinnabarGym.asm and +-- ViridianGym.asm. +-- +-- The port drives this through OverworldState:checkVictoryRewards over +-- data/scripts/victories.lua (gym leaders are not def_trainers entries, so +-- src/script/Commands.lua give_item -- which has always handled a full bag +-- -- is never on this path). Each gym entry splits the hand-over into +-- tmPre (the lead-in), tmDialogue (GiveItem succeeded) and noRoom (the +-- .BagFull line), with gotFlag (EVENT_GOT_TM*) set only on success so the +-- leader's talk script can retry later (offerGymTm via gyms.lua). +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end +local S = require("tests.harness").suite("parity gym TM bag full #797") +local check, eq = S.check, S.eq + +local Bag = require("src.inventory.Bag") +local victories = require("data.scripts.victories") + +-- === (1) data shape: every gym reward carries both branches === +-- A future gym edit must not silently drop the alternate line, so the +-- table check is cheap insurance over the eight leader entries. +do + local n = 0 + for key, entry in pairs(victories) do + if entry.badge then + n = n + 1 + check(type(entry.tmDialogue) == "table" and #entry.tmDialogue > 0, + key .. " has a tmDialogue tail (the GiveItem-succeeded text)") + check(type(entry.noRoom) == "string", + key .. " names a noRoom text (.BagFull branch)") + local body = entry.noRoom and (Data.text or {})[entry.noRoom] + check(type(body) == "string" and body ~= "", + key .. " noRoom label resolves to extracted text") + check(type(entry.gotFlag) == "string" and entry.gotFlag:find("EVENT_GOT_"), + key .. " carries the EVENT_GOT_TM* retry flag") + -- the received-TM tail must not still be baked into `dialogue`, + -- or the full-bag path would print it anyway + for _, label in ipairs(entry.dialogue or {}) do + for _, tail in ipairs(entry.tmDialogue or {}) do + check(label ~= tail, + key .. " dialogue no longer repeats " .. tostring(tail)) + end + end + end + end + eq(n, 8, "all eight gym leader rewards checked") +end + +-- === (2) behavior: Brock with a full bag vs an empty one === + +require("src.render.Font").load(Data) +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local StateStack = require("src.core.StateStack") +local Renderer = require("src.render.Renderer") +local SaveData = require("src.core.SaveData") +local OW = require("src.world.OverworldController") + +Game.data = Data +Game.input = Input; Input:init() +Game.renderer = Renderer; Renderer:init() +Game.stack = StateStack; StateStack:init() + +-- concatenates the pages of the TextBox checkVictoryRewards pushed +local function stackedDialogue() + local top = Game.stack:top() + if not (top and top.pages) then return "" end + local parts = {} + for _, page in ipairs(top.pages) do + parts[#parts + 1] = table.concat(page, "\n") + end + return table.concat(parts, "\n") +end + +local function freshSave() + while Game.stack:top() do Game.stack:pop() end + Game.save = SaveData.newGame() + Game.save.flags = {} + Game.save.inventory = {} + Game.save.bagOrder = nil + Game.save.defeatedTrainers = {} +end + +-- --- full bag: the TM is refused, the NoRoom line replaces the TM text --- +freshSave() +local cap = Bag.capacity(Data) +-- Bag.slots only counts non-badge ids, so distinct filler ids fill it +for i = 1, cap do Game.save.inventory["FILLER" .. i] = 1 end +eq(Bag.slots(Game.save), cap, "bag starts at BAG_ITEM_CAPACITY") + +Game.stack:push(OW, "PEWTER_GYM", 4, 13, "up") +local ow = Game.stack:top() +ow:checkVictoryRewards("OPP_BROCK", 1) +local fullText = stackedDialogue() + +check(Game.save.inventory.TM_BIDE == nil, + "full bag: TM_BIDE is refused (GiveItem carry clear)") +eq(Bag.slots(Game.save), cap, + "full bag: no 21st slot appears (the reporter's symptom)") +check(Game.save.inventory.BOULDERBADGE == 1, + "full bag: .gymVictory still awards BOULDERBADGE") +check(Game.save.flags.EVENT_BEAT_BROCK, + "full bag: EVENT_BEAT_BROCK is still set") +check(fullText:find("room for this", 1, true) ~= nil, + "full bag: dialogue prints _PewterGymTM34NoRoomText") +check(fullText:find("BIDE", 1, true) == nil, + "full bag: the TM34 explanation is skipped") +check(fullText:find("FLASH", 1, true) ~= nil, + "full bag: the BoulderBadge speech still runs") +check(not Game.save.flags.EVENT_GOT_TM34, + "full bag: EVENT_GOT_TM34 stays unset so the talk script retries") + +-- --- empty bag: the success tail still appends and the TM lands --- +freshSave() +Game.stack:push(OW, "PEWTER_GYM", 4, 13, "up") +ow = Game.stack:top() +ow:checkVictoryRewards("OPP_BROCK", 1) +local okText = stackedDialogue() + +check(Game.save.inventory.TM_BIDE == 1, + "empty bag: TM_BIDE lands in the bag") +local order = Bag.order(Game.save) +check(order[1] == "TM_BIDE", + "empty bag: Bag.add kept the wBagItems order (bagOrder) honest") +check(okText:find("BIDE", 1, true) ~= nil, + "empty bag: tmDialogue (TM34 explanation) still appends") +check(Game.save.flags.EVENT_GOT_TM34, + "empty bag: EVENT_GOT_TM34 is set on a successful give") +check(okText:find("room for this", 1, true) == nil, + "empty bag: the NoRoom line is not printed") + +-- --- one more leader, to prove the split is not Pewter-only --- +freshSave() +for i = 1, cap do Game.save.inventory["FILLER" .. i] = 1 end +Game.stack:push(OW, "CERULEAN_GYM", 4, 10, "up") +ow = Game.stack:top() +ow:checkVictoryRewards("OPP_MISTY", 1) +local mistyText = stackedDialogue() +check(Game.save.inventory.TM_BUBBLEBEAM == nil, + "Misty full bag: TM_BUBBLEBEAM is refused") +check(Game.save.inventory.CASCADEBADGE == 1, + "Misty full bag: CASCADEBADGE still awarded") +eq(Bag.slots(Game.save), cap, "Misty full bag: still at capacity") +check(mistyText:find((Data.text or {})._CeruleanGymMistyTM11NoRoomText + :match("^[^\n]+") or "\1", 1, true) ~= nil, + "Misty full bag: dialogue prints _CeruleanGymMistyTM11NoRoomText") + +while Game.stack:top() do Game.stack:pop() end + +S.finish() diff --git a/tests/parity_hof.lua b/tests/parity_hof.lua index b38175ab..0f427eea 100644 --- a/tests/parity_hof.lua +++ b/tests/parity_hof.lua @@ -62,7 +62,9 @@ local expected = 100 + 128 + 16 + 20 + 600 for _, s in ipairs(credits.screens) do expected = expected + (s.fade and 20 or 0) + (s.mon and (s.fade and 90 or 110) or (s.fade and 120 or 140)) - + (s.mon and 27 or 0) + -- DisplayCreditsMon: 3 x CreditsCopyTileMapToVRAM (Delay3) then 27 scroll + -- frames (#703) + + (s.mon and (9 + 27) or 0) end while roll.phase ~= "end_wait" and frame < expected + 120 do frame = frame + 1 @@ -114,16 +116,20 @@ local HallOfFame = require("src.ui.HallOfFame") check(getmetatable(stack2:top()) == HallOfFame, "induction showcase pushed") -- Gen1 layout (issue #102): pic rests at hlcoord (12,5); mon phase starts --- with the LEVEL/TYPE info box (not a top "HALL OF FAME" banner alone) +-- with the LEVEL/TYPE info box (not a top "HALL OF FAME" banner alone). +-- HoFShowMonOrPlayer sweeps the BACK pic across the screen first (hSCX +-- $c0 -> $a0) and only then scrolls the front pic in (#847), so the +-- induction opens on the back pass. local hofUi = stack2:top() -eq(hofUi.phase, "mons", "induction opens on the mon showcase phase") -eq(hofUi.scrollX < 12 * 8, true, "front pic starts off-screen left of (12,5)") --- drive past the scroll so the info box is armed +eq(hofUi.phase, "back", "induction opens on the back pic sweep (#847)") +eq(hofUi.scrollX, 160, "back pic enters at the right edge (hSCX = $c0)") +-- drive the back sweep and the front scroll so the info box is armed local scrollGuard = 0 -while hofUi.scrollX < 12 * 8 and scrollGuard < 200 do +while (hofUi.phase == "back" or hofUi.scrollX < 12 * 8) and scrollGuard < 400 do scrollGuard = scrollGuard + 1 hofUi:update(1 / 60) end +eq(hofUi.phase, "mons", "the front pic phase follows the back sweep (#847)") eq(hofUi.scrollX, 12 * 8, "front pic settles at hlcoord (12,5)") eq(hofUi.showHofBanner, false, "bottom HALL OF FAME banner waits for the 80-frame hold") check(hofUi.timer == 80 or hofUi.timer < 80, diff --git a/tests/parity_marowak_ball.lua b/tests/parity_marowak_ball.lua deleted file mode 100644 index d385db42..00000000 --- a/tests/parity_marowak_ball.lua +++ /dev/null @@ -1,188 +0,0 @@ --- Parity test: a ball thrown at the POKEMON_TOWER_6F RESTLESS SOUL is --- always dodged, scope or no scope. --- --- ItemUseBall reaches the $10 "can't be caught" anim data by TWO --- independent routes (engine/items/item_effects.asm): --- --- :149-153 callfar IsGhostBattle / ld b, $10 / jp z, .setAnimData --- :166-175 .notOldManBattle -- wCurMap == POKEMON_TOWER_6F and --- wEnemyMonSpecies2 == RESTLESS_SOUL -> the same $10 --- --- The port only had the first, as the scope-less disguise flag --- self.ghost. Once the SILPH_SCOPE revealed the MAROWAK the battle was --- an ordinary wild one, so throwBall ran the capture roll and a MASTER --- BALL caught it outright. That result is "caught", not "win" or the --- POKE DOLL escape, so PokemonTower6F's script never set --- EVENT_BEAT_GHOST_MAROWAK and the (10,16) trigger re-fired forever --- (#444). The map+species half sits BEFORE .loop, hence before the --- MASTER_BALL shortcut, so even a Master Ball is dodged. --- --- Run-away parity is the other side of this: only IsGhostBattle grants --- the free escape (engine/battle/core.asm TryRunningFromBattle), so a --- revealed MAROWAK keeps normal flee rolls and self.ghost stays the sole --- gate there. --- --- Self-contained; run via `luajit tests/parity_marowak_ball.lua`. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local S = require("tests.harness").suite("parity marowak ball") -local check, eq = S.check, S.eq - -local BattleState = require("src.battle.BattleState") - --- ---- 1. the 6F script arms noCatch with and without the scope ----------- -do - local realTextBox = package.loaded["src.render.TextBox"] - local realBattleState = package.loaded["src.battle.BattleState"] - package.loaded["src.render.TextBox"] = { - new = function(_, text, done) return { text = text, done = done } end, - } - local made = {} - package.loaded["src.battle.BattleState"] = { - newWild = function(_, species, level) - local b = { species = species, level = level, ghost = false } - b.makeGhost = function(self) self.ghost = true end - -- the scope's branch (#492): disguised on entry, but IsGhostBattle - -- false, which is exactly the state the dodge below has to survive - b.makeUnveiledGhost = function(self) self.scopeReveal = true end - made[#made + 1] = b - return b - end, - } - - local tower = dofile("data/scripts/story3.lua").POKEMON_TOWER_6F - local function trigger(inventory) - local pushed = {} - local game = { - save = { inventory = inventory, flags = {} }, - data = { text = {} }, - stack = { push = function(_, box) pushed[#pushed + 1] = box end }, - } - local ow = { - player = {}, - scriptMove = function() end, - afterBattle = function() end, - } - check(tower.onStep(game, ow, 10, 16), "the trigger fires on (10,16)") - pushed[1].done() - return made[#made] - end - - local noScope = trigger({}) - check(noScope.ghost, "without the scope the battle is still disguised") - check(noScope.noCatch, "and noCatch is set") - - local withScope = trigger({ SILPH_SCOPE = 1 }) - check(not withScope.ghost, "with the scope IsGhostBattle is false") - check(withScope.scopeReveal, "and the unveil plays instead (#492)") - check(withScope.noCatch, - "but noCatch survives it -- balls are dodged either way") - - package.loaded["src.render.TextBox"] = realTextBox - package.loaded["src.battle.BattleState"] = realBattleState -end - --- ---- 2. throwBall takes the dodge branch on noCatch alone --------------- -local realSound = package.loaded["src.core.Sound"] -package.loaded["src.core.Sound"] = { play = function() end } - --- A real BattleState minus the pieces the decision does not touch: the --- capture roll and the ball chain record that they were reached, which is --- exactly the bug (a MASTER BALL catching the revealed MAROWAK). -local function throw(flags, ball) - local self = setmetatable({ - kind = "wild", - ghost = flags.ghost or false, - noCatch = flags.noCatch or false, - queue = {}, - rolled = false, - chained = false, - enemyMoved = false, - turnEnded = false, - data = { items = { MASTER_BALL = { name = "MASTER BALL" }, - POKE_BALL = { name = "POKé BALL" } }, - text = {} }, - game = { save = { player = { name = "RED" } } }, - player = {}, - enemy = {}, - }, BattleState) - self.ballDef = function() return nil end - self.catchAttempt = function(s) s.rolled = true return false, 3 end - self.ballChain = function(s) s.chained = true end - self.enemyAction = function() return {} end - self.executeAction = function(s) s.enemyMoved = true end - self.endOfTurn = function(s) s.turnEnded = true end - self:throwBall(ball) - -- the whole outcome lives in the act() closure throwBall queues, and - -- that closure queues more rows, so drain like updateQueue does: run - -- each fn row once, with nextInsert pointing at it. - local ran = {} - local more = true - while more do - more = false - for i, row in ipairs(self.queue) do - if row.fn and not ran[row] then - ran[row] = true - self.nextInsert = i - row.fn() - more = true - break - end - end - end - local texts = {} - for _, row in ipairs(self.queue) do - if row.text then texts[#texts + 1] = tostring(row.text) end - end - self.texts = table.concat(texts, "|") - return self -end - -local function assertDodge(b, label) - check(not b.rolled, label .. ": no capture roll") - check(not b.chained, label .. ": no wobble chain") - check(b.texts:find("It dodged the", 1, true) ~= nil, - label .. ": ItemUseBallText00 line 1") - check(b.texts:find("can't be caught", 1, true) ~= nil, - label .. ": ItemUseBallText00 line 2") - check(b.enemyMoved, label .. ": the turn is spent, the foe moves") - check(b.turnEnded, label .. ": and the turn ends") -end - -assertDodge(throw({ ghost = true }, "POKE_BALL"), "IsGhostBattle exit") -assertDodge(throw({ noCatch = true }, "POKE_BALL"), ".notOldManBattle exit") --- the regression itself: revealed by the scope, so ghost is false -assertDodge(throw({ noCatch = true }, "MASTER_BALL"), "MASTER BALL") - -do - local plain = throw({}, "MASTER_BALL") - check(plain.rolled, - "an ordinary wild mon still rolls -- the guard is not global") -end - --- The dodged toss keeps the arc the thrown ball picked (TossBallAnimation --- reads wCurItem), so the Master Ball flicker is not lost. -do - local b = throw({ noCatch = true }, "MASTER_BALL") - local anim - for _, row in ipairs(b.queue) do - if row.anim then anim = row.anim break end - end - eq("ULTRATOSS_ANIM", anim, "a dodged MASTER BALL still tosses as ULTRATOSS") -end - -package.loaded["src.core.Sound"] = realSound - --- ---- 3. noCatch grants no free escape ---------------------------------- -do - local function roll(flags) - local b = { ghost = flags.ghost or false, noCatch = flags.noCatch or false, - runAttempts = 1, rng = function() return 255 end } - return BattleState.runRollVanilla(b, 10, 100) - end - check(roll({ ghost = true }), "IsGhostBattle still always escapes") - check(not roll({ noCatch = true }), - "a revealed MAROWAK takes the normal flee roll") -end - -S.finish() diff --git a/tests/parity_midstep_buttons.lua b/tests/parity_midstep_buttons.lua deleted file mode 100644 index 04031b9d..00000000 --- a/tests/parity_midstep_buttons.lua +++ /dev/null @@ -1,154 +0,0 @@ --- Parity test: A/START are never handled mid-step (#286). --- Self-contained: run via `luajit tests/parity_midstep_buttons.lua`; also --- dofile'd by tests/run_tests.lua's aggregator. --- --- Oracle: home/overworld.asm OverworldLoop reads wWalkCounter and, when it --- is nonzero ("the player sprite has not yet completed the walking --- animation"), jumps straight to .moveAhead -- JoypadOverworld, and with --- it the START check, the A check, and every direction initiation, only --- ever runs while the player stands on a tile. --- --- The port ran handleInput() every frame regardless of player.moving, so a --- mid-step A/START press pushed its TextBox/StartMenu right there and --- froze Red between tiles, mid-animation (#286: running up to Nurse Joy --- and mashing A stops him half off the tile). --- --- Second oracle, engine/joypad.asm _Joypad: hJoyPressed is --- (hJoyLast ^ hJoyInput) & hJoyInput, and hJoyLast only advances on an --- explicit `call Joypad`. vblank's per-frame ReadJoypad writes hJoyInput --- alone, and the mid-step path never calls Joypad, so hJoyLast is FROZEN --- for the whole animation. A button pressed mid-step and still held when --- the step lands therefore reads as a fresh press at the next poll; one --- released before the step lands is genuinely lost. The port used to drop --- both, which on the Cycling Road roll made START a coin flip (#525). --- --- The invariant: while a step is in progress, A and START change nothing --- (no TextBox, no StartMenu, the step completes). On the landing frame a --- still-held A or START is acted on, a released one is not. - -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local Data = require("src.core.Data") -if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end -local S = require("tests.harness").suite("parity midstep buttons") -local check, eq = S.check, S.eq - -require("src.render.Font").load(Data) -local Game = require("src.core.Game") -local Input = require("src.core.Input") -local StateStack = require("src.core.StateStack") -local Renderer = require("src.render.Renderer") -local SaveData = require("src.core.SaveData") -local OW = require("src.world.OverworldController") - -Game.data = Data -Game.input = Input; Input:init() -Game.renderer = Renderer; Renderer:init() -Game.stack = StateStack -StateStack:init() - --- PALLET_TOWN (6,9) facing down: open grass, several free tiles south -Game.save = SaveData.newGame() -Game.stack:push(OW, "PALLET_TOWN", 6, 9, "down") -local ow = Game.stack:top() - -local function step(pressedBtn) - -- the real driver: Game:step promotes pressQueue edges via Input:step() - -- (which also expires them) before stack:update - if pressedBtn then table.insert(Input.pressQueue, pressedBtn) end - Input:step() - ow:update(1 / 60) -end - --- A synthetic pressQueue inject has no source entry, so Input:step sets --- state[btn] = true and nothing ever clears it (src/core/Input.lua) -- the --- harness models a HELD button. Most cases below want a tap, so release it --- explicitly; the held cases are called out where they matter. -local function tap(btn) - step(btn) - Input.state[btn] = false -end - --- start a step south (held direction, like hJoyHeld) -Input.state.down = true -step() -Input.state.down = false -check(ow.player.moving, "held direction starts a step") -local startY = ow.player.cellY - --- spy on interact(): a mid-step A press must not even reach it -local interactCalls = 0 -local baseInteract = ow.interact -ow.interact = function(self, ...) - interactCalls = interactCalls + 1 - return baseInteract(self, ...) -end - --- mid-step A press: nothing may happen (the original acts on nothing here) -tap("a") -eq(interactCalls, 0, "mid-step A never reaches interact()") -check(Game.stack:top() == ow, "mid-step A pushes no TextBox") -check(ow.player.moving, "mid-step A does not interrupt the step") - --- mid-step START press: no start menu either -tap("start") -check(Game.stack:top() == ow, "mid-step START opens no menu") -check(ow.player.moving, "mid-step START does not interrupt the step") - --- run the step out: the player lands on the next tile, unfrozen -local guard = 0 -while ow.player.moving and guard < 60 do step(); guard = guard + 1 end -eq(ow.player.cellY, startY + 1, "the step completes onto the next tile") - --- the issue's actual repro ("press A quickly/early" running up to Nurse --- Joy): start another step and press A on its FINAL mid-step frame, then --- RELEASE it before the step lands. hJoyLast is frozen through the --- animation, so the next poll sees the button already up and computes no --- edge (engine/joypad.asm) -- this press really is lost. -Input.state.down = true -step() -Input.state.down = false -check(ow.player.moving, "second step starts") -guard = 0 -while ow.player.moving and guard < 60 do - guard = guard + 1 - if guard == (ow.player.stepFramesCur or 16) - 1 then - tap("a") -- the last frame before landing, released immediately - else - step() - end -end -check(not ow.player.moving, "the second step completes") -step() -- the landing frame, where a still-held button would be polled -eq(interactCalls, 0, "a mid-step A released before landing is still lost") -check(Game.stack:top() == ow, "the released last-frame A pushes no TextBox") - --- ...but a mid-step A that is STILL HELD when the step lands is delivered --- on the landing frame, because hJoyLast never advanced (#525). Nothing --- happens mid-step either way: the poll is deferred, not the action. -Input.state.down = true -step() -Input.state.down = false -check(ow.player.moving, "third step starts") -step("a") -- pressed mid-step and left held -eq(interactCalls, 0, "the held A still does nothing mid-step") -check(ow.player.moving, "the held A does not interrupt the step") -guard = 0 -while ow.player.moving and guard < 60 do step(); guard = guard + 1 end -eq(interactCalls, 0, "still nothing while the step runs out") -step() -- landing frame -eq(interactCalls, 1, "a held mid-step A is polled on the landing frame") -Input.state.a = false - --- standing on the tile again, START and A work as always -interactCalls = 0 -tap("start") -check(Game.stack:top() ~= ow, "START opens the start menu on a tile") -while Game.stack:top() do Game.stack:pop() end -Game.stack:push(OW, "PALLET_TOWN", 6, 9, "down") -ow = Game.stack:top() -interactCalls = 0 -- OW is a singleton: the spy survives the re-push -step("a") -eq(interactCalls, 1, "A on a tile runs interact() (the gate is movement-only)") - -S.finish() diff --git a/tests/parity_move_swap.lua b/tests/parity_move_swap.lua deleted file mode 100644 index 7f4c0996..00000000 --- a/tests/parity_move_swap.lua +++ /dev/null @@ -1,129 +0,0 @@ --- Parity / regression for #73: Gen 1 fight-menu SELECT reorders moves. --- --- Select marks a slot, move the cursor, Select (or A) swaps. Defaults: --- Tab / either Shift / gamepad Back. Self-contained; also picked up by --- tests/run_tests.lua's parity_* glob. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end - -local Data = require("src.core.Data") -if not (Data.pokemon and Data.pokemon.RATTATA) then Data:load() end -local TypeChart = require("src.battle.TypeChart") -TypeChart.load(Data) - -local Pokemon = require("src.pokemon.Pokemon") -local BattleState = require("src.battle.BattleState") -local Input = require("src.core.Input") -local S = require("tests.harness").suite("parity move swap") -local check, eq = S.check, S.eq - -local function freshGame() - local mon = Pokemon.new(Data, "NIDORAN_M", 8) - mon.moves = { - { id = "TACKLE", pp = 35 }, - { id = "LEER", pp = 30 }, - { id = "HORN_ATTACK", pp = 25 }, - { id = "POISON_STING", pp = 35 }, - } - return { - data = Data, - input = Input, - save = { - party = { mon }, - player = { name = "RED" }, - inventory = {}, - options = {}, - pokedex = { seen = {}, owned = {} }, - flags = {}, - money = 0, - }, - stack = { push = function() end, pop = function() end, top = function() end }, - } -end - -local function tapKey(battle, key) - Input:keypressed(key) - Input:step() - battle:update(0) - Input:keyreleased(key) -end - -local function tapPad(battle, button) - Input:gamepadpressed(nil, button) - Input:step() - battle:update(0) - Input:gamepadreleased(nil, button) -end - --- Default Select sources all edge the logical select button. -do - Input:init() - for _, key in ipairs({ "tab", "rshift", "lshift" }) do - Input:reset() - Input:keypressed(key) - Input:step() - check(Input:wasPressed("select"), key .. " maps to select") - end - Input:reset() - Input:gamepadpressed(nil, "back") - Input:step() - check(Input:wasPressed("select"), "gamepad back maps to select") -end - --- Fight menu: Select, move, Select swaps slots 1 and 2. -do - Input:init() - local game = freshGame() - local battle = BattleState.newWild(game, "PIDGEY", 5) - battle.phase = "moveSelect" - battle.moveIndex = 1 - battle.moveSwapIndex = nil - local a = battle.player.curMoves[1].id - local b = battle.player.curMoves[2].id - tapKey(battle, "tab") - eq(battle.moveSwapIndex, 1, "first Select marks the current slot") - tapKey(battle, "down") - eq(battle.moveIndex, 2, "cursor moved to slot 2") - tapKey(battle, "tab") - check(battle.moveSwapIndex == nil, "second Select clears the mark") - eq(battle.player.curMoves[1].id, b, "slot 1 holds the former slot 2 move") - eq(battle.player.curMoves[2].id, a, "slot 2 holds the former slot 1 move") - eq(battle.player.mon.moves[1].id, b, "party moves table stays in sync") -end - --- Same reorder via gamepad Back (SDL "back" = controller Select/View). -do - Input:init() - local game = freshGame() - local battle = BattleState.newWild(game, "PIDGEY", 5) - battle.phase = "moveSelect" - battle.moveIndex = 1 - battle.moveSwapIndex = nil - local a = battle.player.curMoves[1].id - local b = battle.player.curMoves[2].id - tapPad(battle, "back") - tapPad(battle, "dpdown") - tapPad(battle, "back") - eq(battle.player.curMoves[1].id, b, "pad Select swaps slot 1") - eq(battle.player.curMoves[2].id, a, "pad Select swaps slot 2") -end - --- A confirms a pending swap (bag-style), without starting the turn. -do - Input:init() - local game = freshGame() - local battle = BattleState.newWild(game, "PIDGEY", 5) - battle.phase = "moveSelect" - battle.moveIndex = 1 - battle.moveSwapIndex = nil - local a = battle.player.curMoves[1].id - local b = battle.player.curMoves[2].id - tapKey(battle, "tab") - tapKey(battle, "down") - tapKey(battle, "z") -- A - eq(battle.phase, "moveSelect", "A completes a pending swap without attacking") - eq(battle.player.curMoves[1].id, b, "A-confirm swapped slot 1") - eq(battle.player.curMoves[2].id, a, "A-confirm swapped slot 2") -end - -S.finish() diff --git a/tests/parity_picker_pointer_grab.lua b/tests/parity_picker_pointer_grab.lua index f7b1a4e2..77b7e877 100644 --- a/tests/parity_picker_pointer_grab.lua +++ b/tests/parity_picker_pointer_grab.lua @@ -14,8 +14,29 @@ local check, eq = S.check, S.eq local RomImporter = require("src.import.RomImporter") -- ---------------------------------------------------------------- the funnel --- The release lives in commandOutput because all three pickers reach popen --- through it; a fourth picker calling io.popen directly would bring #254 back. +-- The release lives in HostShell.popen because every host spawn reaches the +-- OS through it; a caller reaching for io.popen directly would bring #254 +-- back. This assertion used to count io.popen calls in RomImporter, which is +-- where the release started out, and it went red the day the call was hoisted +-- into HostShell and nobody moved the check with it: RomImporter has held +-- zero io.popen calls since, so the count could never be the 1 it wanted. +-- Point it at the funnel that actually exists now. +-- Matched as pcall(io.popen rather than io.popen( because the spawn is +-- wrapped to swallow lua errors, so the call form never appears bare. +do + local f = io.open("src/core/HostShell.lua", "rb") + check(f ~= nil, "HostShell source is readable") + if f then + local src = f:read("*a") + f:close() + local calls = 0 + for _ in src:gmatch("pcall%(io%.popen") do calls = calls + 1 end + eq(calls, 1, "every host spawn still funnels through the one io.popen" + .. " call, which is where the pointer grab is released (#254)") + end +end + +-- RomImporter must not grow a picker that goes around HostShell. do local f = io.open("src/import/RomImporter.lua", "rb") check(f ~= nil, "RomImporter source is readable") @@ -24,8 +45,7 @@ do f:close() local calls = 0 for _ in src:gmatch("io%.popen%(") do calls = calls + 1 end - eq(calls, 1, "every desktop picker still funnels through the one io.popen" - .. " call, which is where the pointer grab is released (#254)") + eq(calls, 0, "no picker calls io.popen behind HostShell's back (#254)") end end diff --git a/tests/parity_rare_candy_menu.lua b/tests/parity_rare_candy_menu.lua new file mode 100644 index 00000000..ed03d62b --- /dev/null +++ b/tests/parity_rare_candy_menu.lua @@ -0,0 +1,179 @@ +-- Parity test: using a RARE CANDY from the bag returns to the bag (#796). +-- +-- pokered files RARE_CANDY under UsableItems_PartyMenu (data/items/ +-- use_party.asm), so after UseItem runs, start_sub_menus.asm's +-- .useItem_partyMenu jumps back to StartMenu_Item with wBagSavedMenuItem +-- still pointing at the candy -- the level text, PrintStatsBox, the +-- level-up moves and any level evolution all happen first, then the item +-- list is back with the cursor on the candy, which is what lets the +-- original button-mash through a stack of them. The port closed the bag +-- list before the level-up sequence and never reopened it, so every candy +-- cost a full START -> ITEM trip. +-- +-- Self-contained; run via `luajit tests/parity_rare_candy_menu.lua`. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local S = require("tests.harness").suite("parity rare candy menu") +local check, eq = S.check, S.eq + +local Data = require("src.core.Data") +Data:load() + +local Pokemon = require("src.pokemon.Pokemon") +local Bag = require("src.inventory.Bag") + +-- Real TextBoxes want a Font atlas; the flow under test only cares which +-- states land on the stack and what each onDone does. BagMenu binds +-- TextBox at require time, so it is reloaded against the stub here and +-- dropped again at the bottom. +local realTextBox = package.loaded["src.render.TextBox"] +local realBag = package.loaded["src.ui.BagMenu"] +local realParty = package.loaded["src.ui.PartyMenu"] +package.loaded["src.render.TextBox"] = { + new = function(_, text, done) return { textBox = true, text = text, done = done } end, +} +package.loaded["src.ui.BagMenu"] = nil +package.loaded["src.ui.PartyMenu"] = nil +local BagMenu = require("src.ui.BagMenu") +local PartyMenu = require("src.ui.PartyMenu") +require("src.ui.Screens").invalidate() + +-- A stack that behaves like StateStack for the two things this flow reads: +-- top() identity (ListMenu:close) and push/pop ordering. +local function newStack() + local stack = { states = {} } + function stack:push(s) self.states[#self.states + 1] = s end + function stack:pop() return table.remove(self.states) end + function stack:top() return self.states[#self.states] end + return stack +end + +-- One button per call: PartyMenu:update reads game.input once per fixed step. +local function newInput() + local input = { pressed = nil } + function input:wasPressed(b) return self.pressed == b end + return input +end + +-- CHARIZARD learns nothing at level 51 and has no evolution left, so a +-- candy on it ends after the stat window -- no MoveLearnMenu, no +-- EvolutionState clouding which state the stack returns to. +local function freshGame(candies) + local lead = Pokemon.new(Data, "CHARIZARD", 50) + local game = { + data = Data, + stack = newStack(), + input = newInput(), + save = { + party = { lead }, + player = { name = "RED" }, + inventory = {}, + options = { battleStyle = "set", battleAnim = "on" }, + pokedex = { seen = {}, owned = {} }, + flags = {}, + money = 0, + }, + } + Bag.add(game.save, "RARE_CANDY", candies or 3) + return game, lead +end + +local function isPicker(s) return getmetatable(s) == PartyMenu end +local function isBox(s) return type(s) == "table" and s.textBox == true end + +-- TextBox pops itself BEFORE firing onDone. +local function dismiss(stack, box) + if stack:top() == box then stack:pop() end + if box.done then box.done() end +end + +local function rowFor(list, id) + for i, r in ipairs(list.items) do + if r.value == id then return i end + end + return nil +end + +-- From an already-open bag list: choose the candy, take USE off the +-- submenu, press A on the party picker. Returns the level-up text box. +local function useCandy(game, list) + local row = rowFor(list, "RARE_CANDY") + if not row then return nil, "no RARE CANDY row in the bag" end + list.index = row + list.onChoose(list.items[row], list) + -- out of battle the bag offers USE / TOSS first (start_sub_menus.asm) + local sub = game.stack:top() + if sub and sub.items and sub.items[1] and sub.items[1].onSelect then + game.stack:pop() + sub.items[1].onSelect() + end + local picker = game.stack:top() + if not isPicker(picker) then return nil, "party picker never opened" end + game.input.pressed = "a" + picker:update(1 / 60) + game.input.pressed = nil + return game.stack:top() +end + +-- Walk the rest of the level-up sequence: dismiss the level text, then A +-- through the stat window (PrintStatsBox). +local function finishLevelUp(game, box) + dismiss(game.stack, box) + local top = game.stack:top() + if isBox(top) then return end -- a "learned MOVE" line, dismissed by caller + if top and top.update then + game.input.pressed = "a" + top:update(1 / 60) + game.input.pressed = nil + end +end + +-- ---- the report: one candy closed the whole menu ------------------------- +do + local game, lead = freshGame(3) + local list = BagMenu.new(game, {}) + game.stack:push(list) + local row = rowFor(list, "RARE_CANDY") + check(row ~= nil, "the candy is in the bag") + + local box = useCandy(game, list) + check(isBox(box), "the level text opened") + eq(lead.level, 51, "the candy leveled the mon") + eq(game.save.inventory.RARE_CANDY, 2, "the candy was consumed") + check(game.stack.states[1] == list, + "the bag list is STILL on the stack under the level text (#796)") + + finishLevelUp(game, box) + eq(game.stack:top(), list, + "after the stat window the bag is back on top (StartMenu_Item)") + eq(list.index, row, "the cursor is still on the RARE CANDY row") + eq(list.items[row] and list.items[row].right, "x2", + "the count refreshed in place") + + -- the point of the original's behavior: a second candy needs no menu trip + local box2 = useCandy(game, list) + check(isBox(box2), "a second candy fires from the still-open bag") + eq(lead.level, 52, "and it levels the mon again") + finishLevelUp(game, box2) + eq(game.stack:top(), list, "still on the bag after the second candy") + eq(game.save.inventory.RARE_CANDY, 1, "two candies spent") +end + +-- ---- the last candy: the row empties but the bag still stays up ---------- +do + local game = freshGame(1) + local list = BagMenu.new(game, {}) + game.stack:push(list) + local box = useCandy(game, list) + check(isBox(box), "the last candy levels too") + finishLevelUp(game, box) + eq(game.stack:top(), list, "the bag stays open after the last candy") + eq(#list.items, 0, "the emptied row left the list") + eq(game.save.inventory.RARE_CANDY, nil, "no candies left in the inventory") +end + +package.loaded["src.render.TextBox"] = realTextBox +package.loaded["src.ui.BagMenu"] = realBag +package.loaded["src.ui.PartyMenu"] = realParty +require("src.ui.Screens").invalidate() +S.finish() diff --git a/tests/parity_shift_exp_share.lua b/tests/parity_shift_exp_share.lua deleted file mode 100644 index 601af1a7..00000000 --- a/tests/parity_shift_exp_share.lua +++ /dev/null @@ -1,206 +0,0 @@ --- Parity test: the SHIFT free switch hands the WHOLE exp share to the mon --- coming in (#275). EnemySendOutFirstMon zeroes wPartyGainExpFlags and --- wPartyFoughtCurrentEnemyFlags before jumping to SwitchPlayerMon, which sets --- only the incoming mon's bit (engine/battle/core.asm:1436-1443, 2424-2433); --- GiveExperiencePoints divides by the set bits (experience.asm:295-300), so a --- leftover flag halves the payout. The reset was never ported. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end - -local Data = require("src.core.Data") -if not (Data.pokemon and Data.pokemon.RATTATA) then Data:load() end -local TypeChart = require("src.battle.TypeChart") -TypeChart.load(Data) - -local Pokemon = require("src.pokemon.Pokemon") -local BattleState = require("src.battle.BattleState") -local Experience = require("src.battle.Experience") -local Screens = require("src.ui.Screens") -local S = require("tests.harness").suite("parity shift exp share") -local check, eq = S.check, S.eq - --- Minimal game stub: what BattleState.newTrainer / enemyMonFainted touch. --- battleStyle is per-scenario, so the caller sets it. -local function freshGame(style) - return { - data = Data, - save = { - party = { - Pokemon.new(Data, "BULBASAUR", 50), - Pokemon.new(Data, "SQUIRTLE", 40), - }, - player = { name = "RED" }, - inventory = {}, - options = { battleStyle = style }, - pokedex = { seen = {}, owned = {} }, - flags = {}, - money = 0, - }, - stack = { push = function() end, pop = function() end, top = function() end }, - } -end - --- Drain the queue, running act rows and answering the SHIFT prompt. `yes` --- picks YES (the free switch) or NO; `pick` is the party mon the battle --- PartyMenu would hand back. Text rows are collected in order so the exp --- line can be read the way the player reads it. -local function pump(b, yes, pick, seen) - local origPush = Screens.push - Screens.push = function(_, id, opts) - if id == "PartyMenu" and opts and opts.onSwitch and pick then - opts.onSwitch(pick) - end - end - local ok, err = pcall(function() - local n = 0 - while #b.queue > 0 and n < 500 do - n = n + 1 - local item = table.remove(b.queue, 1) - if item.fn then - b.nextInsert = 0 - item.fn() - elseif item.text then - seen[#seen + 1] = item.text - if item.choice and item.text:find("change POKéMON", 1, true) then - item.choice(yes) - end - end - end - end) - Screens.push = origPush - return ok, err -end - --- the number _ExpPointsText prints (wExpAmountGained), out of the port's --- "%s gained\n%d EXP. Points!" row -local function expLine(seen) - for _, t in ipairs(seen) do - local n = t:match("gained\n(%d+) EXP%. Points!") - if n then return tonumber(n), t end - end - return nil -end - --- OPP_YOUNGSTER 1 is RATTATA 11 / EKANS 11 in both versions: two slots, so --- there is a second mon to KO after the switch. -local YOUNGSTER, ROSTER = "OPP_YOUNGSTER", 1 - --- Set up the fight at the moment the first enemy mon drops, with the lead the --- only participant (as markParticipant left it), so the caller only has to pump. -local function atFirstKO(style) - local Game = freshGame(style) - local b = BattleState.newTrainer(Game, YOUNGSTER, ROSTER) - b.enemyParty[1].hp = 0 - b.enemyIndex = 1 - b.enemy.mon = b.enemyParty[1] - b.participants = { [Game.save.party[1]] = true } - b:enemyMonFainted() - return Game, b -end - --- KO whatever is out now and read back the exp line for it. -local function koAndRead(b) - local before = {} - for i, mon in ipairs(b.game.save.party) do before[i] = mon.exp end - local seen = {} - b.enemy.mon.hp = 0 - -- updateQueue zeroes this before every act row it runs; calling - -- enemyMonFainted straight from the test has to do the same, or the *Next - -- inserters index past the end of the drained queue and leave a hole - b.nextInsert = 0 - b:enemyMonFainted() - local ok, err = pump(b, false, nil, seen) - local delta = {} - for i, mon in ipairs(b.game.save.party) do delta[i] = mon.exp - before[i] end - return ok, err, seen, delta -end - -do - local Game, b = atFirstKO("shift") - eq(#b.enemyParty, 2, "OPP_YOUNGSTER roster " .. ROSTER .. " has two mons") - local lead, reserve = Game.save.party[1], Game.save.party[2] - - -- KO one: the SHIFT prompt, answered YES with the reserve picked. - local seen = {} - local ok, err = pump(b, true, reserve, seen) - check(ok, "the SHIFT switch pumped without error: " .. tostring(err)) - check(b.player.mon == reserve, "the free switch put the reserve on the field") - check(b.enemy.mon.hp > 0, "the foe's second mon is out") - - -- The participant set is the mechanism; the exp number below is the symptom. - check(b.participants[reserve] == true, "the switch-in is a participant") - check(b.participants[lead] == nil, - "the mon that was out when the foe fainted is no longer one (#275)") - - -- KO two: the reserve fights alone, so it must be paid as a single - -- participant. - local ok2, err2, seen2, delta = koAndRead(b) - check(ok2, "the second KO pumped without error: " .. tostring(err2)) - - local foeDef = Data.pokemon[b.enemyParty[2].species] - local solo = Experience.gainFor(foeDef, b.enemyParty[2].level, true, 1, nil, - Data.constants) - local halved = Experience.gainFor(foeDef, b.enemyParty[2].level, true, 2, nil, - Data.constants) - check(solo > halved, - "the two divisors are distinguishable for this foe (" .. - solo .. " vs " .. halved .. ")") - - local shown, line = expLine(seen2) - check(shown ~= nil, "the KO printed an EXP. Points! line") - eq(shown, solo, "the switch-in is paid a whole share, not a split one (#275)") - check(shown ~= halved, - "the printed number is not the two-way split (" .. tostring(line) .. ")") - eq(delta[2], solo, "the reserve's exp rose by exactly that share") - eq(delta[1], 0, "the mon left behind is paid nothing for a KO it missed") - - local lines = 0 - for _, t in ipairs(seen2) do - if t:find("EXP%. Points!") then lines = lines + 1 end - end - eq(lines, 1, "exactly one mon is announced as gaining exp") -end - --- Control: SET style has no free switch, so the lead fights both mons and is --- paid a whole share for each. Pin it here: the SHIFT switch-in above must --- earn the same number. -do - local Game, b = atFirstKO("set") - local seen = {} - local ok = pump(b, false, nil, seen) - check(ok, "SET style pumped without error") - check(b.player.mon == Game.save.party[1], "SET style never offered a switch") - - local ok2, _, seen2, delta = koAndRead(b) - check(ok2, "the SET second KO pumped without error") - local foeDef = Data.pokemon[b.enemyParty[2].species] - local solo = Experience.gainFor(foeDef, b.enemyParty[2].level, true, 1, nil, - Data.constants) - local shown = expLine(seen2) - eq(shown, solo, "SET style pays the lead a whole share") - eq(delta[1], solo, "and the lead's exp rises by it") -end - --- The path the reset must NOT touch: the party-menu SwitchPlayerMon --- (core.asm:2424-2433, from PartyMenuOrRockOrRun) sets the incoming mon's bit --- without zeroing the flag bytes, which is the exp-share trick every player --- uses: send a weak mon in, switch it straight out, it still splits the KO. -do - local Game = freshGame("shift") - local b = BattleState.newTrainer(Game, YOUNGSTER, ROSTER) - local lead, reserve = Game.save.party[1], Game.save.party[2] - b.participants = { [lead] = true } - b:resolveSwitch(reserve) - local n = 0 - while #b.queue > 0 and n < 200 do - n = n + 1 - local item = table.remove(b.queue, 1) - if item.fn then b.nextInsert = 0; item.fn() end - end - check(b.player.mon == reserve, "the voluntary switch went through") - check(b.participants[reserve] == true, "the mon coming in participates") - check(b.participants[lead] == true, - "a VOLUNTARY switch keeps the outgoing mon flagged (the exp share)") -end - -S.finish() diff --git a/tests/parity_silph_lapras_bug1049.lua b/tests/parity_silph_lapras_bug1049.lua new file mode 100644 index 00000000..4d2de480 --- /dev/null +++ b/tests/parity_silph_lapras_bug1049.lua @@ -0,0 +1,135 @@ +-- Parity: the Silph Co. 7F worker's LAPRAS gift offers the nickname prompt (#1049). +-- pokered scripts/SilphCo7F.asm SilphCo7FSilphWorkerM1Text. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end + +local S = require("tests.harness").suite("parity Silph LAPRAS") +local check, eq = S.check, S.eq + +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local StateStack = require("src.core.StateStack") +local SaveData = require("src.core.SaveData") +local ScriptRunner = require("src.script.ScriptRunner") +local Commands = require("src.script.Commands") +local Flags = require("src.script.Flags") +local Pokemon = require("src.pokemon.Pokemon") +local Boxes = require("src.pokemon.Boxes") +local mapScripts = require("data.scripts.init") + +Game.data = Data +Game.input = Input; Input:init() +Game.stack = StateStack; StateStack:init() +Game.save = SaveData.newGame() +require("src.render.Font").load(Data) + +local MAP, TEXT = "SILPH_CO_7F", "TEXT_SILPHCO7F_SILPH_WORKER_M1" + +-- === 1) the worker is command rows, so the run carries a ScriptRunner === +local script = mapScripts.talkScript(MAP, TEXT) +check(type(script) == "table", + "the LAPRAS worker is a row list, not a bare callback (#1049)") +eq(#ScriptRunner.validate(script), 0, "the rows validate cleanly") +local gives = 0 +for _, row in ipairs(type(script) == "table" and script or {}) do + if row[1] == "give_pokemon" then + gives = gives + 1 + eq(row[2], "LAPRAS", "the gift species is LAPRAS") + eq(row[3], 15, "the gift is level 15 (lb bc, LAPRAS, 15)") + check(row[4] == nil, "no skipNickname: AskName is left to run") + end +end +eq(gives, 1, "exactly one give_pokemon row") + +-- === harness: run the talk script headless, A every frame, recording show_text ids +local shown = {} +local origShow = Commands.show_text +-- forward extraOpts: Commands.ask rides show_text's 4th argument +Commands.show_text = function(ctx, textId, subs, ...) + table.insert(shown, textId) + return origShow(ctx, textId, subs, ...) +end + +local function runScript() + shown = {} + local ow = { map = { id = MAP, def = { label = MAP } }, + npcs = {}, entities = {} } + local r = ScriptRunner.new(Game, ow) + r:run(script, { npc = { def = {}, facePlayer = function() end }, + overworld = ow }) + local guard = 0 + while r:isRunning() and guard < 3000 do + guard = guard + 1 + Input.pressed = { a = true } + StateStack:update(1 / 60) + r:update() + end + Input.pressed = {} + return not r:isRunning() +end + +local function shownIs(want, msg) + eq(table.concat(shown, ","), table.concat(want, ","), msg) +end + +-- === 2) the gift itself: thanks, nickname prompt, GotMonText, blurb === +Game.save = SaveData.newGame() +check(runScript(), "LAPRAS gift script completes") +shownIs({ "_SilphCo7FSilphWorkerM1HaveThisPokemonText", + "_DoYouWantToNicknameText", "_GotMonText", + "_SilphCo7FSilphWorkerM1LaprasDescriptionText" }, + "thanks, nickname prompt, got-mon line, then the LAPRAS blurb") +eq(#Game.save.party, 1, "LAPRAS joins the party") +local lapras = Game.save.party[1] or {} +eq(lapras.species, "LAPRAS", "gift species is LAPRAS") +eq(lapras.level, 15, "LAPRAS is level 15") +eq(lapras.nickname, "AAAAAAAAAA", + "the nickname prompt reaches the NamingScreen (A-mash)") +check(Flags.get(Game.save, "EVENT_GOT_LAPRAS"), "BIT_GOT_LAPRAS is set") +check(Game.save.pokedex.owned.LAPRAS, "LAPRAS is registered owned") + +-- === 3) after the gift he worries about the PRESIDENT, and only after +check(runScript(), "post-gift script completes") +shownIs({ "_SilphCo7FSilphWorkerM1IsOurPresidentOkText" }, + "before Giovanni: the worried line, and no second LAPRAS") +eq(#Game.save.party, 1, "no second LAPRAS") + +Flags.set(Game.save, "EVENT_BEAT_SILPH_CO_GIOVANNI") +check(runScript(), "post-Giovanni script completes") +shownIs({ "_SilphCo7FSilphWorkerM1SavedText" }, + "after Giovanni: saved at last") + +-- === 4) party full, box has room: SendNewMonToBox still asks the name === +Game.save = SaveData.newGame() +for i = 1, 6 do Game.save.party[i] = Pokemon.new(Data, "PIDGEY", 5) end +check(runScript(), "full-party gift script completes") +shownIs({ "_SilphCo7FSilphWorkerM1HaveThisPokemonText", + "_DoYouWantToNicknameText", "_SentToBoxText", "_GotMonText", + "_SilphCo7FSilphWorkerM1LaprasDescriptionText" }, + "full party: nickname, sent-to-box, got-mon line, blurb") +local boxed = false +for _, box in ipairs(Boxes.ensure(Game.save)) do + for _, m in ipairs(box) do + if m.species == "LAPRAS" then boxed = true end + end +end +check(boxed, "full-party LAPRAS lands in a box") +check(Flags.get(Game.save, "EVENT_GOT_LAPRAS"), "full-party gift sets the flag") + +-- === 5) party AND every box full: BoxIsFullText, no got-mon line, flag +Game.save = SaveData.newGame() +for i = 1, 6 do Game.save.party[i] = Pokemon.new(Data, "PIDGEY", 5) end +Boxes.ensure(Game.save) +for b = 1, Boxes.COUNT do + for s = 1, Boxes.CAPACITY do Game.save.boxes[b][s] = { species = "PIDGEY" } end +end +check(runScript(), "full-everything script completes") +shownIs({ "_SilphCo7FSilphWorkerM1HaveThisPokemonText", "_BoxIsFullText" }, + "no room: the box-full line, never a got-mon line for a mon you lack") +check(not Flags.get(Game.save, "EVENT_GOT_LAPRAS"), + "a failed give leaves BIT_GOT_LAPRAS clear, so the gift stays claimable") + +Commands.show_text = origShow +S.finish() diff --git a/tests/parity_starter_dex.lua b/tests/parity_starter_dex.lua deleted file mode 100644 index 49935232..00000000 --- a/tests/parity_starter_dex.lua +++ /dev/null @@ -1,117 +0,0 @@ --- Parity: Oak's lab starter-ball Pokédex preview (#110). --- pret StarterDex (engine/events/starter_dex.asm) temporarily sets the --- owned bits so ShowPokedexData prints the full entry before the player --- has caught anything. Also: English R/B prints only the kind string --- (no " POKéMON" suffix -- that clipped "LIZARD" to "LIZARD POKé"). -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local Data = require("src.core.Data") -if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end - -local S = require("tests.harness").suite("parity starter dex") -local check, eq = S.check, S.eq - -local Font = require("src.render.Font") -Font.load(Data) - -local DexEntryMenu = require("src.ui.DexEntryMenu") -local SaveData = require("src.core.SaveData") -local mapScripts = require("data.scripts.init") - -local function fakeGame() - return { - data = Data, - save = SaveData.newGame(), - input = { wasPressed = function() return false end }, - stack = { pop = function() end }, - } -end - -local function drawCapture(menu) - local drawn = {} - local saved = Font.draw - Font.draw = function(text, x, y) - drawn[#drawn + 1] = { text = tostring(text), x = x, y = y } - return Font.width(text) - end - menu:draw() - Font.draw = saved - return drawn -end - -local function findText(drawn, needle) - for _, d in ipairs(drawn) do - if d.text == needle or d.text:find(needle, 1, true) then return d end - end - return nil -end - --- === 1) unowned entry without forceOwned stays "Data unknown." === -do - local game = fakeGame() - game.save.pokedex = { seen = {}, owned = {} } - local menu = DexEntryMenu.new(game, "CHARMANDER") - local drawn = drawCapture(menu) - check(findText(drawn, "Data unknown."), - "unowned Charmander shows Data unknown without forceOwned") - check(not findText(drawn, "Obviously prefers"), - "unowned Charmander hides description without forceOwned") - check(not findText(drawn, "HT "), - "unowned Charmander hides height without forceOwned") -end - --- === 2) forceOwned shows full entry without mutating save === -do - local game = fakeGame() - game.save.pokedex = { seen = {}, owned = {} } - local menu = DexEntryMenu.new(game, { species = "CHARMANDER", forceOwned = true }) - check(menu.forceOwned, "forceOwned flag sticks on the menu") - local drawn = drawCapture(menu) - check(findText(drawn, "Obviously prefers"), - "forceOwned Charmander shows dex description") - check(findText(drawn, "HT "), - "forceOwned Charmander shows height") - check(not findText(drawn, "Data unknown."), - "forceOwned Charmander does not show Data unknown") - check(not game.save.pokedex.owned.CHARMANDER, - "forceOwned preview does not mark Charmander owned") -end - --- === 3) kind is the bare English string (no POKéMON suffix) === -do - local game = fakeGame() - game.save.pokedex = { seen = {}, owned = { CHARMANDER = true } } - local menu = DexEntryMenu.new(game, "CHARMANDER") - local drawn = drawCapture(menu) - local kind = findText(drawn, "LIZARD") - check(kind and kind.text == "LIZARD", - "kind draws as LIZARD only (English R/B PlaceString)") - check(not findText(drawn, "POKéMON"), - "kind line does not append POKéMON") - check(kind.x + Font.width(kind.text) <= 160, - "LIZARD kind fits on-screen (no clip)") -end - --- === 4) Oak's lab starter scripts request forceOwned === -do - local balls = { - "TEXT_OAKSLAB_CHARMANDER_POKE_BALL", - "TEXT_OAKSLAB_SQUIRTLE_POKE_BALL", - "TEXT_OAKSLAB_BULBASAUR_POKE_BALL", - } - for _, textId in ipairs(balls) do - local script = mapScripts.talkScript("OAKS_LAB", textId) - local found - for _, row in ipairs(script) do - if row[1] == "push_screen" and row[2] == "DexEntryMenu" then - found = row[3] - break - end - end - check(type(found) == "table" and found.forceOwned == true - and type(found.species) == "string", - textId .. " pushes DexEntryMenu with forceOwned") - end -end - -S.finish() diff --git a/tests/parity_substitute_anim.lua b/tests/parity_substitute_anim.lua new file mode 100644 index 00000000..1071d150 --- /dev/null +++ b/tests/parity_substitute_anim.lua @@ -0,0 +1,103 @@ +-- Parity test: Substitute's failure branches play no animation (#644). +-- SUBSTITUTE_EFFECT is a ResidualEffects1 entry +-- (data/battle/residual_effects_1.asm), so the caller never plays the move +-- animation; SubstituteEffect_ (engine/battle/move_effects/substitute.asm) +-- reaches PlayCurrentMoveAnimation / AnimationSubstitute only after +-- `set HAS_SUBSTITUTE_UP, [hl]`, while .alreadyHasSubstitute and +-- .notEnoughHP jump straight to PrintText. The animation opens with +-- SE_SLIDE_MON_OFF, which hides the user's pic until the doll replaces it, +-- so a failed Substitute that still animated left the user invisible. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not (Data.moves and Data.moves.SUBSTITUTE) then Data:load() end +local TypeChart = require("src.battle.TypeChart") +TypeChart.load(Data) + +local Font = require("src.render.Font") +if not pcall(Font.encode, "A") then Font.load(Data) end + +local Game = require("src.core.Game") +Game.data = Data +Game.save = require("src.core.SaveData").newGame() + +local Pokemon = require("src.pokemon.Pokemon") +local BattleState = require("src.battle.BattleState") +local S = require("tests.harness").suite("parity substitute anim") +local check, eq = S.check, S.eq + +local function freshBattle() + Game.save.party = { Pokemon.new(Data, "BULBASAUR", 20) } + local tb = BattleState.newWild(Game, "PIDGEY", 10) + tb.queue, tb.nextInsert = {}, 0 + return tb +end + +local function anyAnim(tb, name) + for _, row in ipairs(tb.queue) do + if row.anim == name then return true end + end + return false +end + +local function anyText(tb, needle) + for _, row in ipairs(tb.queue) do + if row.text and row.text:gsub("\n", " "):find(needle, 1, true) then return true end + end + return false +end + +do + local anims = Data.battle_anims and Data.battle_anims.moveAnims + check(anims ~= nil, "battle_anims carries moveAnims") + check(anims == nil or anims.SUBSTITUTE ~= nil, "SUBSTITUTE has an animation") +end + +-- success: the doll animation plays and the substitute stands +do + local tb = freshBattle() + tb.enemy.mon.hp = tb.enemy.mon.stats.hp + tb:performMove(tb.enemy, tb.player, { id = "SUBSTITUTE", pp = 10 }, false) + check(tb.enemy.substituteHP ~= nil, "a healthy user builds its substitute") + check(anyAnim(tb, "SUBSTITUTE"), "and the doll animation plays") +end + +-- .notEnoughHP: text only, no animation, and no dangling row +do + local tb = freshBattle() + tb.enemy.mon.hp = math.floor(tb.enemy.mon.stats.hp / 4) - 1 + tb:performMove(tb.enemy, tb.player, { id = "SUBSTITUTE", pp = 10 }, false) + check(tb.enemy.substituteHP == nil, "too little HP fails the substitute") + check(not anyAnim(tb, "SUBSTITUTE"), + "a failed substitute plays no animation (#644)") + check(tb.moveAnimRow == nil, "the peeled move-anim row is not left dangling") + check(anyText(tb, "SUBSTITUTE"), "the failure text still prints") +end + +-- Exact quarter HP is also not enough: accepting it would leave the user at +-- 0 HP with substituteHP set, so the next trainer-battle turn cannot advance. +do + local tb = freshBattle() + local cost = math.floor(tb.enemy.mon.stats.hp / 4) + tb.enemy.mon.hp = cost + tb:performMove(tb.enemy, tb.player, { id = "SUBSTITUTE", pp = 10 }, false) + check(tb.enemy.substituteHP == nil and tb.enemy.mon.hp == cost, + "exact quarter HP cannot create a zero-HP substitute") + check(not anyAnim(tb, "SUBSTITUTE"), + "the exact-boundary failure plays no animation") +end + +-- .alreadyHasSubstitute: same, with a doll already standing +do + local tb = freshBattle() + tb.enemy.mon.hp = tb.enemy.mon.stats.hp + tb.enemy.substituteHP = 10 + tb:performMove(tb.enemy, tb.player, { id = "SUBSTITUTE", pp = 10 }, false) + eq(tb.enemy.substituteHP, 10, "the standing substitute is untouched") + check(not anyAnim(tb, "SUBSTITUTE"), + "a second substitute plays no animation (#644)") + check(tb.moveAnimRow == nil, "and peels its anim row") +end + +S.finish() diff --git a/tests/parity_trade_gift.lua b/tests/parity_trade_gift.lua index ab0383c4..6ed91552 100644 --- a/tests/parity_trade_gift.lua +++ b/tests/parity_trade_gift.lua @@ -98,9 +98,13 @@ for _, modname in ipairs({ "data.scripts.story", "data.scripts.story2", check(WIRED[idx] == flag, ("%s/%s: trade %s pairs with %s"):format( mapId, const, tostring(idx), tostring(flag))) - check(not seen[idx], - ("trade index %s wired by only one NPC"):format(tostring(idx))) - seen[idx] = true + -- one NPC per index PER VERSION: Route 18 gate wires slot 6 + -- twice on purpose (Red's YOUNGSTER/MARC, Yellow's COOK/SPIKE; + -- pokeyellow/scripts/Route18Gate2F.asm, #651) -- each version's + -- map only spawns its own NPC, so a same-map twin is fine + check(not seen[idx] or seen[idx] == mapId, + ("trade index %s wired by only one NPC per version"):format(tostring(idx))) + seen[idx] = mapId end end end @@ -116,9 +120,12 @@ check(not seen[3], "unused CHIKUCHIKU trade (index 3) stays unwired") -- === harness: run a talk script headless, recording show_text ids === local shown = {} local origShow = Commands.show_text -Commands.show_text = function(ctx, textId, subs) +-- forward extraOpts too: Commands.ask rides show_text's 4th argument +-- (opts.choice, so the YES/NO box pops over the still-visible question); +-- dropping it here would strand every prompt on the NO branch +Commands.show_text = function(ctx, textId, subs, ...) table.insert(shown, textId) - return origShow(ctx, textId, subs) + return origShow(ctx, textId, subs, ...) end -- pressFn returns the Input.pressed table for this frame (default: A) diff --git a/tests/parity_trainer_evolution_order.lua b/tests/parity_trainer_evolution_order.lua index f4f39b8e..d457f4bc 100644 --- a/tests/parity_trainer_evolution_order.lua +++ b/tests/parity_trainer_evolution_order.lua @@ -77,8 +77,13 @@ check(#Game.stack.states == 1, Game.stack:pop() moreText.onDone() -local evolutionText = Game.stack:top() -check(stateHas(evolutionText, "evolving"), +-- The evolution runs as the EvolutionState cutscene screen now (the +-- evolve_mon.asm sequence lives in src/ui/EvolutionState.lua), not a bare +-- "evolving!" text box, so assert the screen itself took the stack. +local evolution = Game.stack:top() +check(evolution ~= nil + and (evolution.screenId == "EvolutionState" + or stateHas(evolution, "evolving")), "the level evolution starts after trainer after-text closes") S.finish() diff --git a/tests/parity_trainer_victory_text.lua b/tests/parity_trainer_victory_text.lua deleted file mode 100644 index 45cf8446..00000000 --- a/tests/parity_trainer_victory_text.lua +++ /dev/null @@ -1,198 +0,0 @@ --- Parity: the beaten trainer's own loss line prints ON the battle screen, --- between the pic scrolling back in and the prize money (#282). --- TrainerBattleVictory (engine/battle/core.asm:915-949) runs TrainerDefeatedText, --- ScrollTrainerPicAfterBattle, PrintEndBattleText, then MoneyForWinningText. --- The scroll (scroll_draw_trainer_pic.asm:1-31) rewrites tilemap columns only, --- so the pokeball row ClearSprites emptied does not come back with the pic. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local S = require("tests.harness").suite("parity trainer victory text") -local check, eq = S.check, S.eq - -local Data = require("src.core.Data") -if not Data.maps then Data:load() end -local Font = require("src.render.Font") -Font.load(Data) - -local BattleState = require("src.battle.BattleState") -local Pokemon = require("src.pokemon.Pokemon") -local SaveData = require("src.core.SaveData") -local Sound = require("src.core.Sound") -local Music = require("src.core.Music") - -Sound.playCry = function() end -Sound.play = function() end -Sound.playMove = function() end -Sound.playMoveCry = function() end -Sound.stopLoop = function() end -Music.playBattle = function() end -Music.play = function() end - -local press = {} -local function makeGame(party) - local save = SaveData.newGame() - save.party = party - local stack = { states = {} } - function stack:push(state) self.states[#self.states + 1] = state end - function stack:pop() return table.remove(self.states) end - function stack:top() return self.states[#self.states] end - -- isDown as well as wasPressed: battle text collapses PrintLetterDelay - -- while A or B is held, and the typing path reads it every frame - return { data = Data, save = save, stack = stack, - input = { wasPressed = function(_, b) return press[b] == true end, - isDown = function(_, b) return press[b] == true end } } -end - --- A held: updateQueue only reads the button once a page is typed out, so an --- early press is ignored and the queue drains at a player's pace. -local function step(battle) - press.a = true - battle:update(1 / 60) - press.a = false -end - --- Fight a YOUNGSTER, wipe its party, and record every message row in the order --- it reached the screen plus what the foe's pic slot was doing at the time. -local function fightAndWin(endBattleText) - local game = makeGame({ Pokemon.new(Data, "BULBASAUR", 60) }) - local battle = BattleState.newTrainer(game, "OPP_YOUNGSTER", 1) - battle.endBattleText = endBattleText - local result, resultAt - battle.onFinish = function(r) result = r end - battle:enter() - for _ = 1, 500 do - step(battle) - if battle.phase == "menu" then break end - end - - -- the KO itself, through the real faint path (onFaint queues the slide, - -- the faint text and the enemyMonFainted act) - for _, mon in ipairs(battle.enemyParty) do mon.hp = 0 end - battle.enemy.mon.hp = 0 - battle.phase = "messages" - battle.nextInsert = 0 - battle:onFaint(battle.enemy) - - local pages, foeOffAt, foeShownAt = {}, {}, {} - local ballRowsSeen, frame, foeMax, foeSteps = 0, 0, 0, 0 - local realRow = battle.drawBallRow - battle.drawBallRow = function() ballRowsSeen = ballRowsSeen + 1 end - local lastOff = battle:picOffset("foe") - for f = 1, 2000 do - frame = f - step(battle) - local cur = battle.current - local text = cur and cur.text - if text and pages[#pages] ~= text then - pages[#pages + 1] = text - foeOffAt[text] = battle:picOffset("foe") - foeShownAt[text] = battle.showEnemyTrainer and true or false - end - local off = battle:picOffset("foe") - if off > foeMax then foeMax = off end - -- count only the inward frames; the jump from 0 to 64 is the program - -- being armed off-screen, not a step of the scroll - if off < lastOff then foeSteps = foeSteps + 1 end - lastOff = off - -- drawHUDs is the only place a ball row can come from; sample it while - -- the beaten trainer is back on screen - if battle.showEnemyTrainer then pcall(battle.drawHUDs, battle, 0) end - if result then resultAt = f break end - end - battle.drawBallRow = realRow - return { - battle = battle, pages = pages, result = result, resultAt = resultAt, - foeOffAt = foeOffAt, foeShownAt = foeShownAt, ballRowsSeen = ballRowsSeen, - frames = frame, foeMax = foeMax, foeSteps = foeSteps, - } -end - -local function indexOf(pages, fragment) - for i, p in ipairs(pages) do - if p:find(fragment, 1, true) then return i end - end - return nil -end - --- ------------------------------------------------- the full victory order -local LOSS = "What a total\nwaste of time!" -local run = fightAndWin(LOSS) - -eq(run.result, "win", "the battle resolves as a win") -local defeated = indexOf(run.pages, "defeated") -local loss = indexOf(run.pages, "waste of time") -local money = indexOf(run.pages, "for winning") -check(defeated ~= nil, "TrainerDefeatedText prints (\"RED defeated YOUNGSTER!\")") -check(loss ~= nil, - "the trainer's own EndBattleText prints INSIDE the battle (#282)") -check(money ~= nil, "MoneyForWinningText prints") -check(defeated and loss and defeated < loss, - "the defeat line comes before the trainer's loss line") -check(loss and money and loss < money, - "PrintEndBattleText comes before MoneyForWinningText (core.asm:942-949)") -check(money == #run.pages, - "the prize money is the LAST thing on the battle screen") - --- the pic is back, at rest, with no ball row beside it -if loss then - local text = run.pages[loss] - eq(run.foeShownAt[text], true, - "the beaten trainer's pic is on screen for his loss line") - eq(run.foeOffAt[text], 16, - "the pic has come to rest two tiles right of the battle slot " - .. "(_ScrollTrainerPicAfterBattle ends at hlcoord 14,0)") -end -eq(run.ballRowsSeen, 0, - "no pokeball row comes back with the pic (ClearSprites emptied that OAM; " - .. "_ScrollTrainerPicAfterBattle only rewrites tilemap columns)") -eq(run.battle.introBalls, nil, "the DrawAllPokeballs window stays closed") - --- The pic is on screen well before the LAST page: the plain act() this used to --- ride appended to the end of the queue, so the trainer flashed up one row --- before finish() popped the battle. -if money then - eq(run.foeShownAt[run.pages[money]], true, - "the trainer's pic is already back for the money line, not flashed up " - .. "one row before the battle pops (#282)") -end - --- and it really scrolls rather than popping into place: 64px off the right --- edge, then 2px a frame down to the resting 16 -eq(run.foeMax, 64, "the scroll-in starts 8 tiles off the right edge") -eq(run.foeSteps, 24, - "it takes 24 frames to walk in (six 4-frame columns, " - .. "scroll_draw_trainer_pic.asm:1-31)") - --- ------------------------------------------------------ ordering vs onFinish --- finish() pops the battle, so anything the overworld pushes afterwards is a --- second screen cut. Every trainer-victory row must be consumed before it. -check(run.resultAt ~= nil and run.resultAt >= run.frames, - "onFinish fires only once the whole sequence has drained") - --- ------------------------------------------------------------- \f pages --- Five EndBattleTexts carry a `para` (e.g. _Route9Youngster1EndBattleText). --- BattleState:startMessage only splits \n and \v, so an unsplit \f would --- render as a garbage glyph instead of starting a new page. -local para = fightAndWin("Oh well.\fI give up!") -check(indexOf(para.pages, "Oh well.") ~= nil, - "a \\f EndBattleText prints its first page") -check(indexOf(para.pages, "I give up!") ~= nil, - "a \\f EndBattleText prints its second page") -local p1, p2 = indexOf(para.pages, "Oh well."), indexOf(para.pages, "I give up!") -check(p1 and p2 and p2 == p1 + 1, "the two pages are consecutive rows") -for _, page in ipairs(para.pages) do - check(page:find("\f", 1, true) == nil, - "no page still carries a raw \\f: " .. (page:gsub("\n", " / "))) -end - --- --------------------------------------------------- scripted battles --- Commands.start_battle never sets endBattleText; those scripts print their --- own follow-up, so the sequence must simply skip the row. -local none = fightAndWin(nil) -eq(none.result, "win", "a battle with no EndBattleText still resolves") -local d2, m2 = indexOf(none.pages, "defeated"), indexOf(none.pages, "for winning") -check(d2 and m2 and d2 < m2, - "defeat text then money, with nothing between them") -eq(m2, #none.pages, "the prize money is still last") - -S.finish() diff --git a/tests/parity_true_color_ui.lua b/tests/parity_true_color_ui.lua index 415f470a..0bfa5015 100644 --- a/tests/parity_true_color_ui.lua +++ b/tests/parity_true_color_ui.lua @@ -93,6 +93,7 @@ local titleGame = { field = { title = { cycleSpecies = { "PIKACHU" } } } }, } local title = TitleState.new(titleGame, {}) +title.phase, title.scy = "loop", 0 local titleSprite, titleTrueColor = title:currentSprite() check(titleSprite and titleTrueColor, "title cache keeps a Pokemon sprite's trueColor flag") diff --git a/tests/parity_wardens_house_bug535.lua b/tests/parity_wardens_house_bug535.lua deleted file mode 100644 index 7ab69ee5..00000000 --- a/tests/parity_wardens_house_bug535.lua +++ /dev/null @@ -1,127 +0,0 @@ --- Regression (#535): after handing over the GOLD TEETH and receiving --- HM04, every later talk to the Warden must still say something. --- --- data/scripts/story.lua's TEXT_WARDENSHOUSE_WARDEN pointed the --- EVENT_GOT_HM04 branch (row 3, jump_if_true) at the same silent-end jump --- the give-then-thank fallthrough uses (row 13), so ScriptRunner's pc ran --- straight past the end of the row list with zero show_text calls -- the --- Warden went mute on every visit after the trade. pokered's .got_item --- branch (scripts/WardensHouse.asm) instead prints .HM04ExplanationText --- (text/WardensHouse.asm: "HM04 teaches STRENGTH ... SECRET HOUSE in --- SAFARI ZONE") on every subsequent talk. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end -local Data = require("src.core.Data") -if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end - -local S = require("tests.harness").suite("parity wardens house (#535)") -local check, eq = S.check, S.eq - -local Commands = require("src.script.Commands") -local Flags = require("src.script.Flags") -local Game = require("src.core.Game") -local Input = require("src.core.Input") -local SaveData = require("src.core.SaveData") -local ScriptRunner = require("src.script.ScriptRunner") -local StateStack = require("src.core.StateStack") - -Game.data = Data -Game.input = Input; Input:init() -Game.stack = StateStack; StateStack:init() -require("src.render.Font").load(Data) - -local story = require("data.scripts.story") -local script = story.WARDENS_HOUSE.talk.TEXT_WARDENSHOUSE_WARDEN - --- instrument show_text the way parity_gift_atomicity.lua does, to record --- exactly which text ids actually printed -local shown = {} --- forward EVERY argument: the 4th is extraOpts, which is how Commands.ask --- hands down its `choice` callback. A wrapper that stops at `subs` silently --- turns every ask in the script back into a plain show_text -- no YES/NO box, --- and ctx.lastCheck left holding whatever the previous check_* put there. -local origShow = Commands.show_text -Commands.show_text = function(ctx, textId, ...) - shown[#shown + 1] = textId - return origShow(ctx, textId, ...) -end - --- `button` drives the whole conversation: both A and B page a text box, and --- on the YES/NO box A takes the cursor's default (YES) while B snaps to NO --- and answers false (ChoiceBox:update, .choseSecondMenuItem). So holding A --- runs the yes branch and holding B runs the no branch, with no reaching --- into the choice box from the test. -local function runScript(button) - shown = {} - StateStack:init() - local ow = { map = { id = "WARDENS_HOUSE", def = { label = "WardensHouse" } }, - npcs = {}, entities = {} } - local r = ScriptRunner.new(Game, ow) - r:run(script, { npc = { def = {}, facePlayer = function() end }, - overworld = ow }) - local guard = 0 - while r:isRunning() and guard < 3000 do - guard = guard + 1 - Input.pressed = { [button or "a"] = true } - StateStack:update(1 / 60) - r:update() - end - Input.pressed = {} - return not r:isRunning() -end - --- === 1) first talk, holding the GOLD TEETH: gives HM04, sets the flag === -Game.save = SaveData.newGame() -Game.save.inventory.GOLD_TEETH = 1 -check(runScript(), "give-the-teeth talk completes") -eq(table.concat(shown, ","), - "_WardensHouseWardenGaveTheGoldTeethText,_WardensHouseWardenThanksText," - .. "_WardensHouseWardenReceivedHM04Text", - "handing over the teeth shows the give/thanks/received sequence, nothing after") -check(Flags.get(Game.save, "EVENT_GOT_HM04"), "EVENT_GOT_HM04 is set") -check(Flags.get(Game.save, "EVENT_GAVE_GOLD_TEETH"), "EVENT_GAVE_GOLD_TEETH is set") -check(Game.save.inventory.HM_STRENGTH ~= nil, "HM04 (Strength) lands in the bag") -check(not Game.save.inventory.GOLD_TEETH, "the GOLD TEETH is taken") - --- === 2) the regression itself: every later talk, once EVENT_GOT_HM04 is --- set, must print the explanation text instead of nothing === -check(runScript(), "post-gift talk completes") -eq(table.concat(shown, ","), "_WardensHouseWardenHM04ExplanationText", - "every subsequent talk now prints the HM04/Safari Zone explanation (#535)") - --- run it again to confirm this is not a one-shot: it repeats every visit -check(runScript(), "a third talk completes") -eq(table.concat(shown, ","), "_WardensHouseWardenHM04ExplanationText", - "the explanation text repeats on every later talk, not just the first") - --- === 3) no GOLD TEETH yet: the gibberish question, then a YES/NO, then the --- warden's answer -- Gibberish2 on yes, Gibberish3 on no (#645). --- The port used to stop dead after the question. === -Game.save = SaveData.newGame() -check(runScript("a"), "empty-handed talk completes on yes") -eq(table.concat(shown, ","), - "_WardensHouseWardenGibberish1Text,_WardensHouseWardenGibberish2Text", - "answering YES gets the warden's reply, not silence (#645)") -check(not Flags.get(Game.save, "EVENT_GOT_HM04"), "no HM04 yet") - -Game.save = SaveData.newGame() -check(runScript("b"), "empty-handed talk completes on no") -eq(table.concat(shown, ","), - "_WardensHouseWardenGibberish1Text,_WardensHouseWardenGibberish3Text", - "and answering NO gets the other reply (#645)") - --- the question is asked, not just printed: `ask` is what puts the YES/NO box --- up, so a future edit that downgrades it back to show_text fails here -local askRow -for _, row in ipairs(script) do - if row[2] == "_WardensHouseWardenGibberish1Text" then askRow = row[1] end -end -eq(askRow, "ask", "the gibberish line is asked with a YES/NO, not just shown") - --- neither answer touches the teeth trade -check(not Flags.get(Game.save, "EVENT_GAVE_GOLD_TEETH"), - "and neither answer hands over teeth the player does not have") - -Commands.show_text = origShow - -S.finish() diff --git a/tests/parity_yellow_oak_speech.lua b/tests/parity_yellow_oak_speech.lua new file mode 100644 index 00000000..2b74da3f --- /dev/null +++ b/tests/parity_yellow_oak_speech.lua @@ -0,0 +1,51 @@ +-- Pokemon Yellow's Oak-speech show-off mon is the player's Pikachu, not +-- Red/Blue's NIDORINO (engine/battle/core.asm BATTLE_TYPE_PIKACHU, the +-- ProfOak demo; engine/movie/oak_speech/oak_speech.asm). The import +-- manifest must carry field.oakSpeech.demoSpecies, and +-- Data:applyVersionedFieldData repairs Yellow caches made before the +-- manifest carried it (#915). +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not (Data.field and Data.field.oakSpeech) then Data:load() end +local GameVersion = require("src.core.GameVersion") +local S = require("tests.harness").suite("parity Yellow Oak speech") +local check, eq = S.check, S.eq + +local oldVersion = GameVersion.get() +local oldTrades = Data.field.trades +local oldOldManBattle = Data.field.oldManBattle + +local manifestFile = assert(io.open("tools/rom_manifest_yellow.json", "r")) +local manifest = manifestFile:read("*a") +manifestFile:close() + +check(manifest:find('"demoSpecies": "PIKACHU"') ~= nil, + "Yellow manifest stamps field.oakSpeech.demoSpecies as PIKACHU") + +-- a stale Yellow cache carries shrink frames but no demoSpecies +local stale = { shrink1 = "assets/generated/intro/shrink1.png", + shrink2 = "assets/generated/intro/shrink2.png" } +local oldOakSpeech = Data.field.oakSpeech +Data.field.oakSpeech = stale + +GameVersion.set("yellow") +Data:applyVersionedFieldData() +eq(Data.field.oakSpeech.demoSpecies, "PIKACHU", + "applyVersionedFieldData fills a stale Yellow cache with PIKACHU") + +-- fill-if-absent: an importer that learns to stamp the key wins +local preStamped = { demoSpecies = "RAICHU", + shrink1 = "assets/generated/intro/shrink1.png" } +Data.field.oakSpeech = preStamped +Data:applyVersionedFieldData() +eq(Data.field.oakSpeech.demoSpecies, "RAICHU", + "applyVersionedFieldData leaves an already-stamped demoSpecies alone") + +Data.field.oakSpeech = oldOakSpeech +Data.field.trades = oldTrades +Data.field.oldManBattle = oldOldManBattle +GameVersion.set(oldVersion) + +return S:finish() diff --git a/tests/parity_yellow_old_man.lua b/tests/parity_yellow_old_man.lua deleted file mode 100644 index 60ce0319..00000000 --- a/tests/parity_yellow_old_man.lua +++ /dev/null @@ -1,290 +0,0 @@ --- Parity (#617): Yellow's Viridian old man is the OLD_MAN2 at (18,9), --- not the Red/Blue OLD_MAN at (17,5), and his dialog has no yes/no --- choice -- the apology speech runs the RATTATA demo battle straight --- away, the post-battle line is the losing-my-touch text, and he walks --- off and hides. --- --- Oracle: pokeyellow scripts/OaksLab.asm (OaksLabOakGivesPokedexScript: --- HideObject TOGGLE_LYING_OLD_MAN / ShowObject TOGGLE_OLD_MAN_2), --- scripts/ViridianCity.asm (ViridianCityCheckWaitingOldMan, --- ViridianCityOldMan2Text, ...InitialCatchTrainingScript, --- ...PostInitialCatchTraining) and scripts/ViridianCity_2.asm --- (ViridianCityPrintOldManText). The Red/Blue "Are you in a hurry?" --- script was running against Yellow's text: YES printed the TimeIsMoney --- alias (_ViridianCityOldManLosingMyTouchText) and NO ran the demo -- --- every talk, forever. --- --- Self-contained: `luajit tests/parity_yellow_old_man.lua`; also globbed --- by tests/run_tests.lua. -package.path = "./?.lua;./?/init.lua;" .. package.path -if not _G.love then _G.love = require("tests.love_stub") end - -local Data = require("src.core.Data") -if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end -local GameVersion = require("src.core.GameVersion") -local SaveData = require("src.core.SaveData") -local ScriptRunner = require("src.script.ScriptRunner") -local TextBox = require("src.render.TextBox") -local BattleState = require("src.battle.BattleState") -local Pokemon = require("src.pokemon.Pokemon") - -local S = require("tests.harness").suite("parity Yellow old man (#617)") -local check, eq = S.check, S.eq - -local oldVersion = GameVersion.get() - -local MAP = "VIRIDIAN_CITY" -local SLEEPER = "VIRIDIANCITY_OLD_MAN_SLEEPY" -local WALKER = "VIRIDIANCITY_OLD_MAN" -local OLD_MAN2 = "VIRIDIANCITY_OLD_MAN2" -local DONE_FLAG = "EVENT_COMPLETED_CATCH_TRAINING" - --- The Yellow wiring must be attached before anything else caches the --- map-script registry: data.scripts.init branches on GameVersion at --- load, so flip it first (this file owns its own process when run --- standalone). Under tests/run_tests.lua the registry is already --- cached with the Red wiring, so attach the Yellow modules directly --- afterwards -- attachBase merges per TEXT constant and replaces hooks, --- which is a no-op on a fresh process and the fix on a shared one. -GameVersion.set("yellow") -local mapScripts = require("data.scripts.init") -local MapScripts = require("src.script.MapScripts") -MapScripts.attachBase(MAP, - require("data.scripts.yellow_viridian_old_man").VIRIDIAN_CITY) -MapScripts.attachBase("OAKS_LAB", - require("data.scripts.oaks_lab_yellow")) -local oldManMod = require("data.scripts.yellow_viridian_old_man") - --- ------------------------------------------------------- the demo species --- The catch demo is a RATTATA in Yellow (SetupBattle sets wCurOpponent --- = RATTATA) but the Yellow manifest inherited Red's WEEDLE; the runtime --- override in Data:applyVersionedFieldData repairs old caches. Kept --- active until the end of this file so the demo-battle assertions below --- run against the Yellow value; restored before S.finish() like --- parity_yellow_trades does for its trades table. -local originalOldManBattle = Data.field.oldManBattle - or { species = "WEEDLE", level = 5 } -- the fixture carries no oldManBattle -local originalTrades = Data.field.trades -eq(originalOldManBattle.species, "WEEDLE", - "Red/Blue's old man still demos a Weedle") -GameVersion.set("yellow") -Data:applyVersionedFieldData() -eq(Data.field.oldManBattle.species, "RATTATA", - "Yellow's old man demos a Rattata (#617)") - -local manifestFile = assert(io.open("tools/rom_manifest_yellow.json", "r")) -local yellowManifest = manifestFile:read("*a") -manifestFile:close() -check(yellowManifest:find('"species": "RATTATA"', 1, true) ~= nil, - "the Yellow manifest stamps RATTATA for fresh imports") -local redManifestFile = assert(io.open("tools/rom_manifest.json", "r")) -local redManifest = redManifestFile:read("*a") -redManifestFile:close() -check(redManifest:find('"species": "WEEDLE"', 1, true) ~= nil, - "and the Red/Blue manifest keeps WEEDLE") - --- ------------------------------------------------------- the Pokedex swap --- OaksLabOakGivesPokedexScript shows TOGGLE_OLD_MAN_2 (the tutorial old --- man standing on the sleeper's cell), never the Red/Blue walker -local oaksRows = mapScripts.talkScript("OAKS_LAB", "TEXT_OAKSLAB_OAK1") -check(type(oaksRows) == "table", - "the Yellow OaksLab Oak talk resolves to rows") -local sawSleepHide, sawOldMan2Show, sawOldManShow = false, false, false -for _, row in ipairs(oaksRows or {}) do - if row[1] == "hide_object" and row[3] == SLEEPER then sawSleepHide = true end - if row[1] == "show_object" and row[3] == OLD_MAN2 then sawOldMan2Show = true end - if row[1] == "show_object" and row[3] == WALKER then sawOldManShow = true end -end -check(sawSleepHide, "the Pokédex hand-over hides the lying old man") -check(sawOldMan2Show, "it shows OLD_MAN2 on the sleeper's cell") -check(not sawOldManShow, "it never shows the Red/Blue OLD_MAN (#617)") - --- both Yellow gamblers default hidden (toggle OFF), like pokeyellow --- data/maps/toggleable_objects.asm. OLD_MAN2 only exists in a Yellow --- import -- a Red-imported checkout carries just OLD_MAN -- so the --- dataset checks tolerate its absence and the Yellow manifest carries --- the OLD_MAN2 default instead. -local walkerDef, oldMan2Def -if Data.maps[MAP] then - for _, o in ipairs(Data.maps[MAP].objects or {}) do - if o.name == WALKER then walkerDef = o end - if o.name == OLD_MAN2 then oldMan2Def = o end - end -end -check(walkerDef == nil or walkerDef.hidden == true, - "VIRIDIANCITY_OLD_MAN defaults hidden in Yellow") -check(oldMan2Def == nil or oldMan2Def.hidden == true, - "VIRIDIANCITY_OLD_MAN2 defaults hidden in Yellow") -local om2Name = yellowManifest:find('"name": "VIRIDIANCITY_OLD_MAN2"', 1, true) -local om2Hidden = om2Name and yellowManifest:sub( - math.max(1, om2Name - 40), om2Name):find('"hidden": true', 1, true) -check(om2Hidden ~= nil, - "the Yellow manifest ships OLD_MAN2 with the toggle OFF") - --- ------------------------------------------------------- script registry -local talk = mapScripts.talkScript(MAP, "TEXT_VIRIDIANCITY_OLD_MAN2") -check(type(talk) == "function", - "TEXT_VIRIDIANCITY_OLD_MAN2 resolves to the Yellow handler") -check(type(mapScripts.talkScript(MAP, "TEXT_VIRIDIANCITY_OLD_MAN")) == "table", - "the Red/Blue OLD_MAN talk is still registered (unreachable in Yellow)") -local hooks = mapScripts.get(MAP) -check(hooks and type(hooks.onEnter) == "function", - "VIRIDIAN_CITY.onEnter is the Yellow swap") -check(hooks and type(hooks.onStep) == "function", - "VIRIDIAN_CITY.onStep chains the gym lock and sleeper gate") -check(oldManMod.VIRIDIAN_CITY and oldManMod.VIRIDIAN_CITY.talk - and oldManMod.VIRIDIAN_CITY.talk.TEXT_VIRIDIANCITY_OLD_MAN2 == talk, - "the handler is the module's own, not a leftover merge") - --- ------------------------------------------------------- completed branch -do - local pushed = {} - local game = { - data = Data, - save = SaveData.newGame(), - stack = { push = function(_, s) pushed[#pushed + 1] = s end }, - } - game.save.flags.EVENT_COMPLETED_CATCH_TRAINING = true - local done = false - talk(game, nil, {}, function() done = true end) - eq(#pushed, 1, "a second talk only prints one box") - eq(getmetatable(pushed[1]), TextBox, "the losing-my-touch line, in a box") - pushed[1].onDone() - check(done, "closing it hands input back") -end - --- ------------------------------- the initial tutorial, end to end --- Needs real species in the dataset (the fixture carries only FIX_*); --- the engine's old-man demo machinery itself is parity_J's territory. -if Data.pokemon.RATTATA and Data.pokemon.PIKACHU then -do - require("src.render.Font").load(Data) - local pushed = {} - local save = SaveData.newGame() - save.party = { Pokemon.new(Data, "PIKACHU", 12) } - local moves = {} - local man = { def = { index = 8, name = OLD_MAN2 } } - local ow = { - map = { id = MAP, def = { label = "ViridianCity" } }, - npcs = { man }, entities = { man }, - player = { cellX = 19, cellY = 9, facing = "left" }, - scriptMove = function(_, _, dir, _, cb) moves[#moves + 1] = dir; cb() end, - npcByIndex = function(_, i) if i == 8 then return man end end, - } - local game = { - data = Data, - save = save, - stack = { push = function(_, s) pushed[#pushed + 1] = s end }, - } - local runner = ScriptRunner.new(game, ow) - ow.runner = runner - local done = false - talk(game, ow, man, function() done = true end) - - eq(#pushed, 1, "the initial talk opens the apology speech") - eq(getmetatable(pushed[1]), TextBox, "in a text box") - pushed[1].onDone() -- A: the apology closes, the demo battle starts - - eq(#pushed, 2, "the demo battle starts with no choice in between") - local battle = pushed[2] - check(battle and battle.demo, "it is the old-man demo battle") - eq(battle and battle.enemy and battle.enemy.mon.species, "RATTATA", - "the demo is a RATTATA in Yellow (#617)") - check(battle and battle.demoFails, - "the initial training throw breaks out, never catches (#636)") - eq(save.flags[DONE_FLAG], nil, "the flag is still clear mid-demo") - battle.onFinish() -- the battle ends, the post-battle text prints - - eq(save.flags[DONE_FLAG], true, "EVENT_COMPLETED_CATCH_TRAINING is set") - eq(#pushed, 3, "the losing-my-touch line follows the demo") - pushed[3].onDone() -- A: the old man walks off - - eq(#moves, 6, "with the player on (19,9) he walks down 6 tiles") - check(moves[1] == "down" and moves[6] == "down", - "all six steps are the ViridianCityOldManMovementData2 walk") - eq(save.objectToggles[MAP] and save.objectToggles[MAP][OLD_MAN2], false, - "TOGGLE_OLD_MAN_2 hides once the walk finishes") - check(done, "and the talk hands input back") -end - --- ---------------------------------- side talk: player not on (19,9) cell -do - local pushed = {} - local save = SaveData.newGame() - save.party = { Pokemon.new(Data, "PIKACHU", 12) } - local moves = {} - local man = { def = { index = 8, name = OLD_MAN2 } } - local pika = { def = { index = 99, name = "PIKACHU_FOLLOWER" }, - pikachuFollower = true } - local ow = { - map = { id = MAP, def = { label = "ViridianCity" } }, - npcs = { man, pika }, entities = { man, pika }, - player = { cellX = 18, cellY = 8, facing = "down" }, - scriptMove = function(_, _, dir, _, cb) moves[#moves + 1] = dir; cb() end, - npcByIndex = function(_, i) if i == 8 then return man elseif i == 99 then return pika end end, - } - local game = { - data = Data, - save = save, - stack = { push = function(_, s) pushed[#pushed + 1] = s end }, - } - local runner = ScriptRunner.new(game, ow) - ow.runner = runner - talk(game, ow, man, function() end) - pushed[1].onDone() - pushed[2].onFinish() - pushed[3].onDone() - eq(moves[1], "right", "Pikachu steps aside first (ViridianCityMovePikachu)") - eq(moves[2], "right", "then the old man turns right one tile") - eq(#moves, 2, "and no more") -end -else - check(true, "fixture dataset: demo-battle flow skipped (no RATTATA)") -end - --- --------------------------------------------------------- the (19,9) step -do - local pushed = {} - local save = SaveData.newGame() - local man = { def = { index = 8, name = OLD_MAN2 } } - local ow = { - map = { id = MAP, def = { label = "ViridianCity" } }, - npcs = { man }, entities = { man }, - player = { cellX = 19, cellY = 9, facing = "down" }, - scriptMove = function(_, _, _, _, cb) cb() end, - npcByIndex = function() end, - } - local game = { - data = Data, - save = save, - stack = { push = function(_, s) pushed[#pushed + 1] = s end }, - } - local runner = ScriptRunner.new(game, ow) - ow.runner = runner - - check(not hooks.onStep(game, ow, 5, 5), - "off the trigger cell the step passes through") - check(hooks.onStep(game, ow, 19, 9), - "pre-Pokedex the sleeper gate owns (19,9)") - eq(#pushed, 1, "with the sleepy text box") - check(save.flags[DONE_FLAG] ~= true, "the tutorial is not running") - - save.flags.EVENT_GOT_POKEDEX = true - check(hooks.onStep(game, ow, 19, 9), - "with the Pokedex, (19,9) starts the tutorial") - eq(man.facing, "right", "the old man faces the player") - eq(ow.player.facing, "left", "and the player turns to face him") - eq(#pushed, 2, "the apology box is up") - check(save.flags[DONE_FLAG] ~= true, - "no flag until the demo battle actually runs") - - save.flags.EVENT_COMPLETED_CATCH_TRAINING = true - check(not hooks.onStep(game, ow, 19, 9), - "once the tutorial is done the cell is quiet again") -end - -Data.field.trades = originalTrades -Data.field.oldManBattle = originalOldManBattle -GameVersion.set(oldVersion) - -S.finish() diff --git a/tests/rom_importer_cursor_bug781_test.lua b/tests/rom_importer_cursor_bug781_test.lua new file mode 100644 index 00000000..8768b627 --- /dev/null +++ b/tests/rom_importer_cursor_bug781_test.lua @@ -0,0 +1,71 @@ +-- #781: Linux launcher mouse-dead behind the pad cursor. Reproduces the +-- X11 multi-monitor failure mode (polled love.mouse.getPosition frozen on +-- desktop-virtual coords, so the motion yield in _updatePadCursor never +-- fires) and asserts a host-forwarded mousepressed reclaims the pointer. +-- Self-contained: `luajit tests/rom_importer_cursor_bug781_test.lua`. +-- Should eventually merge into tests/rom_importer_cursor_test.lua (dofile'd +-- by tests/run_tests.lua). +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local S = require("tests.harness").suite("rom importer pad cursor #781") +local eq = S.eq + +local RomImporter = require("src.import.RomImporter") + +-- Bare importer with just the pad-cursor state new() would build; isNX +-- false keeps _updatePadCursor on the desktop path (polled motion yield), +-- _flex nil keeps the right-stick branch out of LauncherView. +local function makeImporter() + return setmetatable({ + android = false, + isNX = false, + _flex = nil, + _padCursor = { x = 320, y = 260 }, + _padCursorActive = false, + _padAxis = { leftx = 0, lefty = 0, righty = 0 }, + _padDir = {}, + _padInited = true, + }, RomImporter) +end + +-- Failure mode: SDL's polled mouse state stuck on coordinates outside the +-- window (primary display away from desktop 0,0). Successive samples are +-- identical, so the motion yield sees zero delta and never releases the +-- pad cursor no matter how much the real mouse moves. +local ri = makeImporter() +love.mouse.getPosition = function() return 2960, 4130 end +ri._padCursorActive = true +ri:_updatePadCursor(1 / 60) -- seeds _lastMouseX/_lastMouseY +ri:_updatePadCursor(1 / 60) +ri:_updatePadCursor(1 / 60) +eq(ri._padCursorActive, true, + "frozen polled coords starve the motion yield (the #781 trap)") + +-- The fix: the host-forwarded real press must win the pointer back, same +-- contract as PadCursor.yieldToPointer in the overlay hosts. This is the +-- half that un-gates LauncherView.update's click minting. +ri:mousepressed(10, 10, 1) +eq(ri._padCursorActive, false, + "mousepressed reclaims the pointer even when the yield is starved (#781)") + +-- A reclaimed pointer must stay reclaimed: the next pad-cursor tick with +-- still-frozen polled coords may not re-arm it by itself. +ri:_updatePadCursor(1 / 60) +eq(ri._padCursorActive, false, + "an idle pad tick does not re-steal the pointer after reclaim") + +-- Regression guard for the healthy desktop path: when polled coords do +-- move (window-relative, single monitor), the existing motion yield still +-- releases the pad cursor without needing a click. +local ri2 = makeImporter() +local px = 100 +love.mouse.getPosition = function() return px, 100 end +ri2._padCursorActive = true +ri2:_updatePadCursor(1 / 60) +px = 140 +ri2:_updatePadCursor(1 / 60) +eq(ri2._padCursorActive, false, + "real mouse motion still yields the pad cursor on sane polled coords") + +S.finish() diff --git a/tests/rom_importer_cursor_test.lua b/tests/rom_importer_cursor_test.lua index 2f8a41f3..47eee9ea 100644 --- a/tests/rom_importer_cursor_test.lua +++ b/tests/rom_importer_cursor_test.lua @@ -45,4 +45,12 @@ ri:play("red") eq(booted, "red", "unsupported system cursors still allow boot") eq(currentCursor, "hand", "unsupported system cursors leave the existing cursor alone") +-- #781: a host-forwarded real mouse press must win the pointer back from +-- the pad cursor. While it is active LauncherView.update refuses to mint +-- mouse clicks, so a stuck motion yield (X11 multi-monitor polled coords) +-- left the Linux launcher mouse-dead until this reclaim existed. +ri._padCursorActive = true +ri:mousepressed(10, 10, 1) +eq(ri._padCursorActive, false, "mouse press yields the pad cursor (#781)") + S.finish() diff --git a/tests/rom_importer_double_pick_test.lua b/tests/rom_importer_double_pick_test.lua index e7eba41e..c5f16518 100644 --- a/tests/rom_importer_double_pick_test.lua +++ b/tests/rom_importer_double_pick_test.lua @@ -23,11 +23,14 @@ local S = require("tests.harness").suite("rom importer double pick (#553)") local check = S.check local RomImporter = require("src.import.RomImporter") +local Platform = require("src.core.Platform") love.system = love.system or {} local saved = { getOS = love.system.getOS, pickFile = love.system.pickFile, + getPickedFile = love.system.getPickedFile, + getPickError = love.system.getPickError, getDirectoryItems = love.filesystem.getDirectoryItems, getInfo = love.filesystem.getInfo, read = love.filesystem.read, @@ -51,6 +54,7 @@ love.filesystem.remove = function(name) saveDir[name] = nil; return true end local function importer(os) love.system.getOS = function() return os end + Platform._resetForTests() local ri = RomImporter.new(function() end, { launcher = true }) ri.ready = { red = false, blue = false, yellow = false } return ri @@ -66,6 +70,7 @@ local ios = importer("iOS") check(ios.pickPending, "iOS still boots armed (unchanged by this fix)") love.system.getOS = function() return "OS X" end +Platform._resetForTests() local desktop = RomImporter.new(function() end, { launcher = true }) check(not desktop.pickPending, "desktop does not poll: it has no save-dir picks") @@ -161,9 +166,12 @@ check(not touch._flex, love.system.getOS = saved.getOS love.system.pickFile = saved.pickFile +love.system.getPickedFile = saved.getPickedFile +love.system.getPickError = saved.getPickError love.filesystem.getDirectoryItems = saved.getDirectoryItems love.filesystem.getInfo = saved.getInfo love.filesystem.read = saved.read love.filesystem.remove = saved.remove +Platform._resetForTests() S.finish() diff --git a/tests/rom_importer_last_version_test.lua b/tests/rom_importer_last_version_test.lua new file mode 100644 index 00000000..07e3543d --- /dev/null +++ b/tests/rom_importer_last_version_test.lua @@ -0,0 +1,89 @@ +-- #835: the launcher must open on the game that was played last, instead of +-- always opening on Red. Two halves, both asserted here: RomImporter:play +-- writes the chosen version to options.lua, and RomImporter:_applyLastVersionTab +-- reads it back when the constructor has finished filling self.ready. +-- Self-contained: `luajit tests/rom_importer_last_version_test.lua`; also +-- dofile'd by tests/run_tests.lua. +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local S = require("tests.harness").suite("rom importer last version") +local eq = S.eq + +love.mouse.isCursorSupported = function() return false end + +local SaveData = require("src.core.SaveData") +local LaunchOptions = require("src.core.LaunchOptions") +local RomImporter = require("src.import.RomImporter") + +-- The options round trip must not touch the developer's real save directory. +-- SaveData.persistFs consults SaveData.portableFs() before love.filesystem +-- (src/core/SaveData.lua:203-208), so overriding that one hook reroutes both +-- loadOptions and saveOptions onto this in-memory volume. saveOptions reads +-- the file back after writing (#828), so read/write have to be truthful. +-- The override is process-global and tests/run_tests.lua dofiles every suite +-- into one process, so it MUST be put back before this file returns: leaving +-- it in place reroutes every later suite's save I/O into `disk` (parity_hof +-- reads its own save back and fails 6 assertions if this leaks). +local realPortableFs = SaveData.portableFs +local disk = {} +SaveData.portableFs = function() + return { + getInfo = function(name) return disk[name] and { type = "file" } or nil end, + read = function(name) return disk[name] or nil, "no file: " .. name end, + write = function(name, data) disk[name] = data return true end, + remove = function(name) disk[name] = nil end, + } +end + +local function newImporter(fields) + local ri = setmetatable(fields, RomImporter) + return ri +end + +-- ---- write side: play() records the version it hands off to boot + +local booted = nil +local ri = newImporter({ + android = true, -- skips the cursor restore; see #114 suite + workState = nil, + tab = "red", + ready = { yellow = true }, + onComplete = function(version) booted = version end, +}) +ri:play("yellow") +eq(booted, "yellow", "play boots the chosen version") +eq(SaveData.loadOptions().lastVersion, "yellow", "play remembers the version played") + +-- ---- read side: a fresh launcher opens on that column + +local ri2 = newImporter({ tab = "red", ready = { red = true, yellow = true } }) +ri2:_applyLastVersionTab() +eq(ri2.tab, "yellow", "launcher opens on the last played version") + +-- A remembered version whose cache is gone or stale must not open a column +-- with no Play button in it. +local ri3 = newImporter({ tab = "red", ready = { red = true, yellow = false } }) +ri3:_applyLastVersionTab() +eq(ri3.tab, "red", "an unready remembered version leaves the tab alone") + +-- An explicit --game shortcut (main.lua sets LaunchOptions.pendingTab) wins +-- over the remembered version. +LaunchOptions.pendingTab = "blue" +local ri4 = newImporter({ tab = "blue", ready = { red = true, yellow = true } }) +ri4:_applyLastVersionTab() +eq(ri4.tab, "blue", "an explicit --game tab beats the remembered version") +LaunchOptions.pendingTab = nil -- module is a singleton: do not leak this + +-- A junk value in options.lua (hand-edited file, a build that knew other +-- versions) must not select a tab that does not exist. +local opts = SaveData.loadOptions() +opts.lastVersion = "gold" +SaveData.saveOptions(opts) +local ri5 = newImporter({ tab = "red", ready = { red = true, yellow = true } }) +ri5:_applyLastVersionTab() +eq(ri5.tab, "red", "an unknown remembered version leaves the tab alone") + +SaveData.portableFs = realPortableFs + +S.finish() diff --git a/tests/run_save_editor_tests.lua b/tests/run_save_editor_tests.lua index 000d2a31..986e3dfc 100644 --- a/tests/run_save_editor_tests.lua +++ b/tests/run_save_editor_tests.lua @@ -291,6 +291,106 @@ do eq(mon.level, 1, "stepSpecies keeps the level") end +do + -- Nicknames: the editor edits mon.nickname, which is nil when un-nicknamed + -- (every display site reads `mon.nickname or def.name`, GenSave.lua). The + -- game's naming screen caps at 10 glyphs and treats an empty confirm as "no + -- nickname", so the verbs below mirror that: "" clears, a name matching the + -- species' standard name normalizes back to nil, too-long or unrenderable + -- names refuse with a status line, and nothing silently no-ops. + local S = State.new() + S.data = Data + S.cat = Catalog.build(Data) + S.save = SaveData.newGame() + local mon = MonOps.create(Data, "CHARIZARD", 50) + S.save.party = { mon } + S.editingMon = mon + + eq(Ops.nicknameLength("POKEMON"), 7, "nicknameLength counts ASCII glyphs") + eq(Ops.nicknameLength("ééé"), 3, "nicknameLength counts a multi-byte char as one glyph") + eq(Ops.nicknameLength("♂♀!"), 3, "nicknameLength counts symbol glyphs") + check(Ops.nicknameUsable(S, "CHARIZARD"), "ASCII letters are renderable") + check(Ops.nicknameUsable(S, "Nidoking"), "lower case is renderable") + check(Ops.nicknameUsable(S, "é") == true, "a charmap glyph is renderable") + check(Ops.nicknameUsable(S, "PIKA€") == false, "a non-charmap glyph is not renderable") + check(Ops.nicknameUsable(S, "🤖") == false, "an emoji is not renderable") + -- "@" is the Gen1 string terminator: the codec has an entry for it but the + -- game font has no tile, so Font.encode draws it as a space in-game + check(Ops.nicknameUsable(S, "POKE@MON") == false, + "the terminator @ is not a renderable nickname glyph") + check(Ops.nicknameUsable(S, "POKE#MON") == false, + "the # marker is not a renderable nickname glyph") + + -- the field gate: sanitize skips unrenderable glyphs and clamps at 10, so + -- what reaches the mon can only ever be a legal Gen 1 nickname + eq(Ops.nicknameSanitize(S, "PIKA\226\130\172"), "PIKA", + "sanitize drops an unrenderable glyph") + eq(Ops.nicknameSanitize(S, "PIKA\226\130\172CHU"), "PIKACHU", + "sanitize skips a bad glyph mid-name instead of aborting the rest") + eq(Ops.nicknameSanitize(S, "POKE@MON"), "POKEMON", + "sanitize strips the invisible @ terminator") + eq(Ops.nicknameSanitize(S, "1234567890123"), "1234567890", + "sanitize clamps the draft at 10 glyphs") + eq(Ops.nicknameSanitize(S, "\195\169"), "\195\169", + "sanitize keeps a charmap glyph") + eq(Ops.nicknameSanitize(S, ""), "", "sanitize of empty is empty") + + Ops.setNickname(S, mon, "SPARKY") + eq(mon.nickname, "SPARKY", "setNickname stores the name") + check(S.dirty == true, "setNickname marks the save dirty") + eq(S.status:match("SPARKY") ~= nil, true, "setNickname narrates the new name") + S.dirty = false + + check(Ops.setNickname(S, mon, "SPARKY") == false, + "setting the same nickname again is a no-op") + check(S.dirty == false, "the no-op did not dirty the save") + check(S.status:match("Already nicknamed") ~= nil, "the no-op explains itself") + + -- a name matching the species' standard name is the un-nicknamed state + Ops.setNickname(S, mon, "CHARIZARD") + eq(mon.nickname, nil, "a name equal to the standard name normalizes to nil") + eq(S.status:match("standard name") ~= nil, true, "the normalization explains itself") + + check(Ops.clearNickname(S, mon) == false, + "clearing an already-un-nicknamed mon is a no-op") + check(S.status:match("no nickname") ~= nil, "the no-op explains itself") + + -- empty input means clear, like an empty naming-screen confirm + Ops.setNickname(S, mon, "SPARKY") + eq(mon.nickname, "SPARKY", "re-nicknamed for the empty-clear check") + check(Ops.setNickname(S, mon, "") == true, "an empty name is a valid clear") + eq(mon.nickname, nil, "an empty name clears the nickname") + check(S.status:match("Cleared") ~= nil, "the clear narrates") + + Ops.setNickname(S, mon, "1234567890") + eq(mon.nickname, "1234567890", "a 10-glyph name is accepted") + S.dirty = false + check(Ops.setNickname(S, mon, "12345678901") == false, + "an 11-glyph name is refused") + eq(mon.nickname, "1234567890", "a refused name leaves the mon alone") + check(S.dirty == false, "a refused name does not dirty the save") + check(S.status:match("capped at 10") ~= nil, "the length refusal explains itself") + + check(Ops.setNickname(S, mon, "PIKA€") == false, + "a name with an unrenderable glyph is refused") + eq(mon.nickname, "1234567890", "a refused glyph leaves the mon alone") + check(S.status:match("cannot render") ~= nil, "the glyph refusal explains itself") + check(Ops.setNickname(S, mon, "POKE@MON") == false, + "a name with the invisible @ terminator is refused") + eq(mon.nickname, "1234567890", "a refused @ name leaves the mon alone") + check(S.status:match("cannot render") ~= nil, "the @ refusal explains itself") + + check(Ops.setNickname(S, nil, "X") == false, "setNickname without a mon refuses") + check(S.status:match("Pick a slot") ~= nil, "and explains itself") + check(Ops.clearNickname(S, nil) == false, "clearNickname without a mon refuses") + + -- the canonical round trip: what the game reads back is the same either way + mon.nickname = "SPARKY" + local encoded = SaveData.encode(S.save) + local back = SaveData.decode(encoded) + eq(back.party[1].nickname, "SPARKY", "a nickname survives a save round trip") +end + -- App.load corrupt-save vs missing-save (Important fix #2): App.load takes -- an optional path override precisely so tests can drive this without -- touching the real default save file. @@ -799,6 +899,83 @@ do for _, bak in ipairs(FsIo.globPrefix(tmpPath .. ".bak-")) do os.remove(bak) end end +do + -- The inspector's nickname field is commit-on-Enter: the draft lives in + -- S.nicknameDraft while typing, Enter commits it through Ops.setNickname, + -- and Escape discards it. Drive it through App the way a player would: + -- focus the field (a click is just a Kit.focus assignment here), type, + -- drain the edits with a draw, then press Enter / Escape. + local Kit = require("Kit") + local tmpPath = os.tmpname() .. "-nickname-save.lua" + local data = SaveData.newGame() + data.party = { MonOps.create(Data, "CHARIZARD", 50) } + local f = io.open(tmpPath, "wb") + f:write(SaveData.encode(data)) + f:close() + + App.load(tmpPath, { version = "red" }) + local S = App.getState() + S.tab = "party" + Ops.selectParty(S, 1) + local mon = S.editingMon + eq(mon.nickname, nil, "the save starts un-nicknamed") + + -- type "SPARKY" and commit with Enter + Kit.focus = "mon-nickname" + App.textinput("SPARKY") + App.draw() + eq(S.nicknameDraft, "SPARKY", "typed text lands in the draft") + App.keypressed("return") + eq(mon.nickname, "SPARKY", "Enter commits the draft to the mon") + check(S.dirty == true, "the commit marks the save dirty") + check(Kit.focus == nil, "Enter blurs the field") + S.dirty = false + + -- The field has no select-all, so a rename is backspace-then-type (the + -- caret parks at the end, exactly like the editor's other fields). + local function clearField(n) + Kit.focus = "mon-nickname" + for _ = 1, n do App.keypressed("backspace") end + App.draw() + end + + -- type junk, then Escape: nothing is committed and the draft is discarded + clearField(#mon.nickname) + App.textinput("ZEPTO") + App.draw() + eq(S.nicknameDraft, "ZEPTO", "the draft holds the new typing") + App.keypressed("escape") + eq(mon.nickname, "SPARKY", "Escape does not commit") + eq(S.nicknameDraft, "SPARKY", "Escape resets the draft to the committed name") + check(Kit.focus == nil, "Escape blurs the field") + + -- an unrenderable glyph is blocked AT INPUT: the euro sign never reaches + -- the draft, so the field can only ever hold what the game can render + clearField(#mon.nickname) + App.textinput("PIKA\226\130\172") -- PIKA + euro sign, not a charmap glyph + App.draw() + eq(S.nicknameDraft, "PIKA", "an unrenderable glyph is dropped at input") + App.keypressed("return") + eq(mon.nickname, "PIKA", "the clean draft commits on Enter") + + -- and the 10-glyph cap blocks extra input the same way + clearField(#mon.nickname) + App.textinput("123456789012345") + App.draw() + eq(S.nicknameDraft, "1234567890", "typing past 10 glyphs clamps at 10") + + -- the @ terminator never reaches the draft either: it draws as a space + -- in-game, so the field strips it like any other unrenderable glyph. The + -- clamp test above left an uncommitted draft, so clear the whole draft. + clearField(#S.nicknameDraft) + App.textinput("POKE@MON") + App.draw() + eq(S.nicknameDraft, "POKEMON", "the @ terminator is stripped at input") + + os.remove(tmpPath) + for _, bak in ipairs(FsIo.globPrefix(tmpPath .. ".bak-")) do os.remove(bak) end +end + do -- #541 modal shield. Kit hit-tests without a z-order, so the picker cannot -- simply be drawn last: the chrome and the panel underneath would take the diff --git a/tests/run_tests.lua b/tests/run_tests.lua index 6119e18c..389cea06 100644 --- a/tests/run_tests.lua +++ b/tests/run_tests.lua @@ -114,6 +114,12 @@ do "cont boundary waits for A with contAdvance") check(not box.done, "cont wait is not the final done prompt") eq(box.lineIndex, 2, "cont wait stays on the finished line until A") + -- ProtectedDelay3 (home/text.asm:265): the ▼ swallows the button for + -- three frames before ManualTextScroll starts listening + for _ = 1, 80 do + if (box.preWait or 0) == 0 then break end + box:update(0) + end pressed.a = true box:update(0) check(not box.waiting and not box.contAdvance, "A clears cont wait") @@ -277,6 +283,16 @@ do eq(ItemEffects.use(Data, save, "HM_SURF", pikachu, {}), "failed", "HM refuses mid-battle") end +do + local rRepel, repelMsg = ItemEffects.use(Data, save, "MAX_REPEL", nil, {}) + eq(rRepel, "failed", "Max Repel refuses mid-battle (#894)") + check(repelMsg and repelMsg[1] and repelMsg[1]:find("isn't the", 1, true), + "Max Repel mid-battle Oak text") + eq(ItemEffects.use(Data, save, "REPEL", nil, {}), "failed", + "Repel refuses mid-battle") + eq(ItemEffects.use(Data, save, "SUPER_REPEL", nil, {}), "failed", + "Super Repel refuses mid-battle") +end local r5, _, extra = ItemEffects.use(Data, save, "THUNDER_STONE", pikachu) eq(r5, "consumed", "Thunder Stone works on Pikachu") eq(extra.evolveTo, "RAICHU", "Thunder Stone evolves Pikachu to Raichu") @@ -430,16 +446,17 @@ check(misted2.stages.attack == nil and mistMsgs[1]:find("MIST", 1, true) ~= nil, "primary stat drop still blocked by MIST") --- Substitute boundary: built at exactly 1/4 max HP, leaving 0 HP --- (substitute.asm only fails on subtraction underflow) +-- Substitute boundary: the move must fail when its quarter-HP cost would +-- consume all current HP, preventing a zero-HP user with a live substitute. local subUser = { mon = { stats = { hp = 40 }, hp = 10 }, name = "SUBBY" } -MoveEffects.primary.SUBSTITUTE_EFFECT(sideRng, subUser) -check(subUser.substituteHP ~= nil and subUser.mon.hp == 0, - "substitute built at exactly 1/4 max HP leaves 0 HP") -local subUser2 = { mon = { stats = { hp = 40 }, hp = 9 }, name = "SUBBY" } -local subMsgs = MoveEffects.primary.SUBSTITUTE_EFFECT(sideRng, subUser2) -check(subUser2.substituteHP == nil +local subMsgs = MoveEffects.primary.SUBSTITUTE_EFFECT(sideRng, subUser) +check(subUser.substituteHP == nil and subUser.mon.hp == 10 and subMsgs[1]:find("weak", 1, true) ~= nil, + "substitute fails at exactly 1/4 max HP") +local subUser2 = { mon = { stats = { hp = 40 }, hp = 9 }, name = "SUBBY" } +local subMsgs2 = MoveEffects.primary.SUBSTITUTE_EFFECT(sideRng, subUser2) +check(subUser2.substituteHP == nil and subUser2.mon.hp == 9 + and subMsgs2[1]:find("weak", 1, true) ~= nil, "substitute fails below 1/4 max HP") -- Haze clears Disable/X ACCURACY on both sides and forfeits the turn of @@ -1044,7 +1061,7 @@ do kb.player.mon.status = "PSN" kb.enemy.mon.hp = 0 -- the opponent was already knocked out this turn local hpBefore = kb.player.mon.hp - kb:endOfTurn() + kb:residualFor(kb.player, kb.enemy) eq(kb.player.mon.hp, hpBefore, "no residual poison on the turn the poisoned mon lands the KO") @@ -1052,7 +1069,7 @@ do local lb = BattleState.newWild(Game, "RATTATA", 5) lb.player.mon.status = "PSN" local live = lb.player.mon.hp - lb:endOfTurn() + lb:residualFor(lb.player, lb.enemy) check(lb.player.mon.hp < live, "poison still ticks while the opponent lives") end @@ -1126,14 +1143,17 @@ do eq(pb:sendOutText("PIKA"), "The enemy's weak!\nGet'm! PIKA!", "send-out below 10%") - -- HP-bar drain converges at UpdateHPBar's pixel pace (maxHP/96/frame) + -- HP-bar drain converges at UpdateHPBar's per-side pace (2 frames per + -- bar pixel, enemy HP steps free; hp_bar.asm:81-148 via Timing) local db = BattleState.newWild(Game, "RATTATA", 5) local maxHP = db.enemy.mon.stats.hp + local startHP = db.enemy.mon.hp db.enemy.mon.hp = math.max(0, db.enemy.mon.hp - 5) local frames = 0 while db:stepHPDrain() and frames < 2000 do frames = frames + 1 end eq(db.enemy.shownHP, db.enemy.mon.hp, "drain settles on the true HP") - local expect = math.ceil(5 / (maxHP / 96)) + local expect = require("src.core.Timing").hpDrainFrames( + startHP, db.enemy.mon.hp, maxHP, false) check(math.abs(frames - expect) <= 1, ("drain speed ~2 frames per bar pixel (%d ~ %d)"):format(frames, expect)) @@ -2631,7 +2651,13 @@ do local popped = false local og = { data = Data, save = SD.newGame(), input = OInput, stack = { pop = function() popped = true end }, - writeOptions = function(self) SD.saveOptions(self.save.options) end } + writeOptions = function(self) SD.saveOptions(self.save.options) end, + -- the PERFORMANCE row routes through Game:applyOptions; the + -- stub carries the headless slice of it (the tier record), + -- the display modules are re-applied at the end of the suite + applyOptions = function(self, o) + require("src.core.Performance").applyOptions(o) + end } local om = OptionsMenu.new(og) local function press(btn) OInput.pressed = { [btn] = true } @@ -2655,9 +2681,9 @@ do "A switches the battle screen to the WIDE layout") press("a") eq(og.save.options.battleLayout, "og", "BATTLE LAYOUT wraps back to OG") - for _ = 1, 2 do press("down") end - eq(om.index, 6, "cursor reaches MUSIC VOL") - eq(om.scroll, 2, "viewport scrolls to keep MUSIC VOL on screen") + for _ = 1, 5 do press("down") end + eq(om.index, 9, "cursor reaches MUSIC VOL") + eq(om.scroll, 5, "viewport scrolls to keep MUSIC VOL on screen") press("left") eq(og.save.options.musicVol, 6, "left lowers MUSIC VOL") press("right") @@ -2672,25 +2698,33 @@ do press("a") eq(og.save.options.musicFilter, 0, "MUSIC FILTER wraps back to OFF") press("down") - eq(om.index, 9, "cursor reaches COLORS") + eq(om.index, 12, "cursor reaches PERFORMANCE") + press("a") + eq(og.save.options.performance, "high", "A cycles PERFORMANCE to HIGH") + eq(require("src.core.Performance").tier, "high", + "the live tier tracks the PERFORMANCE option") + for _ = 1, 3 do press("a") end + eq(og.save.options.performance, "auto", "PERFORMANCE wraps back to AUTO") + press("down") + eq(om.index, 13, "cursor reaches COLORS") press("a") for _ = 1, 4 do press("a") end press("down") - eq(om.index, 10, "cursor reaches TILT") + eq(om.index, 14, "cursor reaches TILT") press("a") eq(og.save.options.tilt, 1, "A cycles TILT to 15") eq(Tilt.level, 1, "Tilt level tracks TILT option") press("a"); press("a"); press("a") eq(og.save.options.tilt, 0, "TILT wraps back to OFF") press("down") - eq(om.index, 11, "cursor reaches GBC FX") + eq(om.index, 15, "cursor reaches GBC FX") press("a") eq(og.save.options.gbcfx, 1, "A cycles GBC FX to 1") eq(GBCFX.level, 1, "GBCFX level tracks GBC FX option") for _ = 1, 4 do press("a") end eq(og.save.options.gbcfx, 0, "GBC FX wraps back to OFF") press("down") - eq(om.index, 12, "cursor reaches ZOOM") + eq(om.index, 16, "cursor reaches ZOOM") local ZoomOpt = require("src.render.Zoom") press("a") eq(og.save.options.zoom, 1, "A cycles ZOOM to IN1") @@ -2698,7 +2732,7 @@ do press("left") eq(og.save.options.zoom, 0, "left steps ZOOM back to FIT") press("down") - eq(om.index, 13, "cursor reaches VOID FILL") + eq(om.index, 17, "cursor reaches VOID FILL") local TR = require("src.render.TileRenderer") press("a") eq(og.save.options.voidFill, "water", "A cycles VOID FILL to WATER") @@ -2708,7 +2742,7 @@ do press("a") eq(og.save.options.voidFill, "trees", "VOID FILL wraps back to TREES") press("down") - eq(om.index, 14, "cursor reaches VIDEO MODE") + eq(om.index, 18, "cursor reaches VIDEO MODE") press("a") eq(og.save.options.videoMode, "borderless", "A cycles VIDEO MODE to BORDERLESS") @@ -2716,7 +2750,9 @@ do eq(og.save.options.videoMode, "windowed", "VIDEO MODE wraps back to WINDOWED") press("down") - eq(om.index, 15, "cursor reaches MAX FPS") + eq(om.index, 19, "cursor reaches FAITHFUL RATIO") + press("down") + eq(om.index, 20, "cursor reaches MAX FPS") press("a") eq(og.save.options.fpsCap, 75, "A cycles MAX FPS up from 60 to 75") eq(FrameCap.current, 75, "the live render cap tracks the MAX FPS option") @@ -2724,29 +2760,43 @@ do -- SPEED below: a full loop of #STEPS presses returns to the 60 default. for _ = 1, #FrameCap.STEPS - 1 do press("a") end eq(og.save.options.fpsCap, 60, "MAX FPS wraps back to 60") + -- RFC 0007: the single GAME SPEED row is now three independent rows, + -- one per GameSpeed.CATEGORIES entry. press("down") - eq(om.index, 16, "cursor reaches GAME SPEED") + eq(om.index, 21, "cursor reaches OVERWORLD SPEED") press("a") - eq(og.save.options.speed, 2, "A cycles GAME SPEED to 2X") + eq(og.save.options.speedOverworld, 2, "A cycles OVERWORLD SPEED to 2X") -- Driven by the level list rather than a literal press count: adding a -- speed (20X went in for the bot runs) otherwise fails this as a wrap -- bug when the cycling is fine and the row is simply one longer. for _ = 1, #GameSpeed.LEVELS - 1 do press("a") end - eq(og.save.options.speed, 1, "GAME SPEED wraps back to NORMAL") + eq(og.save.options.speedOverworld, 1, "OVERWORLD SPEED wraps back to NORMAL") press("down") - eq(om.index, 17, "cursor reaches MODS") + eq(om.index, 22, "cursor reaches BATTLE SPEED") + press("a") + eq(og.save.options.speedBattle, 2, "A cycles BATTLE SPEED to 2X") + for _ = 1, #GameSpeed.LEVELS - 1 do press("a") end + eq(og.save.options.speedBattle, 1, "BATTLE SPEED wraps back to NORMAL") press("down") - eq(om.index, 18, "cursor reaches CONTROLS") + eq(om.index, 23, "cursor reaches MENU SPEED") + press("a") + eq(og.save.options.speedMenu, 2, "A cycles MENU SPEED to 2X") + for _ = 1, #GameSpeed.LEVELS - 1 do press("a") end + eq(og.save.options.speedMenu, 1, "MENU SPEED wraps back to NORMAL") press("down") - eq(om.index, 19, "CANCEL stays the fixed final row") - eq(om.scroll, 14, "CANCEL keeps the last option boxes on screen") + eq(om.index, 24, "cursor reaches MODS") + press("down") + eq(om.index, 25, "cursor reaches CONTROLS") + press("down") + eq(om.index, 26, "CANCEL stays the fixed final row") + eq(om.scroll, 21, "CANCEL keeps the last option boxes on screen") om:draw() -- smoke: scrolled layout draws under the headless stub press("a") check(popped, "A on CANCEL closes the options menu") local om2 = OptionsMenu.new(og) OInput.pressed = { up = true }; om2:update(1 / 60); OInput.pressed = {} - eq(om2.index, 19, "up from the top wraps to CANCEL") - eq(om2.scroll, 14, "wrapping to CANCEL scrolls to the tail") + eq(om2.index, 26, "up from the top wraps to CANCEL") + eq(om2.scroll, 21, "wrapping to CANCEL scrolls to the tail") -- headless-safe: no love.audio, setters only update internal state require("src.core.Music").applyOptions(og.save.options) require("src.core.Sound").applyOptions(og.save.options) @@ -2809,16 +2859,19 @@ do menu:update(0) eq(game.popCount(), 1, "Menu START-press closes when startCloses (start menu's PAD_START mask; no beep per HandleMenuInput_)") + -- both DisplayTwoOptionMenu branches hold 15 frames with the menu still + -- on screen before the answer lands (ChoiceBox.pending), so pump the + -- hold out after the press game = stubGame({ a = true }) local yes local box = ChoiceBox.new(game, function(v) yes = v end) - box:update(0) + for _ = 0, require("src.core.Timing").YES_NO_ANSWER do box:update(0) end eq(yes, true, "ChoiceBox A on YES chooses true") game = stubGame({ b = true }) local no box = ChoiceBox.new(game, function(v) no = v end) - box:update(0) + for _ = 0, require("src.core.Timing").YES_NO_ANSWER do box:update(0) end eq(no, false, "ChoiceBox B chooses false") end end @@ -2837,7 +2890,8 @@ do local qreturned = 0 local qg = { data = Data, save = qsave, stack = qstack, - input = { wasPressed = function(_, k) return qpressed[k] end }, + input = { wasPressed = function(_, k) return qpressed[k] end, + isDown = function(_, k) return qpressed[k] or false end }, returnToTitle = function() qreturned = qreturned + 1 end, } local qmenu = StartMenuQ.new(qg) @@ -2858,7 +2912,12 @@ do check(qbox ~= qmenu and qbox ~= nil and qbox.pages ~= nil, "QUIT pushes a confirmation textbox") eq(qbox.pages[1][1], "RETURN TO MAIN", "confirm asks RETURN TO MAIN MENU?") - qbox.onDone() + -- opts.choice: the box pushes the YES/NO itself once the last page has + -- typed out, so pump it rather than reaching for the old onDone hook + for _ = 1, 600 do + if qstack:top() ~= qbox then break end + qbox:update(1 / 60) + end local qchoice = qstack:top() check(qchoice ~= qbox and qchoice ~= nil and qchoice.onChoose ~= nil, "textbox is followed by a YES/NO choice") @@ -3366,6 +3425,9 @@ runSuites({ "tests/input_hold_test.lua" }) -- ---------------------------------------------- launcher cursor (#114) runSuites({ "tests/rom_importer_cursor_test.lua" }) +-- ---------------------------------------------- launcher last played tab (#835) +runSuites({ "tests/rom_importer_last_version_test.lua" }) + -- ---------------------------------------------- Android second ROM pick (#167) runSuites({ "tests/rom_importer_android_pick_test.lua" }) diff --git a/tests/save_convert_tests.lua b/tests/save_convert_tests.lua index a775f0c4..bd499eb2 100644 --- a/tests/save_convert_tests.lua +++ b/tests/save_convert_tests.lua @@ -590,5 +590,37 @@ do .. data.pokemon.NIDORAN_F.catchRate .. ")") end +-- (4) name-field tails: real cartridge saves legitimately hold 0x00 (and +-- stale glyph) bytes after a name's $50 terminator, and rewriting them broke +-- the import -> export byte-identical round trip. With a template every +-- tail byte must survive verbatim; only a templateless (engine-origin) +-- export $50-pads the tail (#206). +do + local s = SaveData.newGame({ playerName = "RED" }) + for i = 1, 12 do s.boxes = s.boxes or {}; s.boxes[i] = {} end + local b1 = GenSave.encode(s, data, nil) + -- templateless: the tail past "RED@" must be all $50 padding + local ok50 = true + for i = 4, 10 do + if b1:byte(OFF.playerName + i + 1) ~= 0x50 then ok50 = false end + end + check(ok50, "templateless export $50-pads the player-name tail (#206)") + -- build a template whose player-name tail mixes 0x00 and stale glyphs + -- after the terminator, exactly like a real traded/edited cartridge save + local tpl = {} + for i = 1, #b1 do tpl[i] = b1:sub(i, i) end + tpl[OFF.playerName + 5] = string.char(0x00) + tpl[OFF.playerName + 6] = string.char(0x81) + tpl[OFF.playerName + 7] = string.char(0x00) + local tplBytes = table.concat(tpl) + local decoded = GenSave.decode(tplBytes, data) + check(decoded.player.name == "RED", + "a 0x00/stale tail past the terminator does not leak into the name") + local b2 = GenSave.encode(decoded, data) + check(b2:sub(OFF.playerName + 1, OFF.playerName + 11) + == tplBytes:sub(OFF.playerName + 1, OFF.playerName + 11), + "template name tails (incl. 0x00 bytes) round-trip byte-identical") +end + print(string.format("save convert: %d/%d checks passed", checks - failures, checks)) if failures > 0 then os.exit(1) end diff --git a/tests/save_convert_yellow_bug838_test.lua b/tests/save_convert_yellow_bug838_test.lua new file mode 100644 index 00000000..3e96eb1c --- /dev/null +++ b/tests/save_convert_yellow_bug838_test.lua @@ -0,0 +1,208 @@ +-- Yellow save export/import checks for #838: the codec used to run the +-- Red/Blue tables unmodified for Yellow, so (1) event flags went through +-- pokered's bit numbering even though pokeyellow renumbers wEventFlags, +-- and (2) wPikachuHappiness (pokeyellow d46f, absolute 0x271C in SRAM) +-- was never encoded or decoded. Yellow offsets are verified against the +-- pokeyellow symbol file -- no local pokeyellow checkout exists, so +-- ../pokered can only vouch for the shared R/B layout, which pokeyellow's +-- sram.asm matches byte for byte. Needs data/generated/, same as +-- tests/save_convert_tests.lua (its natural eventual home). +-- +-- Run: luajit tests/save_convert_yellow_bug838_test.lua + +package.path = "./?.lua;" .. package.path +_G.love = require("tests.love_stub") + +local GenSave = require("src.save_convert.GenSave") +local SaveConvert = require("src.save_convert.SaveConvert") +local SaveData = require("src.core.SaveData") + +local checks, failures = 0, 0 +local function check(cond, msg) + checks = checks + 1 + if not cond then + failures = failures + 1 + print("FAIL: " .. msg) + end +end + +GenSave.setCharmap(loadfile("src/save_convert/data/charmap.lua")()) + +local redFlags = loadfile("src/save_convert/data/event_flags.lua")() +local yellowFlags = loadfile("src/save_convert/data/event_flags_yellow.lua")() + +-- Red/Blue and Yellow crosswalk sets over the same generated tables; the +-- only differences the codec keys off are the event-flag numbering and the +-- gameVersion tag (SaveConvert.ensureData stamps the same shape, #838). +local shared = { + pokemon = loadfile("data/generated/pokemon.lua")(), + moves = loadfile("data/generated/moves.lua")(), + items = loadfile("data/generated/items.lua")(), + maps = loadfile("data/generated/maps.lua")(), +} +local redData = { + pokemon = shared.pokemon, moves = shared.moves, items = shared.items, + maps = shared.maps, eventFlags = redFlags, +} +local yellowData = { + pokemon = shared.pokemon, moves = shared.moves, items = shared.items, + maps = shared.maps, eventFlags = yellowFlags, gameVersion = "yellow", +} + +local OFF = GenSave.OFFSETS + +-- ------------------------------------------------------------------ +-- the Yellow event-flag table itself: pokeyellow's renumbering, not a +-- copy of the Red table under a new filename +-- ------------------------------------------------------------------ + +check(yellowFlags.count == 2560, + "yellow table covers the full 2560-bit wEventFlags array") +check(type(yellowFlags.byName) == "table" and type(yellowFlags.byBit) == "table", + "yellow table has the byName/byBit shape the codec reads") + +-- shared names on DIFFERENT bits: Yellow inserts events ahead of them +check(redFlags.byName.EVENT_GOT_DOME_FOSSIL == 1406 + and yellowFlags.byName.EVENT_GOT_DOME_FOSSIL == 1400, + "EVENT_GOT_DOME_FOSSIL sits on red bit 1406 vs yellow bit 1400") +check(redFlags.byName.EVENT_BEAT_MT_MOON_3_TRAINER_0 == 1402 + and yellowFlags.byName.EVENT_BEAT_MT_MOON_3_TRAINER_0 == 1403, + "the Mt Moon 3 trainer block shifts +1 in yellow (Jessie & James insert)") +check(redFlags.byName.EVENT_BEAT_SILPH_CO_11F_TRAINER_0 == 1924 + and yellowFlags.byName.EVENT_BEAT_SILPH_CO_11F_TRAINER_0 == 1925, + "the Silph Co 11F trainer block shifts +1 in yellow") + +-- yellow-only names the port's Yellow scripts set (data/scripts/ +-- yellow_jessie_james.lua and the catch-training tutorial): absent from +-- the Red table, so exporting through it silently dropped them +check(yellowFlags.byName.EVENT_BEAT_MT_MOON_3_JESSIE_JAMES == 1402 + and redFlags.byName.EVENT_BEAT_MT_MOON_3_JESSIE_JAMES == nil, + "EVENT_BEAT_MT_MOON_3_JESSIE_JAMES is yellow bit 1402, unknown to red") +check(yellowFlags.byName.EVENT_COMPLETED_CATCH_TRAINING == 45 + and redFlags.byName.EVENT_COMPLETED_CATCH_TRAINING == nil, + "EVENT_COMPLETED_CATCH_TRAINING is yellow bit 45, unknown to red") +check(yellowFlags.byName.EVENT_GOT_SQUIRTLE_FROM_OFFICER_JENNY ~= nil + and redFlags.byName.EVENT_GOT_SQUIRTLE_FROM_OFFICER_JENNY == nil, + "the Officer Jenny Squirtle event exists only in the yellow table") + +-- byBit/byName agree on the renumbered entries +check(yellowFlags.byBit[1400] == "EVENT_GOT_DOME_FOSSIL" + and yellowFlags.byBit[1402] == "EVENT_BEAT_MT_MOON_3_JESSIE_JAMES", + "yellow byBit resolves the renumbered bits back to their names") + +-- ------------------------------------------------------------------ +-- wPikachuHappiness offset: d46f - wMainDataStart d2f6 = 377 past +-- sMainData, absolute 0x271C (per the pokeyellow symbol file) +-- ------------------------------------------------------------------ + +check(OFF.pikachuHappiness == 10012, + "OFFSETS.pikachuHappiness is absolute 0x271C (got " + .. tostring(OFF.pikachuHappiness) .. ")") +check(OFF.pikachuHappiness == OFF.mainData + 377, + "pikachuHappiness sits 377 bytes past sMainData (wram d46f - d2f6)") +-- the byte is INSIDE the checksummed main-data window, so writing it +-- without recomputing the checksum would brick the save on a cartridge +check(OFF.pikachuHappiness >= OFF.checksumStart + and OFF.pikachuHappiness < OFF.checksumEnd, + "pikachuHappiness lies inside the main checksum window") + +-- ------------------------------------------------------------------ +-- encode/decode gate: yellow data writes and reads the byte, R/B data +-- leaves it alone (in Red/Blue it is current-map scratch) +-- ------------------------------------------------------------------ + +local save = SaveData.newGame({ playerName = "RED", rivalName = "BLUE" }) +save.pikachuHappiness = 200 +-- the follower seeds happiness at 90 (src/world/PikachuFollower.lua), so +-- 200 can only come from this table -- no default could fake the check + +local yBytes = GenSave.encode(save, yellowData, nil) +check(#yBytes == GenSave.SAVE_SIZE, "yellow encode produces exactly 32768 bytes") +check(yBytes:byte(OFF.pikachuHappiness + 1) == 200, + "yellow encode writes pikachuHappiness to 0x271C (got " + .. yBytes:byte(OFF.pikachuHappiness + 1) .. ")") +check(GenSave.mainChecksumValid(yBytes), + "yellow encode still emits a valid main-data checksum") +local yDec = GenSave.decode(yBytes, yellowData) +check(yDec.pikachuHappiness == 200, + "yellow decode reads pikachuHappiness back (got " + .. tostring(yDec.pikachuHappiness) .. ")") + +-- a yellow save whose table never held the field falls back to the +-- follower's seed value instead of exporting friendship 0 +local noHap = SaveData.newGame({ playerName = "RED", rivalName = "BLUE" }) +noHap.pikachuHappiness = nil +local seedBytes = GenSave.encode(noHap, yellowData, nil) +check(seedBytes:byte(OFF.pikachuHappiness + 1) == 90, + "a missing pikachuHappiness exports as the follower seed 90, not 0") + +-- R/B output unchanged: same save through the red data set leaves the +-- scratch byte zero-filled and decode never invents the field +local rBytes = GenSave.encode(save, redData, nil) +check(rBytes:byte(OFF.pikachuHappiness + 1) == 0, + "red/blue encode leaves the 0x271C scratch byte zero-filled") +check(GenSave.decode(rBytes, redData).pikachuHappiness == nil, + "red/blue decode does not fabricate a pikachuHappiness field") + +-- ------------------------------------------------------------------ +-- event flags land on pokeyellow bits. Independent LSB-first flag_array +-- read (pokered home FlagAction convention: byte N/8, bit N%8) so the +-- assertions cannot inherit a codec bit-order bug. +-- ------------------------------------------------------------------ + +local bit = require("bit") +local function flagBit(bytes, index) + local b = bytes:byte(OFF.eventFlags + math.floor(index / 8) + 1) + return bit.band(bit.rshift(b, index % 8), 1) == 1 +end + +local fsave = SaveData.newGame({ playerName = "ASH", rivalName = "GARY" }) +fsave.flags = { + EVENT_GOT_DOME_FOSSIL = true, + EVENT_BEAT_MT_MOON_3_JESSIE_JAMES = true, + EVENT_COMPLETED_CATCH_TRAINING = true, +} + +local yfBytes = GenSave.encode(fsave, yellowData, nil) +check(flagBit(yfBytes, 1400) and not flagBit(yfBytes, 1406), + "yellow export puts EVENT_GOT_DOME_FOSSIL on bit 1400, not red's 1406") +check(flagBit(yfBytes, 1402), + "yellow export carries EVENT_BEAT_MT_MOON_3_JESSIE_JAMES on bit 1402") +check(flagBit(yfBytes, 45), + "yellow export carries EVENT_COMPLETED_CATCH_TRAINING on bit 45") +local yfDec = GenSave.decode(yfBytes, yellowData) +check(yfDec.flags.EVENT_GOT_DOME_FOSSIL + and yfDec.flags.EVENT_BEAT_MT_MOON_3_JESSIE_JAMES + and yfDec.flags.EVENT_COMPLETED_CATCH_TRAINING, + "yellow-numbered flags round-trip through decode") + +-- the pre-fix failure mode, pinned so it can never quietly return: the +-- red table lands the fossil on the wrong yellow bit and drops the +-- yellow-only names entirely +local rfBytes = GenSave.encode(fsave, redData, nil) +check(flagBit(rfBytes, 1406) and not flagBit(rfBytes, 1400), + "the red table writes the fossil on 1406, which yellow reads as another event") +check(not flagBit(rfBytes, 1402) and not flagBit(rfBytes, 45), + "the red table silently drops both yellow-only flags") + +-- ------------------------------------------------------------------ +-- SaveConvert.ensureData substitutes the yellow flag table (and stamps +-- gameVersion) when the caller names yellow; the versionless set still +-- resolves red numbering, per-version cached separately (#420 pattern) +-- ------------------------------------------------------------------ + +local yData, yErr = SaveConvert.loadData("yellow") +check(yData ~= nil, "SaveConvert.loadData('yellow') resolves (" .. tostring(yErr) .. ")") +check(yData and yData.gameVersion == "yellow", + "loadData('yellow') stamps gameVersion for the codec's byte gate") +check(yData and yData.eventFlags.byName.EVENT_GOT_DOME_FOSSIL == 1400 + and yData.eventFlags.byName.EVENT_BEAT_MT_MOON_3_JESSIE_JAMES == 1402, + "loadData('yellow') serves the pokeyellow flag numbering") +local dData = SaveConvert.loadData() +check(dData and dData.eventFlags.byName.EVENT_GOT_DOME_FOSSIL == 1406 + and dData.gameVersion == nil, + "versionless loadData still serves the red numbering, untagged") + +print(string.format("save convert yellow #838: %d/%d checks passed", + checks - failures, checks)) +if failures > 0 then os.exit(1) end diff --git a/tests/save_editor_task7_tests.lua b/tests/save_editor_task7_tests.lua index b45179fc..b6c8e4c7 100644 --- a/tests/save_editor_task7_tests.lua +++ b/tests/save_editor_task7_tests.lua @@ -39,6 +39,7 @@ print("== save editor task 7 tests (Events + Dex) ==") local Ops = require("Ops") local State = require("State") +local Catalog = require("Catalog") local function newState() local S = State.new() @@ -209,5 +210,118 @@ do check(owned > 0, "the dex was not wiped by the unrelated click") end +-- Dex sort ----------------------------------------------------------- + +do + -- Ops.dexList orders the grid by the active mode. This block drives the + -- real generated data (State.new alone carries no Data), so the vanilla + -- 1-151 numbering is what the "dex" mode asserts against. + local Data = require("src.core.Data") + Data:load() + local S = State.new() + S.data = Data + S.cat = Catalog.build(Data) + S.save = require("src.core.SaveData").newGame() + + -- default mode is "dex": number order + eq(S.dexSort, "dex", "a fresh state sorts the dex by number by default") + local byDex = Ops.dexList(S) + eq(#byDex, #S.cat.species, "dexList covers every species") + eq(byDex[1], "BULBASAUR", "dex order starts at #1") + eq(byDex[4], "CHARMANDER", "dex order puts Charmander fourth") + eq(byDex[25], "PIKACHU", "dex order puts Pikachu at #25") + eq(byDex[151], "MEW", "dex order ends at #151") + + -- "name" mode: alphabetical by display name + Ops.dexSort(S, "name") + eq(S.dexSort, "name", "dexSort switches the mode") + local byName = Ops.dexList(S) + eq(#byName, #S.cat.species, "the name sort covers every species too") + eq(byName[1], "ABRA", "the name sort leads with ABRA") + local sorted = true + for i = 2, #byName do + local a = S.data.pokemon[byName[i - 1]] + local b = S.data.pokemon[byName[i]] + local an = (a and a.name or byName[i - 1]):lower() + local bn = (b and b.name or byName[i]):lower() + if an > bn then sorted = false break end + end + check(sorted, "the name sort is alphabetical over display names") + local mi = nil + for i, id in ipairs(byName) do + if id == "MEW" then mi = i + elseif id == "MR_MIME" and mi then + check(i > mi, "MEW sorts before MR.MIME in the name sort") + end + end + local fIdx, mIdx = nil, nil + for i, id in ipairs(byName) do + if id == "NIDORAN_F" then fIdx = i elseif id == "NIDORAN_M" then mIdx = i end + end + check(fIdx and mIdx and fIdx < mIdx, "NIDORAN_F sorts before NIDORAN_M") + + -- switching back to the number order restores the original sequence + Ops.dexSort(S, "dex") + local back = Ops.dexList(S) + eq(back[1], "BULBASAUR", "switching back restores number order") +end + +do + -- the switch is view-only: it resets the grid scroll but never dirties the + -- save, and a re-click on the active mode is a narrated no-op + local S = newState() + S.data = { pokemon = { BULBASAUR = { dex = 1, name = "BULBASAUR" }, + CHARMANDER = { dex = 4, name = "CHARMANDER" }, + PIKACHU = { dex = 25, name = "PIKACHU" }, + SQUIRTLE = { dex = 7, name = "SQUIRTLE" } } } + S.cat = { species = { "BULBASAUR", "CHARMANDER", "SQUIRTLE", "PIKACHU" }, + items = {}, moves = {} } + S.dexOffset = 9 + S.dirty = false + + check(Ops.dexSort(S, "name") == true, "dexSort switches the mode without dirtying") + eq(S.dirty, false, "a sort never dirties the save") + eq(S.dexOffset, 0, "changing the sort resets the grid scroll") + eq(S.status, "", "a sort leaves the status bar alone") + + S.dexOffset = 4 + check(Ops.dexSort(S, "name") == false, "re-clicking the active mode is a no-op") + eq(S.dexOffset, 4, "a no-op sort leaves the scroll alone") + eq(S.dirty, false, "a no-op sort does not dirty either") + eq(S.status, "", "a no-op sort does not narrate either") + + check(Ops.dexSort(S, "bogus") == false, "an unknown mode is refused") + eq(S.dexSort, "name", "a refused mode leaves the sort unchanged") + + -- the keyed list sorts against this mini dataset too + local byDex = Ops.dexList(S) + eq(byDex[1], "BULBASAUR", "mini-catalog dex order is #1 first") + eq(byDex[2], "CHARMANDER", "mini-catalog dex order is #4 second") + Ops.dexSort(S, "name") + eq(Ops.dexList(S)[1], "BULBASAUR", "mini-catalog name order leads with BULBASAUR") +end + +do + -- robustness: a mod-shaped partial record (no name, no dex) must not crash + -- the sort or disappear from the grid -- it just sorts last + local S = newState() + S.data = { pokemon = { BULBASAUR = { dex = 1, name = "BULBASAUR" }, + PARTIAL = { baseStats = { hp = 40 } } } } + S.cat = { species = { "BULBASAUR", "PARTIAL" }, items = {}, moves = {} } + + local byDex = Ops.dexList(S) + eq(#byDex, 2, "a partial record still appears in the dex order") + eq(byDex[2], "PARTIAL", "a record without a dex number sorts last") + + Ops.dexSort(S, "name") + local byName = Ops.dexList(S) + eq(#byName, 2, "a partial record still appears in the name order") + eq(byName[2], "PARTIAL", "a record without a name sorts last, by its id") + + -- and with no data/catalog at all, the list degrades to empty, not nil + local bare = State.new() + eq(#Ops.dexList(bare), 0, "a state with no catalog yields an empty list") +end + print(string.format("save editor task 7 tests: %d passed, %d failed", passed, failed)) if failed > 0 then os.exit(1) end diff --git a/tests/save_oversize_vendor_test.lua b/tests/save_oversize_vendor_test.lua new file mode 100644 index 00000000..c1c0452a --- /dev/null +++ b/tests/save_oversize_vendor_test.lua @@ -0,0 +1,174 @@ +-- Independent-oracle test for the oversize-save import path in +-- src/import/SaveFileIO.lua (importToSlot force/truncate when a .sav exceeds +-- 32768 bytes with a valid main-data checksum -- i.e. a cartridge save padded +-- with an emulator RTC footer). +-- +-- The fixture is built by the VENDOR codec (tools/save_convert/vendor/ +-- gen1lib.lua, a PKHeX-derived Gen1 .sav<->JSON codec): the bytes GenSave +-- later imports were never produced by GenSave. The post-truncation export is +-- then re-parsed by that SAME vendor codec -- a second source independent of +-- GenSave -- confirming the forced truncation drops only the footer. +-- +-- Runs under stock Lua 5.3/5.4/5.5 (gen1lib needs native bitwise operators and +-- cannot even be parsed by LuaJIT); GenSave gets a `bit` shim backed by those +-- operators, exactly like tools/save_convert/crosscheck.lua. +-- lua tests/save_oversize_vendor_test.lua +-- +-- The luajit side of this policy lives in save_file_io_tests.lua; this file is +-- the out-of-band vendor oracle (see save_convert_tests.lua for the same +-- split). It lives OUTSIDE tests/engine/ on purpose: tier_runner globs that +-- directory under luajit, which cannot parse gen1lib. scripts/test.sh runs it +-- as its own lua5.4 tier when that interpreter is available. + +package.path = "./?.lua;./?/init.lua;" .. package.path + +-- `bit` shim backed by native Lua 5.3+ operators (crosscheck.lua's). +if not pcall(require, "bit") then + package.preload["bit"] = function() + local M = {} + function M.band(a, ...) local r = a; for _, v in ipairs({...}) do r = r & v end; return r & 0xFFFFFFFF end + function M.bor(a, ...) local r = a; for _, v in ipairs({...}) do r = r | v end; return r & 0xFFFFFFFF end + function M.bxor(a, ...) local r = a; for _, v in ipairs({...}) do r = r ~ v end; return r & 0xFFFFFFFF end + function M.bnot(a) return (~a) & 0xFFFFFFFF end + function M.lshift(a, n) return (a << n) & 0xFFFFFFFF end + function M.rshift(a, n) return (a & 0xFFFFFFFF) >> n end + return M + end +end + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local GenSave = require("src.save_convert.GenSave") +local SaveConvert = require("src.save_convert.SaveConvert") +local SaveData = require("src.core.SaveData") +local GameVersion = require("src.core.GameVersion") +local SaveFileIO = require("src.import.SaveFileIO") + +local gen1 = dofile("tools/save_convert/vendor/gen1lib.lua") + +-- The vendor fixture needs no data/generated for BUILDING, but the import +-- (SaveConvert.importSav -> GenSave.decode) needs the crosswalk tables, so +-- skip cleanly on a checkout that never imported a ROM (like the luajit suite). +local loadPokemon = loadfile("data/generated/pokemon.lua") +if not loadPokemon then + print("save_oversize_vendor skipped (needs data/generated/ for GenSave codec)") + os.exit(0) +end + +local realFS = love.filesystem + +-- Same love.filesystem stub as save_file_io_tests.lua: keyed by full path with +-- the export surface SaveFileIO reaches (createDirectory/getSaveDirectory). +local function memfs(files) + return { + files = files, + write = function(path, content) files[path] = content return true end, + read = function(path) return files[path] end, + remove = function(path) files[path] = nil return true end, + getInfo = function(path) + if files[path] then return { type = "file" } end + local prefix = path .. "/" + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then return { type = "directory" } end + end + return nil + end, + createDirectory = function() return true end, + getSaveDirectory = function() return "/fake/save" end, + } +end + +local function fresh() + local files = {} + love.filesystem = memfs(files) + SaveData.resetSlotState() + GameVersion.set("red") + return files +end + +-- DroppedFile-shaped source (readSource treats a raw string of length != 32768 +-- as a path, so hand a file object). +local function fileSource(bytes) + return { + _bytes = bytes, + open = function() return true end, + getSize = function(self) return #self._bytes end, + read = function(self) return self._bytes end, + close = function() return true end, + } +end + +-- A realistic 44-byte VBA MBC3 RTC footer (bgb.bircd.org/rtcsave.html). +local function rtcFooter() + local parts = {} + local function pushLe(v) + parts[#parts + 1] = string.char(v % 256, math.floor(v / 256) % 256, + math.floor(v / 65536) % 256, math.floor(v / 16777216) % 256) + end + pushLe(27); pushLe(29); pushLe(11); pushLe(200) + parts[#parts + 1] = string.rep("\0", 16) + parts[#parts + 1] = string.rep("\0", 8) + pushLe(0x669A00BF) + return table.concat(parts) +end + +-- Build a 32768-byte save ENTIRELY through the vendor codec: zero base buffer, +-- trainer/party/box fields written by gen1lib, checksum recomputed by +-- gen1lib. GenSave had no part in producing these bytes. +local function vendorSave() + local data = { + raw_base64 = gen1.base64_encode(string.rep("\0", GenSave.SAVE_SIZE)), + trainer = { + name = "VENDOR", id = 12345, rival_name = "BLUE", + money = 4321, coins = 0, badges = 0, options = 0, starter = 0, + pikachu_friendship = 0, pikachu_beach_score = 0, + }, + current_box = 1, + party = {}, + boxes = {}, + } + return gen1.build_save(data) +end + +-- ---------------------------------------------- vendor-built oversize round trip + +do + local base = vendorSave() + eq(#base, GenSave.SAVE_SIZE, "the vendor codec builds a 32768-byte save") + eq(SaveConvert.mainChecksumValid(base), true, + "the vendor-built save carries a valid main-data checksum") + + local oversize = base .. rtcFooter() + eq(#oversize, 32768 + 44, "the oversize fixture is 32812 bytes") + + local files = fresh() + -- the save the project has never seen imports cleanly once force truncates + local ok, slotId = SaveFileIO.importToSlot(fileSource(oversize), "red", true) + eq(ok, true, "the vendor-built oversize save imports with force") + local loaded = SaveData.load("red") + eq(loaded and loaded.player.name, "VENDOR", "the imported save keeps the vendor-written name") + eq(loaded and loaded.money, 4321, "the imported save keeps the vendor-written money") + + local eok, path = SaveFileIO.exportActiveSlot("red") + eq(eok, true, "the forced import exports") + local rel = path:gsub("^/fake/save/", "") + local outBytes = files[rel] + eq(outBytes and #outBytes, GenSave.SAVE_SIZE, "the export is exactly 32768 bytes") + + -- INDEPENDENT ORACLE: the vendor codec re-parses the project's export. If + -- the truncation had damaged the save, or GenSave's codec self-consistently + -- corrupted it, parse_save would disagree here. + local outBuf = gen1.string_to_bytes(outBytes) + local parsed = gen1.parse_save(outBuf) + eq(parsed.trainer.name, "VENDOR", "vendor parse of the export: name intact") + eq(parsed.trainer.money, 4321, "vendor parse of the export: money intact") + eq(parsed.trainer.id, 12345, "vendor parse of the export: trainer id intact") + eq(#parsed.party, 0, "vendor parse of the export: empty party preserved") + eq(#parsed.boxes, 12, "vendor parse of the export: 12 boxes present") +end + +love.filesystem = realFS + +T.finish("save_oversize_vendor") diff --git a/tests/switch_ci_workflows_test.lua b/tests/switch_ci_workflows_test.lua index 8225cc46..5ae2d693 100644 --- a/tests/switch_ci_workflows_test.lua +++ b/tests/switch_ci_workflows_test.lua @@ -152,27 +152,28 @@ check(comment_wf:find("comment-tag: switch-build-result", 1, true) -- --- SWCI-08 / SWCI-09: docs CI vs release --- local build_doc = read("docs/switch-build.md") -local development = read("docs/switch-development.md") local readme = read("README.md") mustContain(build_doc, "Path-gated", "switch-build.md") mustContain(build_doc, "ubuntu-latest", "switch-build.md") mustContain(build_doc, "selftest_build_switch.sh", "switch-build.md") -mustContain(build_doc, "canonical", "switch-build.md") +mustContain(build_doc, "main repo", "switch-build.md") mustContain(build_doc, "gen1recomp-switch-nro", "switch-build.md") mustContain(build_doc, "switch-build-result", "switch-build.md") mustContain(build_doc, "hard gate", "switch-build.md") mustContain(build_doc, "continue-on-error", "switch-build.md") mustContain(build_doc, "nacptool", "switch-build.md") mustContain(build_doc, "Docker", "switch-build.md") -mustContain(build_doc, "Fork → canonical", "switch-build.md") +mustContain(build_doc, "Fork PRs into the main repo", "switch-build.md") mustContain(build_doc, "skip Switch fused", "switch-build.md") - -mustContain(development, "Switch CI", "switch-development.md") -mustContain(development, "selftest_build_switch.sh", "switch-development.md") +mustContain(build_doc, "CI and release", "switch-build.md") +mustNotContain(build_doc, "switch-development", "switch-build.md") +mustNotContain(build_doc, "switch-hardware-evidence", "switch-build.md") mustContain(readme, "CI vs release", "README.md") mustContain(readme, "switch-build.md", "README.md") +mustNotContain(readme, "switch-development", "README.md") +mustNotContain(readme, "switch-hardware-evidence", "README.md") -- --- SWFIX-03: headless suite also runs the content gates --- local test_sh = read("scripts/test.sh") diff --git a/tests/switch_transfer_docs_test.lua b/tests/switch_transfer_docs_test.lua index ee80d11d..808f76a3 100644 --- a/tests/switch_transfer_docs_test.lua +++ b/tests/switch_transfer_docs_test.lua @@ -32,7 +32,7 @@ mustContain(transfer, "imports/", "transfer") mustContain(transfer, "imports/mods/", "transfer") mustContain(transfer, "1: SD Card", "transfer") mustContain(transfer, "Scan again", "transfer") -mustContain(transfer, "one contributor example", "transfer") +mustContain(transfer, "documented example", "transfer") mustContain(transfer, "Linux", "transfer") mustContain(transfer, "Windows", "transfer") mustContain(transfer, "macOS", "transfer") @@ -46,7 +46,7 @@ mustContain(transfer, "Portable Devices", "transfer") mustContain(transfer, "MTP USB Device", "transfer") mustContain(transfer, "AppleDouble", "transfer") mustContain(transfer, "card reader", "transfer") -mustContain(transfer, "Canonical methods", "transfer") +mustContain(transfer, "Transfer methods", "transfer") mustContain(transfer, "OpenMTP", "transfer") mustContain(transfer, "only one", "transfer") mustContain(transfer, "USB-C", "transfer") @@ -54,46 +54,27 @@ mustContain(transfer, "USB-C", "transfer") mustContain(transfer, "If MTP is unavailable or flaky on Linux", "transfer") mustContain(transfer, "If MTP is unavailable or flaky on Windows", "transfer") mustContain(transfer, "Joy-Con display chords (stock engine)", "transfer") -mustNotContain(transfer, "VoxelMod", "transfer") +mustNotContain(transfer, "switch-development", "transfer") +mustNotContain(transfer, "switch-hardware-evidence", "transfer") local install = read("docs/switch-install.md") local build = read("docs/switch-build.md") mustContain(install, "switch-transfer.md", "install") mustContain(install, "## Community mods", "install") mustContain(install, "Select + **A**", "install") +mustContain(install, "Select + **L**", "install") mustContain(install, "COLORS", "install") mustContain(install, "TILT", "install") mustContain(install, "GBC FX", "install") mustContain(install, "PERFORMANCE", "install") mustContain(install, "Stock engine effect", "install") +mustContain(install, "## Limitations", "install") +mustContain(install, "Launch with title override", "install") mustNotContain(install, "VoxelMod", "install") +mustNotContain(install, "switch-development", "install") mustContain(build, "switch-transfer.md", "build") mustContain(build, "nxlink", "build") - -local development = read("docs/switch-development.md") -mustContain(development, "switch-transfer.md", "development") -mustContain(development, "## Joy-Con display chords (Select + face)", "development") -mustContain(development, "Stock engine effect", "development") -mustContain(development, "claimed by the engine before mod pipeline", "development") -mustContain(development, "Select + **L**", "development") -mustContain(development, "OPTIONS → PERFORMANCE", "development") -mustNotContain(development, "VoxelMod", "development") -check(development:find("Non-macOS contributor MTP runbooks", 1, true) == nil, - "development must not list Non-macOS runbooks as absent") - -local evidence = read("docs/switch-hardware-evidence.md") -local nxStart = evidence:find("## NXMOD-12", 1, true) -check(nxStart ~= nil, "NXMOD-12 section present") -local nxmod = evidence:sub(nxStart) -mustContain(nxmod, "**pass**", "NXMOD-12") -mustContain(nxmod, "531", "NXMOD-12") -mustContain(nxmod, "switch-oled-photos", "NXMOD-12") -mustContain(nxmod, "IMG_1766.jpg", "NXMOD-12") -mustContain(nxmod, "IMG_1771.jpg", "NXMOD-12") -mustContain(nxmod, "Community mod zip", "NXMOD-12") -mustNotContain(nxmod, "VoxelMod", "NXMOD-12") -check(nxmod:find("Status | **pending**", 1, true) == nil - and nxmod:find("| **pending** |", 1, true) == nil, - "NXMOD-12 must not keep pending status/checklist") +mustNotContain(build, "switch-development", "build") +mustNotContain(build, "switch-hardware-evidence", "build") T.finish("switch_transfer_docs_test") diff --git a/tools/build_rom_data.py b/tools/build_rom_data.py index 8f0e02a4..8c4a9325 100755 --- a/tools/build_rom_data.py +++ b/tools/build_rom_data.py @@ -92,6 +92,17 @@ def version_for_manifest(manifest, requested_version=None, manifest_explicit=Fal return detected or requested_version or "red" +def detect_rom_version(path): + """Read a canonical ROM once and return its supported game version.""" + rom = RomImage(path, None) + version = SHA1_TO_VERSION.get(rom.sha1) + if version is None: + expected = ", ".join(VERSION_SHA1[name] for name in VERSION_MANIFESTS) + raise ValueError( + f"unsupported ROM SHA-1 {rom.sha1}; expected one of {expected}") + return version, rom + + def extract_constants(manifest, out_dir): data = manifest["constants"] util.write_lua( @@ -1736,6 +1747,17 @@ def extract_field(rom, symbols, manifest, out_dir, assets_dir): "title/copyright.png") raw_2bpp( "GameFreakLogoGraphics", 72, 8, "title/gamefreak_inc.png") + # Yellow NineTile (pokeyellow gfx/font.asm): final "9" of (c)1995-1999, + # parked in the 16 bytes between GameFreakLogoGraphics and TextBoxGraphics. + if _has_symbol(symbols, "GameFreakLogoGraphics") \ + and _has_symbol(symbols, "TextBoxGraphics"): + gf = _symbol(symbols, "GameFreakLogoGraphics") + tb = _symbol(symbols, "TextBoxGraphics") + if tb.address == gf.address + 9 * 16 + 16: + nine_raw = rom.bytes(gf.bank, gf.address + 9 * 16, 16) + _save_png( + _decode_2bpp(nine_raw, 8, 8, False), + os.path.join(assets_dir, "title/nine.png")) # Yellow fixed Pikachu title (pret/pokeyellow title_yellow.asm): tilemap # composition over both tile banks -- PokemonLogoGraphics in vChars2 @@ -2086,48 +2108,73 @@ def build(rom, symbols, manifest, out_dir, assets_dir, datasets): return results -def main(): +def main(argv=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--rom", required=True, help="canonical US Pokemon Red, Blue, or Yellow ROM") parser.add_argument( - "--version", choices=sorted(VERSION_MANIFESTS), default="red", - help="select the shipped manifest for this version (default: red)") + "--version", choices=["auto", *sorted(VERSION_MANIFESTS)], default="auto", + help="select the shipped manifest for this version (default: detect from ROM)") parser.add_argument( "--manifest", default=None, help="explicit manifest path (overrides --version default path; " "RomImage hash still comes from the file's romSha1)") - parser.add_argument("--out", default="data/generated") - parser.add_argument("--assets", default="assets/generated") + parser.add_argument( + "--out", default=None, + help="generated data directory (default: version-specific cache path)") + parser.add_argument( + "--assets", default=None, + help="generated assets directory (default: version-specific cache path)") parser.add_argument("--clean", action="store_true") parser.add_argument( "--only", action="append", choices=DATASETS, help="build one dataset (repeatable); default builds all implemented") - args = parser.parse_args() + args = parser.parse_args(argv) try: manifest_explicit = args.manifest is not None - manifest_path = resolve_manifest_path(args.version, args.manifest) - manifest = load_manifest(manifest_path) - version = version_for_manifest( - manifest, args.version, manifest_explicit=manifest_explicit) - expected_sha1 = manifest.get("romSha1") or VERSION_SHA1[version] - rom = RomImage(args.rom, expected_sha1) + requested_version = None if args.version == "auto" else args.version + if manifest_explicit: + manifest_path = resolve_manifest_path( + requested_version or "red", args.manifest) + manifest = load_manifest(manifest_path) + version = version_for_manifest( + manifest, requested_version, manifest_explicit=True) + expected_sha1 = manifest.get("romSha1") or VERSION_SHA1[version] + rom = RomImage(args.rom, expected_sha1) + elif requested_version is None: + version, rom = detect_rom_version(args.rom) + manifest_path = resolve_manifest_path(version, None) + manifest = load_manifest(manifest_path) + expected_sha1 = manifest.get("romSha1") or VERSION_SHA1[version] + if rom.sha1 != expected_sha1: + raise ValueError( + f"unsupported ROM SHA-1 {rom.sha1}; expected {expected_sha1}") + else: + version = requested_version + manifest_path = resolve_manifest_path(version, None) + manifest = load_manifest(manifest_path) + version = version_for_manifest(manifest, version) + expected_sha1 = manifest.get("romSha1") or VERSION_SHA1[version] + rom = RomImage(args.rom, expected_sha1) symbols = SymbolTable(manifest["symbols"]) except (OSError, ValueError, KeyError, json.JSONDecodeError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 + prefix = "" if version == "red" else version + os.sep + out_dir = args.out or prefix + os.path.join("data", "generated") + assets_dir = args.assets or prefix + os.path.join("assets", "generated") if args.clean: - for path in (args.out, args.assets): + for path in (out_dir, assets_dir): if os.path.isdir(path): shutil.rmtree(path) - os.makedirs(args.out, exist_ok=True) - os.makedirs(args.assets, exist_ok=True) + os.makedirs(out_dir, exist_ok=True) + os.makedirs(assets_dir, exist_ok=True) datasets = tuple(args.only) if args.only else DATASETS try: - build(rom, symbols, manifest, args.out, args.assets, datasets) + build(rom, symbols, manifest, out_dir, assets_dir, datasets) except (ValueError, KeyError, IndexError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 diff --git a/tools/extract/field.py b/tools/extract/field.py index 36740642..839be555 100644 --- a/tools/extract/field.py +++ b/tools/extract/field.py @@ -1471,9 +1471,11 @@ def extract(pokered, out_dir): util.die("bike riding tileset extraction sanity check failed") if indoor_encounters["firstIndoorMap"] != 0x25: util.die("indoor encounter boundary sanity check failed") - if len(title) != 5 or any(not v["width"] for v in title.values()) \ + if len(title) not in (5, 6) or any(not v["width"] for v in title.values()) \ or (title["gamefreakInc"]["width"], - title["gamefreakInc"]["height"]) != (72, 8): + title["gamefreakInc"]["height"]) != (72, 8) \ + or ("nine" in title and (title["nine"]["width"], + title["nine"]["height"]) != (8, 8)): util.die("title asset extraction sanity check failed") if any((intro["gengar"][f]["width"], intro["gengar"][f]["height"]) != (56, 56) for f in ("frame1", "frame2", "frame3")) \ diff --git a/tools/extract/gfx.py b/tools/extract/gfx.py index 6a75483b..4dcbbf5b 100644 --- a/tools/extract/gfx.py +++ b/tools/extract/gfx.py @@ -130,6 +130,12 @@ TITLE_GRAPHICS = [ ("gamefreakInc", "gfx/title/gamefreak_inc.png", False), ] +# Yellow only (pokeyellow gfx/font.asm NineTile): final "9" of (c)1995-1999 +# on the title copyright line and intro/credits copyright card. +OPTIONAL_TITLE_GRAPHICS = [ + ("nine", "gfx/title/nine.png", False), +] + def extract_title(pokered, assets_dir): """Convert the title-screen graphics to assets/generated/title/. @@ -150,6 +156,19 @@ def extract_title(pokered, assets_dir): "height": size[1], "source": src_rel, } + for key, src_rel, matte in OPTIONAL_TITLE_GRAPHICS: + src = os.path.join(pokered, src_rel) + if not os.path.isfile(src): + continue + base = os.path.basename(src_rel) + size = convert_png(src, os.path.join(assets_dir, "title", base), + transparent_matte=matte) + out[key] = { + "path": f"assets/generated/title/{base}", + "width": size[0], + "height": size[1], + "source": src_rel, + } return out diff --git a/tools/gbromdiff/.gitignore b/tools/gbromdiff/.gitignore new file mode 100644 index 00000000..036f67bf --- /dev/null +++ b/tools/gbromdiff/.gitignore @@ -0,0 +1,3 @@ +*.gb +*.sym +__pycache__/ diff --git a/libs/flexlove/LICENSE b/tools/gbromdiff/LICENSE similarity index 97% rename from libs/flexlove/LICENSE rename to tools/gbromdiff/LICENSE index 0b2a1f3e..97d18c1c 100644 --- a/libs/flexlove/LICENSE +++ b/tools/gbromdiff/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Mike Freno +Copyright (c) 2026 hernan0078 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tools/gbromdiff/README.md b/tools/gbromdiff/README.md new file mode 100644 index 00000000..37e93e2b --- /dev/null +++ b/tools/gbromdiff/README.md @@ -0,0 +1,97 @@ +# gbromdiff + +Compare two Game Boy ROMs bank by bank, and say **where** and **how** they +disagree. + +Written for one specific problem: bringing a localised Gen 1 Pokémon release +into a disassembly. It is not specific to Pokémon, or to Gen 1 — any pair of +Game Boy ROMs will do. + +## Why + +The European Pokémon releases are rebuilds of their US counterparts. Most +banks are byte-identical, the text banks are entirely different, and a handful +of code banks are the US code with pointers shifted. Which bank is which is +the first thing you need to know and the last thing anyone writes down. + +That distinction is the whole point of this tool: + +``` + bank differing bytes shape + 0x04 30 ( 0.2%) patched + 0x10 16328 ( 99.7%) replaced +``` + +A bank differing in 99.7% of its bytes is a **different payload** — translated +text — and needs new source. A bank differing in 0.2% is **the same code with +values moved**, and usually needs a pointer table corrected rather than a +rewrite. Those two findings lead to completely different work, and a plain +`cmp` cannot tell them apart. + +## The loop it is built for + +``` +1. build the disassembly +2. diff the build against the retail ROM ← this tool +3. fix the banks that disagree +4. go to 1, until nothing disagrees +``` + +Exit status is **0 when the ROMs are identical** and **1 when they differ**, so +it drops straight into that loop: + +```bash +make && ./gbromdiff.py build.gb retail.gb || echo "not there yet" +``` + +## Usage + +```bash +./gbromdiff.py A.gb B.gb # bank-by-bank summary +./gbromdiff.py A.gb B.gb --regions # contiguous differing runs +./gbromdiff.py A.gb B.gb --sym pokeyellow.sym # name the symbols involved +./gbromdiff.py A.gb B.gb --json # machine-readable +``` + +With an rgbds `.sym` file it names the symbol each differing region falls +inside, which turns + +``` +0x0703A1 differs +``` + +into + +``` +0x0703A1-0x0703B4 bank 0x1C 20 bytes TextPredef+0x3A1 +``` + +`--gap N` controls how many matching bytes are tolerated inside one region +(default 16). Without it, a shifted pointer table reports as hundreds of +one-byte findings instead of one region worth looking at. + +No dependencies beyond Python 3. + +## What this does not do + +It does not build anything, and it does not write a disassembly. It tells you +where two ROMs differ. Turning that into a source tree that rebuilds a +localised ROM byte-for-byte is the actual project; this is the instrument you +point at it between iterations. + +## Context + +The Spanish Red and Blue releases are supported in +[gen1recomp](https://github.com/bryanthaboi/gen1recomp) by way of +[einstein95/pokered-es](https://github.com/einstein95/pokered-es), a +shift-matching disassembly whose `.sym` files provide Spanish addresses for +every symbol. + +Spanish **Yellow** has no equivalent, so it cannot be supported the same way. +French (`Narishma-gb/pokeyellow-fr`) and German (`Brianum/pokeyellow-de`) +Yellow disassemblies both exist, so adapting pret's Yellow to a European +release is demonstrably possible — it simply has not been done for Spanish. +This tool exists to make that attempt less tedious. + +MIT licensed. Contributions welcome, particularly from anyone actually +attempting `pokeyellow-es`. diff --git a/tools/gbromdiff/gbromdiff.py b/tools/gbromdiff/gbromdiff.py new file mode 100755 index 00000000..ded86510 --- /dev/null +++ b/tools/gbromdiff/gbromdiff.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +"""Compare two Game Boy ROMs bank by bank, and say where they disagree. + +Written for the problem of bringing a localised Gen 1 release into a +disassembly. The European Pokemon releases are rebuilds of their US +counterparts: most banks are byte-identical, the text banks are wholly +different, and a handful of code banks are the US code with shifted +pointers. Knowing WHICH is which is the first thing you need and the +last thing anyone writes down. + +The loop this is built for: + + 1. build the disassembly + 2. diff the build against the retail ROM + 3. fix the banks that disagree + 4. go to 1, until nothing disagrees + +Step 2 is this script. With a .sym file it also names the symbols that +live inside each differing region, which turns "bank 0x1C differs at +0x4A31" into "bank 0x1C differs, starting inside TextPredef". + +Usage: + + gbromdiff.py A.gb B.gb bank-by-bank summary + gbromdiff.py A.gb B.gb --regions contiguous differing runs + gbromdiff.py A.gb B.gb --sym pokeyellow.sym name the symbols involved + gbromdiff.py A.gb B.gb --json machine-readable + +Exit status is 0 when the ROMs are identical, 1 when they differ, 2 on a +usage error -- so it can drive a build loop directly. +""" + +import argparse +import json +import os +import sys + +BANK_SIZE = 0x4000 + + +def load(path): + with open(path, "rb") as fh: + return fh.read() + + +def header(rom): + """Title, CGB flag and global checksum, straight out of the cartridge + header. Useful for saying WHICH releases are being compared without + the caller having to know the hashes.""" + if len(rom) < 0x150: + return {} + title = rom[0x134:0x143].split(b"\x00")[0] + try: + title = title.decode("ascii", "replace").strip() + except Exception: + title = repr(title) + return { + "title": title, + "cgb": rom[0x143], + "rom_size_code": rom[0x148], + "global_checksum": (rom[0x14E] << 8) | rom[0x14F], + } + + +def banks(rom): + return (len(rom) + BANK_SIZE - 1) // BANK_SIZE + + +def bank_report(a, b): + """Per-bank identical / differs / missing, with a byte count.""" + out = [] + for i in range(max(banks(a), banks(b))): + lo, hi = i * BANK_SIZE, (i + 1) * BANK_SIZE + ba, bb = a[lo:hi], b[lo:hi] + if not ba or not bb: + out.append({"bank": i, "state": "missing", + "in_a": bool(ba), "in_b": bool(bb)}) + continue + if ba == bb: + out.append({"bank": i, "state": "identical", "differing": 0}) + continue + n = sum(1 for x, y in zip(ba, bb) if x != y) + # A bank that differs in nearly every byte is a different payload + # (translated text); one that differs in a scatter of bytes is the + # same code with pointers moved. That distinction is the whole + # reason to look at a percentage rather than a boolean. + pct = 100.0 * n / min(len(ba), len(bb)) + out.append({"bank": i, "state": "differs", "differing": n, + "percent": round(pct, 2), + "shape": "replaced" if pct > 60 else + ("patched" if pct < 5 else "mixed")}) + return out + + +def regions(a, b, gap=16): + """Contiguous runs of differing bytes, merging runs separated by fewer + than `gap` matching bytes -- otherwise a shifted pointer table reads as + hundreds of one-byte findings instead of one region.""" + out = [] + n = min(len(a), len(b)) + start = None + last = None + for i in range(n): + if a[i] != b[i]: + if start is None: + start = i + elif last is not None and i - last > gap: + out.append((start, last)) + start = i + last = i + if start is not None: + out.append((start, last)) + if len(a) != len(b): + out.append((n, max(len(a), len(b)) - 1)) + return out + + +def load_symbols(path): + """An rgbds .sym file: `BB:AAAA Name` per line. Returned as a list of + (absolute_offset, bank, addr, name), sorted, so a region can be mapped + to whatever symbol most recently preceded it.""" + syms = [] + with open(path, "r", encoding="utf-8", errors="replace") as fh: + for line in fh: + line = line.split(";")[0].strip() + if not line or ":" not in line: + continue + try: + where, name = line.split(None, 1) + bank_s, addr_s = where.split(":") + bank, addr = int(bank_s, 16), int(addr_s, 16) + except ValueError: + continue + # bank 0 is 0000-3FFF; every other bank is paged in at 4000 + offset = addr if bank == 0 else bank * BANK_SIZE + (addr - 0x4000) + syms.append((offset, bank, addr, name.strip())) + syms.sort() + return syms + + +def symbol_before(syms, offset): + """The last symbol at or before `offset` -- i.e. the thing this byte is + most likely part of. Binary search over the sorted table.""" + lo, hi = 0, len(syms) - 1 + best = None + while lo <= hi: + mid = (lo + hi) // 2 + if syms[mid][0] <= offset: + best = syms[mid] + lo = mid + 1 + else: + hi = mid - 1 + return best + + +def main(): + ap = argparse.ArgumentParser( + description="Compare two Game Boy ROMs bank by bank.") + ap.add_argument("rom_a") + ap.add_argument("rom_b") + ap.add_argument("--regions", action="store_true", + help="list contiguous differing runs, not just banks") + ap.add_argument("--sym", help="rgbds .sym file, to name the symbols " + "each differing region falls inside") + ap.add_argument("--gap", type=int, default=16, + help="matching bytes tolerated inside one region " + "(default 16)") + ap.add_argument("--limit", type=int, default=40, + help="max regions to print (default 40)") + ap.add_argument("--json", action="store_true") + args = ap.parse_args() + + for p in (args.rom_a, args.rom_b): + if not os.path.isfile(p): + print(f"error: no such file: {p}", file=sys.stderr) + return 2 + + a, b = load(args.rom_a), load(args.rom_b) + rep = bank_report(a, b) + same = [r for r in rep if r["state"] == "identical"] + diff = [r for r in rep if r["state"] == "differs"] + + result = { + "a": {"path": args.rom_a, "bytes": len(a), **header(a)}, + "b": {"path": args.rom_b, "bytes": len(b), **header(b)}, + "banks_total": len(rep), + "banks_identical": len(same), + "banks_differing": len(diff), + "banks": rep, + } + + if args.regions or args.sym: + regs = regions(a, b, args.gap) + syms = load_symbols(args.sym) if args.sym else None + listed = [] + for start, end in regs[: args.limit]: + item = {"start": start, "end": end, "length": end - start + 1, + "bank": start // BANK_SIZE} + if syms: + s = symbol_before(syms, start) + if s: + item["symbol"] = s[3] + item["symbol_offset"] = start - s[0] + listed.append(item) + result["regions_total"] = len(regs) + result["regions"] = listed + + if args.json: + print(json.dumps(result, indent=2)) + return 0 if not diff and len(a) == len(b) else 1 + + ha, hb = result["a"], result["b"] + print(f"A {ha.get('title','?'):<16} {len(a):>8} bytes {args.rom_a}") + print(f"B {hb.get('title','?'):<16} {len(b):>8} bytes {args.rom_b}") + print() + if not diff and len(a) == len(b): + print(f"IDENTICAL — all {len(rep)} banks match") + return 0 + + print(f"{len(same)}/{len(rep)} banks identical, {len(diff)} differ") + print() + print(" bank differing bytes shape") + for r in diff: + if r["state"] != "differs": + continue + print(f" 0x{r['bank']:02X} {r['differing']:>6} " + f"({r['percent']:>5.1f}%) {r['shape']}") + print() + print(" replaced = a different payload (translated text)") + print(" patched = the same code with a few values moved") + + if "regions" in result: + print() + print(f"{result['regions_total']} differing regions " + f"(showing {len(result['regions'])}):") + for item in result["regions"]: + line = (f" 0x{item['start']:06X}-0x{item['end']:06X} " + f"bank 0x{item['bank']:02X} {item['length']:>6} bytes") + if "symbol" in item: + line += f" {item['symbol']}+0x{item['symbol_offset']:X}" + print(line) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/make_rom_manifest.py b/tools/make_rom_manifest.py index 031a849f..1b4ce7b4 100755 --- a/tools/make_rom_manifest.py +++ b/tools/make_rom_manifest.py @@ -567,6 +567,7 @@ FIELD_ASSET_SYMBOLS = { "FightIntroFrontMon", "FightIntroFrontMon2", "FightIntroFrontMon3", + "GameBoyTiles", "GameFreakIntro", "GameFreakLogoGraphics", "GengarIntroTiles1", @@ -575,6 +576,7 @@ FIELD_ASSET_SYMBOLS = { "HappyEmote", "HpBarAndStatusGraphics", "LedgeHoppingShadow", + "LinkCableTiles", "MoveAnimationTiles1", "NintendoCopyrightLogoGraphics", "PlayerCharacterTitleGraphics", @@ -593,6 +595,9 @@ FIELD_ASSET_SYMBOLS = { "SlotMachineTiles2", "TheEndGfx", "TownMapCursor", + "TradeBubbleIconGFX", + "TradingAnimationGraphics", + "TradingAnimationGraphics2", "Version_GFX", "WorldMapTileGraphics", } diff --git a/tools/make_yellow_manifest.py b/tools/make_yellow_manifest.py index 4e0e3782..de491f63 100755 --- a/tools/make_yellow_manifest.py +++ b/tools/make_yellow_manifest.py @@ -481,6 +481,12 @@ def derive(red, pokeyellow, symbols_path): for i, name in enumerate(yellow_bubbles)], } + # The Oak-speech show-off mon is the player's Pikachu in Yellow + # (engine/battle/core.asm BATTLE_TYPE_PIKACHU / the ProfOak demo); + # the deep-copied Red field.oakSpeech has no demoSpecies, so stamp + # it or the import falls back to NIDORINO (#915). + yellow["field"]["oakSpeech"]["demoSpecies"] = "PIKACHU" + # Ensure Melanie / Summer Beach town-map entries exist after rebuild. locations = yellow["field"]["townMap"]["locations"] if "CERULEAN_MELANIES_HOUSE" not in locations \ diff --git a/tools/modkit.py b/tools/modkit.py index fb0b9149..09f868a5 100644 --- a/tools/modkit.py +++ b/tools/modkit.py @@ -7,7 +7,7 @@ Subcommands: scaffold [--profile content|overhaul|total_conversion] [--api 2] [--github owner/repo] [--experimental] [--dest DIR] [--force] translation [--language NAME] [--base auto|fixture|imported] - [--refresh] [--dest DIR] + [--refresh] [--dest DIR] [--pixel-font] validate [--strict] [--base auto|fixture|imported] lint pack [-o out.modpkg] @@ -528,6 +528,7 @@ def cmd_scaffold(args, repo): DRIVER_TEMPLATE = """-- generated by tools/modkit.py; drives the real loader headlessly package.path = "./?.lua;./?/init.lua;" .. package.path +love = require("tests.love_stub") local data = %s local FILES = %s local overlay = {} @@ -715,7 +716,7 @@ def run_loader(repo, mod_dir, findings, base="fixture", notes=None): driver_path = handle.name try: proc = subprocess.run([LUAJIT, driver_path], cwd=repo, - capture_output=True, text=True, timeout=120) + capture_output=True, text=True, encoding="utf-8", timeout=120) except FileNotFoundError: findings.append(Finding("MK100", "error", f"cannot run {LUAJIT} (install luajit or " @@ -1055,7 +1056,7 @@ def check_data_dump(repo, path, base, rel): driver = DUMP_DRIVER % (lua_quote(path), lua_quote(vanilla)) try: proc = subprocess.run([LUAJIT, "-e", driver], cwd=repo, - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, encoding="utf-8", timeout=60) except FileNotFoundError: # the gate must fail closed: a missing interpreter is a broken # environment, not a clean mod @@ -1084,6 +1085,20 @@ def cmd_lint(args, repo): # ---------------------------------------------------------------- pack +def pack_timestamp(): + raw = os.environ.get("SOURCE_DATE_EPOCH") + if raw is None: + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), None + try: + epoch = int(raw, 10) + if epoch < 0: + raise ValueError("negative epoch") + stamp = datetime.fromtimestamp(epoch, timezone.utc) + except (ValueError, OverflowError, OSError): + return None, "SOURCE_DATE_EPOCH must be a nonnegative Unix timestamp" + return stamp.strftime("%Y-%m-%dT%H:%M:%SZ"), None + + def cmd_pack(args, repo): mod_dir = resolve_mod_dir(repo, args.mod) if not mod_dir: @@ -1118,6 +1133,10 @@ def cmd_pack(args, repo): mod_id = manifest["id"] version = manifest.get("version", "0.0.0") out = args.output or f"{mod_id}-{version}.modpkg" + packed_at, timestamp_problem = pack_timestamp() + if timestamp_problem: + print(f"modkit: {timestamp_problem}") + return 2 files = mod_files(mod_dir) records = [] for rel in files: @@ -1126,8 +1145,7 @@ def cmd_pack(args, repo): "sha256": hashlib.sha256(body).hexdigest()}) pack_meta = { "modkit": MODKIT_VERSION, - "packed_at": datetime.now(timezone.utc) - .strftime("%Y-%m-%dT%H:%M:%SZ"), + "packed_at": packed_at, "id": mod_id, "version": version, "api": manifest.get("api", 1), @@ -1398,7 +1416,7 @@ def dump_dataset(repo, base): handle.close() try: proc = subprocess.run([os.environ.get("LUA", "luajit"), handle.name], - cwd=repo, capture_output=True, text=True) + cwd=repo, capture_output=True, text=True, encoding="utf-8") finally: os.unlink(handle.name) if proc.returncode != 0: @@ -1475,6 +1493,17 @@ return function(mod) end -- ---- glyphs ------------------------------------------------------- + -- Text rendering through the bundled Plain Pixel TTF ("Plain Pixel + -- Font" by Douglas Vautour (Burpy Fresh), CC-BY 4.0 -- see + -- assets/fonts/plainpixel/README.md). Registered, it replaces the tile + -- font for ordinary characters, so a translation needs no glyph sheet + -- at all; box borders and -style macros keep their tiles. Options: + -- { file = mod.assets:path("myfont.ttf"), size = 15, spacing = 0, + -- yOffset = -6, bold = true } -- size is the font's design em (Plain + -- Pixel only rasterizes cleanly at multiples of 15), bold thickens a + -- 1px-stroke font that reads too light. + {{ttf_register}}mod.content.font:register("ttf", {}) + -- Register the sheet BEFORE anything asks for a glyph on it. base is -- the first code the page owns; 0x100 and up is free space above the -- vanilla pages, so a new alphabet never collides with them. @@ -1567,6 +1596,14 @@ you can translate straight from it. ## Start with the font, not the text +The fast path: scaffold with `--pixel-font` (or uncomment the +`mod.content.font:register("ttf", {})` line in `main.lua`) and the game +renders text through the engine's bundled Plain Pixel TTF, which already +covers Latin with diacritics, Cyrillic, kana and CJK. No glyph sheet, no +charmap; `lang/font.lua` and `lang/charmap.lua` can stay empty. The rest +of this section is for translations that want the hand-drawn tile look +instead. + The engine draws from **glyph pages**: an image of 8x8 cells plus a charmap saying which byte sequence draws which cell. The vanilla pages sit at `$60` and `$80`. Anything from `0x100` up is free, so a new alphabet is added @@ -1665,7 +1702,13 @@ Nothing is translated yet: {{total}} strings are waiting in `lang/`. - `assets/font/` - your glyph sheet ''' -FONT_README = '''Put your glyph sheet here. +FONT_README = '''You may not need this directory at all: scaffold with +`--pixel-font` (or uncomment the `register("ttf", {})` line in main.lua) +and text renders through the engine's bundled Plain Pixel TTF, which +covers Latin, kana and CJK out of the box. A glyph sheet is only for a +translation that wants the hand-drawn GB look. + +Put your glyph sheet here. A page is a PNG of 8x8 cells, 16 per row by default, black on white. Codes run left to right and top to bottom starting at the page's `base`, so the @@ -1761,6 +1804,7 @@ def cmd_translation(args, repo): "{{extra}}": "", "{{github_line}}": "", "{{experimental}}": "false", + "{{ttf_register}}": "" if args.pixel_font else "-- ", "{{total}}": str(sum(counts.values())), "{{table}}": "\n".join( f"| `lang/{name}.lua` | {counts[name]} |" for name, *_ in catalogs), @@ -1999,6 +2043,9 @@ def main(argv): p.add_argument("--dest") p.add_argument("--base", default="auto", choices=["auto", "fixture", "imported"]) + p.add_argument("--pixel-font", action="store_true", + help="render text through the bundled Plain Pixel TTF " + "instead of the tile font (no glyph sheet needed)") p.add_argument("--refresh", action="store_true", help="re-harvest the catalogs, keeping existing work") p.add_argument("--force", action="store_true") diff --git a/tools/rom_manifest.json b/tools/rom_manifest.json index df06f74e..b8233558 100644 --- a/tools/rom_manifest.json +++ b/tools/rom_manifest.json @@ -20442,6 +20442,10 @@ 19, 21537 ], + "GameBoyTiles": [ + 30, + 23584 + ], "GameCornerPrizeRoom_h": [ 18, 20708 @@ -20818,6 +20822,10 @@ 9, 21671 ], + "LinkCableTiles": [ + 30, + 23632 + ], "LoreleiPic": [ 19, 30585 @@ -22122,10 +22130,22 @@ 28, 20288 ], + "TradeBubbleIconGFX": [ + 28, + 23129 + ], "TradeCenter_h": [ 19, 32004 ], + "TradingAnimationGraphics": [ + 14, + 27070 + ], + "TradingAnimationGraphics2": [ + 14, + 27854 + ], "TrainerAI": [ 14, 25902 diff --git a/tools/rom_manifest_blue.json b/tools/rom_manifest_blue.json index 29ea88c7..753db374 100644 --- a/tools/rom_manifest_blue.json +++ b/tools/rom_manifest_blue.json @@ -20419,6 +20419,10 @@ 19, 21537 ], + "GameBoyTiles": [ + 30, + 23584 + ], "GameCornerPrizeRoom_h": [ 18, 20708 @@ -20795,6 +20799,10 @@ 9, 21671 ], + "LinkCableTiles": [ + 30, + 23632 + ], "LoreleiPic": [ 19, 30585 @@ -22099,10 +22107,22 @@ 28, 20288 ], + "TradeBubbleIconGFX": [ + 28, + 23129 + ], "TradeCenter_h": [ 19, 32004 ], + "TradingAnimationGraphics": [ + 14, + 27070 + ], + "TradingAnimationGraphics2": [ + 14, + 27854 + ], "TrainerAI": [ 14, 25902 diff --git a/tools/rom_manifest_yellow.json b/tools/rom_manifest_yellow.json index d62595fa..9fa9e696 100644 --- a/tools/rom_manifest_yellow.json +++ b/tools/rom_manifest_yellow.json @@ -6566,6 +6566,7 @@ } ], "oakSpeech": { + "demoSpecies": "PIKACHU", "shrink1": "assets/generated/intro/shrink1.png", "shrink2": "assets/generated/intro/shrink2.png" }, @@ -21703,6 +21704,10 @@ 19, 21537 ], + "GameBoyTiles": [ + 30, + 23932 + ], "GameCornerBeauty1Text": [ 18, 19656 @@ -22251,6 +22256,10 @@ 9, 21604 ], + "LinkCableTiles": [ + 30, + 23980 + ], "LoreleiPic": [ 19, 30454 @@ -26371,6 +26380,10 @@ 28, 20420 ], + "TradeBubbleIconGFX": [ + 28, + 23302 + ], "TradeCenterOpponentText": [ 19, 32451 @@ -26379,6 +26392,14 @@ 19, 32377 ], + "TradingAnimationGraphics": [ + 14, + 27240 + ], + "TradingAnimationGraphics2": [ + 14, + 28024 + ], "TrainerAI": [ 14, 26034 diff --git a/tools/save-editor/App.lua b/tools/save-editor/App.lua index 7173ab94..7b5d3c21 100644 --- a/tools/save-editor/App.lua +++ b/tools/save-editor/App.lua @@ -36,6 +36,7 @@ local MapBrowser = require("MapBrowser") local Dex = require("Dex") -- chrome, not a tab panel, so deliberately kept out of PANELS below (#541) local SpeciesPicker = require("SpeciesPicker") +local ItemPicker = require("ItemPicker") local App = {} local S @@ -341,6 +342,37 @@ function App.update(dt) if notches ~= 0 then App.wheelmoved(0, notches) end + + -- Dev harness, the launcher's POKEPORT_LAUNCHER_SHOT for this window: + -- POKEPORT_EDITOR_SHOT=/path.png with POKEPORT_WIN=WxH resizes, lets the + -- view settle, captures one frame and quits, so a scripted run can see the + -- real editor at any window shape. POKEPORT_EDITOR_TAB picks the tab and + -- POKEPORT_EDITOR_ITEMPICK=1 opens the add-item modal. + local shot = os.getenv("POKEPORT_EDITOR_SHOT") + if shot and not App._shotDone then + if not App._shotSized then + App._shotSized = true + local w, h = (os.getenv("POKEPORT_WIN") or ""):match("^(%d+)x(%d+)$") + if w and love.window and love.window.setMode then + pcall(love.window.setMode, tonumber(w), tonumber(h), { resizable = true }) + end + local tab = os.getenv("POKEPORT_EDITOR_TAB") + if tab and tab ~= "" and S then S.tab = tab end + if os.getenv("POKEPORT_EDITOR_ITEMPICK") == "1" and S then + Ops.openItemPicker(S, Kit, "bag") + end + end + App._shotTimer = (App._shotTimer or 0) + dt + if App._shotTimer > 1.0 then + App._shotDone = true + love.graphics.captureScreenshot(function(imagedata) + local fd = imagedata:encode("png") + local f = io.open(shot, "wb") + if f then f:write(fd:getString()) f:close() end + love.event.quit() + end) + end + end end function App.mousepressed(x, y, button) @@ -750,7 +782,7 @@ function App.draw() -- last: the chrome and the panel underneath would take the same tap. The -- shield goes up before anything dispatches and comes down only for the -- picker's own layer at the bottom of this function (#541). - Kit.blockClicks = (S.speciesPicker ~= nil) + Kit.blockClicks = (S.speciesPicker ~= nil) or (S.itemPicker ~= nil) Theme.field(width, height) @@ -779,6 +811,7 @@ function App.draw() drawStatusBar(0, height - statusH, width, statusH) Kit.blockClicks = false SpeciesPicker.draw(S, Kit, width, height) + ItemPicker.draw(S, Kit, width, height) Kit.endFrame() PadInput.draw() @@ -791,6 +824,15 @@ function App.keypressed(key) -- The picker takes Enter and Escape before the focused field does: Kit maps -- both to the same "\r" edit (a blur), which cannot tell "commit the top -- match" apart from "give up" (#541). + if S.itemPicker then + if key == "return" or key == "kpenter" then + ItemPicker.commitFirst(S, Kit) + return + elseif key == "escape" then + Ops.closeItemPicker(S, Kit) + return + end + end if S.speciesPicker then if key == "return" or key == "kpenter" then SpeciesPicker.commitFirst(S, Kit) @@ -800,6 +842,24 @@ function App.keypressed(key) return end end + -- The inspector's nickname field is a commit-on-Enter field, unlike the + -- search fields, which are live view state. Enter commits the draft through + -- Ops and blurs; Escape discards it and blurs. Both must run before + -- Kit.keypressed, which maps return/escape to the same "\r" edit and cannot + -- tell "commit" from "cancel". + if Kit.focus == "mon-nickname" then + if key == "return" or key == "kpenter" then + if S.editingMon and Ops.setNickname(S, S.editingMon, S.nicknameDraft) then + S.nicknameDraft = S.editingMon.nickname or "" + end + Kit.blur() + return + elseif key == "escape" then + Kit.blur() + if S.editingMon then S.nicknameDraft = S.editingMon.nickname or "" end + return + end + end -- A focused text field eats the keys it cares about (typing "s" into the -- map filter must not trigger Save). if Kit.keypressed(key) then return end diff --git a/tools/save-editor/Kit.lua b/tools/save-editor/Kit.lua index d542ed54..2197d7a4 100644 --- a/tools/save-editor/Kit.lua +++ b/tools/save-editor/Kit.lua @@ -309,20 +309,18 @@ end -- good green tint -- safe helpers (Full heal, max a DV) -- danger red tint -- destructive verbs, always two-click -- disabled steel -- never hidden, always explained in the status bar +-- Colour-coded solid keys, matching the launcher exactly (src/ui/kit/Kit.lua): +-- the button IS its colour, with black ink and a two-rect emboss, and hover +-- rings it in white. The editor opens straight off a launcher save row, so a +-- control that behaves the same has to look the same. local KINDS = { - primary = { fillTop = PAL.green, fillBot = PAL.greenDark, aTop = 1, aBot = 1, - ink = PAL.greenInk, border = nil, glow = PAL.green }, - ghost = { fillTop = { 255, 255, 255 }, fillBot = { 255, 255, 255 }, - aTop = 0.14, aBot = 0.03, ink = PAL.heading, - border = { 255, 255, 255 }, borderA = 0.18 }, - accent = { flat = PAL.blue, flatA = 0.14, ink = PAL.blueInk, - border = PAL.cardBorder, borderA = 0.35 }, - good = { flat = PAL.green, flatA = 0.1, ink = PAL.green, - border = PAL.green, borderA = 0.45 }, - danger = { flat = PAL.red, flatA = 0.12, ink = PAL.redSoft, - border = PAL.red, borderA = 0.45 }, - disabled = { flat = { 120, 132, 158 }, flatA = 0.22, ink = PAL.steel, - border = PAL.steel, borderA = 0.3 }, + primary = { fill = PAL.green, ink = PAL.inverse }, + good = { fill = PAL.green, ink = PAL.inverse }, + accent = { fill = PAL.blue, ink = PAL.inverse }, + warn = { fill = PAL.yellow, ink = PAL.inverse }, + danger = { fill = PAL.red, ink = PAL.inverse }, + ghost = { fill = PAL.ink, ink = PAL.inverse }, + disabled = { fill = PAL.steel, ink = PAL.inverse }, } -- opts: { kind, font, enabled, align, radius, glow } @@ -338,31 +336,30 @@ function Kit.button(x, y, w, h, label, opts) local hot = enabled and Kit.hover(x, y, w, h) if G then - if opts.glow and enabled then - Theme.glow(x, y, w, h, r, kind.glow or PAL.green, opts.glow) - end - if kind.flat then - Theme.col(kind.flat, kind.flatA * (hot and 1.6 or 1)) - G.rectangle("fill", x, y, w, h, r, r) - else - Theme.gradRounded(x, y, w, h, r, kind.fillTop, kind.fillBot, - kind.aTop * (hot and 1.4 or 1), kind.aBot * (hot and 1.6 or 1)) - end - if kind.border then - Theme.stroke(x, y, w, h, r, kind.border, kind.borderA * (hot and 1.5 or 1), 1) + Theme.fillRounded(x, y, w, h, kind.fill, enabled and 1 or 0.45) + Theme.emboss(x, y, w, h, enabled and (hot and 1.3 or 1) or 0.4) + if hot then + Theme.strokeRounded(x - 2, y - 2, w + 4, h + 4, PAL.lineStrong, 1, 2, + Theme.radius() + 2) end local f = font(opts.font or "button") if f then G.setFont(f) Theme.col(kind.ink, 1) local ty = y + (h - f:getHeight()) / 2 - if opts.align == "left" then - G.print(label, x + 10 * Kit.scale, ty) - elseif canPrintf() then - G.printf(label, x, ty, w, "center") - else - G.print(label, x + (w - f:getWidth(label)) / 2, ty) + -- Faux bold, the same trick the launcher uses: one weight of face, so + -- an emphasised run is the same text drawn a pixel across. + local function put(dx) + if opts.align == "left" then + G.print(label, x + 10 * Kit.scale + dx, ty) + elseif canPrintf() then + G.printf(label, x + dx, ty, w, "center") + else + G.print(label, x + (w - f:getWidth(label)) / 2 + dx, ty) + end end + put(0) + put(Theme.BOLD_OFFSET or 1) end end return enabled and Kit.press(x, y, w, h) or false @@ -383,12 +380,21 @@ function Kit.chip(x, y, w, h, label, on, onColor, offColor) audit("control", x, y, w, h, label) local c = on and (onColor or PAL.green) or (offColor or PAL.steel) if G then - local r = 6 * Kit.scale - Theme.col(c, on and 0.16 or 0.06) - G.rectangle("fill", x, y, w, h, r, r) - Theme.stroke(x, y, w, h, r, PAL.cardBorder, Kit.hover(x, y, w, h) and 0.5 or 0.28, 1) - Kit.textCenter("micro", label, x, y + (h - Kit.textHeight("micro")) / 2, w, - c, on and 1 or 0.75) + -- Same rule as the launcher's chips: an ON chip is FILLED with its colour + -- and prints black; an OFF chip is an outline. The old alpha-tinted fill + -- read as "slightly different dark rectangle" on a black field. + local hot = Kit.hover(x, y, w, h) + if on then + Theme.fillRounded(x, y, w, h, c, 1) + Theme.emboss(x, y, w, h, 1) + Kit.textCenter("micro", label, x, + y + (h - Kit.textHeight("micro")) / 2, w, PAL.inverse) + else + Theme.fillRounded(x, y, w, h, PAL.bg, 1) + Theme.strokeRounded(x, y, w, h, c, hot and 1 or 0.5, 1) + Kit.textCenter("micro", label, x, + y + (h - Kit.textHeight("micro")) / 2, w, c) + end end return Kit.press(x, y, w, h) end @@ -420,7 +426,12 @@ end -- state because Kit had no input widget; this replaces that hack, and App -- routes love.textinput / love.keypressed in through Kit.textinput / -- Kit.keypressed. Returns the (possibly edited) value; the caller stores it. -function Kit.textfield(id, x, y, w, h, value, placeholder) +-- +-- `opts.sanitize(value)` (optional) is a post-filter run on the merged text +-- right after this frame's edits and BEFORE it draws, so a keystroke or paste +-- the filter rejects never even flashes on screen. It gets the whole value +-- because a paste arrives as one textinput chunk alongside existing text. +function Kit.textfield(id, x, y, w, h, value, placeholder, opts) audit("control", x, y, w, h, id) value = tostring(value or "") if Kit.press(x, y, w, h) then Kit.focus = id end @@ -438,6 +449,9 @@ function Kit.textfield(id, x, y, w, h, value, placeholder) value = value .. e end end + if opts and opts.sanitize then + value = opts.sanitize(value) + end end if G then local r = 8 * Kit.scale diff --git a/tools/save-editor/Ops.lua b/tools/save-editor/Ops.lua index fb367806..5da44907 100644 --- a/tools/save-editor/Ops.lua +++ b/tools/save-editor/Ops.lua @@ -16,12 +16,17 @@ local PartyMod = require("src.pokemon.Party") local BoxesMod = require("src.pokemon.Boxes") local Bag = require("src.inventory.Bag") local MonOps = require("MonOps") +local Charmap = require("src.save_convert.data.charmap") local Ops = {} Ops.MONEY_MAX = 999999 Ops.STACK_MAX = 99 Ops.ARM_SECONDS = 2.5 +-- The in-game naming screen caps a nickname at 10 glyphs +-- (BattleState:askNicknameUI / src/ui/NamingScreen.lua maxLen = 10); the +-- editor mirrors that cap instead of inventing its own. +Ops.NICKNAME_MAX = 10 local function clamp(n, lo, hi) if n < lo then return lo end @@ -266,6 +271,36 @@ function Ops.openSpeciesPicker(S, Kit) return true end +-- The item catalog minus the badges, which are toggles on their own row and +-- would otherwise be "addable" into the bag as ordinary items. +function Ops.itemSearch(S, query) + query = tostring(query or ""):lower():gsub("^%s+", ""):gsub("%s+$", "") + local out = {} + for _, id in ipairs(S.cat.items) do + if not Ops.isBadgeId(id) + and (query == "" or id:lower():find(query, 1, true)) then + out[#out + 1] = id + end + end + return out +end + +-- `dest` is "bag" or "pc"; the picker can flip it while open. `opened` +-- marks the frame it went up, so the click that opened it is not also read +-- as a tap outside (the same rule the species picker follows). +function Ops.openItemPicker(S, Kit, dest) + S.itemPicker = { query = "", offset = 0, opened = true, + dest = dest or "bag" } + -- focus the field on open so the mobile soft keyboard rises with it (#529) + if Kit then Kit.focus = "item-picker" end + return true +end + +function Ops.closeItemPicker(S, Kit) + S.itemPicker = nil + if Kit and Kit.blur then Kit.blur() end +end + function Ops.closeSpeciesPicker(S, Kit) S.speciesPicker = nil if Kit and Kit.blur then Kit.blur() end @@ -370,6 +405,154 @@ function Ops.healMon(S, mon) return Ops.mark(S, ("Healed %s to %d/%d HP"):format(mon.species, mon.hp, mon.stats.hp)) end +-- ----------------------------------------------------------------- nicknames +-- Gen1 has no "is nicknamed" bit: an un-nicknamed mon is mon.nickname == nil, +-- and every display site reads `mon.nickname or def.name` +-- (src/save_convert/GenSave.lua). The editor edits that field directly. + +-- The byte length of the UTF-8 glyph starting at lead byte `b`. Self-contained +-- so this (and eachGlyph) also runs headless under luajit, which has no `utf8` +-- standard library. +local function glyphByteLen(b) + if b < 0x80 then return 1 end + if b < 0xE0 then return 2 end + if b < 0xF0 then return 3 end + return 4 +end + +-- Walk `name` one UTF-8 glyph at a time; fn(glyph) returning false stops the +-- walk early and eachGlyph returns false. Returns true when every glyph was +-- visited. The single place that walks a name, so the count / validate / +-- sanitize paths cannot drift apart (a glyph is "é" or "♂", not one of its +-- bytes, exactly as the naming screen counts its grid cells). +local function eachGlyph(name, fn) + local i, n = 1, #name + while i <= n do + local b = name:byte(i) + local ch = name:sub(i, i + glyphByteLen(b) - 1) + if fn(ch) == false then return false end + i = i + #ch + end + return true +end + +-- Glyph count, not byte count: "é" or "♂" is ONE game character, exactly as +-- the naming screen counts its grid cells and GenSave.encodeName counts a +-- charmap sequence. +function Ops.nicknameLength(name) + local n = 0 + eachGlyph(tostring(name or ""), function() n = n + 1 end) + return n +end + +-- The set of glyphs a nickname may hold: present in BOTH the Gen1 text codec +-- charmap (so the name round-trips through a .sav) and the game's font +-- charmap (so it actually draws). The codec alone is not enough: "@" is the +-- string-terminator byte, and "#" plus the dakuten kana have codec entries +-- but no font tile, so Font.encode (src/render/Font.lua) draws them as a +-- space -- an invisible nickname. Only single-codepoint entries qualify: +-- multi-character macros ("", the 'd ligature) cannot be typed one +-- character at a time, so they have no place in the input gate. +-- Built once per loaded font table (a mod replacing the font rebuilds it); +-- falls back to the codec-only set when no font data is loaded (headless +-- suites that never call Data:load). +local glyphCache, glyphCacheFont +local function nameGlyphSet(S) + local font = S and S.data and S.data.font + if not (font and font.charmap) then return Charmap.byToken end + if glyphCache and glyphCacheFont == font then return glyphCache end + local set = {} + for _, e in ipairs(font.charmap) do + local s = e.seq + if type(s) == "string" and s ~= "" and Charmap.byToken[s] + and #s == glyphByteLen(s:byte(1)) then + set[s] = true + end + end + glyphCache, glyphCacheFont = set, font + return set +end + +-- True when every glyph is a legal nickname glyph (see nameGlyphSet): the +-- name can be stored in a .sav AND draws in the game. Anything else either +-- encodes as "?" (GenSave.encodeName) or renders as a space (Font.encode), +-- which the user did not ask for, so it is refused rather than mangled. +function Ops.nicknameUsable(S, name) + local set = nameGlyphSet(S) + return eachGlyph(tostring(name or ""), function(ch) + return set[ch] ~= nil + end) +end + +-- The species' display name, what an un-nicknamed mon reads as. +local function speciesName(S, species) + local def = species and S.data.pokemon[species] + return (def and def.name) or tostring(species or "") +end + +-- The input gate for the inspector's nickname field. Given the whole draft +-- (existing text plus this frame's keystrokes and any paste), return the +-- version the game can actually hold: every glyph kept draws in the game +-- (see nameGlyphSet) and the result never exceeds the naming screen's +-- 10-glyph cap. Unrenderable glyphs are skipped, not used to abort the rest +-- of the string, so a paste of "PIKA€CHU" lands as "PIKACHU". The field runs +-- this through Kit.textfield's opts.sanitize, so a blocked character never +-- appears at all. +function Ops.nicknameSanitize(S, name) + local set = nameGlyphSet(S) + local out, count = {}, 0 + eachGlyph(tostring(name or ""), function(ch) + if count < Ops.NICKNAME_MAX and set[ch] then + out[#out + 1] = ch + count = count + 1 + end + end) + return table.concat(out) +end + +-- One verb for both writing and clearing. An empty field means "no nickname", +-- exactly like an empty confirm on the in-game naming screen (which falls +-- through to the species' standard name). A name that equals the species' +-- standard name is the un-nicknamed state in this save format +-- (importedNickname in GenSave.lua maps exactly that to nil), so it is +-- normalized to nil rather than stored as a literal copy of the default. +function Ops.setNickname(S, mon, name) + if not mon then return Ops.say(S, "Pick a slot first") end + name = tostring(name or "") + if name == "" then + return Ops.clearNickname(S, mon) + end + if name == mon.nickname then + return Ops.say(S, ("Already nicknamed %s"):format(name)) + end + if name == speciesName(S, mon.species) then + if mon.nickname == nil then + return Ops.say(S, ("%s is already un-nicknamed"):format(mon.species)) + end + mon.nickname = nil + return Ops.mark(S, ("%s matches its standard name; nickname cleared") + :format(name)) + end + if Ops.nicknameLength(name) > Ops.NICKNAME_MAX then + return Ops.say(S, ("Nicknames are capped at %d characters"):format(Ops.NICKNAME_MAX)) + end + if not Ops.nicknameUsable(S, name) then + return Ops.say(S, + "That name has characters the game cannot render or export cleanly") + end + mon.nickname = name + return Ops.mark(S, ("Nicknamed %s \"%s\""):format(mon.species, name)) +end + +function Ops.clearNickname(S, mon) + if not mon then return Ops.say(S, "Pick a slot first") end + if mon.nickname == nil then + return Ops.say(S, ("%s has no nickname to clear"):format(mon.species)) + end + mon.nickname = nil + return Ops.mark(S, ("Cleared %s's nickname"):format(mon.species)) +end + -- ------------------------------------------------------------------ boxes function Ops.boxes(S) return BoxesMod.ensure(S.save) @@ -687,6 +870,58 @@ function Ops.dexClear(S) return Ops.mark(S, "Pokedex wiped") end +-- ------------------------------------------------------------------ dex sort +-- The DEX grid's row order. Sorting is view-only: it never touches the save, +-- so the list itself is computed here (pure, testable) and the switch is +-- narrated through Ops.say, never Ops.mark. +-- +-- "dex" -- by Pokedex number (1-151), the panel default +-- "name" -- by display name, alphabetical (case-insensitive) +-- +-- A species whose record lacks the sort key (a partial mod record) sorts +-- last, ordered by its id, so the grid can never drop a row or crash. +-- table.sort is not stable, so every sort carries the id as a tiebreak and +-- the order is fully deterministic. +local SORT_KEYS = { + dex = function(def, id) + return def and def.dex or math.huge + end, + name = function(def, id) + local name = def and def.name + return (name and tostring(name):lower()) or tostring(id):lower() + end, +} + +function Ops.dexList(S) + local list = S and S.cat and S.cat.species + if not list then return {} end + local make = SORT_KEYS[S.dexSort == "name" and "name" or "dex"] + local data = S.data + local rows = {} + for _, id in ipairs(list) do + rows[#rows + 1] = { key = make(data and data.pokemon and data.pokemon[id], id), + id = id } + end + table.sort(rows, function(a, b) + if a.key ~= b.key then return a.key < b.key end + return a.id < b.id + end) + local out = {} + for i, r in ipairs(rows) do out[i] = r.id end + return out +end + +-- View-only verb: switching the DEX grid's order resets its scroll but never +-- dirties the save or narrates in the status bar (the active chip carries +-- the mode). Returns true when the mode changed, false on a no-op. +function Ops.dexSort(S, mode) + if mode ~= "name" and mode ~= "dex" then return false end + if S.dexSort == mode then return false end + S.dexSort = mode + S.dexOffset = 0 + return true +end + -- -------------------------------------------------------------------- map -- Outdoor is detected the way the game treats LAST_MAP sources: -- OVERWORLD/PLATEAU tilesets, maps with connections, or fly spots the save diff --git a/tools/save-editor/State.lua b/tools/save-editor/State.lua index 61f992db..258e32d2 100644 --- a/tools/save-editor/State.lua +++ b/tools/save-editor/State.lua @@ -43,6 +43,8 @@ function State.new() partyOffset = 0, -- roster scroll position (#715) inspectorScroll = 0, -- MonEditor body pixel scroll (#715) editingMon = nil, -- reference into party or a box + nicknameDraft = nil, -- text being typed in the inspector's nickname field + nicknameMon = nil, -- the mon the draft belongs to (nil for none) -- species picker overlay: nil when closed, otherwise { query, offset } -- plus mode = "box-add" when it is adding to a box instead of changing a -- species (Ops.openBoxAddPicker). Modal in the literal sense -- App @@ -50,6 +52,12 @@ function State.new() -- without a z-order (#541). speciesPicker = nil, + -- item picker overlay: nil when closed, otherwise + -- { query, offset, dest = "bag"|"pc" }. Same modal contract as + -- speciesPicker above -- adding an item is now a full-screen picker + -- rather than a card competing for height inside the Items tab. + itemPicker = nil, + -- boxes selectedBox = 1, selectedBoxSlot = 1, @@ -71,6 +79,7 @@ function State.new() eventsOffset = 0, -- dex + dexSort = "dex", -- how the DEX grid is ordered: "dex" (by number) | "name" (A-Z) dexOffset = 0, -- map diff --git a/tools/save-editor/Theme.lua b/tools/save-editor/Theme.lua index 94cdc30e..0deeec13 100644 --- a/tools/save-editor/Theme.lua +++ b/tools/save-editor/Theme.lua @@ -1,333 +1,89 @@ --- Shared look for the save editor: the launcher's palette and its drawing --- primitives, lifted out so the editor and src/import/RomImporter.lua render --- the same navy field, the same 16px translucent cards and the same neon --- accents. The editor is reachable straight off a launcher save row (Edit), --- so the two windows have to read as one app -- see SaveEditor.dc.html, which --- is the design spec these literals come from. +-- The save editor's look, delegated to the shared high-contrast theme +-- (src/ui/kit/Theme.lua). -- --- Every colour below is 0-255 RGB; alpha is passed per draw call to col(). +-- The editor is reachable straight off a launcher save row (Edit), so the two +-- windows have to read as one app. They used to do that by keeping two +-- copies of the same navy palette in sync by hand; now there is one palette +-- and this file is the adapter. Everything below is either a re-export or a +-- shim for a primitive the old navy design had and the flat one does not: -- --- Everything here degrades when a love.graphics entry point is missing: the --- headless love_stub used by tests/ has no fonts, stencil, mesh or line, so --- each primitive checks for its dependency and falls back to a flat fill (or --- nothing) rather than erroring. That keeps App.draw callable under the stub. +-- gradRounded -> flat fill of the bottom colour (there are no gradients) +-- glow -> nothing (there are no glows) +-- dashed -> a plain hairline (the dashed path sampled a rounded +-- outline into a polyline every frame for an empty-state +-- box, which is a lot of work to say "nothing here") +-- +-- Keeping the old signatures means the editor's panels did not have to be +-- rewritten to change theme, and a panel that has not been revisited yet +-- still lands on the new palette instead of drawing navy into a black window. + +local Shared = require("src.ui.kit.Theme") local Theme = {} -local PAL = { - -- radial background field: bright navy at top-centre -> near black - bgTop = { 22, 34, 74 }, -- #16224a - bgMid = { 12, 19, 48 }, -- #0c1330 - bgBot = { 7, 11, 29 }, -- #070b1d - -- panel + row surfaces - cardTint = { 70, 150, 255 }, -- rgba(70,150,255,0.08) card top light - cardBody = { 12, 18, 40 }, -- rgba(12,18,40,0.5) card interior - cardBorder = { 120, 150, 220 }, -- rgba(120,150,220,0.28) hairline - rowBg = { 9, 14, 34 }, -- rgba(9,14,34,0.60) row interior - -- text - heading = { 255, 255, 255 }, - text = { 223, 230, 245 }, -- #dfe6f5 - detail = { 198, 208, 230 }, -- #c6d0e6 - muted = { 159, 176, 208 }, -- #9fb0d0 - caption = { 143, 163, 200 }, -- #8fa3c8 letterspaced section captions - faint = { 111, 130, 168 }, -- #6f82a8 slot indices, hints - -- semantics: green = safe/confirmed, yellow = attention, red = destructive - green = { 62, 224, 138 }, -- #3ee08a - greenDark = { 22, 163, 90 }, -- #16a35a - greenInk = { 6, 32, 18 }, -- #062012 - yellow = { 255, 203, 5 }, -- #ffcb05 - red = { 255, 92, 103 }, -- #ff5c67 - redSoft = { 255, 143, 150 }, -- #ff8f96 destructive button ink - blue = { 70, 150, 255 }, -- #4696ff - blueInk = { 207, 224, 255 }, -- #cfe0ff ink on blue-tinted controls - steel = { 149, 161, 189 }, -- #95a1bd disabled - -- the tri-colour version rail, identical to the launcher's - railRed = { 255, 60, 72 }, - railBlue = { 70, 150, 255 }, - railGold = { 255, 203, 5 }, - -- chip / tab tile gradient (the launcher's mod chip) - chipTop = { 61, 74, 109 }, -- #3d4a6d - chipBot = { 32, 42, 69 }, -- #202a45 - chipInk = { 207, 224, 255 }, -- #cfe0ff -} +-- The palette itself, plus the aliases the editor's panels already use. +local PAL = {} +for k, v in pairs(Shared.PAL) do PAL[k] = v end +-- Names the editor uses that the shared palette spells differently. +PAL.cardTint = PAL.bg +PAL.cardBody = PAL.bg +PAL.chipTop = PAL.bg +PAL.chipBot = PAL.bg +PAL.chipInk = PAL.text +PAL.bgTop = PAL.bg +PAL.bgMid = PAL.bg +PAL.bgBot = PAL.bg Theme.PAL = PAL -local G = love and love.graphics or nil - --- Feature probes: the headless stub implements only a handful of these. -local has = {} -local function probe(name) - if has[name] == nil then has[name] = (G and type(G[name]) == "function") or false end - return has[name] -end - -function Theme.col(c, a) - if not G then return end - G.setColor(c[1] / 255, c[2] / 255, c[3] / 255, a or 1) -end -local col = Theme.col - -function Theme.clamp(n, lo, hi) - if n < lo then return lo end - if n > hi then return hi end - return n -end -local clamp = Theme.clamp - --- ---------------------------------------------------------------- gradients --- One reusable unit-square mesh whose four corner colours are rewritten per --- call, so a vertical gradient costs a single draw (same trick the launcher --- uses). Nil under the stub, where every gradient degrades to a flat fill. -local gradMesh -local function setGrad(cTop, cBot, aTop, aBot) - if not probe("newMesh") then return false end - if not gradMesh then - gradMesh = G.newMesh({ - { 0, 0, 0, 0, 1, 1, 1, 1 }, - { 1, 0, 1, 0, 1, 1, 1, 1 }, - { 1, 1, 1, 1, 1, 1, 1, 1 }, - { 0, 1, 0, 1, 1, 1, 1, 1 }, - }, "fan", "dynamic") - end - local t = { cTop[1] / 255, cTop[2] / 255, cTop[3] / 255, aTop } - local b = { cBot[1] / 255, cBot[2] / 255, cBot[3] / 255, aBot } - gradMesh:setVertexAttribute(1, 3, t[1], t[2], t[3], t[4]) - gradMesh:setVertexAttribute(2, 3, t[1], t[2], t[3], t[4]) - gradMesh:setVertexAttribute(3, 3, b[1], b[2], b[3], b[4]) - gradMesh:setVertexAttribute(4, 3, b[1], b[2], b[3], b[4]) - return true -end - --- Vertical gradient clipped to a rounded rect. Falls back to a flat fill of --- the bottom colour when the stencil buffer or meshes are unavailable. -function Theme.gradRounded(x, y, w, h, r, cTop, cBot, aTop, aBot) - if not G then return end - if w <= 0 or h <= 0 then return end - if not (probe("stencil") and probe("setStencilTest") and setGrad(cTop, cBot, aTop, aBot)) then - col(cBot, aBot) - G.rectangle("fill", x, y, w, h, r, r) - return - end - G.stencil(function() G.rectangle("fill", x, y, w, h, r, r) end, "replace", 1) - G.setStencilTest("greater", 0) - G.setColor(1, 1, 1, 1) - G.draw(gradMesh, x, y, 0, w, h) - G.setStencilTest() -end - --- The design's standard content panel: a faint top-lit blue tint fading into --- a dark interior behind a 1px cool-gray hairline. Every card in the editor --- (and every card in the launcher) is this shape. -function Theme.card(x, y, w, h, r) - if not G then return end - r = r or 16 - Theme.gradRounded(x, y, w, h, r, PAL.cardTint, PAL.cardBody, 0.08, 0.5) - Theme.stroke(x, y, w, h, r, PAL.cardBorder, 0.28, 1) -end - --- A list row / inner surface: flat dark fill, fainter hairline than a card. -function Theme.row(x, y, w, h, r, alpha) - if not G then return end - col(PAL.rowBg, alpha or 0.6) - G.rectangle("fill", x, y, w, h, r or 12, r or 12) - Theme.stroke(x, y, w, h, r or 12, PAL.cardBorder, 0.22, 1) -end - +Theme.col = Shared.col +Theme.clamp = Shared.clamp +Theme.snap = Shared.snap +-- The editor's stroke carries a corner radius as its 5th argument (the +-- shared one takes the colour there). Route it to the rounded variant so +-- the radius the panels already pass is honoured rather than dropped. function Theme.stroke(x, y, w, h, r, c, a, lw) - if not G then return end - if probe("setLineWidth") then G.setLineWidth(math.max(1, lw or 1)) end - col(c, a or 1) - G.rectangle("line", x, y, w, h, r or 0, r or 0) - if probe("setLineWidth") then G.setLineWidth(1) end + Shared.strokeRounded(x, y, w, h, c, a, lw, math.min(r or 0, 8)) +end +Theme.spaced = Shared.spaced +Theme.spacedWidth = Shared.spacedWidth +Theme.ellipsize = Shared.ellipsize +Theme.ellipsizeLeft = Shared.ellipsizeLeft +Theme.meter = Shared.meter +Theme.versionRail = Shared.versionRail +Theme.fonts = Shared.fonts +Theme.radius = Shared.radius +Theme.fillRounded = Shared.fillRounded +Theme.strokeRounded = Shared.strokeRounded +Theme.emboss = Shared.emboss +Theme.BOLD_OFFSET = Shared.BOLD_OFFSET + +-- The editor calls card/row with a trailing radius (and row with an alpha) +-- that the flat theme has no use for; accept and ignore them. +function Theme.card(x, y, w, h, _r) + Shared.card(x, y, w, h) end --- Soft additive halo around a rounded rect (LOVE has no blur, so stack --- progressively larger, fainter rects). Marks the selected party slot and --- the hot Save button. -function Theme.glow(x, y, w, h, r, c, strength) - if not G or not probe("setBlendMode") then return end - strength = math.max(0, strength or 0) - if strength == 0 then return end - G.setBlendMode("add") - local layers = 7 - for i = 1, layers do - local g = i * 2.2 - G.setColor(c[1] / 255, c[2] / 255, c[3] / 255, - strength * 0.05 * (1 - (i - 1) / layers)) - G.rectangle("fill", x - g, y - g, w + 2 * g, h + 2 * g, r + g, r + g) - end - G.setBlendMode("alpha") +function Theme.row(x, y, w, h, _r, _alpha) + Shared.row(x, y, w, h) end --- Dashed rounded outline (LOVE has no dash pattern): sample the path into a --- polyline, then walk it toggling on/off. Used for empty-state boxes and the --- "add here" slots in the box grid. Caller sets colour + line width. -function Theme.dashed(x, y, w, h, r, dash, gap) - if not G or not probe("line") then return end - if w <= 0 or h <= 0 then return end - r = math.min(r, w / 2, h / 2) - local seg = 4 - local pts = {} - local function arc(cx, cy, a0, a1) - for i = 0, seg do - local a = a0 + (a1 - a0) * (i / seg) - pts[#pts + 1] = cx + math.cos(a) * r - pts[#pts + 1] = cy + math.sin(a) * r - end - end - arc(x + w - r, y + r, -math.pi / 2, 0) - arc(x + w - r, y + h - r, 0, math.pi / 2) - arc(x + r, y + h - r, math.pi / 2, math.pi) - arc(x + r, y + r, math.pi, math.pi * 1.5) - pts[#pts + 1] = pts[1]; pts[#pts + 1] = pts[2] - local remaining, drawing = dash, true - for i = 1, #pts - 2, 2 do - local x1, y1 = pts[i], pts[i + 1] - local dx, dy = pts[i + 2] - x1, pts[i + 3] - y1 - local segLen = math.sqrt(dx * dx + dy * dy) - local pos = 0 - while pos < segLen do - local step = math.min(remaining, segLen - pos) - if drawing then - local t0, t1 = pos / segLen, (pos + step) / segLen - G.line(x1 + dx * t0, y1 + dy * t0, x1 + dx * t1, y1 + dy * t1) - end - pos = pos + step - remaining = remaining - step - if remaining <= 0.0001 then - drawing = not drawing - remaining = drawing and dash or gap - end - end - end +-- Flat fill of the "bottom" colour: the old gradient's endpoint, so a control +-- that used to fade into it keeps roughly its old weight. +function Theme.gradRounded(x, y, w, h, _r, _cTop, cBot, _aTop, aBot) + Shared.fill(x, y, w, h, cBot, aBot) end --- Letterspaced text: the UI font has no tracking control, so advance glyph by --- glyph. Section captions are 12px/2px-tracked uppercase throughout. -function Theme.spaced(font, text, x, y, spacing) - if not G or not font then return 0 end - local cx = x - for i = 1, #text do - local ch = text:sub(i, i) - G.print(ch, cx, y) - cx = cx + font:getWidth(ch) + spacing - end - return math.max(0, cx - x - spacing) +-- No glows in this theme. Kept so call sites do not have to be edited, and +-- so nothing silently starts setting blend modes again. +function Theme.glow() end + +-- The empty-state box is a hairline now, not a dashed outline. +function Theme.dashed(x, y, w, h, _r, _dash, _gap) + Shared.stroke(x, y, w, h, PAL.line, 0.22, 1) end -function Theme.spacedWidth(font, text, spacing) - if not font then return 0 end - local w = 0 - for i = 1, #text do w = w + font:getWidth(text:sub(i, i)) + spacing end - return math.max(0, w - spacing) -end - --- Clip text to a pixel width with a trailing ellipsis. Save paths truncate --- from the LEFT instead (see Theme.ellipsizeLeft) so the filename survives. -function Theme.ellipsize(font, text, maxW) - text = tostring(text or "") - if not font then return text end - -- A non-positive budget means "nothing fits", not "everything fits": the - -- old early-out returned the whole string, which is how a phone-width - -- status bar ended up with two lines of text stacked on top of each other - -- (#715). - if maxW <= 0 then return "" end - if font:getWidth(text) <= maxW then return text end - local ell = "..." - local ew = font:getWidth(ell) - while #text > 0 and font:getWidth(text) + ew > maxW do - text = text:sub(1, #text - 1) - end - return text .. ell -end - -function Theme.ellipsizeLeft(font, text, maxW) - text = tostring(text or "") - if not font then return text end - if maxW <= 0 then return "" end -- same rule as Theme.ellipsize (#715) - if font:getWidth(text) <= maxW then return text end - local ell = "..." - local ew = font:getWidth(ell) - while #text > 0 and font:getWidth(text) + ew > maxW do - text = text:sub(2) - end - return ell .. text -end - --- ------------------------------------------------------------- backgrounds --- The radial navy field, drawn as a triangle fan from the top-centre so the --- falloff matches the CSS radial-gradient in the spec. The screen is cleared --- to the outer colour first so the corners the fan misses match seamlessly. function Theme.field(w, h) - if not G then return end - G.clear(PAL.bgBot[1] / 255, PAL.bgBot[2] / 255, PAL.bgBot[3] / 255, 1) - if not probe("newMesh") then return end - local cx, cy = w / 2, 0 - local rx, ry = w * 1.3, h * 1.08 - local n = 64 - local verts = { { cx, cy, 0, 0, - PAL.bgTop[1] / 255, PAL.bgTop[2] / 255, PAL.bgTop[3] / 255, 1 } } - for i = 0, n do - local a = (i / n) * math.pi * 2 - verts[#verts + 1] = { cx + math.cos(a) * rx, cy + math.sin(a) * ry, 0, 0, - PAL.bgBot[1] / 255, PAL.bgBot[2] / 255, PAL.bgBot[3] / 255, 1 } - end - local mesh = G.newMesh(verts, "fan", "static") - G.setColor(1, 1, 1, 1) - G.draw(mesh) -end - --- The 6px tri-colour rail across the very top of both windows. -function Theme.versionRail(x, y, w, h) - if not G then return end - local seg = w / 3 - local bars = { PAL.railRed, PAL.railBlue, PAL.railGold } - for i, c in ipairs(bars) do - col(c, 1) - G.rectangle("fill", x + (i - 1) * seg, y, seg, h) - end -end - --- A percentage meter (HP, box fill, dex completion, bag slots). pct is 0-100. -function Theme.meter(x, y, w, h, pct, c) - if not G then return end - col(PAL.cardBorder, 0.18) - G.rectangle("fill", x, y, w, h, h / 2, h / 2) - local fill = w * clamp((pct or 0) / 100, 0, 1) - if fill > 0 then - col(c or PAL.blue, 1) - G.rectangle("fill", x, y, math.max(fill, h / 2), h, h / 2, h / 2) - end -end - --- Font set, rebuilt only when the window size changes. `s` is the same --- height/768 scale the launcher derives, so both windows step together. --- Chrome is the default UI face; save DATA is drawn in the mono face, which --- LOVE only ships as the default vector font -- so "mono" here means the --- same face at a tighter size, and the distinction is carried by size and --- colour. A stub with no newFont returns nil fonts and every draw no-ops. -function Theme.fonts(s) - if not probe("newFont") then return {} end - local function f(px) return G.newFont(math.max(8, math.floor(px + 0.5))) end - return { - scale = s, - wordmark = f(14 * s), - brand = f(11 * s), - chip = f(11 * s), -- RED / BLUE version chip - tile = f(13 * s), -- 2-letter tab glyph - tab = f(13 * s), -- tab label - button = f(14 * s), - small = f(12 * s), - tiny = f(11 * s), - micro = f(10 * s), - caption = f(12 * s), -- letterspaced section captions - mono = f(12 * s), - monoRow = f(13 * s), - monoBig = f(18 * s), - title = f(24 * s), -- inspector species name - headline = f(26 * s), -- dex completion / money - stat = f(19 * s), - } + Shared.field(w, h) end return Theme diff --git a/tools/save-editor/panels/Dex.lua b/tools/save-editor/panels/Dex.lua index f8b1629c..d7929485 100644 --- a/tools/save-editor/panels/Dex.lua +++ b/tools/save-editor/panels/Dex.lua @@ -8,6 +8,7 @@ local Theme = require("Theme") local Ops = require("Ops") +local MonEditor = require("MonEditor") local PAL = Theme.PAL local M = {} @@ -24,7 +25,7 @@ function M.draw(S, Kit, x, y, w, h) local s = Kit.scale local pad = 20 * s local dex = Ops.dex(S) - local species = S.cat.species + local species = Ops.dexList(S) local seen, owned, total = Ops.dexCounts(S) Kit.card(x, y, w, h) @@ -44,12 +45,20 @@ function M.draw(S, Kit, x, y, w, h) -- and FLOW, wrapping to further rows when even one is too narrow, so the -- cluster can never paint over the headline or over itself. local actH = 34 * s + -- The two sort chips are view-only (Ops.dexSort never dirties the save); + -- the active mode reads as the accent chip, the other as ghost. They ride + -- the same wrap-aware cluster as the bulk actions so a narrow window flows + -- them to their own rows instead of painting over the headline (#715). local buttons = { { label = "Own party + boxes", kind = "ghost", fn = Ops.dexStamp }, { label = "See all", kind = "accent", fn = Ops.dexSeeAll }, { label = "Own all", kind = "good", fn = Ops.dexOwnAll }, { label = Ops.armLabel(S, "dex-clear", "Wipe dex"), kind = "danger", fn = Ops.dexClear }, + { label = "Dex #", kind = (S.dexSort ~= "name") and "accent" or "ghost", + fn = function(s) Ops.dexSort(s, "dex") end }, + { label = "A-Z", kind = (S.dexSort == "name") and "accent" or "ghost", + fn = function(s) Ops.dexSort(s, "name") end }, } local clusterW = -10 * s for _, b in ipairs(buttons) do @@ -140,9 +149,13 @@ function M.draw(S, Kit, x, y, w, h) Theme.row(rx, ry, colW, rowH, 9 * s, 0.6) local def = S.data.pokemon[id] - Kit.text("micro", ("%03d"):format(def and def.dex or 0), rx + 10 * s, + local dexText = ("%03d"):format(def and def.dex or 0) + Kit.text("micro", dexText, rx + 10 * s, ry + (rowH - Kit.textHeight("micro")) / 2, PAL.faint) - local nameX = rx + 44 * s + local spriteS = 24 * s + local spriteX = rx + 10 * s + Kit.textWidth("micro", dexText) + 8 * s + MonEditor.drawSprite(S, Kit, id, spriteX, ry + (rowH - spriteS) / 2, spriteS) + local nameX = spriteX + spriteS + 6 * s local nameW = colW - 10 * s - 2 * (chipW + 6 * s) - (nameX - rx) Kit.text("mono", Kit.ellipsize("mono", id, nameW), nameX, ry + (rowH - Kit.textHeight("mono")) / 2, diff --git a/tools/save-editor/panels/ItemPicker.lua b/tools/save-editor/panels/ItemPicker.lua new file mode 100644 index 00000000..0bb91dc8 --- /dev/null +++ b/tools/save-editor/panels/ItemPicker.lua @@ -0,0 +1,148 @@ +-- Type-to-search item picker: the items panel's answer to the species +-- picker (panels/SpeciesPicker.lua), and built to the same shape on purpose. +-- +-- Adding an item used to mean an inline catalog card wedged into the Items +-- tab: a search field and a scrolling list competing for height with the bag +-- and PC lists beside it, which on a phone left about three rows visible. +-- Adding a Pokemon was already a full-screen modal with the whole window to +-- work with, and there was no reason for the two to differ. +-- +-- Modal is literal. Kit hit-tests without a z-order, so App.draw raises +-- Kit.blockClicks over the chrome and the panel while this is open and lowers +-- it only for this overlay; nothing underneath can take the same tap. + +local Theme = require("Theme") +local Ops = require("Ops") +local PAL = Theme.PAL + +local Picker = {} + +local FIELD_ID = "item-picker" + +function Picker.results(S) + local p = S.itemPicker + return Ops.itemSearch(S, p and p.query or "") +end + +-- Enter commits the top match into whichever destination the picker was +-- opened for, which is the whole point of a search field. +function Picker.commitFirst(S, Kit) + local hits = Picker.results(S) + if not hits[1] then return Ops.say(S, "No item matches that") end + return Picker.commit(S, Kit, hits[1]) +end + +-- One funnel for both destinations. The picker stays OPEN after a commit: +-- stocking a save means adding several items in a row, and reopening the +-- modal per item is the kind of friction the inline card at least did not +-- have. Escape / Close / tap-outside is the way out. +function Picker.commit(S, Kit, id) + local p = S.itemPicker + local dest = (p and p.dest) or "bag" + if dest == "pc" then return Ops.addToPc(S, id) end + return Ops.addToBag(S, id) +end + +function Picker.draw(S, Kit, width, height) + local p = S.itemPicker + if not p then return end + local s = Kit.scale + + -- The click that opened the picker is still the frame's click: the panel + -- dispatches earlier in App.draw than this overlay does, so without + -- swallowing it the scrim below would read it as a tap outside and shut the + -- picker in the same frame it went up. + if p.opened then + p.opened = nil + Kit.blockClicks = true + end + + -- the scrim doubles as the "tap outside to cancel" target + Theme.col(PAL.bg, 0.82) + love.graphics.rectangle("fill", 0, 0, width, height) + + local w = math.min(width - 32 * s, 520 * s) + local h = math.min(height - 32 * s, 560 * s) + local x = (width - w) / 2 + local y = (height - h) / 2 + if Kit.press(0, 0, width, height) and not Kit.hit(x, y, w, h) then + Ops.closeItemPicker(S, Kit) + return + end + + Kit.card(x, y, w, h) + local pad = 18 * s + local cx, cy = x + pad, y + pad + local inner = w - 2 * pad + + Kit.caption(cx, cy, "ADD AN ITEM") + local closeW = 30 * s + if Kit.button(x + w - pad - closeW, cy - 4 * s, closeW, 26 * s, "x", + { font = "small" }) then + Ops.closeItemPicker(S, Kit) + return + end + cy = cy + Kit.textHeight("caption") + 10 * s + + -- Destination toggle. Which list an item lands in is the only real choice + -- here, so it is a pair of chips at the top rather than two buttons at the + -- bottom that each mean "commit, and also pick a destination". + local half = (inner - 8 * s) / 2 + local destH = 30 * s + if Kit.chip(cx, cy, half, destH, "-> BAG", p.dest ~= "pc", PAL.green, PAL.steel) then + p.dest = "bag" + end + if Kit.chip(cx + half + 8 * s, cy, half, destH, "-> PC", p.dest == "pc", + PAL.green, PAL.steel) then + p.dest = "pc" + end + cy = cy + destH + 10 * s + + local fieldH = 34 * s + p.query = Kit.textfield(FIELD_ID, cx, cy, inner, fieldH, p.query, + "type an item id") + cy = cy + fieldH + 10 * s + + local hits = Picker.results(S) + local rowH = 36 * s + local rowGap = 6 * s + local pagerH = 30 * s + local listH = (y + h - pad - pagerH - 10 * s) - cy + local perPage = math.max(1, math.floor((listH + rowGap) / (rowH + rowGap))) + p.offset = Theme.clamp(p.offset or 0, 0, math.max(0, #hits - perPage)) + -- wheel / touch drag scroll the modal list too; the shield is already + -- lowered for this layer, so Kit.scroll works here and only here + p.offset = Kit.scroll(cx, cy, inner, listH, p.offset, #hits, perPage) + + if #hits == 0 then + Kit.emptyBox(cx, cy, inner, listH, "Nothing matches that.") + else + Kit.pushClip(cx, cy, inner, listH) + for i = 1, perPage do + local id = hits[p.offset + i] + if not id then break end + local ry = cy + (i - 1) * (rowH + rowGap) + if Kit.row(cx, ry, inner, rowH, false, PAL.green, 9 * s) then + Picker.commit(S, Kit, id) + end + -- how many the save already holds, so a second add is an informed one + local have = (S.save.inventory and S.save.inventory[id]) + or (Ops.pcItems(S) or {})[id] + local tail = have and ("x%d"):format(have) or "" + local tailW = Kit.textWidth("tiny", tail) + Kit.text("monoRow", + Kit.ellipsize("monoRow", id, inner - tailW - 30 * s), cx + 10 * s, + ry + (rowH - Kit.textHeight("monoRow")) / 2, PAL.text) + if tail ~= "" then + Kit.textRight("tiny", tail, cx + inner - 10 * s, + ry + (rowH - Kit.textHeight("tiny")) / 2, PAL.caption) + end + end + Kit.popClip() + Kit.scrollbar(cx, cy, inner, listH, p.offset, #hits, perPage) + end + + p.offset = Kit.pager(cx, y + h - pad - pagerH, inner, p.offset, #hits, perPage) +end + +return Picker diff --git a/tools/save-editor/panels/Items.lua b/tools/save-editor/panels/Items.lua index b78d45cf..9b93d3a3 100644 --- a/tools/save-editor/panels/Items.lua +++ b/tools/save-editor/panels/Items.lua @@ -24,11 +24,6 @@ local M = {} local MONEY_STEPS = { -1000, -100, 100, 1000 } -local function matches(id, query) - if query == "" then return true end - return id:lower():find(query:lower(), 1, true) ~= nil -end - -- One quantity row shape, shared by the bag and the PC list: id, qty, then -- the -/+/drop cluster. Returns true when the row body was clicked. local function quantityRow(S, Kit, x, y, w, h, id, qty, selected, onMinus, onPlus, onDrop) @@ -127,76 +122,33 @@ local function drawBadges(S, Kit, x, y, w, h) end end +-- The "add an item" card. It used to hold the whole catalog inline: a search +-- field plus a scrolling list, sharing this tab's height with the bag and PC +-- lists beside it, which on a phone left about three catalog rows visible. +-- Adding a Pokemon was already a full-screen modal; adding an item now opens +-- the same kind (panels/ItemPicker.lua), so this card is just the door. local function drawPicker(S, Kit, x, y, w, h) local s = Kit.scale local pad = 16 * s Kit.card(x, y, w, h) Kit.caption(x + pad, y + pad, "ADD ITEM") - local qy = y + pad + Kit.textHeight("caption") + 8 * s - local prevQuery = S.itemQuery or "" - S.itemQuery = Kit.textfield("item-query", x + pad, qy, w - 2 * pad, 32 * s, - S.itemQuery or "", "search item ids...") - -- a new query is a new list: keep the first hit on screen rather than - -- leaving the view parked wherever the old result set had scrolled to - if S.itemQuery ~= prevQuery then S.itemPickOffset = 0 end + local cy = y + pad + Kit.textHeight("caption") + 10 * s + local inner = w - 2 * pad - local choices = {} - for _, id in ipairs(S.cat.items) do - if not Ops.isBadgeId(id) and matches(id, S.itemQuery) then - choices[#choices + 1] = id - end - end - if not S.selectedItemId or not matches(S.selectedItemId, S.itemQuery) then - S.selectedItemId = choices[1] - end + Kit.text("mono", Kit.ellipsize("mono", + "Search the full item list and add to the bag or the PC.", inner), + x + pad, cy, PAL.muted) + cy = cy + Kit.textHeight("mono") + 12 * s - local addH = 32 * s - local addY = y + h - pad - addH - local listTop = qy + 32 * s + 10 * s - local listBottom = addY - 10 * s - local cRowH = 28 * s - local cGap = 5 * s - local visible = math.max(1, math.floor((listBottom - listTop) / (cRowH + cGap))) - -- #595: the wheel drives the same offset a pager would, so the whole - -- catalog is reachable with the mouse alone. Kit.scroll clamps, which is - -- also what pulls the view back when a narrower query shortens the list. - S.itemPickOffset = Kit.scroll(x + pad, listTop, w - 2 * pad, - listBottom - listTop, S.itemPickOffset or 0, #choices, visible) - Kit.pushClip(x + pad, listTop, w - 2 * pad, listBottom - listTop) - for i = 1, math.min(visible, #choices - S.itemPickOffset) do - local id = choices[S.itemPickOffset + i] - local ry = listTop + (i - 1) * (cRowH + cGap) - if Kit.row(x + pad, ry, w - 2 * pad, cRowH, id == S.selectedItemId, - PAL.green, 8 * s) then - S.selectedItemId = id - Ops.say(S, "Picked " .. id) - end - Kit.text("mono", Kit.ellipsize("mono", id, w - 2 * pad - 20 * s), - x + pad + 10 * s, ry + (cRowH - Kit.textHeight("mono")) / 2, PAL.text) + local btnH = math.max(34 * s, 34) + local half = (inner - 8 * s) / 2 + if Kit.button(x + pad, cy, half, btnH, "+ Add to bag", + { font = "small", kind = "primary" }) then + Ops.openItemPicker(S, Kit, "bag") end - Kit.popClip() - -- the drag/wheel offset is also made visible: on a phone the list looked - -- bottomless-yet-stuck without an indicator (#715) - Kit.scrollbar(x + pad, listTop, w - 2 * pad, listBottom - listTop, - S.itemPickOffset, #choices, visible) - -- the position counter rides the caption line, where it can never collide - -- with the list body or the two add buttons below it - if #choices > visible then - Kit.textRight("micro", ("%d-%d of %d"):format(S.itemPickOffset + 1, - math.min(S.itemPickOffset + visible, #choices), #choices), - x + w - pad, y + pad, PAL.faint) - elseif #choices == 0 then - Kit.text("mono", "no item matches", x + pad + 10 * s, listTop + 8 * s, PAL.faint) - end - - local halfW = (w - 2 * pad - 8 * s) / 2 - if Kit.button(x + pad, addY, halfW, addH, "-> Bag", - { font = "small", radius = 8 * s, enabled = S.selectedItemId ~= nil }) then - Ops.addToBag(S, S.selectedItemId) - end - if Kit.button(x + pad + halfW + 8 * s, addY, halfW, addH, "-> PC", - { font = "small", radius = 8 * s, enabled = S.selectedItemId ~= nil }) then - Ops.addToPc(S, S.selectedItemId) + if Kit.button(x + pad + half + 8 * s, cy, half, btnH, "+ Add to PC", + { font = "small", kind = "accent" }) then + Ops.openItemPicker(S, Kit, "pc") end end diff --git a/tools/save-editor/panels/MonEditor.lua b/tools/save-editor/panels/MonEditor.lua index 0a2230ee..8ccdc13d 100644 --- a/tools/save-editor/panels/MonEditor.lua +++ b/tools/save-editor/panels/MonEditor.lua @@ -218,7 +218,11 @@ function MonEditor.draw(S, Kit, x, y, w, h) else colsH = capH + 10 * s + colRowsH + 12 * s + actH end + -- the nickname section: a caption line (with the Clear button on it) plus + -- the field + Set row + local nickFieldH = 30 * s local contentH = pad + headerH + 18 * s + + capH + 10 * s + nickFieldH + 18 * s + capH + 10 * s + cellH + 18 * s + colsH + pad @@ -266,8 +270,45 @@ function MonEditor.draw(S, Kit, x, y, w, h) drawLevelRow(S, Kit, mon, cx, cy + math.max(sprite, titleH) + 12 * s) end + -- ---------------------------------------------------------- nickname + -- Editing the field is a draft (S.nicknameDraft) held on the mon it belongs + -- to; Set / Enter commit it through Ops.setNickname, which clears on an + -- empty value, and Clear goes through Ops.clearNickname. The draft resets + -- when the selection moves so one mon's typing can never leak onto another. + local nickY = cy + headerH + 18 * s + Kit.caption(cx, nickY, "NICKNAME") + local clearW = 74 * s + local clearH = 24 * s + if Kit.button(cx + inner - clearW, nickY + (capH - clearH) / 2, clearW, clearH, + "Clear", { kind = "danger", font = "micro", radius = 6 * s }) then + Ops.clearNickname(S, mon) + S.nicknameDraft = "" + end + local fieldY = nickY + capH + 10 * s + local setW = 64 * s + local fieldW = inner - setW - 10 * s + if S.nicknameMon ~= mon then + local switching = S.nicknameMon ~= nil + S.nicknameMon = mon + S.nicknameDraft = mon.nickname or "" + -- a still-focused field would keep appending keystrokes to the newly + -- selected mon; the selection move counts as leaving the field. The + -- first sync (nicknameMon starts nil) never blurs: the species picker + -- owns focus when it opens, and blurring there drops the player's typing. + if switching and Kit.focus == "mon-nickname" then Kit.blur() end + end + S.nicknameDraft = Kit.textfield("mon-nickname", cx, fieldY, fieldW, nickFieldH, + S.nicknameDraft or "", "no nickname", + { sanitize = function(value) return Ops.nicknameSanitize(S, value) end }) + if Kit.button(cx + fieldW + 10 * s, fieldY, setW, nickFieldH, "Set", + { kind = "accent", font = "small", radius = 8 * s }) then + if Ops.setNickname(S, mon, S.nicknameDraft) then + S.nicknameDraft = mon.nickname or "" + end + end + -- ------------------------------------------------------- derived stats - local statsY = cy + headerH + 18 * s + local statsY = nickY + capH + 10 * s + nickFieldH + 18 * s Kit.caption(cx, statsY, "STATS . recalculated from level + DVs") statsY = statsY + capH + 10 * s local gap = 12 * s diff --git a/tools/switch-probe/README.md b/tools/switch-probe/README.md index 616c1300..c645324c 100644 --- a/tools/switch-probe/README.md +++ b/tools/switch-probe/README.md @@ -1,4 +1,4 @@ -# switch-probe — love-nx hardware probe +# switch-probe: love-nx hardware probe **NOT FOR RELEASE.** This package is a developer-only diagnostic for Nintendo Switch (love-nx). Do not ship it inside `game.love` or release NRO payloads. @@ -11,7 +11,9 @@ Validate Phase 0 runtime facts on real Switch hardware before running the full G - Save directory path (`love.filesystem.getSaveDirectory()`) - Gamepad / joystick / touch event logging -To date this probe has only been run on **Switch OLED** (see `docs/switch-hardware-evidence.md`); other models are untested. Deploy beside `gen1recomp.nro` remains **manual** (MTP); see `docs/switch-development.md`. +To date this probe has only been run on **Switch OLED**; other models are +untested. Deploy beside `gen1recomp.nro` remains **manual** (MTP). See +`docs/switch-transfer.md`. ## Fields shown on screen @@ -36,7 +38,9 @@ mkdir -p .bazinga/work zip -9 -j .bazinga/work/switch-probe.love tools/switch-probe/main.lua tools/switch-probe/conf.lua ``` -Deploy beside `gen1recomp.nro` (loose mode) per `docs/switch-development.md`, renaming to `game.love` only for a probe run — use a separate SD folder so probe and game builds do not mix. +Deploy beside `gen1recomp.nro` (loose mode) per `docs/switch-build.md` and +`docs/switch-transfer.md`. Rename to `game.love` only for a probe run. Use a +separate SD folder so probe and game builds do not mix. ## Desktop smoke (optional)