diff --git a/README.md b/README.md index af29cbc..2b36341 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ detail. ## Authorship and assets -NETfishing is human-directed and uses a mixture of human-authored assets and +NETfishing uses a mixture of original 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 diff --git a/docs/PORTMASTER-PERFORMANCE.md b/docs/PORTMASTER-PERFORMANCE.md new file mode 100644 index 0000000..8c53f34 --- /dev/null +++ b/docs/PORTMASTER-PERFORMANCE.md @@ -0,0 +1,28 @@ +# PortMaster performance profile + +The PortMaster launcher keeps the normal game profile on unknown and stronger +hardware. It enables the low-end profile only when the Linux device tree +reports one of the identifiers used by the Allwinner H616/H700 XX handhelds: + +- `allwinner,h616` +- `sun50iw9p1` +- `allwinner,sun50i-h700` + +The low-end launch profile passes these options to Godot: + +- `--single-window` +- `--disable-vsync` +- `--max-fps 30` +- `--audio-output-latency 40` + +It also passes `NETFISHING_LOW_END=1`. The game responds by rendering the 3D +world at 75 percent linear resolution with nearest-neighbor scaling. This +reduces 3D pixel work by about 44 percent while the separately rendered UI +retains its canonical resolution. + +Do not use Godot's low processor mode for this profile. It reduces idle CPU +usage by sleeping between updates and is not a game-performance optimization. + +When adding another device family, record its exact NUL-separated device-tree +`compatible` value from the hardware before extending the launcher match. Do +not infer detection from a retail product name alone. diff --git a/docs/PORTMASTER-RELEASE.md b/docs/PORTMASTER-RELEASE.md new file mode 100644 index 0000000..3bfae87 --- /dev/null +++ b/docs/PORTMASTER-RELEASE.md @@ -0,0 +1,137 @@ +# PortMaster release builds + +NETfishing's PortMaster release is a canonical PortMaster package, not a +version-named generic ZIP. The installer-facing archive must be named +`netfishing.zip`, and its root must contain exactly: + +```text +NETfishing.sh +netfishing/ +port.json +``` + +Do not derive a release package from an older local `portmaster-stage` +directory. The templates in `scripts/portmaster/` are authoritative. + +## Release contract + +- `port.json` uses schema version 4 and names `netfishing.zip`. +- `NETfishing.sh` starts with `# PORTMASTER: netfishing.zip, NETfishing.sh`. +- The executable is `netfishing/NETfishing.aarch64`. +- The package declares AArch64, two analog sticks, GLIBC 2.28, and + `weston_pkg_0.2.squashfs`. +- The launcher must not start GPTOKEYB. Godot and NETfishing's controller + mapping manager handle controller input directly. +- Persistent device data remains under `netfishing/conf/data`, + `netfishing/conf/config`, and `netfishing/conf/cache`. +- The archive must not contain `conf/`, saves, identities, logs, source files, + `.git`, or `.godot` content. +- Release downloads must publish `netfishing.zip`. A renamed versioned ZIP is + not a substitute because HarbourMaster identifies the canonical port by + archive name. + +## Build + +Run this only after the release commit and annotated `v` tag +are pinned to the same clean `main` commit: + +```bash +bash scripts/build_portmaster.sh +``` + +The script deletes and regenerates the Linux ARM64 export, stages the package, +validates its structure, and writes: + +```text +builds/v/netfishing.zip +``` + +`--package-only` is reserved for repackaging an already validated ARM64 export. +It does not rebuild game content. + +## Local muOS installation + +The muOS auto-install directory is: + +```text +/mnt/mmc/MUOS/PortMaster/autoinstall/ +``` + +For an explicit offline installation test, copy `netfishing.zip` to a temporary +device directory and run: + +```bash +/mnt/mmc/MUOS/PortMaster/harbourmaster \ + --offline --no-check install ./netfishing.zip +``` + +Before upgrading an existing installation, preserve +`/mnt/mmc/ports/netfishing/conf/` on the device. After installation: + +1. Confirm the installed executable and PCK hashes match the staged release. +2. Confirm the installed launcher contains the canonical PortMaster header and + does not contain `GPTOKEYB`. +3. Confirm `conf/` was not replaced or removed. +4. Launch the installed port through the normal muOS menu. +5. Verify the displayed game version and controller face-button mapping. + +Installing the public PortMaster catalog entry can still install an older +catalog build until the upstream `netfishing.zip` is updated. A local release +test must install the generated local archive explicitly. + +## Template provenance + +The current catalog screenshot is a 640x480 PNG copied +byte-for-byte into the package template: + +```text +f24daae48f543b3a270a5aa46b5e73a31904bf0c7216d4c8a9e15a8dc48a5eed screenshot.png +``` + +The remaining catalog metadata assets were copied from the installed official +`netfishing.zip` package and are intentionally preserved byte-for-byte: + +```text +e2a9d132744684c67865c01eca027a8c9946b4c5a19da57c82f1af0373dc83b7 gameinfo.xml +ff08aacc52bbdc95616320800da3eaee0c0ba5fafdd962bf04f9655859409764 licenses/CREDITS.md +b84fdd2c3da5db56385cdbb639795e90aa3e035c53bc3591135f18df3331451f licenses/Tuffy-LICENSE.txt +``` + +The original catalog porter credit, `Voyager`, remains in `port.json`. + +## muOS H700 controller mapping + +Godot 4.7.1 and the SDL2 utilities shipped by muOS identify the same virtual +controller differently. The PortMaster SDL2 database uses GUID +`19000000010000000100000000010000`, while Godot reports GUID +`19004ca6010000000100000000010000`. Passing the SDL2 entry unchanged leaves +most controls unmapped in Godot. + +The device-verified Godot mapping is: + +- A `b0`, B `b1`, Y `b2`, X `b3` +- left bumper `b4`, right bumper `b5` +- select `b6`, start `b7`, guide `b8` +- left-stick click `b9`, left trigger `b10`, right trigger `b11`, right-stick click `b12` +- D-pad `h0`; left and right sticks `a0..a3` + +`scripts/portmaster/NETfishing.sh` must replace the incompatible SDL2 entry +with the Godot entry when that SDL2 GUID is selected. Do not append the two +entries with a newline: WestonPack evaluates launcher arguments through a +shell and treats the second line as a command. Pass the single selected mapping +as `SDL_GAMECONTROLLERCONFIG` in the game command after Weston initializes; +Weston sources PortMaster's control file internally and otherwise restores the +SDL2 value. + +Do not call `Input.add_joy_mapping(..., true)` for a recognized connected muOS +controller. Updating this virtual controller after connection can stop Godot +from delivering its standardized controller events until restart. + +Before shipping a PortMaster build on H700 hardware, verify all of the +following in the installed game: + +- A, B, X, and Y each register independently. +- The D-pad navigates every menu direction. +- Both sticks, both bumpers, both triggers, Select, Start, L3, and R3 register. +- A single button never cancels auto-map. +- Holding both bumpers together for 1.25 seconds cancels auto-map. diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh new file mode 100755 index 0000000..cea49cd --- /dev/null +++ b/scripts/build_portmaster.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +set -euo pipefail + +readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +readonly TEMPLATE_ROOT="${SCRIPT_DIR}/portmaster" +readonly GODOT_BIN="${GODOT_BIN:-godot}" + +PACKAGE_ONLY=0 +if [[ "${1:-}" == "--package-only" ]]; then + PACKAGE_ONLY=1 + shift +fi +if [[ $# -ne 0 ]]; then + echo "Usage: $0 [--package-only]" >&2 + exit 2 +fi + +readonly PROJECT_VERSION="$( + sed -n 's/^config\/version="\([^"]*\)"/\1/p' "${PROJECT_ROOT}/project.godot" +)" +if [[ -z "${PROJECT_VERSION}" ]]; then + echo "Could not read application/config/version from project.godot." >&2 + exit 1 +fi + +readonly RELEASE_TAG="v${PROJECT_VERSION}" +readonly RELEASE_ROOT="${PROJECT_ROOT}/builds/${RELEASE_TAG}" +readonly ARM64_ROOT="${RELEASE_ROOT}/linux-arm64" +readonly ARM64_EXECUTABLE="${ARM64_ROOT}/NETfishing.arm64" +readonly ARM64_PCK="${ARM64_ROOT}/NETfishing.pck" +readonly STAGE_ROOT="${RELEASE_ROOT}/portmaster-stage" +readonly GAME_ROOT="${STAGE_ROOT}/netfishing" +readonly ARCHIVE="${RELEASE_ROOT}/netfishing.zip" +readonly LEGACY_ARCHIVE="${RELEASE_ROOT}/netfishing-${RELEASE_TAG}-portmaster-arm64.zip" + +readonly HEAD_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" +readonly TAG_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse "${RELEASE_TAG}^{commit}")" +if [[ "${HEAD_COMMIT}" != "${TAG_COMMIT}" ]]; then + echo "HEAD does not match ${RELEASE_TAG}. Refusing to package mutable source." >&2 + exit 1 +fi +if [[ "${NETFISHING_ALLOW_DIRTY_RELEASE:-0}" != "1" ]]; then + dirty_tree=0 + git -C "${PROJECT_ROOT}" diff --quiet || dirty_tree=1 + git -C "${PROJECT_ROOT}" diff --cached --quiet || dirty_tree=1 + if [[ -n "$(git -C "${PROJECT_ROOT}" status --porcelain --untracked-files=normal)" ]]; then + dirty_tree=1 + fi + if [[ ${dirty_tree} -ne 0 ]]; then + echo "The repository is not clean. Refusing to build a release package." >&2 + exit 1 + fi +fi + +for command_name in git file zip unzip sha256sum; do + command -v "${command_name}" >/dev/null 2>&1 || { + echo "Required command not found: ${command_name}" >&2 + exit 1 + } +done + +mkdir -p -- "${ARM64_ROOT}" "${RELEASE_ROOT}" +if [[ ${PACKAGE_ONLY} -eq 0 ]]; then + rm -f -- "${ARM64_EXECUTABLE}" "${ARM64_PCK}" + "${GODOT_BIN}" \ + --headless \ + --path "${PROJECT_ROOT}" \ + --export-release "Linux ARM64" +fi + +test -s "${ARM64_EXECUTABLE}" +test -s "${ARM64_PCK}" +file "${ARM64_EXECUTABLE}" | grep -q "ARM aarch64" + +rm -rf -- "${STAGE_ROOT}" +rm -f -- "${ARCHIVE}" "${LEGACY_ARCHIVE}" +mkdir -p -- "${GAME_ROOT}/licenses" + +install -m 0755 "${TEMPLATE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/NETfishing.sh" +install -m 0644 "${TEMPLATE_ROOT}/port.json" "${STAGE_ROOT}/port.json" +install -m 0755 "${ARM64_EXECUTABLE}" "${GAME_ROOT}/NETfishing.aarch64" +install -m 0644 "${ARM64_PCK}" "${GAME_ROOT}/NETfishing.pck" +install -m 0644 "${TEMPLATE_ROOT}/gameinfo.xml" "${GAME_ROOT}/gameinfo.xml" +install -m 0644 "${TEMPLATE_ROOT}/screenshot.png" "${GAME_ROOT}/screenshot.png" +install -m 0644 \ + "${TEMPLATE_ROOT}/licenses/CREDITS.md" \ + "${GAME_ROOT}/licenses/CREDITS.md" +install -m 0644 \ + "${TEMPLATE_ROOT}/licenses/Tuffy-LICENSE.txt" \ + "${GAME_ROOT}/licenses/Tuffy-LICENSE.txt" + +cat >"${GAME_ROOT}/BUILD-INFO.txt" <"${GAME_ROOT}/README.md" </dev/null +readonly TOP_LEVELS="$( + unzip -Z1 "${ARCHIVE}" | cut -d/ -f1 | sort -u +)" +readonly EXPECTED_TOP_LEVELS="$(printf '%s\n' NETfishing.sh netfishing port.json)" +if [[ "${TOP_LEVELS}" != "${EXPECTED_TOP_LEVELS}" ]]; then + echo "Unexpected PortMaster archive roots:" >&2 + printf '%s\n' "${TOP_LEVELS}" >&2 + exit 1 +fi +grep -q '"version": 4' "${STAGE_ROOT}/port.json" +grep -q '"name": "netfishing.zip"' "${STAGE_ROOT}/port.json" +grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \ + "${STAGE_ROOT}/NETfishing.sh" +if grep -q 'GPTOKEYB' "${STAGE_ROOT}/NETfishing.sh"; then + echo "GPTOKEYB must not be enabled for NETfishing." >&2 + exit 1 +fi +if unzip -Z1 "${ARCHIVE}" | grep -E \ + '(^|/)(conf|\.git|\.godot|logs?|saves?|identities?)(/|$)|\.(gd|tscn|blend)$' \ + >/dev/null; then + echo "The PortMaster archive contains prohibited release content." >&2 + exit 1 +fi + +sha256sum "${ARCHIVE}" +echo "PortMaster package created at ${ARCHIVE}" diff --git a/scripts/portmaster/NETfishing.sh b/scripts/portmaster/NETfishing.sh new file mode 100755 index 0000000..58fbfbd --- /dev/null +++ b/scripts/portmaster/NETfishing.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# PORTMASTER: netfishing.zip, NETfishing.sh + +XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} + +if [ -d "/opt/system/Tools/PortMaster/" ]; then + controlfolder="/opt/system/Tools/PortMaster" +elif [ -d "/opt/tools/PortMaster/" ]; then + controlfolder="/opt/tools/PortMaster" +elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then + controlfolder="$XDG_DATA_HOME/PortMaster" +else + controlfolder="/roms/ports/PortMaster" +fi + +if [ ! -f "$controlfolder/control.txt" ]; then + echo "PortMaster control.txt was not found at $controlfolder" >&2 + exit 1 +fi + +source "$controlfolder/control.txt" +[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" +get_controls + +GAMEDIR="/${directory}/ports/netfishing" +if [ ! -d "$GAMEDIR" ] && [ -d "/mnt/mmc/ports/netfishing" ]; then + GAMEDIR="/mnt/mmc/ports/netfishing" +fi + +CONFDIR="$GAMEDIR/conf" +GAME_EXECUTABLE="$GAMEDIR/NETfishing.aarch64" +WESTON_DIR="/tmp/netfishing-weston" +WESTON_RUNTIME="weston_pkg_0.2" +HARBOURMASTER="$controlfolder/harbourmaster" +if [ ! -x "$HARBOURMASTER" ] && [ -x "/mnt/mmc/MUOS/PortMaster/harbourmaster" ]; then + HARBOURMASTER="/mnt/mmc/MUOS/PortMaster/harbourmaster" +fi + +mkdir -p "$CONFDIR/data" "$CONFDIR/config" "$CONFDIR/cache" "$WESTON_DIR" +chmod +x "$GAME_EXECUTABLE" + +> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1 + +if [ ! -f "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" ]; then + if [ ! -x "$HARBOURMASTER" ]; then + pm_message "NETfishing requires the latest PortMaster and WestonPack runtime." + sleep 5 + exit 1 + fi + $ESUDO "$HARBOURMASTER" --quiet --no-check runtime_check "${WESTON_RUNTIME}.squashfs" +fi + +if [ ! -f "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" ]; then + pm_message "WestonPack could not be installed. Check the network connection and PortMaster runtime manager." + sleep 5 + exit 1 +fi + +if [[ "$PM_CAN_MOUNT" != "N" ]]; then + $ESUDO umount "$WESTON_DIR" 2>/dev/null +fi +$ESUDO mount "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" "$WESTON_DIR" + +cd "$GAMEDIR" || exit 1 + +# Godot 4.7 identifies the muOS H700 virtual controller with a CRC-bearing +# GUID and numbers its buttons without the volume keys exposed by SDL2. The +# PortMaster SDL2 database entry therefore cannot drive Godot's gamepad layer. +GODOT_MUOS_MAPPING="19004ca6010000000100000000010000,muOS-Keys,a:b0,b:b1,x:b3,y:b2,leftshoulder:b4,rightshoulder:b5,lefttrigger:b10,righttrigger:b11,guide:b8,start:b7,back:b6,dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4,leftx:a0,lefty:a1,leftstick:b9,rightx:a2,righty:a3,rightstick:b12,platform:Linux," +MUOS_SDL2_GUID="19000000010000000100000000010000" +if [[ "$sdl_controllerconfig" == "${MUOS_SDL2_GUID},"* ]]; then + netfishing_controllerconfig="$GODOT_MUOS_MAPPING" +elif [ -n "$sdl_controllerconfig" ]; then + netfishing_controllerconfig="$sdl_controllerconfig" +else + netfishing_controllerconfig="$GODOT_MUOS_MAPPING" +fi + +NETFISHING_GODOT_OPTIONS=() +NETFISHING_GAME_ENVIRONMENT=() +DEVICE_COMPATIBILITY="" +if [[ -r /proc/device-tree/compatible ]]; then + DEVICE_COMPATIBILITY="$(tr '\0' ' ' /dev/null +fi +pm_finish diff --git a/scripts/portmaster/gameinfo.xml b/scripts/portmaster/gameinfo.xml new file mode 100755 index 0000000..ef608bd --- /dev/null +++ b/scripts/portmaster/gameinfo.xml @@ -0,0 +1,13 @@ + + + + ./NETfishing.sh + NETfishing + A relaxed multiplayer fishing game about exploring a small island, catching and collecting fish, customizing your character, and spending time by the water. + 20260804T000000 + NETfishing project + Independent + adventure, simulation + ./netfishing/screenshot.png + + diff --git a/scripts/portmaster/licenses/CREDITS.md b/scripts/portmaster/licenses/CREDITS.md new file mode 100755 index 0000000..8edbd4f --- /dev/null +++ b/scripts/portmaster/licenses/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/scripts/portmaster/licenses/Tuffy-LICENSE.txt b/scripts/portmaster/licenses/Tuffy-LICENSE.txt new file mode 100755 index 0000000..defced0 --- /dev/null +++ b/scripts/portmaster/licenses/Tuffy-LICENSE.txt @@ -0,0 +1,11 @@ +We, the copyright holders of this work, hereby release it into the +public domain. This applies worldwide. + +In case this is not legally possible, + +We grant any entity the right to use this work for any purpose, without +any conditions, unless such conditions are required by law. + +Thatcher Ulrich http://tulrich.com +Karoly Barta bartakarcsi@gmail.com +Michael Evans http://www.evertype.com diff --git a/scripts/portmaster/port.json b/scripts/portmaster/port.json new file mode 100644 index 0000000..d14fa8f --- /dev/null +++ b/scripts/portmaster/port.json @@ -0,0 +1,39 @@ +{ + "version": 4, + "name": "netfishing.zip", + "items": [ + "NETfishing.sh", + "netfishing/" + ], + "items_opt": null, + "attr": { + "title": "NETfishing", + "porter": [ + "Voyager", + "asellite" + ], + "desc": "A relaxed multiplayer fishing game about exploring a small island, catching and collecting fish, customizing your character, and spending time by the water.", + "desc_md": null, + "inst": "Ready to run on supported ARM64 PortMaster devices. This alpha package requires two analog sticks.", + "inst_md": null, + "genres": [ + "adventure", + "simulation" + ], + "image": {}, + "rtr": false, + "exp": false, + "runtime": [ + "weston_pkg_0.2.squashfs" + ], + "store": [], + "availability": "full", + "reqs": [ + "analog_2" + ], + "arch": [ + "aarch64" + ], + "min_glibc": "2.28" + } +} diff --git a/scripts/portmaster/screenshot.png b/scripts/portmaster/screenshot.png new file mode 100755 index 0000000..4fe8f2e Binary files /dev/null and b/scripts/portmaster/screenshot.png differ diff --git a/scripts/portmaster/screenshot.png.import b/scripts/portmaster/screenshot.png.import new file mode 100644 index 0000000..3aa5f2d --- /dev/null +++ b/scripts/portmaster/screenshot.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ww0w8iubiwsf" +path="res://.godot/imported/screenshot.png-6eed5589b80ced9ab24aed11cadf34f5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://scripts/portmaster/screenshot.png" +dest_files=["res://.godot/imported/screenshot.png-6eed5589b80ced9ab24aed11cadf34f5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/world/README-WORLD-AUTHORING.md b/world/README-WORLD-AUTHORING.md index d596c05..60cfdc8 100644 --- a/world/README-WORLD-AUTHORING.md +++ b/world/README-WORLD-AUTHORING.md @@ -1,6 +1,6 @@ # World authoring -The active test world uses the human-authored starter island through +The active test world uses the canonical starter island through `world/regions/starter_island_region.tscn`. ## Active composition