diff --git a/artifacts/deposit_import.log b/artifacts/deposit_import.log new file mode 100644 index 0000000..c342736 --- /dev/null +++ b/artifacts/deposit_import.log @@ -0,0 +1,39 @@ +Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org + +[ 0% ] first_scan_filesystem | Started Project initialization (5 steps) +[ 0% ] first_scan_filesystem | Scanning file structure... +[ 16% ] first_scan_filesystem | Loading global class names... +[ 33% ] first_scan_filesystem | Verifying GDExtensions... +[ 50% ] first_scan_filesystem | Creating autoload scripts... +[ 66% ] first_scan_filesystem | Initializing plugins... +Godot MCP Toolkit v1.0.3 — by NPGameDev · npgamedev.com +[MCPExtensions] Hot-reload watcher active +[MCPServer] port: scanning 6550-6560 (default) +[MCPServer] session token written to user://addons/godot_mcp_toolkit/project_instance_32cc2271ac67/mcp_token +[MCPServer] listening on 127.0.0.1:6551 +[MCPRegistry] registered c:/users/tomek/documents/godot/randoloot on port 6551 +[ 83% ] first_scan_filesystem | Starting file scan... +[ DONE ] first_scan_filesystem + +[ 0% ] reimport | Started (Re)Importing Assets (1 steps) +[ 0% ] reimport | Preparing files to reimport... +[ 0% ] reimport | Executing pre-reimport operations... +[ 0% ] reimport | mineral_deposit.png +[ 50% ] reimport | Finalizing Asset Import... +[ DONE ] reimport + +[ 0% ] reimport | Started (Re)Importing Assets (1 steps) +[ 0% ] reimport | Executing post-reimport operations... +[ DONE ] reimport + +[ 0% ] loading_editor_layout | Started Loading editor (5 steps) +[ 0% ] loading_editor_layout | Loading editor layout... +[ 16% ] loading_editor_layout | Loading docks... +[ 33% ] loading_editor_layout | Reopening scenes... +[ 50% ] loading_editor_layout | Loading central editor layout... +[ 66% ] loading_editor_layout | Loading plugin window layout... +[ 83% ] loading_editor_layout | Editor layout ready. +[ DONE ] loading_editor_layout + +[MCPServer] stopped +[MCPRegistry] deregistered c:/users/tomek/documents/godot/randoloot diff --git a/artifacts/deposit_test.log b/artifacts/deposit_test.log new file mode 100644 index 0000000..4a4b635 --- /dev/null +++ b/artifacts/deposit_test.log @@ -0,0 +1,7 @@ +Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org +OpenGL API 3.3.0 NVIDIA 610.88 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 4070 SUPER + +[MCPRuntimeServer] port: scanning 6570-6585 (default) +[MCPRuntimeServer] listening on 127.0.0.1:6570 +[MCPRegistry] runtime port 6570 registered for c:/users/tomek/documents/godot/randoloot +MINERAL DEPOSIT PASS: six biome renders, real ore instead of icon, mipmaps, alpha/soil blending, no stationary flicker, smooth subpixel panning, identical pixels after camera return; max mean pan delta=0.00815902349665 diff --git a/artifacts/deposit_test_errors.log b/artifacts/deposit_test_errors.log new file mode 100644 index 0000000..e69de29 diff --git a/artifacts/freighter_ambience_demo.wav b/artifacts/freighter_ambience_demo.wav new file mode 100644 index 0000000..d0a2734 Binary files /dev/null and b/artifacts/freighter_ambience_demo.wav differ diff --git a/artifacts/freighter_ambience_demo.wav.import b/artifacts/freighter_ambience_demo.wav.import new file mode 100644 index 0000000..3f61506 --- /dev/null +++ b/artifacts/freighter_ambience_demo.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cv70bcvcdiumo" +path="res://.godot/imported/freighter_ambience_demo.wav-ed73b9e788a5c44d755e11fb8d2b254b.sample" + +[deps] + +source_file="res://artifacts/freighter_ambience_demo.wav" +dest_files=["res://.godot/imported/freighter_ambience_demo.wav-ed73b9e788a5c44d755e11fb8d2b254b.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/artifacts/freighter_interior_built.png b/artifacts/freighter_interior_built.png index 74904a9..88e616c 100644 Binary files a/artifacts/freighter_interior_built.png and b/artifacts/freighter_interior_built.png differ diff --git a/artifacts/freighter_interior_hangar.png b/artifacts/freighter_interior_hangar.png index 5aeb4ce..c17b27a 100644 Binary files a/artifacts/freighter_interior_hangar.png and b/artifacts/freighter_interior_hangar.png differ diff --git a/artifacts/freighter_interior_overview.png b/artifacts/freighter_interior_overview.png index b81c8c3..7ec89d3 100644 Binary files a/artifacts/freighter_interior_overview.png and b/artifacts/freighter_interior_overview.png differ diff --git a/artifacts/freighter_interior_renovation.png b/artifacts/freighter_interior_renovation.png index 51c6e3c..62e89fc 100644 Binary files a/artifacts/freighter_interior_renovation.png and b/artifacts/freighter_interior_renovation.png differ diff --git a/artifacts/freighter_interior_second_ship.png b/artifacts/freighter_interior_second_ship.png index ad95d2d..ccd8949 100644 Binary files a/artifacts/freighter_interior_second_ship.png and b/artifacts/freighter_interior_second_ship.png differ diff --git a/artifacts/freighter_interior_workshop.png b/artifacts/freighter_interior_workshop.png index 5b05c8a..297f343 100644 Binary files a/artifacts/freighter_interior_workshop.png and b/artifacts/freighter_interior_workshop.png differ diff --git a/artifacts/mineral_deposit_biome_0.png b/artifacts/mineral_deposit_biome_0.png new file mode 100644 index 0000000..641a187 Binary files /dev/null and b/artifacts/mineral_deposit_biome_0.png differ diff --git a/artifacts/mineral_deposit_biome_1.png b/artifacts/mineral_deposit_biome_1.png new file mode 100644 index 0000000..38a9c8c Binary files /dev/null and b/artifacts/mineral_deposit_biome_1.png differ diff --git a/artifacts/mineral_deposit_biome_2.png b/artifacts/mineral_deposit_biome_2.png new file mode 100644 index 0000000..5d920b9 Binary files /dev/null and b/artifacts/mineral_deposit_biome_2.png differ diff --git a/artifacts/mineral_deposit_biome_3.png b/artifacts/mineral_deposit_biome_3.png new file mode 100644 index 0000000..67cf603 Binary files /dev/null and b/artifacts/mineral_deposit_biome_3.png differ diff --git a/artifacts/mineral_deposit_biome_4.png b/artifacts/mineral_deposit_biome_4.png new file mode 100644 index 0000000..53583a2 Binary files /dev/null and b/artifacts/mineral_deposit_biome_4.png differ diff --git a/artifacts/mineral_deposit_biome_5.png b/artifacts/mineral_deposit_biome_5.png new file mode 100644 index 0000000..011a527 Binary files /dev/null and b/artifacts/mineral_deposit_biome_5.png differ diff --git a/artifacts/mineral_deposit_biomes.png b/artifacts/mineral_deposit_biomes.png new file mode 100644 index 0000000..aa6efb7 Binary files /dev/null and b/artifacts/mineral_deposit_biomes.png differ diff --git a/assets/planets/mineral_deposit.png b/assets/planets/mineral_deposit.png new file mode 100644 index 0000000..3eadabd Binary files /dev/null and b/assets/planets/mineral_deposit.png differ diff --git a/assets/planets/mineral_deposit.png.import b/assets/planets/mineral_deposit.png.import new file mode 100644 index 0000000..8b64d10 --- /dev/null +++ b/assets/planets/mineral_deposit.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1rng0dghwlej" +path="res://.godot/imported/mineral_deposit.png-a3bfae2fe6de97db0a85f346d521f355.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/planets/mineral_deposit.png" +dest_files=["res://.godot/imported/mineral_deposit.png-a3bfae2fe6de97db0a85f346d521f355.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 diff --git a/assets/planets/mineral_deposit_prompt.json b/assets/planets/mineral_deposit_prompt.json new file mode 100644 index 0000000..5169161 --- /dev/null +++ b/assets/planets/mineral_deposit_prompt.json @@ -0,0 +1,6 @@ +{ + "tool": "image_gen (built-in)", + "mode": "new transparent RGBA game sprite", + "asset": "assets/planets/mineral_deposit.png", + "prompt": "Game sprite asset for a realistic top-down planetary exploration game: a small naturally exposed mineral ore deposit emerging flush from soil, STRICT ORTHOGRAPHIC TOP DOWN directly overhead. An irregular broken vein of muted blue-grey metallic ore with a few warm copper mineral seams visible inside jagged earthy rocks. Several substantial partially buried ore fragments, 6 to 10 clearly readable chunks, flat fractured surfaces and natural subdued mineral sheen, surrounded by tiny scattered chips, gravel and a thin loose-soil transition. Make it look like minerals poking out of actual ground, not a collectible crystal, not an icon, not a pile on a platform. No big upright crystal spires. No neon glow, no glitter particles. High fidelity gritty hand-painted 3D game texture style, soft ambient upper-left daylight, shallow baked contact shading immediately under stones. Single compact irregular horizontal oval cluster centered in square canvas, occupies central 65-70 percent of canvas, generous fully transparent margins. True transparent RGBA background. Loose soil and gravel around the edge must disperse naturally into fully transparent pixels, softly feathered alpha, no opaque disc or square of dirt, no hard cutout contour around soil, no backdrop, no outline, no text, no UI. Major stone facets must remain readable when the sprite is reduced to about 100 screen pixels." +} diff --git a/assets/sounds/freighter/CREDITS.md b/assets/sounds/freighter/CREDITS.md new file mode 100644 index 0000000..1a3c99a --- /dev/null +++ b/assets/sounds/freighter/CREDITS.md @@ -0,0 +1,32 @@ +# Frigate interior audio + +The mechanical ambience and foley are edited real-world recordings. They are +not AI-generated sound effects. All external sources below are released under +CC0 1.0: https://creativecommons.org/publicdomain/zero/1.0/ + +- **LEGIT Audio — The Shop, free OpenGameArt samples** + https://opengameart.org/content/the-shop + Recordings: `TheShopCollection_convenience_store_drinks_fridge_drone.wav` + and `TheShopCollection_convenience_store_drinks_fridge_drone_2.wav`. + Used for reactor and ventilation beds. CC0 applies specifically to the free + files published on OpenGameArt, not to other commercial LEGIT Audio products. +- **bart — 68 Workshop Sounds** + https://opengameart.org/content/68-workshop-sounds + Garage recordings of real tools and metal. Used for motor tools, ratchets, + tapping, clattering, sliding metal and latches. +- **bart — Steam release sounds** + https://opengameart.org/content/steam-release-sounds + Used for pressure equalization, valves and airlock seals. +- **Kenney — Sci-Fi Sounds** + https://opengameart.org/content/sci-fi-sounds + https://kenney.nl/assets/sci-fi-sounds + `computerNoise_000.ogg`, `computerNoise_001.ogg`: quiet console electronics. + +The two-tone service annunciator is original procedural sound design. It is +intentionally electronic, like a real control-panel chime. + +Edits: trimming, DC removal, filtering, mild pitch reduction, layer timing, +short stereo room reflections, loop crossfades and level normalization. +`manifest.json` maps each final asset to its source recordings and processing. +Rebuild using `tools/audio/fetch_freighter_sources.py` followed by +`tools/audio/build_freighter_audio.py` (Python + numpy). diff --git a/assets/sounds/freighter/airlock_close.wav b/assets/sounds/freighter/airlock_close.wav new file mode 100644 index 0000000..ac701e9 Binary files /dev/null and b/assets/sounds/freighter/airlock_close.wav differ diff --git a/assets/sounds/freighter/airlock_close.wav.import b/assets/sounds/freighter/airlock_close.wav.import new file mode 100644 index 0000000..3acf4cb --- /dev/null +++ b/assets/sounds/freighter/airlock_close.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cyfw40asyp3m2" +path="res://.godot/imported/airlock_close.wav-9d7827f22d7e1ff7ceca89394018d8c7.sample" + +[deps] + +source_file="res://assets/sounds/freighter/airlock_close.wav" +dest_files=["res://.godot/imported/airlock_close.wav-9d7827f22d7e1ff7ceca89394018d8c7.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/airlock_open.wav b/assets/sounds/freighter/airlock_open.wav new file mode 100644 index 0000000..86a8afd Binary files /dev/null and b/assets/sounds/freighter/airlock_open.wav differ diff --git a/assets/sounds/freighter/airlock_open.wav.import b/assets/sounds/freighter/airlock_open.wav.import new file mode 100644 index 0000000..56343ac --- /dev/null +++ b/assets/sounds/freighter/airlock_open.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://byimx1n0t66e5" +path="res://.godot/imported/airlock_open.wav-780191e96afd216c23fb84a9b94fabe2.sample" + +[deps] + +source_file="res://assets/sounds/freighter/airlock_open.wav" +dest_files=["res://.godot/imported/airlock_open.wav-780191e96afd216c23fb84a9b94fabe2.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/console_1.ogg b/assets/sounds/freighter/console_1.ogg new file mode 100644 index 0000000..2221e9e Binary files /dev/null and b/assets/sounds/freighter/console_1.ogg differ diff --git a/assets/sounds/freighter/console_1.ogg.import b/assets/sounds/freighter/console_1.ogg.import new file mode 100644 index 0000000..39ac2ed --- /dev/null +++ b/assets/sounds/freighter/console_1.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://u3oifo5jhk23" +path="res://.godot/imported/console_1.ogg-00748b20716b1db4ba0330ad0d96fd4c.oggvorbisstr" + +[deps] + +source_file="res://assets/sounds/freighter/console_1.ogg" +dest_files=["res://.godot/imported/console_1.ogg-00748b20716b1db4ba0330ad0d96fd4c.oggvorbisstr"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sounds/freighter/console_2.ogg b/assets/sounds/freighter/console_2.ogg new file mode 100644 index 0000000..ae1cde0 Binary files /dev/null and b/assets/sounds/freighter/console_2.ogg differ diff --git a/assets/sounds/freighter/console_2.ogg.import b/assets/sounds/freighter/console_2.ogg.import new file mode 100644 index 0000000..cfb0e22 --- /dev/null +++ b/assets/sounds/freighter/console_2.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://cbbslo1pl5tx1" +path="res://.godot/imported/console_2.ogg-95d3ba6ee4c4be080a4d9012fa9f5ab4.oggvorbisstr" + +[deps] + +source_file="res://assets/sounds/freighter/console_2.ogg" +dest_files=["res://.godot/imported/console_2.ogg-95d3ba6ee4c4be080a4d9012fa9f5ab4.oggvorbisstr"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sounds/freighter/docking_clamp.wav b/assets/sounds/freighter/docking_clamp.wav new file mode 100644 index 0000000..35f1333 Binary files /dev/null and b/assets/sounds/freighter/docking_clamp.wav differ diff --git a/assets/sounds/freighter/docking_clamp.wav.import b/assets/sounds/freighter/docking_clamp.wav.import new file mode 100644 index 0000000..76a2e4c --- /dev/null +++ b/assets/sounds/freighter/docking_clamp.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://gxwhgoj8l167" +path="res://.godot/imported/docking_clamp.wav-36a697b9f91ddbc7d8667d7653a2a56a.sample" + +[deps] + +source_file="res://assets/sounds/freighter/docking_clamp.wav" +dest_files=["res://.godot/imported/docking_clamp.wav-36a697b9f91ddbc7d8667d7653a2a56a.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/maintenance_alarm.wav b/assets/sounds/freighter/maintenance_alarm.wav new file mode 100644 index 0000000..c3535d2 Binary files /dev/null and b/assets/sounds/freighter/maintenance_alarm.wav differ diff --git a/assets/sounds/freighter/maintenance_alarm.wav.import b/assets/sounds/freighter/maintenance_alarm.wav.import new file mode 100644 index 0000000..d757fd0 --- /dev/null +++ b/assets/sounds/freighter/maintenance_alarm.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://76r3bo438dqt" +path="res://.godot/imported/maintenance_alarm.wav-cd9e4e3bc6bef5557ea2d0a2c6daa2fa.sample" + +[deps] + +source_file="res://assets/sounds/freighter/maintenance_alarm.wav" +dest_files=["res://.godot/imported/maintenance_alarm.wav-cd9e4e3bc6bef5557ea2d0a2c6daa2fa.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/manifest.json b/assets/sounds/freighter/manifest.json new file mode 100644 index 0000000..2a38974 --- /dev/null +++ b/assets/sounds/freighter/manifest.json @@ -0,0 +1,145 @@ +{ + "reactor_loop": { + "sources": [ + "shop/TheShopCollection_convenience_store_drinks_fridge_drone.wav" + ], + "processing": "Slowed 0.64x; DC removal; band limiting 28–1150 Hz; 1.2 s loop crossfade", + "loop": true, + "seconds": 16.671, + "peak_dbfs": -3.5, + "rms_dbfs": -16.46 + }, + "ventilation_loop": { + "sources": [ + "shop/TheShopCollection_convenience_store_drinks_fridge_drone_2.wav" + ], + "processing": "DC removal; band limiting 110–2600 Hz; 1.2 s loop crossfade", + "loop": true, + "seconds": 12.342, + "peak_dbfs": -3.5, + "rms_dbfs": -17.53 + }, + "tool_1": { + "sources": [ + "workshop/workshop/workshop - drill short 1.wav" + ], + "processing": "Trim, reduced motor speed, softened highs, short industrial room reflections", + "loop": false, + "seconds": 2.782, + "peak_dbfs": -2.0, + "rms_dbfs": -25.83 + }, + "tool_2": { + "sources": [ + "workshop/workshop/workshop - drill short 2.wav" + ], + "processing": "Trim, reduced motor speed, softened highs, short industrial room reflections", + "loop": false, + "seconds": 1.964, + "peak_dbfs": -2.0, + "rms_dbfs": -28.28 + }, + "tool_3": { + "sources": [ + "workshop/workshop/workshop - ratchet1.wav" + ], + "processing": "Trim, reduced motor speed, softened highs, short industrial room reflections", + "loop": false, + "seconds": 6.022, + "peak_dbfs": -2.0, + "rms_dbfs": -24.45 + }, + "metal_1": { + "sources": [ + "workshop/workshop/workshop - tool clatter.wav" + ], + "processing": "Trim, mild pitch lowering, low-pass, short stereo reflections", + "loop": false, + "seconds": 1.55, + "peak_dbfs": -2.0, + "rms_dbfs": -24.84 + }, + "metal_2": { + "sources": [ + "workshop/workshop/workshop - metal tapping.wav" + ], + "processing": "Trim, mild pitch lowering, low-pass, short stereo reflections", + "loop": false, + "seconds": 5.961, + "peak_dbfs": -2.0, + "rms_dbfs": -33.68 + }, + "airlock_open": { + "sources": [ + "workshop/workshop/workshop - clink thud.wav", + "steam/steam hisses - Marker #1.wav", + "workshop/workshop/workshop - dragging metal.wav" + ], + "processing": "Layered latch, pressure equalization, slow metal slide; EQ and shared room reflections", + "loop": false, + "seconds": 2.53, + "peak_dbfs": -2.0, + "rms_dbfs": -22.08 + }, + "airlock_close": { + "sources": [ + "workshop/workshop/workshop - dragging metal.wav", + "steam/steam hisses - Marker #1.wav", + "workshop/workshop/workshop - clink thud.wav" + ], + "processing": "Metal slide, pressure seal and locking impact; shared short reflections", + "loop": false, + "seconds": 2.368, + "peak_dbfs": -2.0, + "rms_dbfs": -24.96 + }, + "pressure_release": { + "sources": [ + "steam/steam hisses - Marker #1.wav" + ], + "processing": "Softened steam recording, pressure-release envelope, short reflections", + "loop": false, + "seconds": 1.51, + "peak_dbfs": -2.0, + "rms_dbfs": -23.32 + }, + "docking_clamp": { + "sources": [ + "workshop/workshop/workshop - low hammering.wav", + "workshop/workshop/workshop - clink thud.wav" + ], + "processing": "Layered low mechanical impact and latch, softened transient, stereo reflections", + "loop": false, + "seconds": 1.518, + "peak_dbfs": -2.0, + "rms_dbfs": -23.29 + }, + "service_chime": { + "sources": [], + "processing": "Original two-tone 540/680 Hz service annunciator, smooth envelope, short reflections", + "loop": false, + "seconds": 2.15, + "peak_dbfs": -2.0, + "rms_dbfs": -16.47 + }, + "maintenance_alarm": { + "sources": [], + "processing": "Original restrained maintenance warning, two paired tones with room reflections", + "loop": false, + "seconds": 3.3, + "peak_dbfs": -2.0, + "rms_dbfs": -16.03 + }, + "console_1": { + "sources": [ + "scifi/Audio/computerNoise_000.ogg" + ], + "processing": "Unmodified CC0 Kenney electronic effect, mixed quietly in-engine" + }, + "console_2": { + "sources": [ + "scifi/Audio/computerNoise_001.ogg" + ], + "processing": "Unmodified CC0 Kenney electronic effect, mixed quietly in-engine" + } +} \ No newline at end of file diff --git a/assets/sounds/freighter/metal_1.wav b/assets/sounds/freighter/metal_1.wav new file mode 100644 index 0000000..c96d78f Binary files /dev/null and b/assets/sounds/freighter/metal_1.wav differ diff --git a/assets/sounds/freighter/metal_1.wav.import b/assets/sounds/freighter/metal_1.wav.import new file mode 100644 index 0000000..d2bd1a0 --- /dev/null +++ b/assets/sounds/freighter/metal_1.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cfygep3j0a0yb" +path="res://.godot/imported/metal_1.wav-fef3e8abe502627a1a5d423c54ce7664.sample" + +[deps] + +source_file="res://assets/sounds/freighter/metal_1.wav" +dest_files=["res://.godot/imported/metal_1.wav-fef3e8abe502627a1a5d423c54ce7664.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/metal_2.wav b/assets/sounds/freighter/metal_2.wav new file mode 100644 index 0000000..4c6de0a Binary files /dev/null and b/assets/sounds/freighter/metal_2.wav differ diff --git a/assets/sounds/freighter/metal_2.wav.import b/assets/sounds/freighter/metal_2.wav.import new file mode 100644 index 0000000..f9ae69f --- /dev/null +++ b/assets/sounds/freighter/metal_2.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://dojhloap2i21u" +path="res://.godot/imported/metal_2.wav-76016f4aa9d8bb64300640867d45119f.sample" + +[deps] + +source_file="res://assets/sounds/freighter/metal_2.wav" +dest_files=["res://.godot/imported/metal_2.wav-76016f4aa9d8bb64300640867d45119f.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/pressure_release.wav b/assets/sounds/freighter/pressure_release.wav new file mode 100644 index 0000000..a210b42 Binary files /dev/null and b/assets/sounds/freighter/pressure_release.wav differ diff --git a/assets/sounds/freighter/pressure_release.wav.import b/assets/sounds/freighter/pressure_release.wav.import new file mode 100644 index 0000000..4232e48 --- /dev/null +++ b/assets/sounds/freighter/pressure_release.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://bojna6ipk5lvm" +path="res://.godot/imported/pressure_release.wav-821b8ab32f6a5ab01fd649879bfa8d17.sample" + +[deps] + +source_file="res://assets/sounds/freighter/pressure_release.wav" +dest_files=["res://.godot/imported/pressure_release.wav-821b8ab32f6a5ab01fd649879bfa8d17.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/reactor_loop.wav b/assets/sounds/freighter/reactor_loop.wav new file mode 100644 index 0000000..162b797 Binary files /dev/null and b/assets/sounds/freighter/reactor_loop.wav differ diff --git a/assets/sounds/freighter/reactor_loop.wav.import b/assets/sounds/freighter/reactor_loop.wav.import new file mode 100644 index 0000000..8409f38 --- /dev/null +++ b/assets/sounds/freighter/reactor_loop.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://ci6x6304pmqg8" +path="res://.godot/imported/reactor_loop.wav-d882cf9a2dfd0bb084898cf1ecf4f096.sample" + +[deps] + +source_file="res://assets/sounds/freighter/reactor_loop.wav" +dest_files=["res://.godot/imported/reactor_loop.wav-d882cf9a2dfd0bb084898cf1ecf4f096.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/service_chime.wav b/assets/sounds/freighter/service_chime.wav new file mode 100644 index 0000000..c78b9f4 Binary files /dev/null and b/assets/sounds/freighter/service_chime.wav differ diff --git a/assets/sounds/freighter/service_chime.wav.import b/assets/sounds/freighter/service_chime.wav.import new file mode 100644 index 0000000..8105661 --- /dev/null +++ b/assets/sounds/freighter/service_chime.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://cfvw26xmd54f7" +path="res://.godot/imported/service_chime.wav-debd9086148ec31fa72bff6ce8cc346d.sample" + +[deps] + +source_file="res://assets/sounds/freighter/service_chime.wav" +dest_files=["res://.godot/imported/service_chime.wav-debd9086148ec31fa72bff6ce8cc346d.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/tool_1.wav b/assets/sounds/freighter/tool_1.wav new file mode 100644 index 0000000..94889a5 Binary files /dev/null and b/assets/sounds/freighter/tool_1.wav differ diff --git a/assets/sounds/freighter/tool_1.wav.import b/assets/sounds/freighter/tool_1.wav.import new file mode 100644 index 0000000..6b6dd26 --- /dev/null +++ b/assets/sounds/freighter/tool_1.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://bkbfe36sp1gvo" +path="res://.godot/imported/tool_1.wav-95f63081100f01797467f03e9e737afd.sample" + +[deps] + +source_file="res://assets/sounds/freighter/tool_1.wav" +dest_files=["res://.godot/imported/tool_1.wav-95f63081100f01797467f03e9e737afd.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/tool_2.wav b/assets/sounds/freighter/tool_2.wav new file mode 100644 index 0000000..ea315b0 Binary files /dev/null and b/assets/sounds/freighter/tool_2.wav differ diff --git a/assets/sounds/freighter/tool_2.wav.import b/assets/sounds/freighter/tool_2.wav.import new file mode 100644 index 0000000..a989bb3 --- /dev/null +++ b/assets/sounds/freighter/tool_2.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://wgkgk17ypntp" +path="res://.godot/imported/tool_2.wav-89973aa6096ce92b6dd98a99035476ee.sample" + +[deps] + +source_file="res://assets/sounds/freighter/tool_2.wav" +dest_files=["res://.godot/imported/tool_2.wav-89973aa6096ce92b6dd98a99035476ee.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/tool_3.wav b/assets/sounds/freighter/tool_3.wav new file mode 100644 index 0000000..cc90e06 Binary files /dev/null and b/assets/sounds/freighter/tool_3.wav differ diff --git a/assets/sounds/freighter/tool_3.wav.import b/assets/sounds/freighter/tool_3.wav.import new file mode 100644 index 0000000..a3fb394 --- /dev/null +++ b/assets/sounds/freighter/tool_3.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://d4ueit46182u" +path="res://.godot/imported/tool_3.wav-9bf32979fcf03e567d9386f3bec8fb18.sample" + +[deps] + +source_file="res://assets/sounds/freighter/tool_3.wav" +dest_files=["res://.godot/imported/tool_3.wav-9bf32979fcf03e567d9386f3bec8fb18.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/assets/sounds/freighter/ventilation_loop.wav b/assets/sounds/freighter/ventilation_loop.wav new file mode 100644 index 0000000..0c0f3d0 Binary files /dev/null and b/assets/sounds/freighter/ventilation_loop.wav differ diff --git a/assets/sounds/freighter/ventilation_loop.wav.import b/assets/sounds/freighter/ventilation_loop.wav.import new file mode 100644 index 0000000..ff43a09 --- /dev/null +++ b/assets/sounds/freighter/ventilation_loop.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://dlik8ewbunh3h" +path="res://.godot/imported/ventilation_loop.wav-806b0880cd20957493e88125437cc747.sample" + +[deps] + +source_file="res://assets/sounds/freighter/ventilation_loop.wav" +dest_files=["res://.godot/imported/ventilation_loop.wav-806b0880cd20957493e88125437cc747.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/planet_mechanic.txt b/planet_mechanic.txt index 9bc3c5e..fd2dcfa 100644 --- a/planet_mechanic.txt +++ b/planet_mechanic.txt @@ -896,3 +896,35 @@ Warstwy przy fregacie i muzyka dokowania (18.10.2026): podczas pauzy dokowania oraz pobytu na powierzchni, bez restartu. - Rozszerzony test freighter_interior_preview przeszedł: kolejność warstw i rosnąca pozycja odtwarzania muzyki podczas zadokowania. + +Realistyczne audio wnętrza fregaty (18.10.2026): +- Nowe audio jest montażem nagrań realnych urządzeń, narzędzi i pary + na licencji CC0: LEGIT Audio (The Shop), bart (68 Workshop Sounds, + Steam release sounds). Kenney dostarcza dwa ciche efekty elektroniki. + Sygnał serwisowy i alarm techniczny są oryginalną syntezą dwóch tonów. +- Zestaw: 2 pętle instalacji/wentylacji, 3 narzędzia, 2 metaliczne odgłosy, + otwarcie/zamknięcie śluzy, wyrównanie ciśnienia, zaciski dokujące, + sygnał serwisowy, alarm techniczny i 2 efekty konsol (15 plików). +- Edycja obejmuje usunięcie DC, filtrowanie, delikatne obniżenie tonu, + warstwy mechaniczne, krótkie odbicia stereo, normalizację i płynne pętle. + Pliki WAV: 48 kHz, stereo, PCM16, bez przesterowania. +- FreighterAudio działa wyłącznie w scenie wnętrza. Dwie pętle narastają + łagodnie i grają pod nieprzerwaną muzyką. Narzędzia odzywają się + nieregularnie co 10..23 s, konsole co 24..48 s, zawory co 38..75 s. +- Śluzy reagują na wybór i zamknięcie pomieszczenia. Panorama stereo + odpowiada jego położeniu na pokładzie. Hover nie uruchamia dźwięku; + powtarzane kliknięcia są ograniczone, polifonia efektów wynosi 4. +- Alarm serwisowy występuje raz po wejściu z lądownikiem poniżej 30 HP. + Naprawa anuluje oczekujący alarm. Nie ma losowych alarmów awaryjnych. +- Po odlocie odtwarzacze i tymczasowe szyny panoramy są zwalniane. + Muzyka nie jest zatrzymywana ani restartowana. +- Źródła i licencje: assets/sounds/freighter/CREDITS.md; dokładny montaż + i mapowanie próbek: manifest.json w tym samym katalogu. Narzędzia + odtworzenia zestawu: tools/audio/fetch_freighter_sources.py oraz + tools/audio/build_freighter_audio.py (Python + numpy). +- Testy przeszły: freighter_assets_test.py (13 WAV, format, brak DC, + brak clippingu i ciągłość pętli), freighter_audio_test.tscn (15 strumieni, + granie podczas pauzy razem z muzyką, debounce, polifonia, alarm, + sprzątanie po trzech dokowaniach), freighter_interior_preview.tscn. +- Odsłuch demonstracyjny: artifacts/freighter_ambience_demo.wav (34 s, + sam ambient i efekty, bez muzyki; poziom odsłuchu wyrównany osobno). diff --git a/resources/effects/mineral_deposit.gdshader b/resources/effects/mineral_deposit.gdshader new file mode 100644 index 0000000..8189ff8 --- /dev/null +++ b/resources/effects/mineral_deposit.gdshader @@ -0,0 +1,15 @@ +shader_type canvas_item; +uniform vec4 ground_tint : source_color = vec4(0.3,0.32,0.22,1.0); +varying vec4 vertex_tint; +void vertex() { vertex_tint=COLOR; } +void fragment() { + vec4 ore=texture(TEXTURE,UV); + float radius=length((UV-vec2(0.5))*vec2(2.0,2.25)); + float fringe=smoothstep(0.40,0.82,radius); + float feather=1.0-smoothstep(0.65,0.96,radius); + float luminosity=dot(ore.rgb,vec3(0.299,0.587,0.114)); + vec3 soil=ground_tint.rgb*(0.60+luminosity*0.85); + // Only the dispersed soil rim changes hue; the central ore stays legible. + vec3 color=mix(ore.rgb,soil,fringe*0.66); + COLOR=vec4(color*vertex_tint.rgb,ore.a*vertex_tint.a*feather); +} diff --git a/resources/effects/mineral_deposit.gdshader.uid b/resources/effects/mineral_deposit.gdshader.uid new file mode 100644 index 0000000..e3bc364 --- /dev/null +++ b/resources/effects/mineral_deposit.gdshader.uid @@ -0,0 +1 @@ +uid://bqamcqle8mpev diff --git a/scenes/planets/freighter_interior.tscn b/scenes/planets/freighter_interior.tscn index b87472c..76695a6 100644 --- a/scenes/planets/freighter_interior.tscn +++ b/scenes/planets/freighter_interior.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=4 format=3] +[gd_scene load_steps=5 format=3] [ext_resource type="Script" path="res://scripts/planets/freighter_screen.gd" id="1"] [ext_resource type="Script" path="res://scripts/planets/freighter_deck.gd" id="2"] [ext_resource type="Texture2D" path="res://assets/freighter/interior_deck.png" id="3"] +[ext_resource type="Script" path="res://scripts/planets/freighter_audio.gd" id="4"] [node name="FreighterInterior" type="Control"] process_mode = 3 layout_mode = 3 @@ -11,6 +12,9 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") +[node name="FreighterAudio" type="Node" parent="."] +process_mode = 3 +script = ExtResource("4") [node name="Background" type="ColorRect" parent="."] layout_mode = 1 anchors_preset = 15 diff --git a/scripts/planets/freighter_audio.gd b/scripts/planets/freighter_audio.gd new file mode 100644 index 0000000..ebe499e --- /dev/null +++ b/scripts/planets/freighter_audio.gd @@ -0,0 +1,134 @@ +extends Node +## Quiet industrial foley built from CC0 recordings; lives only inside the deck. +const ROOT := "res://assets/sounds/freighter/" +const VOICE_LIMIT := 4 +var _streams: Dictionary = {} +var _beds: Array[AudioStreamPlayer] = [] +var _voices: Array[AudioStreamPlayer] = [] +var _panners: Array[AudioEffectPanner] = [] +var _buses: Array[StringName] = [] +var _rng := RandomNumberGenerator.new() +var _elapsed := 0.0 +var _last_action := -10.0 +var _last_service := -10.0 +var _last_section := "" +var _next_tool := 9.0 +var _next_console := 18.0 +var _next_pressure := 32.0 +var _last_tool := "" +var played_count := 0 +var _maintenance_needed := false +var _alarm_due := INF + +func _ready() -> void: + process_mode = Node.PROCESS_MODE_ALWAYS + _rng.randomize() + for id in ["reactor_loop","ventilation_loop","tool_1","tool_2","tool_3","metal_1","metal_2","airlock_open","airlock_close","pressure_release","docking_clamp","service_chime","maintenance_alarm"]: + _streams[id] = load(ROOT+id+".wav") + for id in ["console_1","console_2"]: _streams[id] = load(ROOT+id+".ogg") + _start_bed("reactor_loop",-14.0) + _start_bed("ventilation_loop",-21.0) + for index in VOICE_LIMIT: + var bus := StringName("Frigate_%d_%d" % [get_instance_id(),index]) + AudioServer.add_bus() + var bus_index := AudioServer.bus_count-1 + AudioServer.set_bus_name(bus_index,bus) + AudioServer.set_bus_send(bus_index,&"Master") + var panner := AudioEffectPanner.new() + AudioServer.add_bus_effect(bus_index,panner) + _buses.append(bus) + _panners.append(panner) + var voice := AudioStreamPlayer.new() + voice.name = "Foley%d" % index + voice.bus = bus + add_child(voice) + _voices.append(voice) + play_effect("docking_clamp",0.65,-13.0,true) + _next_tool = _rng.randf_range(8,15) + _next_console = _rng.randf_range(18,30) + _next_pressure = _rng.randf_range(32,55) + +func _start_bed(id: String, level: float) -> void: + var player := AudioStreamPlayer.new() + player.name = id.to_pascal_case() + var stream := (_streams[id] as AudioStreamWAV).duplicate() as AudioStreamWAV + stream.loop_mode = AudioStreamWAV.LOOP_FORWARD + stream.loop_begin = 0 + stream.loop_end = roundi(stream.get_length()*stream.mix_rate) + player.stream = stream + player.volume_db = -60 + add_child(player) + player.play() + _beds.append(player) + create_tween().tween_property(player,"volume_db",level,1.2) + +func play_effect(id: String, pan: float, level := -15.0, important := false) -> bool: + if not _streams.has(id): return false + var available := -1 + for index in _voices.size(): + if not _voices[index].playing: + available = index + break + if available < 0: + if not important: return false + available = 0 + for index in _voices.size(): + if float(_voices[index].get_meta("started",0.0)) < float(_voices[available].get_meta("started",0.0)): available = index + var voice := _voices[available] + voice.stop() + voice.stream = _streams[id] + voice.volume_db = level + voice.pitch_scale = 1.0 if id == "service_chime" else _rng.randf_range(0.97,1.03) + _panners[available].pan = clampf(pan,-0.75,0.75) + voice.set_meta("started",_elapsed) + voice.play() + played_count += 1 + return true + +func enter_section(id: String, pan: float) -> void: + if id == _last_section or _elapsed-_last_action < 0.7: return + _last_action = _elapsed + _last_section = id + play_effect("airlock_open",pan,-13.5,true) + +func close_section() -> void: + if _last_section.is_empty(): return + _last_section = "" + if _elapsed-_last_action < 0.7: return + _last_action = _elapsed + play_effect("airlock_close",0.0,-17.0) + +func service_completed(pan := 0.0) -> void: + if _elapsed-_last_service < 0.7: return + _last_service = _elapsed + play_effect("service_chime",pan,-20.0) + +func set_maintenance_needed(value: bool) -> void: + if value and not _maintenance_needed: _alarm_due = _elapsed+4.0 + if not value: _alarm_due = INF + _maintenance_needed = value + +func _process(delta: float) -> void: + _elapsed += delta + if _elapsed >= _alarm_due: + play_effect("maintenance_alarm",0.6,-22.0) + _alarm_due = INF # One advisory per damaged-lander visit, no false emergency loop. + if _elapsed >= _next_tool: + var choices := ["tool_1","tool_2","tool_3","metal_1","metal_2"] + choices.erase(_last_tool) + _last_tool = choices[_rng.randi_range(0,choices.size()-1)] + play_effect(_last_tool,-0.48,_rng.randf_range(-17,-13)) + _next_tool = _elapsed+_rng.randf_range(10,23) + if _elapsed >= _next_console: + play_effect("console_%d" % _rng.randi_range(1,2),-0.7,-25.0) + _next_console = _elapsed+_rng.randf_range(24,48) + if _elapsed >= _next_pressure: + play_effect("pressure_release",0.6,-23.0) + _next_pressure = _elapsed+_rng.randf_range(38,75) + +func _exit_tree() -> void: + for voice in _voices: voice.stop() + for bed in _beds: bed.stop() + for bus in _buses: + var index := AudioServer.get_bus_index(bus) + if index >= 0: AudioServer.remove_bus(index) diff --git a/scripts/planets/freighter_audio.gd.uid b/scripts/planets/freighter_audio.gd.uid new file mode 100644 index 0000000..830d7c8 --- /dev/null +++ b/scripts/planets/freighter_audio.gd.uid @@ -0,0 +1 @@ +uid://c667epjjr5bdj diff --git a/scripts/planets/freighter_screen.gd b/scripts/planets/freighter_screen.gd index 908b350..6016efa 100644 --- a/scripts/planets/freighter_screen.gd +++ b/scripts/planets/freighter_screen.gd @@ -13,6 +13,7 @@ var _wallet: Label var _hint: Label var _owned_open := false @onready var deck: Control = $Deck +@onready var _audio: Node = $FreighterAudio func _ready() -> void: if captain == null: @@ -109,11 +110,15 @@ func _hover_section(section: String) -> void: _hint.text = "%s / %s" % [data.title,data.status] if not data.is_empty() else "Wybierz pomieszczenie na pokładzie. · Warsztat i hangar są dostępne." func _select_section(section: String) -> void: + var room: Dictionary = deck.room("expansion_3" if section == "storage" else section) + var pan: float = 0.0 if room.is_empty() else ((room.rect as Rect2).get_center().x / deck.DESIGN_SIZE.x - 0.5)*1.4 + _audio.enter_section(section,pan) _tab = section deck.selected = section _refresh() func _close_section() -> void: + _audio.close_section() _tab = "overview" deck.selected = "" _drawer.hide() @@ -139,6 +144,7 @@ func _label(text: String, size_value := 14, color := Color("c5d3d7")) -> Label: return node func _refresh() -> void: + _audio.set_maintenance_needed(captain.expedition.lander_hp < 30) for child in _content.get_children(): _content.remove_child(child) child.queue_free() @@ -185,6 +191,7 @@ func _construct_room(id: String) -> void: captain.expedition.freighter_rooms[id] = str(deck.plan(id).kind) captain.expedition.emit_changed() deck.sync_rooms(captain.expedition.freighter_rooms) + _audio.service_completed() _hover_section(id) _refresh() @@ -201,7 +208,7 @@ func _build_hangar() -> void: var cost: int = progress.repair_cost() _label("Części: %d · potrzebne do naprawy: %d" % [captain.parts,cost],13) var repair := UI.button(_content,"NAPRAW DO 100%% · %d części" % cost,func(): - progress.repair(captain) + if progress.repair(captain): _audio.service_completed(0.65) _refresh()) repair.disabled = cost == 0 or captain.parts < cost _label("1 część przywraca 1 punkt procentowy HP lądownika. Do następnego lądowania wymagany jest pełny kadłub.",13,Color("819ca5")) @@ -356,6 +363,7 @@ func _craft(kind: String) -> void: var gear: Dictionary = GEAR.craft(captain,kind) if not gear.is_empty(): GEAR.equip(captain,kind,int(gear.uid)) + _audio.service_completed(-0.48) _refresh() func _equip(kind: String, uid: int) -> void: diff --git a/scripts/planets/surface_deposit.gd b/scripts/planets/surface_deposit.gd new file mode 100644 index 0000000..a5ba31f --- /dev/null +++ b/scripts/planets/surface_deposit.gd @@ -0,0 +1,21 @@ +extends Sprite2D +## Static world-space ore: no animated shimmer, screen-space noise or UV snapping. +const ORE := preload("res://assets/planets/mineral_deposit.png") +const WIDTH := 128.0 + +func configure(map: Dictionary, poi: Dictionary) -> void: + name = "MineralDeposit%d" % int(poi.id) + texture = ORE + texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS + position = poi.position + z_index = 10 + var rng := RandomNumberGenerator.new() + rng.seed = int(map.seed) ^ (int(poi.id)*7919) ^ 0x4f524553 + rotation = rng.randf_range(-0.35,0.35) + scale = Vector2.ONE*WIDTH*rng.randf_range(0.95,1.08)/maxf(ORE.get_width(),ORE.get_height()) + var blend := ShaderMaterial.new() + blend.shader = preload("res://resources/effects/mineral_deposit.gdshader") + var tint: Color = (map.landscape.low as Color).lerp(map.landscape.high,0.4)*0.8 + if int(map.profile.temperature) < -10: tint = tint.lerp(Color("b0bbb4"),0.55) + blend.set_shader_parameter("ground_tint",tint) + material = blend diff --git a/scripts/planets/surface_deposit.gd.uid b/scripts/planets/surface_deposit.gd.uid new file mode 100644 index 0000000..2dd88d1 --- /dev/null +++ b/scripts/planets/surface_deposit.gd.uid @@ -0,0 +1 @@ +uid://df4vv3pusii74 diff --git a/scripts/planets/surface_world.gd b/scripts/planets/surface_world.gd index 5556b6f..4308182 100644 --- a/scripts/planets/surface_world.gd +++ b/scripts/planets/surface_world.gd @@ -56,10 +56,14 @@ func _ready() -> void: add_child(mountains) for poi in data.pois: var sprite := Sprite2D.new() - sprite.texture = load("res://assets/planets/%s.png" % ("ruins" if poi.kind == "ruins" else "mine")) if poi.kind != "deposit" else load("res://assets/icons/resources/titanium.png") - sprite.position = poi.position - sprite.scale = Vector2.ONE * (125.0*BUILDING_SCALE if poi.kind != "deposit" else 60.0) / maxf(sprite.texture.get_width(), sprite.texture.get_height()) - sprite.z_index = 10 + if poi.kind == "deposit": + sprite.set_script(preload("res://scripts/planets/surface_deposit.gd")) + sprite.configure(data,poi) + else: + sprite.texture = load("res://assets/planets/%s.png" % ("ruins" if poi.kind == "ruins" else "mine")) + sprite.position = poi.position + sprite.scale = Vector2.ONE * 125.0*BUILDING_SCALE / maxf(sprite.texture.get_width(), sprite.texture.get_height()) + sprite.z_index = 10 add_child(sprite) _poi_sprites[poi.id] = sprite var label := Label.new() diff --git a/tests/audio/freighter_assets_test.py b/tests/audio/freighter_assets_test.py new file mode 100644 index 0000000..d15f390 --- /dev/null +++ b/tests/audio/freighter_assets_test.py @@ -0,0 +1,26 @@ +"""Technical QA for authored frigate audio (Python + numpy).""" +from pathlib import Path +import wave +import numpy as np + +ROOT = Path(__file__).resolve().parents[2] +files = list((ROOT / "assets/sounds/freighter").glob("*.wav")) +assert len(files) == 13 +seams = [] +for path in files: + with wave.open(str(path)) as stream: + assert stream.getnchannels() == 2 + assert stream.getsampwidth() == 2 + assert stream.getframerate() == 48000 + signal = np.frombuffer(stream.readframes(stream.getnframes()),dtype=" void: + GameState.start_new_run() + var game := preload("res://scenes/game/game.tscn").instantiate() + add_child(game) + await get_tree().process_frame + var manager := game.get_node("PlanetExpeditions") + var music: AudioStreamPlayer = game.get_node("GameMusic") + var original_buses := AudioServer.bus_count + for visit in 3: + manager.open_freighter() + var screen: Control = manager.screen + var audio: Node = screen.get_node("FreighterAudio") + assert(get_tree().paused and audio.can_process()) + assert(audio._streams.size() == 15) + assert(audio._beds.size() == 2 and audio._voices.size() == 4) + assert(AudioServer.bus_count == original_buses+4) + for id in audio._streams: + var stream: AudioStream = audio._streams[id] + assert(stream != null and stream.get_length() > 0.1) + for bed in audio._beds: + assert(bed.stream.loop_mode == AudioStreamWAV.LOOP_FORWARD) + assert(bed.stream.loop_end > bed.stream.loop_begin) + assert(bed.playing and not bed.stream_paused) + var music_before := music.get_playback_position() + var bed_before: float = audio._beds[0].get_playback_position() + await get_tree().create_timer(0.25,true).timeout + assert(music.get_playback_position() > music_before) + assert(audio._beds[0].get_playback_position() > bed_before) + screen._select_section("workshop") + var actions: int = audio.played_count + screen._select_section("workshop") + screen._hover_section("hangar") + assert(audio.played_count == actions,"Repeated clicks and hover must not spam doors") + audio._next_tool = audio._elapsed + audio._process(0.1) + assert(audio._last_tool != "") + for index in 20: audio.play_effect("metal_1",0,-20,true) + assert(audio._voices.size() == 4,"Rapid actions must keep bounded polyphony") + for voice in audio._voices: voice.stop() + audio.set_maintenance_needed(true) + audio._next_tool = INF + audio._next_console = INF + audio._next_pressure = INF + audio._process(4.1) + assert(is_inf(audio._alarm_due)) + var warnings: int = audio.played_count + audio._process(1.0) + assert(audio.played_count == warnings,"No repeated false emergency loop") + audio.set_maintenance_needed(false) + assert(is_inf(audio._alarm_due)) + screen.closed.emit() + assert(not get_tree().paused) + assert(AudioServer.bus_count == original_buses,"All temporary panning buses must be removed on departure") + assert(music.playing and not music.stream_paused) + await get_tree().process_frame + game.queue_free() + await get_tree().process_frame + print("FREIGHTER AUDIO PASS: 15 streams, seamless loop configuration, paused playback with uninterrupted music, room foley, hover/click debounce, bounded voices, one maintenance warning, cleanup across three docking visits") + get_tree().quit() diff --git a/tests/levels/freighter_audio_test.gd.uid b/tests/levels/freighter_audio_test.gd.uid new file mode 100644 index 0000000..d89b9d5 --- /dev/null +++ b/tests/levels/freighter_audio_test.gd.uid @@ -0,0 +1 @@ +uid://b7u8secvdwg08 diff --git a/tests/levels/freighter_audio_test.tscn b/tests/levels/freighter_audio_test.tscn new file mode 100644 index 0000000..5566aec --- /dev/null +++ b/tests/levels/freighter_audio_test.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/levels/freighter_audio_test.gd" id="1"] +[node name="FreighterAudioTest" type="Node"] +script = ExtResource("1") diff --git a/tests/previews/surface_deposit_test.gd b/tests/previews/surface_deposit_test.gd new file mode 100644 index 0000000..f0f5dc0 --- /dev/null +++ b/tests/previews/surface_deposit_test.gd @@ -0,0 +1,86 @@ +extends Node + +func settle() -> void: + for frame in 4: await get_tree().process_frame + await RenderingServer.frame_post_draw + +func difference(a: Image, b: Image, center: Vector2) -> float: + var total := 0.0 + var samples := 0 + for y in range(int(center.y)-55,int(center.y)+55,2): + for x in range(int(center.x)-55,int(center.x)+55,2): + if x < 0 or y < 0 or x >= a.get_width() or y >= a.get_height(): continue + var first := a.get_pixel(x,y) + var next := b.get_pixel(x,y) + total += absf(first.r-next.r)+absf(first.g-next.g)+absf(first.b-next.b) + samples += 3 + return total/maxi(1,samples) + +func _ready() -> void: + var sheet := Image.create(1280,1080,false,Image.FORMAT_RGBA8) + var max_pan_change := 0.0 + var progress: Resource = GameState.captain.expedition + for biome in 6: + var profile: Dictionary = preload("res://scripts/planets/planet_profiles.gd").generate(99,biome,"Złoże / biom %d" % (biome+1)) + profile.temperature = 75 if biome == 4 else (-35 if biome == 5 else 20) + progress.lander_hp = 100 + assert(progress.begin_visit(profile)) + progress.visit_seed = 200+biome + var surface := Control.new() + surface.set_script(preload("res://scripts/planets/surface_screen.gd")) + surface.captain = GameState.captain + surface.progress = progress + add_child(surface) + surface.set_process(false) + surface.set_physics_process(false) + surface.lander.hide() + surface._surface_viewport.get_node("SurfaceAtmosphere").hide() + var target := Vector2.INF + var chosen: Sprite2D + for poi in surface.world.data.pois: + if poi.kind != "deposit": continue + var sprite: Sprite2D = surface.world._poi_sprites[poi.id] + assert(sprite.texture.resource_path == "res://assets/planets/mineral_deposit.png") + assert(sprite.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS) + assert(sprite.texture.get_image().has_mipmaps()) + assert(sprite.material.shader.resource_path.ends_with("mineral_deposit.gdshader")) + assert(not sprite.material.shader.code.contains("TIME")) + if poi.position.length_squared() < target.length_squared(): + target = poi.position + chosen = sprite + assert(chosen != null) + surface.world.refresh_pois(target) + surface.camera.position_smoothing_enabled = false + surface.camera.position = target + surface.camera.force_update_scroll() + await settle() + var origin: Vector2 = chosen.get_global_transform_with_canvas().origin/surface.SURFACE_RENDER_SCALE + var initial: Image = get_viewport().get_texture().get_image() + initial.save_png("res://artifacts/mineral_deposit_biome_%d.png" % biome) + var small := initial.duplicate() as Image + small.convert(Image.FORMAT_RGBA8) + small.resize(640,360,Image.INTERPOLATE_LANCZOS) + sheet.blit_rect(small,Rect2i(0,0,640,360),Vector2i(biome%2*640,biome/2*360)) + if biome == 0: + await settle() + assert(difference(initial,get_viewport().get_texture().get_image(),origin) < 0.0001,"Stationary ore must not flicker") + var previous := initial + for frame in 12: + surface.camera.position = target+Vector2((frame+1)*0.25,0) + surface.camera.force_update_scroll() + await settle() + var current: Image = get_viewport().get_texture().get_image() + var change := difference(previous,current,origin) + max_pan_change = maxf(max_pan_change,change) + assert(change < 0.035,"Subpixel camera motion should remain smoothly filtered") + previous = current + surface.camera.position = target + surface.camera.force_update_scroll() + await settle() + assert(difference(initial,get_viewport().get_texture().get_image(),origin) < 0.0001,"Returning to the same camera position restores the same pixels") + progress.finish_visit(GameState.captain,false) + surface.queue_free() + await get_tree().process_frame + sheet.save_png("res://artifacts/mineral_deposit_biomes.png") + print("MINERAL DEPOSIT PASS: six biome renders, real ore instead of icon, mipmaps, alpha/soil blending, no stationary flicker, smooth subpixel panning, identical pixels after camera return; max mean pan delta=",max_pan_change) + get_tree().quit() diff --git a/tests/previews/surface_deposit_test.gd.uid b/tests/previews/surface_deposit_test.gd.uid new file mode 100644 index 0000000..11ad8ac --- /dev/null +++ b/tests/previews/surface_deposit_test.gd.uid @@ -0,0 +1 @@ +uid://xoq3rcr7blj3 diff --git a/tests/previews/surface_deposit_test.tscn b/tests/previews/surface_deposit_test.tscn new file mode 100644 index 0000000..7a5fd6f --- /dev/null +++ b/tests/previews/surface_deposit_test.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/previews/surface_deposit_test.gd" id="1"] +[node name="SurfaceDepositTest" type="Node"] +script = ExtResource("1") diff --git a/tools/audio/build_freighter_audio.py b/tools/audio/build_freighter_audio.py new file mode 100644 index 0000000..6cbda8c --- /dev/null +++ b/tools/audio/build_freighter_audio.py @@ -0,0 +1,163 @@ +"""Edit CC0 field recordings into the frigate's restrained industrial soundscape. + +Run fetch_freighter_sources.py first. Requires numpy, no audio service/API. +Output: 48 kHz, stereo, PCM16 WAV. Source credits live beside the assets. +""" +from pathlib import Path +import json +import shutil +import struct +import wave +import numpy as np + +ROOT = Path(__file__).resolve().parents[2] +SOURCE = ROOT / "tmp/freighter_audio_sources" +OUT = ROOT / "assets/sounds/freighter" +RATE = 48000 +MANIFEST = {} + +def read(relative): + raw = (SOURCE / relative).read_bytes() + cursor = 12 + fmt = data = None + while cursor + 8 <= len(raw): + name, size = struct.unpack_from("<4sI", raw, cursor) + chunk = raw[cursor+8:cursor+8+size] + if name == b"fmt ": fmt = chunk + if name == b"data": data = chunk + cursor += 8 + size + size % 2 + kind, channels, rate, _, _, bits = struct.unpack_from(" max(rms.max()*.045,.00001))[0] + if not len(active): raise ValueError("Silent recording") + return x[max(0,int(active[0])*block-960):min(len(x),(int(active[-1])+1)*block+4800)] + +def room(x, amount=.14): + # Short non-musical reflections, staggered between left and right walls. + output = np.pad(x,((0,int(RATE*.65)),(0,0))) + wet = eq(x,80,3300) + for index, delay in enumerate([.037,.061,.089,.127,.173,.229,.307,.413,.521]): + offset = int(delay*RATE) + output[offset:offset+len(x)] += np.roll(wet,index%2,axis=1)*amount*.76**index + return output + +def mix(*entries): + length = max(int(offset*RATE)+len(x) for x,offset,gain in entries) + output = np.zeros((length,2)) + for x,offset,gain in entries: + start = int(offset*RATE) + output[start:start+len(x)] += x*gain + return output + +def loop(x, seconds=1.2): + n = min(int(seconds*RATE),len(x)//4) + ramp = np.linspace(0,1,n)[:,None] + return np.concatenate([x[n:-n],x[-n:]*(1-ramp)+x[:n]*ramp]) + +def save(name, x, sources, processing, looping=False): + x = peak(x,-3.5 if looping else -2) + assert np.isfinite(x).all() and np.max(np.abs(x)) < 1 + with wave.open(str(OUT/(name+".wav")),"wb") as stream: + stream.setparams((2,2,RATE,0,"NONE","not compressed")) + stream.writeframes((x*32767).astype("=0)&(local<.24),np.sin(np.pi*np.clip(local/.24,0,1))**2,0) + tone += env*(np.sin(2*np.pi*freq*t)+.11*np.sin(2*np.pi*freq*2*t)) + save("service_chime",fade(room(np.repeat(tone[:,None],2,axis=1),.13)),[],"Original two-tone 540/680 Hz service annunciator, smooth envelope, short reflections") + warning = mix((np.repeat(tone[:,None],2,axis=1),0,.8),(np.repeat(tone[:,None],2,axis=1),1.15,.7)) + save("maintenance_alarm",fade(room(warning,.19)),[],"Original restrained maintenance warning, two paired tones with room reflections") + for i in [0,1]: + source = "scifi/Audio/computerNoise_%03d.ogg"%i + shutil.copyfile(SOURCE/source,OUT/("console_%d.ogg"%(i+1))) + MANIFEST["console_%d"%(i+1)] = {"sources":[source],"processing":"Unmodified CC0 Kenney electronic effect, mixed quietly in-engine"} + (OUT/"manifest.json").write_text(json.dumps(MANIFEST,indent=2,ensure_ascii=False),encoding="utf-8") + preview = np.zeros((int(34*RATE),2)) + for name,db in [("reactor_loop",-14),("ventilation_loop",-21)]: + bed = read(OUT/(name+".wav")) + preview += bed[np.arange(len(preview))%len(bed)]*10**(db/20) + for name,offset,db,pan in [("docking_clamp",1,-13,.65),("airlock_open",3,-13.5,-.48),("tool_1",8,-13,-.48),("metal_1",13,-15,-.48),("tool_3",17,-15,-.48),("pressure_release",23,-23,.6),("maintenance_alarm",26,-22,-.7),("airlock_close",30,-17,.2)]: + clip = read(OUT/(name+".wav"))*10**(db/20) + clip *= np.array([min(1,1-pan),min(1,1+pan)]) + start = int(offset*RATE) + preview[start:start+len(clip)] += clip[:len(preview)-start] + preview = fade(peak(preview,-3),1.0,1.4) + path = ROOT/"artifacts/freighter_ambience_demo.wav" + with wave.open(str(path),"wb") as stream: + stream.setparams((2,2,RATE,0,"NONE","not compressed")) + stream.writeframes((preview*32767).astype("