Consolidate project documentation and attribution

Add public-alias credits and a tested in-game credits page, centralize project guidance and asset provenance, import the complete authored fur channel maps, and keep starter-island collision synchronized with imported props.
This commit is contained in:
Alexander Sellite 2026-08-12 22:12:11 -04:00
parent 1940ae13e5
commit db6074ddfa
59 changed files with 1638 additions and 1032 deletions

View file

@ -1,19 +1,20 @@
# NETfishing Asset License 1.0 # NETfishing Asset License 1.0
Copyright © 2026 Alexander Sellite and Rheannon Eisworth. All rights Copyright © 2026 Woofmeow. All rights reserved except for the permissions
reserved except for the permissions expressly granted below. expressly granted below.
## 1. Scope ## 1. Scope
This license applies to original NETfishing artwork, graphics, textures, This license applies to original NETfishing artwork, graphics, textures,
models, animations, music, sound recordings, narrative content, visual models, animations, music, sound recordings, narrative content, visual
designs, characters, and other creative assets owned by Alexander Sellite and designs, characters, and other creative assets owned by the NETfishing owners,
Rheannon Eisworth (the **“NETfishing Assets”**). publishing as Woofmeow (the **“NETfishing Assets”**).
It does not apply to: It does not apply to:
- software source code identified as GPL-3.0-or-later; - software source code identified as GPL-3.0-or-later;
- third-party material identified in `THIRD-PARTY-NOTICES.md`; - third-party material identified in the source repository's
`docs/ATTRIBUTION.md` or the packaged `ATTRIBUTION.md` notice;
- material accompanied by a different license; or - material accompanied by a different license; or
- the NETfishing and Woofmeow names and logos, which are addressed in - the NETfishing and Woofmeow names and logos, which are addressed in
`TRADEMARKS.md`. `TRADEMARKS.md`.
@ -62,9 +63,9 @@ A distributed Mod must:
2. be presented as unofficial community content; 2. be presented as unofficial community content;
3. include the following notice in its documentation or distribution page: 3. include the following notice in its documentation or distribution page:
> Contains or adapts NETfishing assets © 2026 Alexander Sellite and > Contains or adapts NETfishing assets © 2026 Woofmeow, used under the
> Rheannon Eisworth, used under the NETfishing Asset License 1.0. This Mod > NETfishing Asset License 1.0. This Mod is unofficial and is not endorsed
> is unofficial and is not endorsed by Woofmeow. > by Woofmeow.
4. identify material created by the Mod author separately from NETfishing 4. identify material created by the Mod author separately from NETfishing
Assets; and Assets; and
@ -98,9 +99,9 @@ owners grant separate written permission.
## 6. Ownership of Mods ## 6. Ownership of Mods
You retain ownership of your original Mod material. Alexander Sellite and You retain ownership of your original Mod material. The NETfishing owners
Rheannon Eisworth retain ownership of the NETfishing Assets and adaptations of retain ownership of the NETfishing Assets and adaptations of those assets to
those assets to the extent provided by applicable law. the extent provided by applicable law.
This license does not require you to assign your original Mod material to the This license does not require you to assign your original Mod material to the
NETfishing owners. It also does not grant you ownership of the underlying NETfishing owners. It also does not grant you ownership of the underlying
@ -126,9 +127,9 @@ OTHER LIABILITY ARISING FROM THIS LICENSE OR USE OF THE NETFISHING ASSETS.
## 9. Additional permission ## 9. Additional permission
Uses not granted here require written permission from both Alexander Sellite Uses not granted here require written permission from both NETfishing owners.
and Rheannon Eisworth. A permission granted to one person or project does not A permission granted to one person or project does not amend this license for
amend this license for others. others.
This is a custom asset license, not an open-source software license or a This is a custom asset license, not an open-source software license or a
Creative Commons license. Creative Commons license.

View file

@ -21,7 +21,8 @@ separate written terms are needed.
- Keep presentation-only systems out of save files and network messages. - Keep presentation-only systems out of save files and network messages.
- Store runtime assets under repository-owned paths; never reference a - Store runtime assets under repository-owned paths; never reference a
workstation sync or temporary directory. workstation sync or temporary directory.
- Record the source and license of newly imported assets. - Record the source and license of newly imported assets in
[`docs/ATTRIBUTION.md`](docs/ATTRIBUTION.md).
- Do not assume the GPL code license applies to project assets; observe - Do not assume the GPL code license applies to project assets; observe
[`ASSET-LICENSE.md`](ASSET-LICENSE.md) and third-party terms. [`ASSET-LICENSE.md`](ASSET-LICENSE.md) and third-party terms.
- Do not commit `.godot/`, test data, logs, captures, or build outputs. - Do not commit `.godot/`, test data, logs, captures, or build outputs.
@ -34,7 +35,8 @@ scripts/run_validations.sh quick
git status --short git status --short
``` ```
Choose additional focused tests from [`docs/TESTING.md`](docs/TESTING.md). Choose additional focused tests from the validation section of
[`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md#validation).
Changes involving networking should run the network suite and a real Changes involving networking should run the network suite and a real
two-process check. Visual changes still require graphical review; headless two-process check. Visual changes still require graphical review; headless
tests are supporting evidence, not a replacement. tests are supporting evidence, not a replacement.

View file

@ -4,9 +4,10 @@ These terms apply to contributions intentionally submitted for inclusion in
NETfishing after these terms are published. They do not retroactively change NETfishing after these terms are published. They do not retroactively change
ownership of earlier work or replace a separately signed agreement. ownership of earlier work or replace a separately signed agreement.
“Owners” means Alexander Sellite and Rheannon Eisworth. “Project” means the “Owners” means the two NETfishing co-owners who publish the Project as
official NETfishing game, its servers, tools, documentation, websites, ports, Woofmeow and are publicly credited as Voyager and Endeavour. “Project” means
updates, and promotional materials. the official NETfishing game, its servers, tools, documentation, websites,
ports, updates, and promotional materials.
## 1. Your ownership and authority ## 1. Your ownership and authority

View file

@ -1,65 +0,0 @@
# Credits
## Project ownership and development
NETfishing is jointly owned, developed, and published by Alexander Sellite and
Rheannon Eisworth, operating collectively as the independent team Woofmeow.
- Alexander Sellite: co-owner, developer, project director, and 3D artist.
- Rheannon Eisworth: co-owner, developer, and lead 2D artist.
## 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: public-domain font by JLH Fonts, bundled as
`ui/fonts/seattle_avenue.otf`; see `ui/fonts/Seattle-Avenue-LICENSE.txt`.
## Music
- `audio/music/title/as_in_four_wolves.ogg`: original title/ambient music by
Alexander Sellite.
- `audio/music/world/craft.mp3`: original world music composed by Alexander
Sellite.
## 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/
- Rain Loop Ontario by Ayton -- https://freesound.org/s/212799/ -- License:
Attribution 3.0
- Dog_Bark.wav by ivolipa -- https://freesound.org/s/328729/ -- License:
Creative Commons 0
- Cat meow.m4a by Christyboy100 -- https://freesound.org/s/495694/ -- License:
Attribution 3.0
- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound.
Freesound sound 792931, Creative Commons Zero (CC0):
https://freesound.org/s/792931/
- `sound/dialogue/animalese/placeholder/*.wav`: original animalese tones
composed by Alexander Sellite without third-party samples.
## Artwork
- Original 2D artwork: Rheannon Eisworth.
- Original 3D models: Alexander Sellite.
Asset categories and release-record requirements are listed in
[`docs/ASSET-PROVENANCE.md`](docs/ASSET-PROVENANCE.md).
Complete license wording, modification notices, and Godot Engine attribution
are recorded in [`THIRD-PARTY-NOTICES.md`](THIRD-PARTY-NOTICES.md).

View file

@ -9,7 +9,7 @@ The project is under active development. Interfaces, balance, content, and
save migrations may continue to change before a stable release. save migrations may continue to change before a stable release.
NETfishing is developed and published by **Woofmeow**, the independent team of NETfishing is developed and published by **Woofmeow**, the independent team of
co-owners Alexander Sellite and Rheannon Eisworth. co-owners publicly credited as Voyager and Endeavour.
## Requirements ## Requirements
@ -38,8 +38,8 @@ Run the fast, deterministic suite before submitting a change:
scripts/run_validations.sh quick scripts/run_validations.sh quick
``` ```
Additional suites and individual commands are documented in Additional suites and individual commands are documented in the
[`docs/TESTING.md`](docs/TESTING.md). [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md#validation) validation section.
## Building ## Building
@ -68,9 +68,10 @@ scripts do not create or publish tags.
- `tests/`: focused Godot validation scripts - `tests/`: focused Godot validation scripts
- `scripts/`: build, bake, and validation entry points - `scripts/`: build, bake, and validation entry points
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and All maintained project guidance starts at the
[`world/README-WORLD-AUTHORING.md`](world/README-WORLD-AUTHORING.md) for more [`docs/` documentation index](docs/README.md). Architecture, content policy,
detail. world and UI authoring, and testing are consolidated in
[`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md).
## Licensing ## Licensing
@ -82,11 +83,11 @@ detail.
unrelated products. unrelated products.
- The NETfishing and Woofmeow names and branding are reserved as described in - The NETfishing and Woofmeow names and branding are reserved as described in
[TRADEMARKS.md](TRADEMARKS.md). [TRADEMARKS.md](TRADEMARKS.md).
- Third-party materials retain their own terms; see - Third-party materials retain their own terms; see the consolidated
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md). [credits, notices, and provenance record](docs/ATTRIBUTION.md).
- Contributions are accepted under [CONTRIBUTOR-TERMS.md](CONTRIBUTOR-TERMS.md). - Contributions are accepted under [CONTRIBUTOR-TERMS.md](CONTRIBUTOR-TERMS.md).
The material boundaries and binary-release requirements are summarized in Material boundaries and binary-release requirements are recorded in that same
[`docs/LICENSING.md`](docs/LICENSING.md). [`docs/ATTRIBUTION.md`](docs/ATTRIBUTION.md) reference.
Copyright © 2026 Alexander Sellite and Rheannon Eisworth. Copyright © 2026 Woofmeow.

View file

@ -1,135 +0,0 @@
# NETfishing third-party notices
NETfishing includes or is distributed with the materials identified below.
They are not relicensed by the NETfishing GPL or Asset License; their own
terms continue to apply.
## Godot Engine
NETfishing uses Godot Engine. Godot Engine is licensed under the MIT License:
> Copyright (c) 2014-present Godot Engine contributors.
> Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
>
> 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.
Godot Engine and its bundled third-party-component notices are documented at:
- https://godotengine.org/license/
- https://github.com/godotengine/godot/blob/master/COPYRIGHT.txt
Godot uses FreeType for font rendering. Portions of the engine are copyright
The FreeType Project (https://www.freetype.org). All rights reserved under the
FreeType Projects applicable license.
## Fonts
### Tuffy Bold
- Creators: Thatcher Ulrich, Karoly Barta, and Michael Evans
- Repository file: `ui/fonts/Tuffy_Bold.otf`
- Status: dedicated to the public domain
- Bundled notice: `ui/fonts/Tuffy-LICENSE.txt`
### Seattle Avenue
- Creator: JLH Fonts
- Repository file: `ui/fonts/seattle_avenue.otf`
- Status: dedicated to the public domain by the creator
- Source record:
https://www.publicdomainfiles.com/show_file.php?id=13486266913972
- Additional creator attribution and public-domain listing:
https://www.dafont.com/seattle-avenue.font
- Bundled notice: `ui/fonts/Seattle-Avenue-LICENSE.txt`
Public-domain status means these fonts are not subject to the NETfishing Asset
License.
## Sound recordings
### Spinning Reel
- Original title: “Spinning reel.wav”
- Creator: Freesound user `tosha73`
- Source: https://freesound.org/s/509902/
- License: Creative Commons Zero (CC0)
- Used for: `audio/sfx/fishing/fighting.wav` and
`audio/sfx/fishing/reeling.wav`
- Changes: edited into separate looping fight and manual-reeling sounds;
converted/exported as runtime WAV files
CC0 information: https://creativecommons.org/publicdomain/zero/1.0/
### Gentle Ocean Waves Loop
- Original title: “Gentle Ocean Waves Loop”
- Creator: Freesound user `kkenny101`
- Source: https://freesound.org/s/852826/
- License: Creative Commons Zero (CC0)
- Used for: `audio/ambience/waves.wav`
- Changes: configured as a quiet looping saltwater ambience
### Quick Water Droplet
- Original title: “Quick Water Droplet”
- Creator: Freesound user `qubodup`
- Source: https://freesound.org/s/792931/
- License: Creative Commons Zero (CC0)
- Used for: `audio/sfx/fishing/bobber.wav`
- Changes: used as the bobber water-impact sound
### Dog Bark
- Original title: “Dog_Bark.wav”
- Creator: Freesound user `ivolipa`
- Source: https://freesound.org/s/328729/
- License: Creative Commons Zero (CC0)
- Used for: `sound/dialogue/calls/bark.wav`
- Changes: edited and exported as a short in-game character-call sound
### Rain Loop Ontario
- Original title: “Rain Loop Ontario”
- Creator: Freesound user `Ayton`
- Source: https://freesound.org/s/212799/
- License: Creative Commons Attribution 3.0
- License text: https://creativecommons.org/licenses/by/3.0/legalcode
- Used for: `audio/ambience/rain_loop_ontario.ogg`
- Changes: converted from the supplied AIFF source to Ogg Vorbis and configured
as looping weather ambience
### Cat Meow
- Original title: “Cat meow.m4a”
- Creator: Freesound user `Christyboy100`
- Source: https://freesound.org/s/495694/
- License: Creative Commons Attribution 3.0
- License text: https://creativecommons.org/licenses/by/3.0/legalcode
- Used for: `sound/dialogue/calls/meow.wav`
- Changes: converted to WAV and edited into a short in-game character-call
sound
The attribution above does not imply that any third-party creator endorses
NETfishing, Woofmeow, Alexander Sellite, or Rheannon Eisworth.
## Complete provenance records
Source filenames, transformations, and available SHA-256 records are kept in
`docs/ASSET-PROVENANCE.md`. Absence from that development record does not
remove an applicable third-party license or attribution requirement.

View file

@ -1,9 +1,9 @@
# NETfishing and Woofmeow branding # NETfishing and Woofmeow branding
The NETfishing and Woofmeow names, logos, icons, trade dress, and other source The NETfishing and Woofmeow names, logos, icons, trade dress, and other source
identifiers are reserved by Alexander Sellite and Rheannon Eisworth. Their identifiers are reserved by the NETfishing owners, publishing as Woofmeow.
presence in a GPL-licensed repository does not grant trademark rights or imply Their presence in a GPL-licensed repository does not grant trademark rights or
permission to represent another product as official. imply permission to represent another product as official.
No trademark registration is asserted by this notice. Applicable rights may No trademark registration is asserted by this notice. Applicable rights may
exist through use and other law. exist through use and other law.
@ -39,4 +39,4 @@ These branding terms do not limit rights granted by the GPL to the software
code or lawful nominative, descriptive, comparative, or fair use. code or lawful nominative, descriptive, comparative, or fair use.
Permission for broader branding use must be obtained in writing from both Permission for broader branding use must be obtained in writing from both
Alexander Sellite and Rheannon Eisworth. NETfishing owners.

View file

@ -1,24 +0,0 @@
# Drop-in facial features
Facial feature textures are discovered when the game starts. Put new PNGs in
the matching directory, restart the game, and the option will appear in the
Profile customization page.
```text
eyes/<id>.png
noses/<id>.png
mouth/<id>.png
```
The category directory is authoritative. A category prefix is optional, so
both `sleepy.png` and `eyes_sleepy.png` become the option ID `sleepy`.
Filenames are normalized to lowercase snake_case. Use stable names because
the resulting IDs are stored in appearance snapshots and sent to peers.
Keep each PNG on the established facial-feature canvas with an RGBA
transparent background. Godot imports the PNG automatically in the editor;
the runtime registry ignores `.import` files and other non-PNG files.
The current game bundles its facial assets under `res://`. Adding a file to a
development checkout is picked up on the next startup. An exported build must
be rebuilt to include newly added `res://` files in its package.

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Before After
Before After

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2e0uxfn187gu"
path="res://.godot/imported/body_arms_bengal.png-350ed15f218f0efa6ae7573ad27d3c65.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/exported/characters/patterns/bengal/body_arms_bengal.png"
dest_files=["res://.godot/imported/body_arms_bengal.png-350ed15f218f0efa6ae7573ad27d3c65.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=true
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cn71sswmj0tue"
path="res://.godot/imported/body_main_bengal.png-59815350b807220c52317e683582847e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/exported/characters/patterns/bengal/body_main_bengal.png"
dest_files=["res://.godot/imported/body_main_bengal.png-59815350b807220c52317e683582847e.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=true
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rv36ws5wuakb"
path="res://.godot/imported/head_round_bengal.png-dfa502a4514c1605f5c8aff839dd8765.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/exported/characters/patterns/bengal/head_round_bengal.png"
dest_files=["res://.godot/imported/head_round_bengal.png-dfa502a4514c1605f5c8aff839dd8765.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=true
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

View file

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

View file

@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://bdwqv502so784" uid="uid://p5cg2bwrxf7v"
path="res://.godot/imported/body_arms.png-a4119ca84faf9d8bfa9bc0ab6f8ca133.ctex" path="res://.godot/imported/body_arms_fox.png-91ab21ce544fdc29b8a0ebf3e54ef7cd.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://art/exported/characters/patterns/fox/body_arms.png" source_file="res://art/exported/characters/patterns/fox/body_arms_fox.png"
dest_files=["res://.godot/imported/body_arms.png-a4119ca84faf9d8bfa9bc0ab6f8ca133.ctex"] dest_files=["res://.godot/imported/body_arms_fox.png-91ab21ce544fdc29b8a0ebf3e54ef7cd.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Before After
Before After

View file

@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://ccbaq4d4nt3bm" uid="uid://wcsadkjjpaei"
path="res://.godot/imported/body_main.png-c83ac58a68a1485819fba3e07de784cd.ctex" path="res://.godot/imported/body_main_fox.png-25e4fa03a48053f6b37b3bbadb1f01ba.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://art/exported/characters/patterns/fox/body_main.png" source_file="res://art/exported/characters/patterns/fox/body_main_fox.png"
dest_files=["res://.godot/imported/body_main.png-c83ac58a68a1485819fba3e07de784cd.ctex"] dest_files=["res://.godot/imported/body_main_fox.png-25e4fa03a48053f6b37b3bbadb1f01ba.ctex"]
[params] [params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://cybpx6ua1vaw" uid="uid://c4vlbrp7qevvo"
path="res://.godot/imported/body_main.png-eff221076f4e25415158ecf38fdf03da.ctex" path="res://.godot/imported/head_pointy_fox.png-ef3535a6c05a49aae36fe5514844a3df.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://art/exported/characters/patterns/spots_bengal/body_main.png" source_file="res://art/exported/characters/patterns/fox/head_pointy_fox.png"
dest_files=["res://.godot/imported/body_main.png-eff221076f4e25415158ecf38fdf03da.ctex"] dest_files=["res://.godot/imported/head_pointy_fox.png-ef3535a6c05a49aae36fe5514844a3df.ctex"]
[params] [params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://m2un0oahhss3" uid="uid://cyr0wcavb1cmi"
path="res://.godot/imported/body_arms.png-a55eeb0787397c58508a7c65cc2d04f8.ctex" path="res://.godot/imported/tails_fox_fox.png-061a224c36e90f10b33d37e616ff7e94.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://art/exported/characters/patterns/spots_bengal/body_arms.png" source_file="res://art/exported/characters/patterns/fox/tails_fox_fox.png"
dest_files=["res://.godot/imported/body_arms.png-a55eeb0787397c58508a7c65cc2d04f8.ctex"] dest_files=["res://.godot/imported/tails_fox_fox.png-061a224c36e90f10b33d37e616ff7e94.ctex"]
[params] [params]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

View file

@ -1,88 +0,0 @@
# 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.
`DiscoveryClient` is an optional directory layer beside `NetworkSession`. An
open host may publish a short-lived room lease, and the shared Join Game page
may browse compatible leases before handing the selected address back to the
existing direct ENet connection flow. The directory does not carry gameplay
traffic or become a gameplay authority. Its base URL comes from
`network/discovery/base_url`, with `NETFISHING_DISCOVERY_URL` available as a
development/deployment override.
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).
Moderation follows the same boundary. Player hosts may grant session-scoped
operator status to an authenticated identity. Dedicated servers derive
operators from their configured fingerprint allowlist. Operator status is
replicated for presentation, but kick, ban, unban, and artwork-reset requests
are always reauthorized against the authenticated sender by the host. Only a
player host can grant or revoke operators; operators cannot moderate the host
or another operator.
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).

View file

@ -1,161 +0,0 @@
# 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/` | Original 2D artwork by co-owner Rheannon Eisworth. |
| Item icons | `items/icons/` | Original 2D artwork by co-owner Rheannon Eisworth. |
| Inventory notepad | `art/ui/ui_notepad.png` | Original 2D artwork by co-owner Rheannon Eisworth; integrated without a runtime external path. |
| Environment textures | `art/exported/environment/textures/` | Original 2D artwork by co-owner Rheannon Eisworth. |
| UI patterns | `art/patterns/` | Original 2D artwork by co-owner Rheannon Eisworth. |
| Character and world models | `art/exported/` | Original 3D models by co-owner Alexander Sellite. Any embedded original 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` | Public-domain font by JLH Fonts; source record and hash in `ui/fonts/Seattle-Avenue-LICENSE.txt`. |
| Title music | `audio/music/title/as_in_four_wolves.ogg` | Original music composed and owned by co-owner Alexander Sellite. |
| Dusk music | `audio/music/world/craft.mp3` | Original music composed and owned by co-owner Alexander Sellite. |
| 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. |
| Rain ambience | `audio/ambience/rain_loop_ontario.ogg` | Converted from “Rain Loop Ontario” by Freesound user Ayton, sound 212799, CC BY 3.0. |
| Character bark call | `sound/dialogue/calls/bark.wav` | Edited from `Dog_Bark.wav` by Freesound user ivolipa, sound 328729, CC0. |
| Character meow call | `sound/dialogue/calls/meow.wav` | Edited from `Cat meow.m4a` by Freesound user Christyboy100, sound 495694, Attribution 3.0. |
| Animalese placeholder tones | `sound/dialogue/animalese/placeholder/` | Original tones composed and owned by co-owner Alexander Sellite without third-party samples. |
### 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.
### Character-call source records
#### Bark
- Source page: https://freesound.org/s/328729/
- Creator: ivolipa
- Source title: `Dog_Bark.wav`
- License: Creative Commons Zero (CC0)
- Downloaded source filename: `328729__ivolipa__dog_bark.wav`
- Downloaded source SHA-256:
`c3d542dee7bfcc1901428e0c1408c0bd522d778d8e5e1dbca156cab008f3d712`
- Runtime edit source/destination SHA-256:
`5ac2f32f33724a2fc232d725c5c37d4cfe3e93b3c1f19aa4177d2c8e3009ec78`
- Runtime format: 44.1 kHz, 16-bit, stereo PCM WAV; 0.446780 seconds;
one-shot.
#### Meow
- Source page: https://freesound.org/s/495694/
- Creator: Christyboy100
- Source title: `Cat meow.m4a`
- License: Attribution 3.0
- Downloaded source filename: `495694__christyboy100__cat-meow.m4a`
- Downloaded source SHA-256:
`afa3f6ef7afa2f7fee1e634d5505d2101cbba4421f8d8ff9adf562939c7a18f8`
- Converted working WAV SHA-256:
`a505e6e230e7d9d76b65c46270d97a35a1acc510b43aa5e7343d7b04f424cade`
- Runtime edit source/destination SHA-256:
`cce22ce1fbbc9325126a153aedc4526fde1aacf78780574fb432197dd44eb6b3`
- Runtime format: 44.1 kHz, 16-bit, stereo PCM WAV; 0.605420 seconds;
one-shot.
- The downloaded sources and working edits remain in the project owner's
source-work archive; only the finished runtime sounds ship in the game.
### Rain ambience source record
- Source page: https://freesound.org/s/212799/
- Creator: Ayton
- Source title: `Rain Loop Ontario`
- License: Creative Commons Attribution 3.0
- Downloaded source filename: `212799__ayton__rain-loop-ontario.aiff`
- Downloaded source SHA-256:
`b491fc036b78a274d9ac7f6acbd014af1b5d24f5cba9bd7e3fdc05c5432ee66f`
- Runtime edit SHA-256:
`b1979609b20fe34ed6f8902f3de65ae787a09abd71429e8aab850e87b74a4fa2`
- Changes: converted from AIFF to Ogg Vorbis and configured as looping
weather ambience.
## 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. Search exported resources for external filesystem paths.
2. Preserve `LICENSE`, `ASSET-LICENSE.md`, `TRADEMARKS.md`, credits, and all
required third-party notices with the package.
3. Publish or offer the exact corresponding GPL source for every distributed
binary release.

321
docs/ATTRIBUTION.md Normal file
View file

@ -0,0 +1,321 @@
# Attribution, licensing, and asset provenance
This is the authoritative project record for ownership, credits, license
boundaries, third-party notices, asset provenance, and redistribution checks.
A Git commit proves when bytes entered the repository; it does not by itself
prove authorship or licensing.
## Ownership and credits
NETfishing is jointly owned, developed, and published by its creators,
publicly credited as Voyager and Endeavour and operating collectively as the
independent team Woofmeow.
- Voyager: co-owner, developer, project director, creator of the game's 3D
art, and composer of the original title/ambient track
`audio/music/title/as_in_four_wolves.ogg`, world track
`audio/music/world/craft.mp3`, and animalese placeholder tones.
- Endeavour: co-owner, developer, and creator of the game's 2D art, including
the original fish, environment, UI, and other artwork.
- chillnfill: contributor of original 3D models for the character bodies and
arms, ears and tails, and multiple world props and decorative assets,
including trees and bridges. The contributor requested to be credited as
`chillnfill`.
- Tekgator: creator of the cooler-capacity and barrier-power shop icons.
Voyager is also the original catalog porter credit in PortMaster metadata.
Third-party creators are credited with their individual records below. Those
credits do not imply endorsement of NETfishing, Woofmeow, or its owners.
## License map
Do not describe the complete repository as covered only by the GPL.
| Material | Governing terms |
| --- | --- |
| Project-owned software code, shaders, code-oriented scene/resource configuration, tests, build tooling, and server software | GPL-3.0-or-later; source-root `LICENSE` and packaged `GPL-3.0-or-later.txt` |
| Project-owned artwork, textures, models, animation, music, audio, narrative content, characters, and other creative assets | Source-root or packaged `ASSET-LICENSE.md` |
| NETfishing and Woofmeow names, logos, icons, and official identity | Source-root or packaged `TRADEMARKS.md` |
| Third-party engines, fonts, sounds, and other external material | Their original terms, recorded below and in adjacent license texts where applicable |
| New outside contributions | Source-root `CONTRIBUTOR-TERMS.md` plus any separately signed agreement |
Generated Godot import metadata and deterministic generated resources follow
the terms of their source material or generating code. Repository location
alone does not relicense third-party material.
The GPL permits study, modification, redistribution, and forks of covered
code. It does not grant rights to NETfishing creative assets or branding. The
NETfishing Asset License separately permits qualifying noncommercial add-on
Mods for official NETfishing software; it does not permit those assets to
accompany code forks, clones, standalone games, unrelated products, or
general-purpose asset packs. A GPL fork must replace NETfishing assets and
reserved branding with independently licensed material and a distinct
identity.
Project-wide code, asset, branding, or contributor-license changes require
written approval from both NETfishing owners under their private joint
ownership agreement.
## Godot Engine notice
NETfishing uses Godot Engine under the MIT License:
> Copyright (c) 2014-present Godot Engine contributors.
> Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
>
> 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.
Godot licensing and bundled third-party-component notices are available at
https://godotengine.org/license/ and
https://github.com/godotengine/godot/blob/master/COPYRIGHT.txt. Godot uses
FreeType for font rendering; portions are copyright The FreeType Project
(https://www.freetype.org) under its applicable license.
## Font notices
- **Tuffy Bold** — Thatcher Ulrich, Karoly Barta, and Michael Evans; dedicated
to the public domain. Runtime file: `ui/fonts/Tuffy_Bold.otf`; original notice:
`ui/fonts/Tuffy-LICENSE.txt`.
- **Seattle Avenue** — JLH Fonts; dedicated to the public domain by the creator.
Runtime file: `ui/fonts/seattle_avenue.otf`; source, hash, and original notice:
`ui/fonts/Seattle-Avenue-LICENSE.txt`.
Public-domain fonts are not governed by the NETfishing Asset License.
## 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.
Maintainers must also obtain affirmative agreement to the source-root
`CONTRIBUTOR-TERMS.md` before accepting an outside contribution and retain
that agreement in a durable pull request, issue, email, or signed document.
Runtime resources must use `res://` paths. Workstation Sync folders are intake
locations only and must never appear in scenes or resources.
## Asset inventory and provenance
| Asset family | Repository location | Recorded provenance |
| --- | --- | --- |
| Fish art and portraits | `fish/species/`, `art/exported/fish/` | Original 2D artwork by co-owner Endeavour. |
| Item icons | `items/icons/` | Original 2D artwork by co-owner Endeavour except where separately credited below. |
| Cooler-capacity shop icon | `items/icons/equipment/64_cooler_plus.png` | Original artwork by Tekgator. Repository SHA-256: `b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7`. |
| Barrier-power shop icon | `items/icons/shop/64_power_plus.png` | Original artwork by Tekgator. Repository SHA-256: `164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7`. |
| Inventory notepad | `art/ui/ui_notepad.png` | Original 2D artwork by co-owner Endeavour; integrated without a runtime external path. |
| Environment textures | `art/exported/environment/textures/` | Original 2D artwork by co-owner Endeavour. |
| UI patterns | `art/patterns/` | Original 2D artwork by co-owner Endeavour. |
| Fur customization channel maps | `art/exported/characters/patterns/` | Original 2D channel-map artwork by co-owner Endeavour. Exact file hashes are recorded below. |
| Character base meshes and appendages | `art/source/characters/base/`, `art/exported/characters/base/`, and derived character resources | Original body and arm models and original ear and tail models by chillnfill. Any embedded original 2D artwork is by Endeavour. |
| World and decorative prop models | `art/source/environment/`, `art/exported/environment/`, and derived world resources | Original prop and decorative models by chillnfill, including trees and bridges. Any embedded original 2D artwork is by Endeavour. |
| Tuffy font | `ui/fonts/Tuffy_Bold.otf` | Public-domain dedication in `ui/fonts/Tuffy-LICENSE.txt`. |
| Seattle Avenue font | `ui/fonts/seattle_avenue.otf` | Public-domain font by JLH Fonts; source record and hash in `ui/fonts/Seattle-Avenue-LICENSE.txt`. |
| Title music | `audio/music/title/as_in_four_wolves.ogg` | Original music composed and owned by co-owner Voyager. |
| Dusk music | `audio/music/world/craft.mp3` | Original music composed and owned by co-owner Voyager. |
| 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. |
| Rain ambience | `audio/ambience/rain_loop_ontario.ogg` | Converted from “Rain Loop Ontario” by Freesound user Ayton, sound 212799, CC BY 3.0. |
| Character bark call | `sound/dialogue/calls/bark.wav` | Edited from `Dog_Bark.wav` by Freesound user ivolipa, sound 328729, CC0. |
| Character meow call | `sound/dialogue/calls/meow.wav` | Edited from `Cat meow.m4a` by Freesound user Christyboy100, sound 495694, Attribution 3.0. |
| Animalese placeholder tones | `sound/dialogue/animalese/placeholder/` | Original tones composed and owned by co-owner Voyager without third-party samples. |
Creative Commons Zero information:
https://creativecommons.org/publicdomain/zero/1.0/. Creative Commons
Attribution 3.0 legal text:
https://creativecommons.org/licenses/by/3.0/legalcode.
### chillnfill 3D model contribution record
- Requested credit: `chillnfill`.
- Original contribution families: character bodies and arms; character ears
and tails; and multiple world props and decorative assets, including trees
and bridges.
- Repository mapping: character contributions are incorporated into the
character source/export families identified above. Prop contributions may
be incorporated into combined environment source and export files rather
than retained as one file per originally supplied model.
- Permission record: because these historical contributions predate the
current `CONTRIBUTOR-TERMS.md`, retain the original permission record or a
later written confirmation covering Project use, modification,
distribution, commercial release, and applicable sublicensing.
### Fur customization channel-map record
Original channel-map artwork by Endeavour. Repository paths preserve the
authored `mesh_style.png` filenames.
| Style and mesh | Source filename | Repository file | SHA-256 |
| --- | --- | --- | --- |
| Bengal spots, body and arms | `body_arms_bengal.png` | `art/exported/characters/patterns/bengal/body_arms_bengal.png` | `fa90bd4e74b0b770ce97bf69d069676f5d4bd1f5d649f89e09638a66b61a8470` |
| Bengal spots, main body | `body_main_bengal.png` | `art/exported/characters/patterns/bengal/body_main_bengal.png` | `8662fa198220cfdbe0aa83bdeeef7b855e20124d74f8481467f750d32b67ee8b` |
| Bengal spots, round head | `head_round_bengal.png` | `art/exported/characters/patterns/bengal/head_round_bengal.png` | `7a48c5ac47cfd35ddec1783e6c4defa7807b5ef3f1c6b428157fbc0651e71ef3` |
| Fox, body and arms | `body_arms_fox.png` | `art/exported/characters/patterns/fox/body_arms_fox.png` | `fd2ab31ede371129ca4806480783d57153318a909d6b87c593b6ff82d03d65eb` |
| Fox, main body | `body_main_fox.png` | `art/exported/characters/patterns/fox/body_main_fox.png` | `d2a975cf042728299de4819a023a95e9c1467671a6886f6c6eaf8b44a5a9ca3a` |
| Fox, pointy head | `head_pointy_fox.png` | `art/exported/characters/patterns/fox/head_pointy_fox.png` | `8e74685ef7a9f61b5ae75c16e86898ea80f3996471e2999862479798ce28aa52` |
| Fox tail | `tails_fox_fox.png` | `art/exported/characters/patterns/fox/tails_fox_fox.png` | `72da9673f038e2c3bb8aca19ed99932085fa3b162cab95366901c4e97153dd3e` |
### Tekgator shop icon contribution record
- Requested credit: `Tekgator`.
- Original contributions: cooler-capacity and barrier-power shop icons.
- Repository files and SHA-256:
- `items/icons/equipment/64_cooler_plus.png`:
`b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7`
- `items/icons/shop/64_power_plus.png`:
`164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7`
- Permission record: retain the original permission record or a later written
confirmation covering Project use, modification, distribution, commercial
release, and applicable sublicensing.
### 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.
### Character-call source records
#### Bark
- Source page: https://freesound.org/s/328729/
- Creator: ivolipa
- Source title: `Dog_Bark.wav`
- License: Creative Commons Zero (CC0)
- Downloaded source filename: `328729__ivolipa__dog_bark.wav`
- Downloaded source SHA-256:
`c3d542dee7bfcc1901428e0c1408c0bd522d778d8e5e1dbca156cab008f3d712`
- Runtime edit source/destination SHA-256:
`5ac2f32f33724a2fc232d725c5c37d4cfe3e93b3c1f19aa4177d2c8e3009ec78`
- Runtime format: 44.1 kHz, 16-bit, stereo PCM WAV; 0.446780 seconds;
one-shot.
#### Meow
- Source page: https://freesound.org/s/495694/
- Creator: Christyboy100
- Source title: `Cat meow.m4a`
- License: Attribution 3.0
- Downloaded source filename: `495694__christyboy100__cat-meow.m4a`
- Downloaded source SHA-256:
`afa3f6ef7afa2f7fee1e634d5505d2101cbba4421f8d8ff9adf562939c7a18f8`
- Converted working WAV SHA-256:
`a505e6e230e7d9d76b65c46270d97a35a1acc510b43aa5e7343d7b04f424cade`
- Runtime edit source/destination SHA-256:
`cce22ce1fbbc9325126a153aedc4526fde1aacf78780574fb432197dd44eb6b3`
- Runtime format: 44.1 kHz, 16-bit, stereo PCM WAV; 0.605420 seconds;
one-shot.
- The downloaded sources and working edits remain in the project owner's
source-work archive; only the finished runtime sounds ship in the game.
### Rain ambience source record
- Source page: https://freesound.org/s/212799/
- Creator: Ayton
- Source title: `Rain Loop Ontario`
- License: Creative Commons Attribution 3.0
- Downloaded source filename: `212799__ayton__rain-loop-ontario.aiff`
- Downloaded source SHA-256:
`b491fc036b78a274d9ac7f6acbd014af1b5d24f5cba9bd7e3fdc05c5432ee66f`
- Runtime edit SHA-256:
`b1979609b20fe34ed6f8902f3de65ae787a09abd71429e8aab850e87b74a4fa2`
- Changes: converted from AIFF to Ogg Vorbis and configured as looping
weather ambience.
## 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. Search exported resources for external filesystem paths.
2. Include or make readily accessible the complete GPL text and identify the
exact corresponding source tag or commit.
3. Include `ASSET-LICENSE.md`, `TRADEMARKS.md`, this attribution/provenance
record, both font notices, and every applicable third-party license.
4. Retain the complete Godot Engine MIT notice above.
5. Preserve third-party terms without adding incompatible restrictions.
6. Avoid referring to a dirty or unavailable source revision.
7. Publish or offer equivalent access to exact corresponding GPL source for
every distributed binary release.
Desktop and PortMaster build scripts stage these authoritative files as
sidecars. Godot export presets include them in exported resource packs for
platforms where sidecar documents are not normally visible. Store and release
pages should link to the exact source revision and public notices.

View file

@ -1,41 +0,0 @@
# 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.

331
docs/DEVELOPMENT.md Normal file
View file

@ -0,0 +1,331 @@
# Development reference
This is the authoritative reference for NETfishing architecture, engineering
decisions, content policy, authoring, and validation.
## 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 and authority
`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.
`DiscoveryClient` is an optional directory layer beside `NetworkSession`. An
open host may publish a short-lived room lease, and the shared Join Game page
may browse compatible leases before handing the selected address back to the
existing direct ENet connection flow. The directory does not carry gameplay
traffic or become a gameplay authority. Its base URL comes from
`network/discovery/base_url`, with `NETFISHING_DISCOVERY_URL` available as a
development/deployment override.
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. Private single-player uses the same host path. Client evidence is
bounded and reconstructed where possible; UI activation is never proof that a
server-side mutation succeeded.
Moderation follows the same boundary. Player hosts may grant session-scoped
operator status to an authenticated identity. Dedicated servers derive
operators from their configured fingerprint allowlist. Operator status is
replicated for presentation, but kick, ban, unban, and artwork-reset requests
are always reauthorized against the authenticated sender by the host. Only a
player host can grant or revoke operators; operators cannot moderate the host
or another operator.
Protocol compatibility is defined in `network/network_protocol.gd`. A visible
release version is not a reason to change the protocol number.
### Persistence and stable identifiers
`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.
Repository-owned resources are 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. Removing or changing a
stable ID requires an explicit compatibility plan.
### 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.
### Engineering consequences
- Shared mutations must behave consistently for private hosts, open hosts,
dedicated servers, and joined clients.
- Networking changes require local-host and paired host/client validation.
- Protocol payloads and rejection cleanup are explicit and bounded.
- Renaming visible UI does not require a save migration.
- Content validation covers stable-ID uniqueness, catalog completeness, pool
membership, and typed habitat compatibility.
- New authored assets require provenance records as well as resource
references.
## Content and balance
Balance is authored data and should change deliberately. 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 and saltwater habitat is validated by the host in addition to
pool membership.
- Value, rarity, quality, weight ranges, and authored barrier-health bands
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 and 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.
Record affected stable IDs and resource paths, old and new values, intended
player-facing outcome, interactions with quality/availability/economy,
deterministic validation, and save-compatibility impact with each balance
change. Do not bump a save schema or network protocol merely because an
authored number changed.
## Authoring
### World composition
The active test world uses the canonical starter island through
`world/regions/starter_island_region.tscn`.
`world/test_world.tscn` owns the gameplay-wide environment, sun, world bounds,
and below-world failsafe. `StarterIslandRegion` owns the placed island content:
```text
StarterIslandRegion
├── Terrain
│ ├── Visual
│ └── Collision
├── WaterBodies
│ ├── Pond
│ │ ├── VisualWater
│ │ ├── FishingRegion
│ │ └── RecoveryRegion
│ └── Ocean
│ ├── VisualWater
│ ├── FishingRegions
│ └── RecoveryRegions
├── PlayerSpawn
├── SafeRespawns
└── Interactables
├── FishingShopWorld
└── PelicanCoolerPerch
```
Move a meaningful feature root rather than one of its implementation children.
Child transforms are local offsets owned by that feature.
Every fishable region authors its `water_type` explicitly. The starter pond is
`FRESH_WATER`; the starter coast is `SALT_WATER`. Fish species use the same
central type through an allowed-water-type bitmask. Do not infer habitat from
node names, pool filenames, coordinates, or water height.
Select `WaterBodies/Pond` to move or resize the pond. Its transform is the
authoritative surface position; surface size and fishing/recovery coverage are
owned together. Select `WaterBodies/Ocean` to move the surrounding water as one
feature. Its explicit visual, fishing, and recovery lobes remain local to that
root.
Placed-feature ownership:
- `PlayerSpawn` defines the initial player transform and facing.
- `SafeRespawns` contains authoritative recovery destinations. Recovery
preserves current facing.
- `Interactables/FishingShopWorld` owns the shop visual, collision, interaction
area, prompt, and entrance marker.
- `Interactables/PelicanCoolerPerch` owns the Pelican landmark visual; selling
remains a Cooler action.
- `Terrain` owns imported visuals and generated collision.
- `BelowWorldFailsafe/Coverage` owns below-world recovery coverage.
The starter-island GLB hierarchy is the terrain-collision authority. The
region rebuilds one concave collision shape from every imported
`MeshInstance3D` when it loads, so newly exported terrain and props participate
without a separate stale collision bake. Keep purely decorative meshes out of
that hierarchy if they should not block players.
The active `Environment`, `Sun`, and starter-island grass material remain
Inspector-authored and are not recreated by runtime scripts.
### Reusable world props
Use one placed `WorldProp` root for a complete landmark:
```text
WorldProp
├── Visual
└── Collision
└── CollisionShape3D
```
Put optional interaction areas, labels, and markers beneath the same root.
Instance imported GLB content under `Visual` and keep gameplay collision under
`Collision`. Author reusable assets at scale `(1, 1, 1)`. Uniform provisional
root scaling is acceptable when visual and collision scale together; avoid
non-uniform root scaling. Make mutable per-instance shapes, meshes, and
materials local to the scene.
### Shoreline tide ribbons
Maps using generated tide ribbons own a `ShorelineRibbonBaker` and one
`ShorelineRibbonConfig` per water body. Each configuration selects only its
static terrain source, water height, generation bounds, water-facing reference,
smoothing overrides, and generated `.tres` path. Props, players, bobbers, and
gameplay areas are not scanned unless selected explicitly. Only `SALT_WATER`
bodies generate ribbons.
In the editor, select the baker and press **Rebuild Shoreline Ribbons**. The
equivalent validation command is:
```sh
godot --headless --path . --script scripts/bake_shoreline_ribbons.gd
```
Use `debug_path_stage` only while comparing extraction stages and leave it Off
in committed scenes. Rebuild when saltwater terrain at the waterline, water
height, or generation bounds change. Normal gameplay loads committed generated
meshes and never runs extraction.
### Facial-feature textures
Put new PNGs under the matching directory in
`art/exported/characters/faces/`:
```text
eyes/<id>.png
noses/<id>.png
mouth/<id>.png
```
The category directory is authoritative. A category prefix is optional, so
both `sleepy.png` and `eyes_sleepy.png` produce the stable option ID `sleepy`.
Filenames normalize to lowercase snake_case and are stored in appearance
snapshots, so do not casually rename them. Preserve the established RGBA
transparent canvas. Restart a development build after adding an image; rebuild
exports to package new `res://` files.
### Bubble menus
Instance `ui/components/bubble_menu/bubble_button.tscn` for each action, or
attach `bubble_button.gd` to an existing button. Author neutral size, desktop
and compact anchors, font-size limits, and deterministic motion in the
Inspector. A label child may be assigned with `label_control_path`.
Place buttons under a Control using `bubble_cluster.gd`, pass ordered button
references to `configure()`, and call `apply_layout()` when available size or
responsive layout changes. Order defines keyboard/controller focus neighbors.
The shared profile owns palette, styles, proportional-font ratio, hover
response, and contact tuning. Parent menus own labels, actions, anchors,
availability, and confirmation behavior.
`motion_scale = 0.0` disables idle drift/deformation while preserving hover and
focus feedback. Contact correction is deterministic and bounded; do not replace
it with physics that destabilizes layout, focus order, or hit targets.
## 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.
### 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 checks.
- `full` adds socket-free scene/runtime checks.
- `host` runs single-process authority checks that bind local UDP.
- `network` runs loopback host/client pairs.
- `all` runs `full`, `host`, and `network`.
Set `GODOT_BIN` to select an executable and `TEST_TIMEOUT_SECONDS` to change
the per-process timeout. The runner isolates and removes XDG roots.
For 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 point `NETFISHING_DATA_DIR` at an arbitrary empty directory; it is an
explicit portable-data override and must identify a valid data root.
Headless tests cannot prove visual alignment, mouse routing, shader appearance,
controller feel, or resize behavior. Presentation changes require graphical
review at the canonical 1280×720 layout and relevant low/high and ultrawide
resolutions.
Network tests require free loopback UDP ports and local-socket permission. A
release audit additionally requires clean Git/tag verification, editor import,
platform exports, executable smoke tests, archive inspection, and SHA-256
verification; those checks are intentionally outside the development runner.

View file

@ -1,77 +0,0 @@
# NETfishing licensing and release notices
NETfishing uses separate licenses for code, creative assets, third-party
material, and branding. Do not describe the complete repository as being
covered only by the GPL.
## Ownership
Copyright © 2026 Alexander Sellite and Rheannon Eisworth, operating
collectively as the independent development and publishing team Woofmeow.
## License map
| Material | Terms |
| --- | --- |
| Project-owned software code, shaders, scripts, code-oriented scene/resource configuration, tests, build tooling, and server software | GPL-3.0-or-later; full text in `LICENSE` |
| Project-owned artwork, textures, models, animation, music, audio, narrative content, characters, and other creative assets | `ASSET-LICENSE.md` |
| NETfishing and Woofmeow names, logos, icons, and official identity | `TRADEMARKS.md` |
| Third-party engines, fonts, sounds, and other external material | Their original terms, recorded in `THIRD-PARTY-NOTICES.md` and `docs/ASSET-PROVENANCE.md` |
| New outside contributions | `CONTRIBUTOR-TERMS.md` plus any separately signed agreement |
Generated Godot import metadata and deterministic generated resources follow
the terms of the source material or code from which they are derived. Merely
being stored in this repository does not relicense third-party material.
## Modding boundary
The GPL permits community members to study, modify, redistribute, and fork the
covered code under the GPL. It does not grant rights to NETfishing creative
assets or branding.
The NETfishing Asset License separately permits noncommercial add-on Mods that
require and operate with official NETfishing software. It does not permit the
assets to accompany code forks, clones, standalone games, unrelated products,
or general-purpose asset packs.
A fork may exercise its GPL code rights by replacing all NETfishing assets and
reserved branding with independently licensed material and a distinct
identity.
## Contributor intake
Maintainers must obtain affirmative agreement to `CONTRIBUTOR-TERMS.md` before
accepting a new outside contribution. Record the agreement in a durable pull
request, issue, email, or signed document.
For each external asset, preserve the creator, source, original filename,
license, transformations, and available hashes in `docs/ASSET-PROVENANCE.md`.
## Binary-release checklist
Every public binary package must:
1. include or make readily accessible the complete GPL text;
2. identify the exact corresponding source tag or commit and provide
equivalent access to that source;
3. include `ASSET-LICENSE.md`, `TRADEMARKS.md`, `CREDITS.md`, and
`THIRD-PARTY-NOTICES.md`;
4. include the public-domain font records and every required third-party
attribution;
5. retain the Godot Engine MIT notice;
6. avoid referring to a dirty or unavailable source revision; and
7. preserve third-party license terms without adding incompatible
restrictions.
Desktop and PortMaster build scripts stage these notices as sidecar files.
Godot export presets also include the authoritative notice files in exported
resource packs for platforms such as Android and macOS. Store and release
pages should link to the exact source revision and the public notices. A future
in-game legal-notices screen should expose the packed notices directly on
platforms where sidecar documents are not normally visible.
## Changing terms
Project-wide code, asset, branding, or contributor-license changes require
written approval from both Alexander Sellite and Rheannon Eisworth under their
joint ownership agreement.

View file

@ -1,28 +0,0 @@
# 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.

View file

@ -1,4 +1,4 @@
# PortMaster release builds # PortMaster reference
NETfishing's PortMaster release is a canonical PortMaster package, not a NETfishing's PortMaster release is a canonical PortMaster package, not a
version-named generic ZIP. The installer-facing archive must be named version-named generic ZIP. The installer-facing archive must be named
@ -95,10 +95,10 @@ The remaining catalog metadata assets were copied from the installed official
e2a9d132744684c67865c01eca027a8c9946b4c5a19da57c82f1af0373dc83b7 gameinfo.xml e2a9d132744684c67865c01eca027a8c9946b4c5a19da57c82f1af0373dc83b7 gameinfo.xml
``` ```
The packages `licenses/` directory is now assembled from the authoritative The packages `licenses/` directory is assembled from the authoritative root
root license, asset-license, credits, trademark, font, and third-party-notice licenses and trademarks, the consolidated `docs/ATTRIBUTION.md`, and the font
files at build time. It also records the exact release tag and corresponding notices at build time. It also records the exact release tag and corresponding
GPL source commit. Do not restore stale duplicated license templates beneath GPL source commit. Do not restore stale duplicated notice templates beneath
`scripts/portmaster/`. `scripts/portmaster/`.
The original catalog porter credit, `Voyager`, remains in `port.json`. The original catalog porter credit, `Voyager`, remains in `port.json`.
@ -139,3 +139,31 @@ following in the installed game:
- Both sticks, both bumpers, both triggers, Select, Start, L3, and R3 register. - Both sticks, both bumpers, both triggers, Select, Start, L3, and R3 register.
- A single button never cancels auto-map. - A single button never cancels auto-map.
- Holding both bumpers together for 1.25 seconds cancels auto-map. - Holding both bumpers together for 1.25 seconds cancels auto-map.
## Low-end performance profile
The 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 Allwinner H616/H700 XX handhelds:
- `allwinner,h616`
- `sun50iw9p1`
- `allwinner,sun50i-h700`
The low-end profile passes these Godot options:
- `--single-window`
- `--disable-vsync`
- `--max-fps 30`
- `--audio-output-latency 40`
It also passes `NETFISHING_LOW_END=1`. The game renders the 3D world at 75%
linear resolution with nearest-neighbor scaling, reducing 3D pixel work by
about 44% 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.
Before adding a device family, record its exact NUL-separated device-tree
`compatible` value from hardware. Do not infer detection from a retail product
name alone.

60
docs/README.md Normal file
View file

@ -0,0 +1,60 @@
# NETfishing documentation
This directory is the single home for maintained project documentation.
Start here instead of searching feature, asset, world, or script directories
for local README files.
## Authoritative references
- [`DEVELOPMENT.md`](DEVELOPMENT.md): architecture, engineering decisions,
content and balance policy, authoring guidance, and validation.
- [`ATTRIBUTION.md`](ATTRIBUTION.md): ownership, credits, licensing map,
third-party notices, asset provenance, and redistribution requirements.
- [`PORTMASTER.md`](PORTMASTER.md): PortMaster packaging, installation,
controller mapping, performance behavior, and hardware release checks.
- [`README-PLAYTEST.txt`](README-PLAYTEST.txt): the player-facing README staged
into desktop playtest packages.
## Related repositories
- [Discovery service API](https://forge.makearmy.io/woofmeow/netfishing-discovery-server/src/branch/main/docs/API.md)
stays with the service implementation and defines its versioned HTTP
contract.
- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/netfishing-dedicated-server/src/branch/main/README.md)
stays with its installer and update scripts. It consumes this repository's
attribution and legal notices from the exact pinned game commit instead of
maintaining copies.
Those code-coupled references remain in their own repositories. Game-wide
architecture, authoring, licensing, credits, and provenance are not duplicated
there.
Exact runtime values and behavior remain authoritative in source code, scenes,
and resources. These documents describe boundaries, policy, and maintenance
procedures; they should not duplicate changing constants or inventories that
can be derived reliably from the project.
## Files intentionally kept at the repository root
The following are entry points or governing legal documents rather than
general project documentation, so they remain at conventional root paths:
- `README.md`
- `CONTRIBUTING.md`
- `LICENSE`
- `ASSET-LICENSE.md`
- `TRADEMARKS.md`
- `CONTRIBUTOR-TERMS.md`
Third-party font license texts stay beside their font files under `ui/fonts/`
so the licensed files and their original notices cannot drift apart. Build
scripts collect those legal files and the authoritative attribution record
when assembling releases.
## Maintenance rule
Do not add feature-specific README files elsewhere in the repository. Add or
update the appropriate reference above and link to its heading when source
comments need more context. Player-facing package instructions belong only in
`README-PLAYTEST.txt`; source and scripts remain authoritative for command-line
usage.

View file

@ -1,63 +0,0 @@
# 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.

View file

@ -1,27 +0,0 @@
# 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.

View file

@ -1,27 +0,0 @@
# 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.

View file

@ -7,8 +7,8 @@ advanced_options=false
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,THIRD-PARTY-NOTICES.md,CREDITS.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.6.7-alpha/windows-x86_64/NETfishing.exe" export_path="builds/v0.6.7-alpha/windows-x86_64/NETfishing.exe"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@ -40,7 +40,7 @@ application/product_version="0.6.7.0"
application/company_name="Woofmeow" application/company_name="Woofmeow"
application/product_name="NETfishing" application/product_name="NETfishing"
application/file_description="NETfishing v0.6.7-alpha" application/file_description="NETfishing v0.6.7-alpha"
application/copyright="Copyright © 2026 Alexander Sellite and Rheannon Eisworth" application/copyright="Copyright © 2026 Woofmeow"
application/trademarks="NETfishing and Woofmeow branding is reserved; see TRADEMARKS.md" application/trademarks="NETfishing and Woofmeow branding is reserved; see TRADEMARKS.md"
application/export_angle=0 application/export_angle=0
ssh_remote_deploy/enabled=false ssh_remote_deploy/enabled=false
@ -60,8 +60,8 @@ advanced_options=false
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,THIRD-PARTY-NOTICES.md,CREDITS.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.6.7-alpha/linux-arm64/NETfishing.arm64" export_path="builds/v0.6.7-alpha/linux-arm64/NETfishing.arm64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@ -90,8 +90,8 @@ advanced_options=false
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,THIRD-PARTY-NOTICES.md,CREDITS.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.6.7-alpha/macos/NETfishing.zip" export_path="builds/v0.6.7-alpha/macos/NETfishing.zip"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@ -123,8 +123,8 @@ advanced_options=false
dedicated_server=true dedicated_server=true
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,THIRD-PARTY-NOTICES.md,CREDITS.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,playtest/*,scripts/*,tests/*" exclude_filter="builds/*,scripts/*,tests/*"
export_path="builds/v0.6.7-alpha/server-linux-x86_64/NETfishingServer.x86_64" export_path="builds/v0.6.7-alpha/server-linux-x86_64/NETfishingServer.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@ -153,8 +153,8 @@ advanced_options=false
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,THIRD-PARTY-NOTICES.md,CREDITS.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.6.7-alpha/android/NETfishing.apk" export_path="builds/v0.6.7-alpha/android/NETfishing.apk"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@ -212,8 +212,8 @@ advanced_options=false
dedicated_server=false dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,THIRD-PARTY-NOTICES.md,CREDITS.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.6.7-alpha/linux-x86_64/NETfishing.x86_64" export_path="builds/v0.6.7-alpha/linux-x86_64/NETfishing.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

Before

Width:  |  Height:  |  Size: 995 B

View file

@ -1,43 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cxpiqjwgff0xr"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -124,11 +124,15 @@ const OPTIONS: Dictionary = {
"textures": { "textures": {
"body_main": ( "body_main": (
"res://art/exported/characters/patterns/" "res://art/exported/characters/patterns/"
+ "spots_bengal/body_main.png" + "bengal/body_main_bengal.png"
), ),
"body_arms": ( "body_arms": (
"res://art/exported/characters/patterns/" "res://art/exported/characters/patterns/"
+ "spots_bengal/body_arms.png" + "bengal/body_arms_bengal.png"
),
"head_round": (
"res://art/exported/characters/patterns/"
+ "bengal/head_round_bengal.png"
), ),
}, },
}, },
@ -138,11 +142,19 @@ const OPTIONS: Dictionary = {
"textures": { "textures": {
"body_main": ( "body_main": (
"res://art/exported/characters/patterns/" "res://art/exported/characters/patterns/"
+ "fox/body_main.png" + "fox/body_main_fox.png"
), ),
"body_arms": ( "body_arms": (
"res://art/exported/characters/patterns/" "res://art/exported/characters/patterns/"
+ "fox/body_arms.png" + "fox/body_arms_fox.png"
),
"head_pointy": (
"res://art/exported/characters/patterns/"
+ "fox/head_pointy_fox.png"
),
"tails_fox": (
"res://art/exported/characters/patterns/"
+ "fox/tails_fox_fox.png"
), ),
}, },
}, },

View file

@ -7,7 +7,7 @@ readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.6.7-alpha" readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.6.7-alpha"
readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64" readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64"
readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64" readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64"
readonly README_SOURCE="${PROJECT_ROOT}/playtest/README-PLAYTEST.txt" readonly README_SOURCE="${PROJECT_ROOT}/docs/README-PLAYTEST.txt"
readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)"
readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/netfishing" readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/netfishing"
readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.6.7-alpha-windows-x86_64.zip" readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.6.7-alpha-windows-x86_64.zip"
@ -61,12 +61,10 @@ stage_licenses() {
"${destination}/GPL-3.0-or-later.txt" "${destination}/GPL-3.0-or-later.txt"
install -m 0644 "${PROJECT_ROOT}/ASSET-LICENSE.md" \ install -m 0644 "${PROJECT_ROOT}/ASSET-LICENSE.md" \
"${destination}/NETFISHING-ASSET-LICENSE.md" "${destination}/NETFISHING-ASSET-LICENSE.md"
install -m 0644 "${PROJECT_ROOT}/THIRD-PARTY-NOTICES.md" \ install -m 0644 "${PROJECT_ROOT}/docs/ATTRIBUTION.md" \
"${destination}/THIRD-PARTY-NOTICES.md" "${destination}/ATTRIBUTION.md"
install -m 0644 "${PROJECT_ROOT}/TRADEMARKS.md" \ install -m 0644 "${PROJECT_ROOT}/TRADEMARKS.md" \
"${destination}/TRADEMARKS.md" "${destination}/TRADEMARKS.md"
install -m 0644 "${PROJECT_ROOT}/CREDITS.md" \
"${destination}/CREDITS.md"
install -m 0644 "${PROJECT_ROOT}/ui/fonts/Tuffy-LICENSE.txt" \ install -m 0644 "${PROJECT_ROOT}/ui/fonts/Tuffy-LICENSE.txt" \
"${destination}/Tuffy-LICENSE.txt" "${destination}/Tuffy-LICENSE.txt"
install -m 0644 "${PROJECT_ROOT}/ui/fonts/Seattle-Avenue-LICENSE.txt" \ install -m 0644 "${PROJECT_ROOT}/ui/fonts/Seattle-Avenue-LICENSE.txt" \

View file

@ -85,17 +85,14 @@ 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}/gameinfo.xml" "${GAME_ROOT}/gameinfo.xml"
install -m 0644 "${TEMPLATE_ROOT}/screenshot.png" "${GAME_ROOT}/screenshot.png" install -m 0644 "${TEMPLATE_ROOT}/screenshot.png" "${GAME_ROOT}/screenshot.png"
install -m 0644 \ install -m 0644 \
"${PROJECT_ROOT}/CREDITS.md" \ "${PROJECT_ROOT}/docs/ATTRIBUTION.md" \
"${GAME_ROOT}/licenses/CREDITS.md" "${GAME_ROOT}/licenses/ATTRIBUTION.md"
install -m 0644 \ install -m 0644 \
"${PROJECT_ROOT}/LICENSE" \ "${PROJECT_ROOT}/LICENSE" \
"${GAME_ROOT}/licenses/GPL-3.0-or-later.txt" "${GAME_ROOT}/licenses/GPL-3.0-or-later.txt"
install -m 0644 \ install -m 0644 \
"${PROJECT_ROOT}/ASSET-LICENSE.md" \ "${PROJECT_ROOT}/ASSET-LICENSE.md" \
"${GAME_ROOT}/licenses/NETFISHING-ASSET-LICENSE.md" "${GAME_ROOT}/licenses/NETFISHING-ASSET-LICENSE.md"
install -m 0644 \
"${PROJECT_ROOT}/THIRD-PARTY-NOTICES.md" \
"${GAME_ROOT}/licenses/THIRD-PARTY-NOTICES.md"
install -m 0644 \ install -m 0644 \
"${PROJECT_ROOT}/TRADEMARKS.md" \ "${PROJECT_ROOT}/TRADEMARKS.md" \
"${GAME_ROOT}/licenses/TRADEMARKS.md" "${GAME_ROOT}/licenses/TRADEMARKS.md"

View file

@ -9,8 +9,7 @@
"attr": { "attr": {
"title": "NETfishing", "title": "NETfishing",
"porter": [ "porter": [
"Voyager", "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": "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, "desc_md": null,

View file

@ -29,6 +29,7 @@ readonly -a RUNTIME_TESTS=(
"tests/inventory_notepad_art_validation.gd" "tests/inventory_notepad_art_validation.gd"
"tests/logbook_runtime_validation.gd" "tests/logbook_runtime_validation.gd"
"tests/player_experience_ui_validation.gd" "tests/player_experience_ui_validation.gd"
"tests/title_credits_validation.gd"
"tests/ui_scaling_runtime_validation.gd" "tests/ui_scaling_runtime_validation.gd"
) )

View file

@ -67,12 +67,26 @@ func _run() -> void:
assert(fox_arm_pattern_texture != null) assert(fox_arm_pattern_texture != null)
assert(fox_arm_pattern_texture.get_width() == 1024) assert(fox_arm_pattern_texture.get_width() == 1024)
assert(fox_arm_pattern_texture.get_height() == 1024) assert(fox_arm_pattern_texture.get_height() == 1024)
assert( var bengal_head_texture := (
CharacterCustomizationCatalog.fur_pattern_texture( CharacterCustomizationCatalog.fur_pattern_texture(
"spots_bengal", "spots_bengal", "head_round"
"head_round",
) == null
) )
)
assert(bengal_head_texture != null)
assert(bengal_head_texture.get_width() == 1024)
assert(bengal_head_texture.get_height() == 1024)
var fox_head_texture := CharacterCustomizationCatalog.fur_pattern_texture(
"fox", "head_pointy"
)
assert(fox_head_texture != null)
assert(fox_head_texture.get_width() == 1024)
assert(fox_head_texture.get_height() == 1024)
var fox_tail_texture := CharacterCustomizationCatalog.fur_pattern_texture(
"fox", "tails_fox"
)
assert(fox_tail_texture != null)
assert(fox_tail_texture.get_width() == 1024)
assert(fox_tail_texture.get_height() == 1024)
var patterned := migrated.duplicate(true) var patterned := migrated.duplicate(true)
patterned["fur_pattern"] = "orange" patterned["fur_pattern"] = "orange"
@ -117,7 +131,36 @@ func _run() -> void:
) )
var round_head := skeleton.get_node_or_null("head_round") as MeshInstance3D var round_head := skeleton.get_node_or_null("head_round") as MeshInstance3D
assert(round_head != null) assert(round_head != null)
assert(round_head.material_override is StandardMaterial3D) var round_head_material := round_head.material_override as ShaderMaterial
assert(round_head_material != null)
assert(
round_head_material.get_shader_parameter("pattern_texture")
== bengal_head_texture
)
var fox_snapshot := patterned.duplicate(true)
fox_snapshot["species"] = "pointy"
fox_snapshot["fur_style"] = "fox"
fox_snapshot["tail"] = "fox"
PlayerVisualPresenter.apply_appearance(visuals, fox_snapshot)
var pointy_head := (
skeleton.get_node_or_null("head_pointy") as MeshInstance3D
)
assert(pointy_head != null)
var pointy_head_material := pointy_head.material_override as ShaderMaterial
assert(pointy_head_material != null)
assert(
pointy_head_material.get_shader_parameter("pattern_texture")
== fox_head_texture
)
var fox_tail := skeleton.get_node_or_null("tails_fox") as MeshInstance3D
assert(fox_tail != null)
var fox_tail_material := fox_tail.material_override as ShaderMaterial
assert(fox_tail_material != null)
assert(
fox_tail_material.get_shader_parameter("pattern_texture")
== fox_tail_texture
)
var recolored := patterned.duplicate(true) var recolored := patterned.duplicate(true)
recolored["fur_color_3"] = "teal" recolored["fur_color_3"] = "teal"

View file

@ -49,6 +49,7 @@ func _run() -> void:
runtime_terrain.get_surface_override_material(surface_index) == null, runtime_terrain.get_surface_override_material(surface_index) == null,
"Region surface %d must use its Blender material." % surface_index, "Region surface %d must use its Blender material." % surface_index,
) )
_validate_region_collision(region)
region.free() region.free()
_finish() _finish()
@ -118,6 +119,62 @@ func _validate_terrain_materials(
) )
func _validate_region_collision(region: StarterIslandRegion) -> void:
_check(
region.rebuild_terrain_collision_on_ready,
"Starter-island collision must rebuild from the imported GLB.",
)
var visual_root := region.get_node_or_null("Terrain/Visual") as Node3D
var collision_shape := region.get_node_or_null(
"Terrain/Collision/Shape"
) as CollisionShape3D
_check(visual_root != null, "Region must contain its visual hierarchy.")
_check(collision_shape != null, "Region must contain its collision owner.")
if visual_root == null or collision_shape == null:
return
var concave_shape := collision_shape.shape as ConcavePolygonShape3D
_check(
concave_shape != null,
"Region collision must be a generated concave polygon shape.",
)
if concave_shape == null:
return
var expected_faces := PackedVector3Array()
var mesh_count := _append_collision_faces(
region,
visual_root,
expected_faces,
)
var actual_faces := concave_shape.get_faces()
_check(mesh_count > 1, "Collision must include the imported prop meshes.")
_check(not expected_faces.is_empty(), "Imported meshes must provide collision.")
_check(
actual_faces == expected_faces,
"Generated collision must exactly match every imported mesh transform.",
)
func _append_collision_faces(
region: StarterIslandRegion,
node: Node,
faces: PackedVector3Array,
) -> int:
var mesh_count := 0
if node is MeshInstance3D:
var mesh_instance := node as MeshInstance3D
if mesh_instance.mesh != null:
var mesh_shape := mesh_instance.mesh.create_trimesh_shape()
if mesh_shape != null:
mesh_count += 1
for face_vertex: Vector3 in mesh_shape.get_faces():
faces.append(
region.to_local(mesh_instance.to_global(face_vertex))
)
for child: Node in node.get_children():
mesh_count += _append_collision_faces(region, child, faces)
return mesh_count
func _check(condition: bool, message: String) -> void: func _check(condition: bool, message: String) -> void:
if not condition: if not condition:
failures.append(message) failures.append(message)
@ -125,9 +182,9 @@ func _check(condition: bool, message: String) -> void:
func _finish() -> void: func _finish() -> void:
if failures.is_empty(): if failures.is_empty():
print("Blender terrain material validation: PASS") print("Blender terrain and collision validation: PASS")
quit(0) quit(0)
return return
for failure: String in failures: for failure: String in failures:
printerr("Blender terrain material validation: ", failure) printerr("Blender terrain and collision validation: ", failure)
quit(1) quit(1)

View file

@ -0,0 +1,207 @@
extends SceneTree
const TitleScreenScene := preload("res://ui/title_screen.tscn")
const TitleCreditsPageType = preload("res://ui/title_credits_page.gd")
var _failures: Array[String] = []
func _initialize() -> void:
_run.call_deferred()
func _run() -> void:
var original_size: Vector2i = root.size
root.size = Vector2i(1280, 720)
var title_screen := TitleScreenScene.instantiate() as TitleScreen
root.add_child(title_screen)
await process_frame
await process_frame
var credits_button := title_screen.get_node(
"%CreditsButton"
) as BubbleButton
var credits_page := title_screen.get_node(
"%CreditsPage"
) as TitleCreditsPageType
var back_button: Button = credits_page.get_back_button()
var presentation := title_screen.get_node("%Center") as Control
var bubble_field := title_screen.get_node("%BubbleField") as Control
title_screen.call("_update_title_layout")
(title_screen.get_node("%ButtonCenter") as Control).show()
bubble_field.show()
title_screen.call("_set_title_bubbles_interactive", true)
await process_frame
_expect(credits_button != null, "title menu exposes a credits button")
_expect(credits_page != null, "title screen contains the credits page")
_expect(not credits_page.visible, "credits page starts closed")
_expect(
Rect2(Vector2.ZERO, bubble_field.size).encloses(
Rect2(credits_button.position, credits_button.size)
),
"credits bubble stays inside the authored title field",
)
_expect(
credits_button.mouse_filter == Control.MOUSE_FILTER_STOP,
"credits bubble accepts pointer input",
)
if OS.has_environment("NETFISHING_TITLE_MENU_CAPTURE"):
(title_screen.get_node("%StartPromptCenter") as Control).hide()
title_screen.set_process(true)
await create_timer(0.5).timeout
_expect(
_save_capture(
OS.get_environment("NETFISHING_TITLE_MENU_CAPTURE")
) == OK,
"title menu validation capture saves successfully",
)
title_screen.set_process(false)
title_screen.set("_navigation_focus_active", false)
credits_button.pressed.emit()
await process_frame
_expect(credits_page.visible, "pointer activation opens credits")
_expect(not presentation.visible, "credits replace the primary title menu")
_expect(
root.gui_get_focus_owner() != back_button,
"pointer activation does not pseudo-select back",
)
await create_timer(0.25).timeout
_expect(
credits_page.mouse_filter == Control.MOUSE_FILTER_PASS,
"credits page becomes pointer-interactive after entry motion",
)
_expect(
back_button.mouse_filter == Control.MOUSE_FILTER_STOP,
"credits back button accepts pointer clicks",
)
if OS.has_environment("NETFISHING_CREDITS_CAPTURE"):
await process_frame
_expect(
_save_capture(
OS.get_environment("NETFISHING_CREDITS_CAPTURE")
) == OK,
"credits validation capture saves successfully",
)
back_button.pressed.emit()
await process_frame
_expect(not credits_page.visible, "pointer back closes credits")
_expect(presentation.visible, "closing credits restores the title menu")
_expect(
root.gui_get_focus_owner() == null,
"pointer return leaves the title menu unfocused",
)
title_screen.set("_navigation_focus_active", true)
credits_button.grab_focus()
credits_button.pressed.emit()
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == back_button,
"keyboard or controller activation focuses the credits back button",
)
var pointer_motion := InputEventMouseMotion.new()
pointer_motion.position = Vector2(640.0, 360.0)
title_screen._input(pointer_motion)
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"pointer motion clears credits navigation focus",
)
var navigate := InputEventAction.new()
navigate.action = &"ui_down"
navigate.pressed = true
title_screen._input(navigate)
await process_frame
_expect(
root.gui_get_focus_owner() == back_button,
"navigation input restores credits focus",
)
var cancel := InputEventAction.new()
cancel.action = &"ui_cancel"
cancel.pressed = true
title_screen._input(cancel)
await process_frame
_expect(not credits_page.visible, "ui cancel closes credits")
_expect(
root.gui_get_focus_owner() == credits_button,
"navigation return restores focus to the credits bubble",
)
var chillnfill_name := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits/ChillnfillName"
) as Label
var voyager_name := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits/VoyagerName"
) as Label
var endeavour_name := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits/EndeavourName"
) as Label
var chillnfill_credit := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits/ChillnfillCredit"
) as Label
var tekgator_name := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits/TekgatorName"
) as Label
var tekgator_credit := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits/TekgatorCredit"
) as Label
var creative_credits := credits_page.get_node(
"Paper/Margin/Layout/Columns/CreativeCredits"
) as VBoxContainer
_expect(voyager_name.text == "Voyager", "in-game credits use Voyager")
_expect(
endeavour_name.text == "Endeavour",
"in-game credits use Endeavour",
)
_expect(
chillnfill_name.text == "chillnfill",
"in-game credits name chillnfill",
)
_expect(
"trees and bridges" in chillnfill_credit.text.to_lower(),
"in-game credits describe the supplied model families",
)
_expect(
tekgator_name.text == "Tekgator",
"in-game credits name Tekgator",
)
_expect(
"shop icon" in tekgator_credit.text.to_lower(),
"in-game credits describe Tekgator's shop icon artwork",
)
_expect(
Rect2(Vector2.ZERO, creative_credits.size).encloses(
Rect2(tekgator_credit.position, tekgator_credit.size)
),
"Tekgator's credit stays inside the authored credits column",
)
title_screen.queue_free()
root.size = original_size
await process_frame
await process_frame
if _failures.is_empty():
print("Title credits validation: PASS")
quit(0)
return
for failure: String in _failures:
push_error(failure)
quit(1)
func _expect(condition: bool, message: String) -> void:
if not condition:
_failures.append(message)
func _save_capture(path: String) -> Error:
var viewport_texture: Texture2D = root.get_texture()
if viewport_texture == null:
return ERR_UNAVAILABLE
return viewport_texture.get_image().save_png(path)

View file

@ -0,0 +1 @@
uid://dimq4ehtlw64n

View file

@ -1,26 +0,0 @@
# Bubble menu authoring
Instance `bubble_button.tscn` for each action, or attach `bubble_button.gd`
to an existing Button. Set its neutral size, desktop and compact anchors,
font-size limits, and deterministic motion values in the Inspector. A label
child can be assigned through `label_control_path` when wrapped text is needed.
Text size is calculated once per layout from the neutral smaller dimension and
the profile ratio.
Place the buttons under a Control using `bubble_cluster.gd`. Give the cluster
the ordered BubbleButton references with `configure()`, then call
`apply_layout()` when its available size or responsive layout changes. The
button order defines explicit keyboard and controller focus neighbors. Compact
anchors and minimum sizes remain authored per button.
The shared profile owns the palette, rounded styles, proportional-font ratio,
hover response, and contact tuning. Labels, actions, sizes, anchors, per-button
motion, responsive wrapping, availability, and confirmation behavior remain
owned by the menu. Connect each Button's `pressed` signal in that parent menu.
`motion_scale` defaults to `1.0`. Setting it to `0.0` removes idle drift and
deformation while retaining hover and focus feedback.
Contact is a deterministic, bounded visual correction around authored anchors.
Real physics is intentionally avoided so layouts, focus order, and hit targets
remain stable.

87
ui/title_credits_page.gd Normal file
View file

@ -0,0 +1,87 @@
class_name TitleCreditsPage
extends Control
signal back_requested(restore_navigation_focus: bool)
@onready var _paper: PanelContainer = %Paper
@onready var _back_button: Button = %CreditsBackButton
var _navigation_focus_active: bool = false
func _ready() -> void:
UtilityPageStyle.apply_page(self)
_paper.add_theme_stylebox_override(
"panel", UtilityPageStyle.panel_style()
)
UtilityPageStyle.apply_ocean_button(_back_button)
_back_button.pressed.connect(_request_back)
hide()
func open_page(use_navigation_focus: bool) -> void:
_navigation_focus_active = use_navigation_focus
show()
UtilityPageStyle.animate_in(self)
if use_navigation_focus:
call_deferred("_focus_back")
else:
_release_page_focus()
func close_page() -> void:
_navigation_focus_active = false
_release_page_focus()
hide()
scale = Vector2.ONE
modulate.a = 1.0
func handle_input(event: InputEvent) -> bool:
if not visible:
return false
if event is InputEventMouseMotion:
_navigation_focus_active = false
_release_page_focus()
return false
if event is InputEventKey and (event as InputEventKey).echo:
return false
var moves_focus: bool = (
event.is_action_pressed("ui_up")
or event.is_action_pressed("ui_down")
or event.is_action_pressed("ui_left")
or event.is_action_pressed("ui_right")
)
if moves_focus:
_navigation_focus_active = true
if not _page_has_focus():
_back_button.grab_focus()
return true
if event.is_action_pressed("ui_cancel"):
_request_back()
return true
return false
func get_back_button() -> Button:
return _back_button
func _focus_back() -> void:
if visible and _navigation_focus_active:
_back_button.grab_focus()
func _request_back() -> void:
back_requested.emit(_navigation_focus_active)
func _page_has_focus() -> bool:
var focus_owner: Control = get_viewport().gui_get_focus_owner()
return focus_owner != null and is_ancestor_of(focus_owner)
func _release_page_focus() -> void:
var focus_owner: Control = get_viewport().gui_get_focus_owner()
if focus_owner != null and is_ancestor_of(focus_owner):
focus_owner.release_focus()

View file

@ -0,0 +1 @@
uid://c07nehcd5yw6m

180
ui/title_credits_page.tscn Normal file
View file

@ -0,0 +1,180 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://ui/title_credits_page.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[node name="TitleCreditsPage" type="Control"]
visible = false
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
theme = ExtResource("2_theme")
script = ExtResource("1_script")
[node name="Paper" type="PanelContainer" parent="."]
unique_name_in_owner = true
layout_mode = 0
offset_left = 160.0
offset_top = 38.0
offset_right = 1120.0
offset_bottom = 682.0
[node name="Margin" type="MarginContainer" parent="Paper"]
layout_mode = 2
theme_override_constants/margin_left = 52
theme_override_constants/margin_top = 26
theme_override_constants/margin_right = 52
theme_override_constants/margin_bottom = 24
[node name="Layout" type="VBoxContainer" parent="Paper/Margin"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="Title" type="Label" parent="Paper/Margin/Layout"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 36
text = "credits"
horizontal_alignment = 1
[node name="Subtitle" type="Label" parent="Paper/Margin/Layout"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 18
text = "NETfishing is created by Woofmeow"
horizontal_alignment = 1
[node name="Columns" type="HBoxContainer" parent="Paper/Margin/Layout"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 52
[node name="CreativeCredits" type="VBoxContainer" parent="Paper/Margin/Layout/Columns"]
custom_minimum_size = Vector2(404, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 5
[node name="CreativeHeading" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 23
text = "creative team"
[node name="VoyagerName" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 18
text = "Voyager"
[node name="VoyagerRoles" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15
text = "co-owner • development • project direction • 3d art • music"
autowrap_mode = 2
[node name="EndeavourName" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 18
text = "Endeavour"
[node name="EndeavourRoles" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15
text = "co-owner • development • 2d art"
autowrap_mode = 2
[node name="ContributorSpacer" type="Control" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
custom_minimum_size = Vector2(0, 9)
layout_mode = 2
[node name="ContributorHeading" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 23
text = "additional contributors"
[node name="ChillnfillName" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 18
text = "chillnfill"
[node name="ChillnfillCredit" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15
text = "original 3d models for character bodies and arms, ears and tails, and multiple world props and decorative assets including trees and bridges"
autowrap_mode = 2
[node name="TekgatorName" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 18
text = "Tekgator"
[node name="TekgatorCredit" type="Label" parent="Paper/Margin/Layout/Columns/CreativeCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15
text = "cooler-capacity and barrier-power shop icon artwork"
autowrap_mode = 2
[node name="AdditionalCredits" type="VBoxContainer" parent="Paper/Margin/Layout/Columns"]
custom_minimum_size = Vector2(404, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 5
[node name="AudioHeading" type="Label" parent="Paper/Margin/Layout/Columns/AdditionalCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 23
text = "additional audio"
[node name="AudioCredits" type="Label" parent="Paper/Margin/Layout/Columns/AdditionalCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15
text = "tosha73 • fishing reel sounds\nkkenny101 • ocean ambience\nqubodup • water impact\nAyton • rain ambience\nivolipa • dog bark\nChristyboy100 • cat meow"
[node name="TechnologySpacer" type="Control" parent="Paper/Margin/Layout/Columns/AdditionalCredits"]
custom_minimum_size = Vector2(0, 9)
layout_mode = 2
[node name="TechnologyHeading" type="Label" parent="Paper/Margin/Layout/Columns/AdditionalCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.902, 0.969, 0.969, 1)
theme_override_font_sizes/font_size = 23
text = "fonts & technology"
[node name="TechnologyCredits" type="Label" parent="Paper/Margin/Layout/Columns/AdditionalCredits"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15
text = "Tuffy • Thatcher Ulrich, Karoly Barta & Michael Evans\nSeattle Avenue • JLH Fonts\nbuilt with Godot Engine"
autowrap_mode = 2
[node name="Notice" type="Label" parent="Paper/Margin/Layout"]
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 13
text = "complete attribution, licensing, and asset provenance are included with every release"
horizontal_alignment = 1
autowrap_mode = 2
[node name="BackCenter" type="CenterContainer" parent="Paper/Margin/Layout"]
layout_mode = 2
[node name="CreditsBackButton" type="Button" parent="Paper/Margin/Layout/BackCenter"]
unique_name_in_owner = true
custom_minimum_size = Vector2(112, 50)
layout_mode = 2
text = "back"

View file

@ -16,6 +16,7 @@ const BubbleClusterType = preload(
const TitleConfirmationBubblePageType = preload( const TitleConfirmationBubblePageType = preload(
"res://ui/title_confirmation_bubble_page.gd" "res://ui/title_confirmation_bubble_page.gd"
) )
const TitleCreditsPageType = preload("res://ui/title_credits_page.gd")
const NetworkSessionType = preload("res://network/network_session.gd") const NetworkSessionType = preload("res://network/network_session.gd")
const SavedServerStoreType = preload("res://network/saved_server_store.gd") const SavedServerStoreType = preload("res://network/saved_server_store.gd")
const JoinGamePageType = preload("res://ui/network/join_game_page.gd") const JoinGamePageType = preload("res://ui/network/join_game_page.gd")
@ -70,7 +71,7 @@ const LOGO_MAX_WIDTH: float = 662.0
const TITLE_HORIZONTAL_MARGIN: float = 48.0 const TITLE_HORIZONTAL_MARGIN: float = 48.0
const TITLE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0) const TITLE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
const TITLE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0) const TITLE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
const BUBBLE_FIELD_MAX_WIDTH: float = 396.0 const BUBBLE_FIELD_MAX_WIDTH: float = 440.0
const BUBBLE_FIELD_DESKTOP_HEIGHT: float = 318.0 const BUBBLE_FIELD_DESKTOP_HEIGHT: float = 318.0
const BUBBLE_FIELD_COMPACT_WIDTH: float = 294.0 const BUBBLE_FIELD_COMPACT_WIDTH: float = 294.0
const BUBBLE_FIELD_COMPACT_HEIGHT: float = 200.0 const BUBBLE_FIELD_COMPACT_HEIGHT: float = 200.0
@ -99,6 +100,7 @@ enum ConfirmationAction {
@onready var _continue_button: BubbleButtonType = %ContinueButton @onready var _continue_button: BubbleButtonType = %ContinueButton
@onready var _new_game_button: BubbleButtonType = %NewGameButton @onready var _new_game_button: BubbleButtonType = %NewGameButton
@onready var _settings_button: BubbleButtonType = %SettingsButton @onready var _settings_button: BubbleButtonType = %SettingsButton
@onready var _credits_button: BubbleButtonType = %CreditsButton
@onready var _delete_button: BubbleButtonType = %DeleteSaveButton @onready var _delete_button: BubbleButtonType = %DeleteSaveButton
@onready var _quit_button: BubbleButtonType = %QuitButton @onready var _quit_button: BubbleButtonType = %QuitButton
@onready var _join_game_button: BubbleButtonType = %JoinGameButton @onready var _join_game_button: BubbleButtonType = %JoinGameButton
@ -134,6 +136,7 @@ enum ConfirmationAction {
@onready var _start_prompt_center: CenterContainer = %StartPromptCenter @onready var _start_prompt_center: CenterContainer = %StartPromptCenter
@onready var _start_prompt_label: Label = %StartPromptLabel @onready var _start_prompt_label: Label = %StartPromptLabel
@onready var _join_game_page: JoinGamePageType = %JoinGamePage @onready var _join_game_page: JoinGamePageType = %JoinGamePage
@onready var _credits_page: TitleCreditsPageType = %CreditsPage
var _save_manager: SaveManagerType var _save_manager: SaveManagerType
var _settings_manager: SettingsManagerType var _settings_manager: SettingsManagerType
@ -200,6 +203,7 @@ func _ready() -> void:
_new_game_button.pressed.connect(_on_new_game_pressed) _new_game_button.pressed.connect(_on_new_game_pressed)
_join_game_button.pressed.connect(_open_join_game) _join_game_button.pressed.connect(_open_join_game)
_settings_button.pressed.connect(_open_settings) _settings_button.pressed.connect(_open_settings)
_credits_button.pressed.connect(_open_credits)
_delete_button.pressed.connect(_on_delete_pressed) _delete_button.pressed.connect(_on_delete_pressed)
%QuitButton.pressed.connect(_on_quit_pressed) %QuitButton.pressed.connect(_on_quit_pressed)
_confirmation_page.confirmed.connect(_on_confirmation_accepted) _confirmation_page.confirmed.connect(_on_confirmation_accepted)
@ -211,6 +215,7 @@ func _ready() -> void:
_settings_panel.navigation_transition_started.connect( _settings_panel.navigation_transition_started.connect(
_emit_navigation_bubble_flurry _emit_navigation_bubble_flurry
) )
_credits_page.back_requested.connect(_close_credits)
_bubble_field.configure(_get_title_buttons()) _bubble_field.configure(_get_title_buttons())
_decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout) _decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout)
_decorative_bubble_event_timer.timeout.connect( _decorative_bubble_event_timer.timeout.connect(
@ -261,6 +266,7 @@ func reopen() -> void:
_reset_confirmation() _reset_confirmation()
_settings_panel.hide() _settings_panel.hide()
_join_game_page.close_page() _join_game_page.close_page()
_credits_page.close_page()
_refresh_save_inspection() _refresh_save_inspection()
show() show()
_start_decorative_presentation() _start_decorative_presentation()
@ -274,6 +280,7 @@ func open_join_game_page(endpoint: String = "") -> void:
_presentation_center.hide() _presentation_center.hide()
_settings_panel.hide() _settings_panel.hide()
_confirmation_page.hide_page() _confirmation_page.hide_page()
_credits_page.close_page()
_join_game_page.open_page(endpoint) _join_game_page.open_page(endpoint)
@ -286,7 +293,11 @@ func report_network_error(message: String) -> void:
func _open_join_game() -> void: func _open_join_game() -> void:
if _action_in_progress or _is_confirmation_active(): if (
_action_in_progress
or _is_confirmation_active()
or _credits_page.visible
):
return return
open_join_game_page() open_join_game_page()
@ -299,6 +310,39 @@ func _close_join_game() -> void:
_focus_initial_button() _focus_initial_button()
func _open_credits() -> void:
if (
_action_in_progress
or _is_confirmation_active()
or _settings_panel.visible
or _join_game_page.visible
or _credits_page.visible
):
return
_hide_continue_stats_context()
var restore_navigation_focus: bool = _navigation_focus_active
_set_title_bubbles_interactive(false)
_release_primary_menu_focus()
_presentation_center.hide()
_start_prompt_center.hide()
_credits_page.open_page(restore_navigation_focus)
func _close_credits(restore_navigation_focus: bool) -> void:
_credits_page.close_page()
_presentation_center.show()
_button_center.show()
_start_prompt_center.hide()
_set_title_bubbles_interactive(true)
if restore_navigation_focus:
_navigation_focus_active = true
_credits_button.grab_focus()
else:
_navigation_focus_active = false
_release_title_focus()
_update_continue_stats_visibility()
func is_awaiting_start_input() -> bool: func is_awaiting_start_input() -> bool:
return _awaiting_start_input return _awaiting_start_input
@ -403,6 +447,7 @@ func _get_title_buttons() -> Array[BubbleButton]:
_new_game_button, _new_game_button,
_join_game_button, _join_game_button,
_settings_button, _settings_button,
_credits_button,
_delete_button, _delete_button,
_quit_button, _quit_button,
] ]
@ -498,6 +543,10 @@ func _input(event: InputEvent) -> void:
and _settings_panel.is_controller_mapping_capturing() and _settings_panel.is_controller_mapping_capturing()
): ):
return return
if _credits_page.visible:
if _credits_page.handle_input(event):
get_viewport().set_input_as_handled()
return
if _join_game_page.visible: if _join_game_page.visible:
if event.is_action_pressed("ui_cancel"): if event.is_action_pressed("ui_cancel"):
_close_join_game() _close_join_game()
@ -549,6 +598,7 @@ func _handle_primary_menu_focus_input(event: InputEvent) -> bool:
not _button_center.visible not _button_center.visible
or _is_confirmation_active() or _is_confirmation_active()
or _settings_panel.visible or _settings_panel.visible
or _credits_page.visible
): ):
return false return false
if event is InputEventMouseMotion: if event is InputEventMouseMotion:
@ -1186,6 +1236,7 @@ func _open_settings() -> void:
_is_confirmation_active() _is_confirmation_active()
or _action_in_progress or _action_in_progress
or _settings_panel.visible or _settings_panel.visible
or _credits_page.visible
or _title_settings_transition_active or _title_settings_transition_active
): ):
return return
@ -1398,6 +1449,7 @@ func _focus_initial_button() -> void:
or not visible or not visible
or _awaiting_start_input or _awaiting_start_input
or not _button_center.visible or not _button_center.visible
or _credits_page.visible
): ):
return return
if not _continue_button.disabled: if not _continue_button.disabled:
@ -1413,6 +1465,7 @@ func _on_quit_pressed() -> void:
not _action_in_progress not _action_in_progress
and not _is_confirmation_active() and not _is_confirmation_active()
and not _settings_panel.visible and not _settings_panel.visible
and not _credits_page.visible
): ):
_hide_continue_stats_context() _hide_continue_stats_context()
quit_requested.emit() quit_requested.emit()
@ -1430,6 +1483,7 @@ func _on_title_visibility_changed() -> void:
_navigation_focus_active = false _navigation_focus_active = false
_modal_restore_navigation_focus = false _modal_restore_navigation_focus = false
_reset_confirmation() _reset_confirmation()
_credits_page.close_page()
_stop_decorative_presentation() _stop_decorative_presentation()

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=17 format=3] [gd_scene load_steps=18 format=3]
[ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"] [ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -14,6 +14,7 @@
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/online_dark.png" id="12_online_dark"] [ext_resource type="Texture2D" path="res://ui/icons/pictograms/online_dark.png" id="12_online_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/settings_dark.png" id="13_settings_dark"] [ext_resource type="Texture2D" path="res://ui/icons/pictograms/settings_dark.png" id="13_settings_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_dark.png" id="14_x_dark"] [ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_dark.png" id="14_x_dark"]
[ext_resource type="PackedScene" path="res://ui/title_credits_page.tscn" id="15_credits_page"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"]
shader = ExtResource("4_water_shader") shader = ExtResource("4_water_shader")
@ -224,21 +225,21 @@ layout_mode = 2
[node name="BubbleLayoutSlot" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter"] [node name="BubbleLayoutSlot" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(396, 318) custom_minimum_size = Vector2(440, 318)
layout_mode = 2 layout_mode = 2
[node name="BubbleMotionRoot" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot"] [node name="BubbleMotionRoot" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 0 layout_mode = 0
offset_right = 396.0 offset_right = 440.0
offset_bottom = 318.0 offset_bottom = 318.0
mouse_filter = 2 mouse_filter = 2
[node name="BubbleField" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot"] [node name="BubbleField" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(396, 318) custom_minimum_size = Vector2(440, 318)
layout_mode = 0 layout_mode = 0
offset_right = 396.0 offset_right = 440.0
offset_bottom = 318.0 offset_bottom = 318.0
script = ExtResource("8_bubble_cluster") script = ExtResource("8_bubble_cluster")
profile = ExtResource("9_bubble_profile") profile = ExtResource("9_bubble_profile")
@ -322,6 +323,30 @@ motion_period = 4.9
motion_phase = 2.5 motion_phase = 2.5
deformation_period = 6.3 deformation_period = 6.3
[node name="CreditsButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 330.0
offset_top = 125.0
offset_right = 414.0
offset_bottom = 207.0
text = "credits"
tooltip_text = "credits"
accessibility_name = "credits"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(84, 82)
desktop_anchor = Vector2(372, 166)
compact_anchor = Vector2(372, 166)
compact_minimum_size = Vector2(62, 62)
minimum_font_size = 12
maximum_font_size = 17
horizontal_amplitude = 1.4
vertical_amplitude = 3.0
motion_period = 5.0
motion_phase = 1.8
deformation_amplitude = 0.015
deformation_period = 5.6
[node name="JoinGameButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"] [node name="JoinGameButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true unique_name_in_owner = true
offset_left = 126.0 offset_left = 126.0
@ -444,6 +469,16 @@ offset_bottom = 300.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
[node name="CreditsPage" parent="ResponsiveTitleStage/TitlePresentationScaleRoot" instance=ExtResource("15_credits_page")]
unique_name_in_owner = true
z_index = 220
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="JoinGamePage" parent="ResponsiveTitleStage/TitlePresentationScaleRoot" instance=ExtResource("11_join_page")] [node name="JoinGamePage" parent="ResponsiveTitleStage/TitlePresentationScaleRoot" instance=ExtResource("11_join_page")]
unique_name_in_owner = true unique_name_in_owner = true
z_index = 220 z_index = 220

View file

@ -1,127 +0,0 @@
# World authoring
The active test world uses the canonical starter island through
`world/regions/starter_island_region.tscn`.
## Active composition
`world/test_world.tscn` owns the gameplay-wide environment, sun, world bounds,
and below-world failsafe. `StarterIslandRegion` owns the placed island content:
```text
StarterIslandRegion
├── Terrain
│ ├── Visual
│ └── Collision
├── WaterBodies
│ ├── Pond
│ │ ├── VisualWater
│ │ ├── FishingRegion
│ │ └── RecoveryRegion
│ └── Ocean
│ ├── VisualWater
│ ├── FishingRegions
│ └── RecoveryRegions
├── PlayerSpawn
├── SafeRespawns
└── Interactables
├── FishingShopWorld
└── PelicanCoolerPerch
```
Move the meaningful feature root rather than one of its implementation
children. Child transforms are local offsets owned by that feature.
## Water bodies
Every fishable region must author its `water_type` explicitly. The starter pond
is `FRESH_WATER`; the starter coast is `SALT_WATER`. Fish species use the same
central type definition through an allowed-water-type bitmask, so pool contents
and authoritative selection are both validated against the region. Do not infer
habitat from node names, pool filenames, coordinates, or water height.
Select `WaterBodies/Pond` to move or resize the pond. Its transform is the
authoritative surface position. The `surface_size`, fishing padding, recovery
padding, and recovery depth properties update the visible water and gameplay
coverage together. Fishing and recovery derive the surface height from the
owned surface transform.
Select `WaterBodies/Ocean` to move the surrounding water as one feature. Its
visual, fishing lobes, and recovery lobes use local transforms beneath the
ocean root. The existing multi-lobe footprint remains intentionally explicit;
changing that footprint still requires editing its owned lobe children.
## Placed features
- `PlayerSpawn` is the authoritative initial player transform. Its rotation
controls initial facing.
- Each marker under `SafeRespawns` is an authoritative recovery destination.
Recovery preserves the player's current facing; marker rotation is
intentionally not applied.
- `Interactables/FishingShopWorld` owns the shop visual, collision,
interaction area, prompt, and entrance marker.
- `Interactables/PelicanCoolerPerch` owns the complete Pelican landmark visual.
Pelican selling remains a Cooler action and has no world interaction area.
- `Terrain` owns both the imported visual and generated terrain collision.
- `BelowWorldFailsafe/Coverage` exposes the below-world recovery coverage as a
saved collision shape in `world/test_world.tscn`.
The active `Environment`, `Sun`, and external starter-island grass material
remain directly editable through the Inspector. Their tuning is not recreated
by runtime scripts.
## Reusable world props
Use a single placed `WorldProp` root for trees, rocks, buildings, benches,
signs, vegetation, and similar landmarks:
```text
WorldProp
├── Visual
└── Collision
└── CollisionShape3D
```
Add optional interaction areas, labels, and markers beneath the same root.
Instance imported GLB visuals beneath `Visual`; keep gameplay collision
Godot-owned beneath `Collision`. Place and duplicate the complete prop scene,
not its visual child.
Author reusable assets at scale `(1, 1, 1)`. Uniform root scaling is acceptable
for provisional props when visual and collision scale together. Avoid
non-uniform root scaling because it can deform collision and imported geometry
unpredictably. Make mutable per-instance shapes, meshes, and materials local to
the scene so editing one prop does not change unrelated instances.
## Shoreline tide ribbons
Each map that uses generated tide ribbons owns a `ShorelineRibbonBaker` node and
one `ShorelineRibbonConfig` resource per water body. Each fishable water body
declares the shared `WaterType` value used by fishing, Logbook classification,
and shoreline presentation. A configuration designates
only the static terrain `CollisionShape3D` or `MeshInstance3D` to intersect, the
water height, explicit generation bounds, water-facing reference, optional
smoothing overrides, and the generated `.tres` output path. Props, docks,
players, bobbers, and gameplay areas are not scanned unless a map author
explicitly selects one as the terrain source. The baker reports every configured
body but intentionally generates ribbons only for `SALT_WATER`; freshwater uses
the shared depth-tinted shader without tide marks or an advancing wash.
To rebuild in the editor, select the map's `ShorelineRibbonBaker` node and press
**Rebuild Shoreline Ribbons** in the Inspector. The equivalent validation/CI
command for the starter map is:
```sh
godot --headless --path . --script scripts/bake_shoreline_ribbons.gd
```
Set the baker's `debug_path_stage` to Raw, Simplified, or Smoothed before an
editor rebuild to compare the extraction stages. Keep it Off in the finished
scene; the temporary editor-only line preview is never saved as gameplay data.
Rebuild a saltwater ribbon only when its terrain at the waterline, configured
water height, or generation bounds change. Freshwater terrain changes do not
require a ribbon rebuild because freshwater bodies have no ribbon. Adding or
moving unrelated props, fishing areas, recovery volumes, or other gameplay
nodes does not require a rebuild. Normal gameplay loads the committed generated
saltwater meshes and never runs the extraction step.

View file

@ -32,7 +32,9 @@ var fishing_shop_path: NodePath = (
^"Interactables/FishingShopWorld/InteractionArea" ^"Interactables/FishingShopWorld/InteractionArea"
) )
@export_group("Terrain Collision") @export_group("Terrain Collision")
@export var rebuild_terrain_collision_on_ready: bool = false # The imported GLB is the collision authority. Rebuild once per region load so
# newly authored terrain and props cannot retain a stale saved fallback shape.
@export var rebuild_terrain_collision_on_ready: bool = true
@export_group("Foliage Wind") @export_group("Foliage Wind")
@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.36 @export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.36
@export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9 @export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9