diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8406c0a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing + +NETfishing is currently developed through focused changes on the repository's +active development branch. Coordinate scope with the project owner before +starting substantial work. + +## Change discipline + +- Keep a change focused; avoid unrelated cleanup. +- Inspect the working tree before editing and preserve existing work. +- Do not change protocol, save, settings, identity, or portable-data versions + as release-label housekeeping. +- Treat the host as authoritative for catches, purchases, sales, jobs, mail, + and other shared state. +- Keep presentation-only systems out of save files and network messages. +- Store runtime assets under repository-owned paths; never reference a + workstation sync or temporary directory. +- Record the source and license of newly imported assets. +- Do not commit `.godot/`, test data, logs, captures, or build outputs. + +## Before requesting review + +```sh +git diff --check +scripts/run_validations.sh quick +git status --short +``` + +Choose additional focused tests from [`docs/TESTING.md`](docs/TESTING.md). +Changes involving networking should run the network suite and a real +two-process check. Visual changes still require graphical review; headless +tests are supporting evidence, not a replacement. + +## Commits + +- Stage audited paths explicitly. +- Use a concise imperative subject that describes the outcome. +- Do not mix generated build artifacts with source changes. +- Do not rewrite published release tags. +- Describe behavior, compatibility impact, validation, and asset provenance in + the review or release record. + +## Style + +Follow the conventions already present in the surrounding GDScript, scene, and +resource files. Prefer typed values, named constants, shared resources, and +small domain-specific services. Avoid duplicating protocol definitions or +using display strings as persistent identifiers. diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..8edbd4f --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,50 @@ +# Credits + +## Project direction + +NETfishing is designed, directed, reviewed, and released by its project +creator. A public-facing creator or studio credit has not yet been recorded in +this repository. + +## Development assistance + +Development has used automated coding assistance for implementation, +investigation, testing, documentation, and review. Design choices, supplied +artwork, acceptance decisions, repository operations, and releases remain +human-controlled. + +This disclosure is intentionally about process. Commit style, code style, or +repository metadata cannot reliably prove whether a particular change was +written with or without assistance. + +## Fonts + +- Tuffy: Thatcher Ulrich, Karoly Barta, and Michael Evans. The bundled license + dedicates the work to the public domain; see `ui/fonts/Tuffy-LICENSE.txt`. +- Seattle Avenue: bundled as `ui/fonts/seattle_avenue.otf`. Author and + redistribution terms still need to be recorded before public distribution. + +## Music + +- `audio/music/title/as_in_four_wolves.ogg`: original title/ambient music by + the project owner. + +## Sound effects + +- “Spinning reel.wav” by tosha73, used as the source for the fishing fight and + manual-reeling loops. Freesound sound 509902, Creative Commons Zero (CC0): + https://freesound.org/s/509902/ +- “Gentle Ocean Waves Loop” by kkenny101, used for the saltwater shoreline + ambience. Freesound sound 852826, Creative Commons Zero (CC0): + https://freesound.org/s/852826/ +- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound. + Freesound sound 792931, Creative Commons Zero (CC0): + https://freesound.org/s/792931/ + +## Artwork + +- 2D artwork: Rheannon Eisworth, contributor. +- 3D models: original work by the project owner. + +Asset categories and release-record requirements are listed in +[`docs/ASSET-PROVENANCE.md`](docs/ASSET-PROVENANCE.md). diff --git a/README.md b/README.md new file mode 100644 index 0000000..af29cbc --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# NETfishing + +NETfishing is a multiplayer-first fishing game built with Godot 4.7. The +current pre-alpha includes host-authoritative fishing and economy flows, +freshwater and saltwater habitats, persistent player data, social features, +customization, jobs, and a controller-aware interface. + +The project is under active development. Interfaces, balance, content, and +save migrations may continue to change before a stable release. + +## Requirements + +- Godot 4.7.x with the GL Compatibility renderer +- Linux, Windows, or an Android development environment supported by Godot +- Bash for the repository helper scripts + +Open `project.godot` in Godot, or start the editor from the repository root: + +```sh +godot --editor --path . +``` + +Run the game directly with: + +```sh +godot --path . +``` + +## Validation + +The validation scripts isolate Godot's settings and application-data roots. +Run the fast, deterministic suite before submitting a change: + +```sh +scripts/run_validations.sh quick +``` + +Additional suites and individual commands are documented in +[`docs/TESTING.md`](docs/TESTING.md). + +## Building + +Export presets are maintained for Linux, Windows, and Android. Repository +scripts expect the matching Godot export templates and platform toolchains to +already be installed: + +```sh +scripts/build_playtest.sh +scripts/build_android_debug.sh +``` + +Build outputs belong under the ignored `builds/` directory. Release builds +must follow the project's separate tagged-release checklist; these convenience +scripts do not create or publish tags. + +## Project map + +- `main/`: composition root and application orchestration +- `world/`: map, environment, water, and world presentation +- `player/`, `fishing/`, `fish/`: player and fishing domain +- `network/`: transport, identity, and host-authoritative services +- `inventory/`, `items/`, `economy/`, `progression/`, `jobs/`: game state +- `save/`, `settings/`: versioned persistence +- `ui/`: gameplay and menu presentation +- `tests/`: focused Godot validation scripts +- `scripts/`: build, bake, and validation entry points + +See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and +[`world/README-WORLD-AUTHORING.md`](world/README-WORLD-AUTHORING.md) for more +detail. + +## Authorship and assets + +NETfishing is human-directed and uses a mixture of human-authored assets and +software-development assistance. The project records this plainly rather than +claiming that repository history can prove how each line was produced. See +[`CREDITS.md`](CREDITS.md) and +[`docs/ASSET-PROVENANCE.md`](docs/ASSET-PROVENANCE.md). + +No project-wide source license has been selected yet. Do not assume permission +to redistribute the source or bundled assets beyond rights stated in their +individual license records. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..373c800 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,72 @@ +# Architecture + +## Composition + +`main/main.tscn` is the application composition root. It owns long-lived +services, the active world, player container, save/settings managers, and the +pixelated UI viewport. Services are scene-owned rather than global autoloads, +which makes their dependencies visible in the main scene and allows focused +tests to instantiate the same composition. + +The main script coordinates title, session, world, and UI lifecycle. Domain +logic remains in typed resources and services rather than being stored solely +in controls. + +## Domain boundaries + +- `fish/` defines species, availability, catches, pools, quality, and selection. +- `fishing/` owns cast/chase state and resolves authoritative fishing surfaces. +- `inventory/`, `items/`, `economy/`, `progression/`, and `jobs/` own persistent + player-facing game state. +- `world/` owns authored map composition and presentation. Water type is a + shared typed definition used by water bodies, fish habitat, fishing + validation, Logbook classification, and saltwater shoreline presentation. +- `ui/` observes and invokes domain services. It does not define stable item or + fish identity. + +Resource files (`.tres`) are authored data. Stable IDs, not display names or +node names, connect persistent and networked state to that data. + +## Networking + +`NetworkSession` owns ENet session lifecycle and peer registration. Dedicated +network services validate and replicate bounded domains such as fishing, +sales, shops, item use, profiles, jobs, mail, chat, drawings, time, and weather. + +The host is authoritative. Clients submit requests or evidence; the host +derives trusted context from registered peers, authoritative regions, and +server-owned state before mutating inventory, wallet, progression, or shared +world state. See [`decisions/0001-host-authority.md`](decisions/0001-host-authority.md). + +Protocol compatibility is defined in `network/network_protocol.gd`. A visible +release version is not a reason to change the protocol number. + +## Persistence + +`PlayerDataRoot` selects and validates a portable data root. Stores receive +paths from that owner rather than inventing unrelated locations. Progression is +written by `PlayerSaveManager`; device settings and social/identity stores have +separate formats and lifecycles. + +Save migrations are sequential and explicit. Existing catches and ownership +are keyed by stable IDs so authored metadata can evolve without rewriting +historical records. Current source constants—not documentation—are +authoritative for format versions. + +## Presentation + +Gameplay is rendered in 3D with the GL Compatibility renderer. The main UI is +presented through a uniformly scaled SubViewport. Shared UI components and +palette resources prevent page-specific geometry and style drift. + +World presentation systems (time/weather visuals, procedural sky and water, +shoreline ribbons, player blob shadows) do not own gameplay collision or +network authority. Generated shoreline meshes are deterministic presentation +resources baked from explicitly configured static terrain. + +## Validation + +Tests are executable Godot `SceneTree` scripts. Some are pure content/domain +checks, some instantiate the main scene, and multiplayer checks run paired host +and client processes on loopback. The repository runner provides isolation and +consistent entry points; see [`TESTING.md`](TESTING.md). diff --git a/docs/ASSET-PROVENANCE.md b/docs/ASSET-PROVENANCE.md new file mode 100644 index 0000000..9c3bc76 --- /dev/null +++ b/docs/ASSET-PROVENANCE.md @@ -0,0 +1,109 @@ +# Asset provenance + +This document records where runtime assets came from and what must be verified +before redistribution. A Git commit proves when bytes entered this repository; +it does not by itself prove authorship or licensing. + +## Intake policy + +For every new external or supplied asset, record: + +- creator or supplying party; +- original source location or delivery channel; +- source filename and repository destination; +- source and destination SHA-256 when copied without modification; +- license or explicit permission; +- any permitted transformations; +- the importing commit or release. + +Runtime resources must use `res://` paths. Workstation Sync folders are intake +locations only and must never appear in scenes or resources. + +## Current inventory + +| Asset family | Repository location | Recorded provenance | +| --- | --- | --- | +| Fish art and portraits | `fish/species/`, `art/exported/fish/` | 2D artwork by contributor Rheannon Eisworth. | +| Item icons | `items/icons/` | 2D artwork by contributor Rheannon Eisworth. | +| Inventory notepad | `art/ui/ui_notepad.png` | 2D artwork by contributor Rheannon Eisworth; integrated without a runtime external path. | +| Environment textures | `art/exported/environment/textures/` | 2D artwork by contributor Rheannon Eisworth. | +| UI patterns | `art/patterns/` | 2D artwork by contributor Rheannon Eisworth. | +| Character and world models | `art/exported/` | Original 3D models by the project owner. Any embedded 2D artwork is by Rheannon Eisworth. | +| Tuffy font | `ui/fonts/Tuffy_Bold.otf` | Public-domain dedication in `ui/fonts/Tuffy-LICENSE.txt`. | +| Seattle Avenue font | `ui/fonts/seattle_avenue.otf` | License and attribution not present; resolve before public distribution. | +| Title music | `audio/music/title/as_in_four_wolves.ogg` | Original music composed and owned by the project owner. | +| Fishing fight loop | `audio/sfx/fishing/fighting.wav` | Edited from “Spinning reel.wav” by Freesound user tosha73, sound 509902, CC0. | +| Manual-reeling loop | `audio/sfx/fishing/reeling.wav` | Edited from the same CC0 “Spinning reel.wav” source. | +| Saltwater wave ambience | `audio/ambience/waves.wav` | “Gentle Ocean Waves Loop” by Freesound user kkenny101, sound 852826, CC0. | +| Bobber water impact | `audio/sfx/fishing/bobber.wav` | “Quick Water Droplet” by Freesound user qubodup, sound 792931, CC0. | + +### Fishing fight loop source record + +- Source page: https://freesound.org/s/509902/ +- Creator: tosha73 +- Source title: `Spinning reel.wav` +- License: Creative Commons Zero (CC0) +- Downloaded source filename: `509902__tosha73__spinning-reel.wav` +- Downloaded source SHA-256: + `9741afdb1ec9c73f5e039f0e5ac174998cb0a1f737b626531e63919d4d232fad` +- Audacity working project: `fighting.aup3` +- Audacity project SHA-256: + `9928f077ecf48d01416f8d05700707e1b07d0f731846db80e27116f5b82dbbf5` +- Runtime edit source/destination SHA-256: + `b6f3f2f94bfaf9254ac04dd96b1fd28c15e1011a1db44bb3e70eeaa95de3c212` +- Runtime format: 48 kHz, 16-bit, stereo PCM WAV; 2.593479 seconds; + configured as a forward loop by the fishing presentation. +- Manual-reeling Audacity project: `reeling.aup3` +- Manual-reeling Audacity project SHA-256: + `20ad52f6bdd16ae6561f06f8355fe309e95023e815053fd5dfbd5a5ce3c1f9f9` +- Manual-reeling runtime source/destination SHA-256: + `e403d5b7c8af1da4b28599cc0f51de3eda70d9e9296db27852fe7dc95617a29d` +- Manual-reeling runtime format: 48 kHz, 16-bit, stereo PCM WAV; + 2.388083 seconds; configured as a forward loop. +- The downloaded source and Audacity projects remain in the project owner's + source-work archive; only the finished runtime edits ship in the game. + +### Saltwater wave ambience source record + +- Source page: https://freesound.org/s/852826/ +- Creator: kkenny101 +- Source title: `Gentle Ocean Waves Loop` +- License: Creative Commons Zero (CC0) +- Downloaded source filename: + `852826__kkenny101__gentle-ocean-waves-loop.wav` +- Downloaded source and runtime source/destination SHA-256: + `f93e9890583f072ac52cf197b8b5942397d3d5e889fbdb9112a0776142f50229` +- Runtime format: 48 kHz, 24-bit, mono PCM WAV; 21.769917 seconds; + configured as a forward loop. +- The downloaded source remains in the project owner's source-work archive; + only the finished runtime loop ships in the game. + +### Bobber water-impact source record + +- Source page: https://freesound.org/s/792931/ +- Creator: qubodup +- Source title: `Quick Water Droplet` +- License: Creative Commons Zero (CC0) +- Downloaded source filename: `792931__qubodup__quick-water-droplet.wav` +- Downloaded source and runtime source/destination SHA-256: + `aef0d16384efed4a7b071c4d80c21597617f882f4e0c1d5295ac194d315eafb7` +- Runtime format: 48 kHz, 16-bit, mono PCM WAV; 0.197146 seconds; one-shot. +- The downloaded source remains in the project owner's source-work archive; + only the finished runtime sound ships in the game. + +## Generated resources + +Godot `.import` sidecars and deterministic shoreline `.tres` meshes are derived +repository resources, not original artwork. `.godot/imported/`, editor caches, +captures, exports, and temporary test data are not tracked. + +## Release gate + +Before a public binary or source release: + +1. Resolve the Seattle Avenue font license and attribution gap above. +2. Record the public owner/studio credit and the contributor's redistribution + permission in the chosen project license or release records. +3. Confirm the project-wide source/content license selected by the owner. +4. Search exported resources for external filesystem paths. +5. Preserve required third-party notices with the package. diff --git a/docs/BALANCE.md b/docs/BALANCE.md new file mode 100644 index 0000000..948b882 --- /dev/null +++ b/docs/BALANCE.md @@ -0,0 +1,41 @@ +# Balance and content notes + +Balance is authored data and should change deliberately. This document records +the policy; exact current values remain authoritative in `.tres` resources and +typed scripts. + +## Fish + +- Stable fish IDs and catalog numbers must not be changed for display cleanup. +- Species availability may constrain water type, time, weather, bait, and other + authored context. +- Location pools control selection weight; the global catalog remains + comprehensive. +- Freshwater/saltwater habitat is validated by the host in addition to pool + membership. +- Value, rarity, quality, and weight ranges should be reviewed together because + they affect economy and catch difficulty. + +## Economy and progression + +- Sales and purchases are host-authoritative and must mutate inventory and + wallet exactly once. +- Reserved assets are rejected atomically; mixed valid/reserved batches must + not partially succeed. +- Item effects, shop prices, cooler capacity, fishing upgrades, jobs, and + experience are separate balance axes. Avoid changing several in an unrelated + presentation pass. + +## Recording a balance change + +Include in the change description: + +1. affected stable IDs and resource paths; +2. old and new values; +3. intended player-facing outcome; +4. interactions with quality, availability, and economy; +5. deterministic validation performed; +6. whether existing saves remain semantically valid. + +Do not bump the save schema or network protocol simply because an authored +number changed. Bump a format only when its serialized contract changes. diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..e70455b --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,63 @@ +# Testing + +NETfishing validations are Godot `SceneTree` scripts. They use assertions and +exit nonzero on failure. Run them with isolated user data so development checks +cannot touch real saves, identity, trust, relationships, or settings. + +## Consolidated runner + +```sh +scripts/run_validations.sh quick +scripts/run_validations.sh full +scripts/run_validations.sh host +scripts/run_validations.sh network +scripts/run_validations.sh all +scripts/run_validations.sh --list +``` + +- `quick` runs deterministic content and domain validations. +- `full` adds socket-free scene/runtime checks suitable for headless execution. +- `host` runs single-process authoritative-host checks that bind a local UDP + port. +- `network` runs the loopback host/client validations in pairs. +- `all` runs `full`, `host`, and then `network`. + +Set `GODOT_BIN` to select another executable. Set `TEST_TIMEOUT_SECONDS` to +change the per-process timeout. The runner creates one temporary XDG root per +process and removes it on exit. + +## One focused test + +```sh +test_root="$(mktemp -d)" +XDG_DATA_HOME="$test_root/data" \ +XDG_CONFIG_HOME="$test_root/config" \ +godot --headless --path . --script tests/fish_catalog_content_validation.gd +rm -rf -- "$test_root" +``` + +Do not set `NETFISHING_DATA_DIR` to an arbitrary empty folder: that variable is +an explicit portable-data override and must point at a valid NETfishing data +root. Isolating XDG paths is sufficient for the validation scripts. + +## Graphical checks + +Headless tests cannot prove visual alignment, actual mouse routing, shader +appearance, controller feel, or window-resize behavior. Presentation changes +need a real graphical startup with isolated XDG roots and inspection at the +canonical 1280×720 layout plus relevant low/high and ultrawide resolutions. + +## Network checks + +Host and paired tests bind loopback UDP ports defined in their scripts. Ensure +no old Godot validation process is holding those ports and that the execution +environment permits local sockets. The runner starts paired hosts before their +clients and requires both processes to exit successfully. Network tests do not +contact external servers. + +## Release checks + +A release audit additionally includes clean Git/tag verification, editor import, +export-template checks, platform exports, executable smoke tests, archive +inspection, and SHA-256 verification. Those checks are intentionally not hidden +inside the development runner. diff --git a/docs/decisions/0001-host-authority.md b/docs/decisions/0001-host-authority.md new file mode 100644 index 0000000..f134d93 --- /dev/null +++ b/docs/decisions/0001-host-authority.md @@ -0,0 +1,27 @@ +# ADR 0001: Host-authoritative shared state + +Status: accepted + +## Context + +NETfishing supports private hosts, open hosts, and joined clients. Catches, +currency, owned items, jobs, mail, drawings, profiles, and moderation affect +state that cannot safely trust a client's local presentation. + +## Decision + +The host validates requests against registered peers and host-owned context, +then performs the authoritative mutation. Clients receive results and replicated +presentation state. Client evidence is bounded and reconstructed where +possible; it is not treated as authority. + +Private single-player uses the same host path. Presentation-only state does not +add RPCs or persistence. + +## Consequences + +- Shared behavior is consistent across private, open-host, and joined-client + modes. +- Tests must cover local-host and paired host/client paths. +- Stable protocol payloads and rejection cleanup require explicit handling. +- UI activation cannot be used as proof that a server-side mutation succeeded. diff --git a/docs/decisions/0002-authored-data-and-stable-ids.md b/docs/decisions/0002-authored-data-and-stable-ids.md new file mode 100644 index 0000000..23163cb --- /dev/null +++ b/docs/decisions/0002-authored-data-and-stable-ids.md @@ -0,0 +1,27 @@ +# ADR 0002: Authored resources and stable identifiers + +Status: accepted + +## Context + +Fish, items, pools, availability, shop stock, and other game content evolve +independently from saved catches and network messages. Display labels and scene +node names are expected to change during development. + +## Decision + +Repository-owned resources are the authoritative authored data. Persistent and +networked records refer to stable IDs. Display names, filenames, node names, +coordinates, and pool names are not compatibility identifiers. + +Cross-domain concepts such as water type use one typed definition rather than +duplicated strings. Comprehensive catalogs remain separate from location- +specific selection pools. + +## Consequences + +- Renaming visible UI does not require save migration. +- Removing or changing a stable ID requires an explicit compatibility plan. +- Content validation checks uniqueness, catalog completeness, pool membership, + and typed habitat compatibility. +- New authored assets need provenance records as well as resource references. diff --git a/scripts/run_validations.sh b/scripts/run_validations.sh new file mode 100755 index 0000000..1fa7312 --- /dev/null +++ b/scripts/run_validations.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash + +set -euo pipefail + +readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +readonly GODOT_BIN="${GODOT_BIN:-godot}" +readonly TEST_TIMEOUT_SECONDS="${TEST_TIMEOUT_SECONDS:-120}" +readonly RUN_ROOT="$(mktemp -d -t netfishing-validations.XXXXXX)" + +readonly -a QUICK_TESTS=( + "tests/android_readiness_validation.gd" + "tests/fish_catalog_content_validation.gd" + "tests/fish_quality_validation.gd" + "tests/fishing_audio_validation.gd" + "tests/fishing_surface_validation.gd" + "tests/logbook_validation.gd" + "tests/player_experience_validation.gd" + "tests/shoreline_ambience_validation.gd" + "tests/surface_drawing_validation.gd" + "tests/terrain_projection_validation.gd" + "tests/world_time_validation.gd" + "tests/world_weather_validation.gd" +) + +readonly -a RUNTIME_TESTS=( + "tests/inventory_notepad_art_validation.gd" + "tests/logbook_runtime_validation.gd" + "tests/player_experience_ui_validation.gd" + "tests/ui_scaling_runtime_validation.gd" +) + +readonly -a HOST_TESTS=( + "tests/art_tools_validation.gd" + "tests/economy_regression_validation.gd" + "tests/fish_hotbar_showcase_validation.gd" + "tests/fishing_authority_validation.gd" + "tests/job_system_validation.gd" + "tests/surface_drawing_runtime_validation.gd" +) + +readonly -a NETWORK_TESTS=( + "tests/economy_regression_validation.gd" + "tests/fish_showcase_multiplayer_validation.gd" + "tests/fishing_multiplayer_validation.gd" + "tests/job_multiplayer_validation.gd" + "tests/surface_drawing_multiplayer_validation.gd" + "tests/world_time_multiplayer_validation.gd" +) + +cleanup() { + rm -rf -- "${RUN_ROOT}" +} +trap cleanup EXIT INT TERM + +usage() { + printf 'Usage: %s {quick|full|host|network|all|--list}\n' "$0" +} + +prepare_root() { + local name="$1" + local root="${RUN_ROOT}/${name}" + mkdir -p -- "${root}/data" "${root}/config" "${root}/cache" + printf '%s\n' "${root}" +} + +run_test() { + local script="$1" + local name="${script#tests/}" + name="${name%.gd}" + local root + root="$(prepare_root "${name}")" + printf '\n==> %s\n' "${script}" + XDG_DATA_HOME="${root}/data" \ + XDG_CONFIG_HOME="${root}/config" \ + XDG_CACHE_HOME="${root}/cache" \ + timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \ + --headless --path "${PROJECT_ROOT}" --script "${script}" +} + +run_network_test() { + local script="$1" + local name="${script#tests/}" + name="${name%.gd}" + local host_root client_root host_pid host_status client_status + host_root="$(prepare_root "${name}-host")" + client_root="$(prepare_root "${name}-client")" + printf '\n==> %s (host + client)\n' "${script}" + XDG_DATA_HOME="${host_root}/data" \ + XDG_CONFIG_HOME="${host_root}/config" \ + XDG_CACHE_HOME="${host_root}/cache" \ + timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \ + --headless --path "${PROJECT_ROOT}" --script "${script}" -- host \ + >"${host_root}/output.log" 2>&1 & + host_pid=$! + sleep 0.75 + set +e + XDG_DATA_HOME="${client_root}/data" \ + XDG_CONFIG_HOME="${client_root}/config" \ + XDG_CACHE_HOME="${client_root}/cache" \ + timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \ + --headless --path "${PROJECT_ROOT}" --script "${script}" -- client \ + >"${client_root}/output.log" 2>&1 + client_status=$? + wait "${host_pid}" + host_status=$? + set -e + printf '%s\n' '-- host output --' + sed -n '1,240p' "${host_root}/output.log" + printf '%s\n' '-- client output --' + sed -n '1,240p' "${client_root}/output.log" + if ((host_status != 0 || client_status != 0)); then + printf 'error: host exited %d; client exited %d\n' \ + "${host_status}" "${client_status}" >&2 + return 1 + fi +} + +run_quick() { + local test_script + for test_script in "${QUICK_TESTS[@]}"; do + run_test "${test_script}" + done +} + +run_full() { + local test_script + run_quick + for test_script in "${RUNTIME_TESTS[@]}"; do + run_test "${test_script}" + done +} + +run_host() { + local test_script + for test_script in "${HOST_TESTS[@]}"; do + run_test "${test_script}" + done +} + +run_network() { + local test_script + for test_script in "${NETWORK_TESTS[@]}"; do + run_network_test "${test_script}" + done +} + +list_tests() { + printf '%s\n' 'Quick tests:' + printf ' %s\n' "${QUICK_TESTS[@]}" + printf '%s\n' 'Additional full-suite tests:' + printf ' %s\n' "${RUNTIME_TESTS[@]}" + printf '%s\n' 'Single-process tests requiring a local UDP bind:' + printf ' %s\n' "${HOST_TESTS[@]}" + printf '%s\n' 'Paired network tests:' + printf ' %s\n' "${NETWORK_TESTS[@]}" +} + +case "${1:-}" in + quick) + run_quick + ;; + full) + run_full + ;; + host) + run_host + ;; + network) + run_network + ;; + all) + run_full + run_host + run_network + ;; + --list) + list_tests + ;; + *) + usage >&2 + exit 2 + ;; +esac + +printf '\nAll requested validations passed.\n'