Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ASSETS_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ These assets were made for this project, either by hand, by procedure, or with i
| Bay Area Blitz music | `Audio/Platformer/Bay Area Blitz/` | Music by Erik Thorelli, made with Suno v6 for this game. Erik holds the rights and grants their use in the game and in this repository. Prompts, Suno song IDs, loop points and delivery steps in `PROMPTS.md`. | ✅ |
| Orbital Ops music | `Audio/Platformer/Obital Ops/` | Music by Erik Thorelli, made with Suno v6 for this game. Erik holds the rights and grants their use in the game and in this repository. Prompts, Suno song IDs, loop points and delivery steps in `PROMPTS.md`. | ✅ |
| Hoppy 3D mascot | `runner/hoppy/` | Built by `tools/blender/build_hoppy.py` | ⚠️ brand sign-off |
| Platformer stage-clear stinger | `Audio/Platformer/stinger_stage_clear.ogg` | Music by Erik Thorelli, made with Suno v6 for this game. Erik holds the rights and grants their use in the game and in this repository. Prompt, Suno song ID and edit in `Audio/Platformer/PROMPTS.md`. | ✅ |
| Runner props, previews and procedural assets | `runner/` | See [`runner/LICENSES.md`](hoppys-adventure-arcade/Assets/runner/LICENSES.md) | ✅ |
| Bay Area Blitz pack | `bay-area-blitz-assets-only/` | Image generation | ⚠️ confirm the generator's terms allow redistribution |
| Orbital Ops pack | `orbital-ops-assets-only/` | Image generation | ⚠️ same |
Expand Down
2 changes: 1 addition & 1 deletion docs/bay-area-blitz.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Run the project normally: **Platformer Game → Level Select → Play Bay Area B
- Swipe: J/X, controller X/Square. Landing on an enemy also deals damage and bounces Hoppy.
- Pause/resume: Escape or controller Start. Focus loss pauses the game.
- Defeat the Merge Conflict Monster, then swipe or land on the MERGE button to finish.
- Pause and results provide fresh retry, platformer menu, and arcade return.
- Pause and results provide fresh retry, platformer menu, and arcade return. Finishing a level (any of the three) plays a short stage-clear stinger (`Assets/Audio/Platformer/stinger_stage_clear.ogg`) over the MERGE SUCCESSFUL results while the paused level music stays silent; a knockout keeps its ducked level music instead.

## Implemented

Expand Down
1 change: 1 addition & 0 deletions docs/soundtrack.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Shared settings: Instrumental on, Variety Off. The lyrics field holds section ta
| **Villain boss theme** (shared, as the GDD specifies) | All Heroes vs. Villains bosses | 1:00+ loop, strong hit in the first second | `Saturday-morning cartoon villain battle, 16-bit FM chiptune, 232 BPM double-time feel, minor key, campy menace, orchestra-hit stabs, chromatic runs, motif in minor, huge downbeat in the first second, loopable` |
| **Opening fanfare** | Heroes vs. Villains intro | 10–15 s, hard stop | Not 8-bit: the theme's chorus and gang shout, cut from track A. |
| **MERGE SUCCESSFUL** | Code City finale, ending and results | 3.5 s one-shot, then a 16.552 s loop | Delivered: original F-major square-wave synthesis at 116 BPM; rising F-A-C motif resolving to F. See the reproducible build below. |
| **Platformer stage clear** | Platformer MERGE SUCCESSFUL results | 4.7 s, one-shot | Delivered: `Assets/Audio/Platformer/stinger_stage_clear.ogg`, a Suno Sounds one-shot in F major; see that folder's `PROMPTS.md`. Plays on the Music bus while the results pause the level music. |
| **BUILD FAILED** | Lose results | 2–3 s, one-shot | Sounds, One-Shot: `8-bit comic fail sting, three-note motif descending in minor, wobbly square lead` |
| **Capability unlocked** (×4) | REVIEW / TRIAGE / CHANGE STACK / SECURITY cards | 1–2 s, one-shot | Sounds, One-Shot: `chiptune power-up chime, three-note glockenspiel-like square arpeggio` (one take per capability, rising in pitch) |
| **BOSS DOWN!** | Boss defeat | about 2 s | Keep Juhani Junkala's CC0 `fanfare3`, or a Sounds one-shot of the motif. |
Expand Down
21 changes: 21 additions & 0 deletions hoppys-adventure-arcade/Assets/Audio/Platformer/PROMPTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Platformer stage-clear stinger

Music by Erik Thorelli, made with Suno v6 on his Premier plan for Hoppy's Adventure Arcade. Erik holds the rights and grants their use in the game and in this repository. The soundtrack plan is [`docs/soundtrack.md`](../../../../docs/soundtrack.md); level music has its own `PROMPTS.md` in each level's folder.

| File | Plays | Suno song | Measured | Length |
|---|---|---|---|---|
| `stinger_stage_clear.ogg` | Once, when any platformer level finishes and the MERGE SUCCESSFUL results open (`modes/platformer/level.gd`, `_finish(true)`) | [`73fc8220-4010-43da-b82f-95e63862062e`](https://suno.com/song/73fc8220-4010-43da-b82f-95e63862062e) | F major, the theme's key, at 116 BPM | 4.74 s, one-shot |

## Prompt

Suno Sounds, One-Shot, BPM 116, key F major:

```text
chiptune level-clear jingle, bouncy two-bar fanfare, three-note rising motif F A C then a held tonic chord hit, square-wave lead with triangle bass and a noise-channel drum fill, cheerful arcade stage clear, ends cleanly, about 4 seconds
```

Generated and downloaded as WAV on October 15, 2026. Takes compared: this one and `4cc54a58-bf2a-44a4-8e24-ae2010b2de84`. This one is clearly in F major (Krumhansl fit 0.94; the other reads as C major) and scored higher on Meta's audiobox-aesthetics model (content enjoyment 6.38 against 6.08, production quality 7.59 against 7.12).

## Delivery

Suno returned an 8.2 s phrase. It was raised 1.5 dB to −14 LUFS and cut to its closing phrase, the two bars before the final decay (3.415–8.154 s in the take), keeping the natural decay. A 4 ms fade-in and a 30 ms fade-out were applied, then the cut was encoded with `oggenc -q 6`, keeping Suno's embedded comment with the song ID. The `.import` leaves `loop` off.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[remap]

importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://qpafxw1argjw"
path="res://.godot/imported/stinger_stage_clear.ogg-f6facadb7bc526d5171cf1e53fcea724.oggvorbisstr"

[deps]

source_file="res://Assets/Audio/Platformer/stinger_stage_clear.ogg"
dest_files=["res://.godot/imported/stinger_stage_clear.ogg-f6facadb7bc526d5171cf1e53fcea724.oggvorbisstr"]

[params]

loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4
13 changes: 13 additions & 0 deletions hoppys-adventure-arcade/modes/platformer/level.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const SFX := {
"hurt": preload("res://Assets/Backgrounds/Sunny-land-forest-files/demo/assets/sound/wavs/hurt.wav"),
"boss_down": preload("res://Assets/Audio/The Essential Retro Video Game Sound Effects Collection [512 sounds] By Juhani Junkala/General Sounds/Fanfares/sfx_sounds_fanfare3.wav")
}
## Plays once over the MERGE SUCCESSFUL results, while the paused level music is silent.
const STAGE_CLEAR := preload("res://Assets/Audio/Platformer/stinger_stage_clear.ogg")
const EXIT_TOP := 540.0
const PAUSE_SHADE := Color(0.025, 0.06, 0.1, 0.96)
# Lighter than the pause shade so the frozen final moment stays readable behind the results.
Expand Down Expand Up @@ -53,6 +55,7 @@ var overlay_card: Control
var results_reveal: Tween
var exit_sprite: AnimatedSprite2D
var music: AudioStreamPlayer
var stage_clear: AudioStreamPlayer
var sfx_players := {}
var encounter := "approach"
var intro_remaining := 0.0
Expand Down Expand Up @@ -222,6 +225,8 @@ func _hud() -> void:
var button := Style.button(entry[0])
col.add_child(button)
button.pressed.connect(func():
# Leaving the results unpauses the level music; end the stinger before they overlap.
stage_clear.stop()
get_tree().paused = false
AppRouter.go(entry[1]))
overlay.hide()
Expand Down Expand Up @@ -266,6 +271,7 @@ func _finish(won: bool) -> void:
overlay_detail.text = "%06d POINTS / %02d:%02d\n%s" % [score, int(elapsed) / 60, int(elapsed) % 60, level_title + " complete." if won else "Fresh branch. Another try."]
resume_button.hide()
if won:
stage_clear.play()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
_reveal_results(0.35)
else:
_start_knockout()
Expand Down Expand Up @@ -506,5 +512,12 @@ func _setup_session() -> void:
sfx_players[clip] = sound
for child in get_children():
if not child is CanvasLayer: child.process_mode = Node.PROCESS_MODE_PAUSABLE
# The results pause the tree, so the stage-clear stinger has to keep processing.
stage_clear = AudioStreamPlayer.new()
stage_clear.bus = "Music"
stage_clear.stream = STAGE_CLEAR
stage_clear.volume_db = -13
stage_clear.process_mode = Node.PROCESS_MODE_ALWAYS
add_child(stage_clear)
# Everything above was placed, not moved: start interpolation from those positions.
reset_physics_interpolation()
56 changes: 56 additions & 0 deletions hoppys-adventure-arcade/tests/platformer_stage_clear.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
extends SceneTree
## Finishing any platformer level plays the stage-clear stinger once over the MERGE SUCCESSFUL
## results: it keeps playing while the results pause the tree, the paused level music stays
## silent under it, and a knockout never plays it. Run headless with
## --script res://tests/platformer_stage_clear.gd.
const LEVELS := ["bay_area_blitz", "enchanted_repo", "orbital_ops"]
const STINGER := "res://Assets/Audio/Platformer/stinger_stage_clear.ogg"
var failures := 0
var game: Node

func _initialize() -> void:
run.call_deferred()

func check(ok: bool, message: String) -> void:
print("PASS: " if ok else "FAIL: ", message)
if not ok: failures += 1

func open_level(route: String) -> void:
change_scene_to_file("res://scenes/platformer/" + route + ".tscn")
await process_frame
await process_frame
game = current_scene
game._resume()
await create_timer(0.2).timeout

func run() -> void:
var stream := load(STINGER) as AudioStreamOggVorbis
check(stream != null and not stream.loop and stream.get_length() > 2.0 and stream.get_length() < 6.0,
"The stage-clear stinger is a short one-shot")
for route in LEVELS:
await open_level(route)
check(game.stage_clear.stream == stream and not game.stage_clear.playing, route + ": the stinger waits for the finish")
game._finish(true)
await create_timer(0.5, true, false, true).timeout
check(paused and game.stage_clear.playing and game.stage_clear.bus == &"Music",
route + ": MERGE SUCCESSFUL plays the stinger on the Music bus while the results pause the tree")
check(game.music.process_mode != Node.PROCESS_MODE_ALWAYS and game.music.can_process() == false,
route + ": the level music stays paused under the stinger")
var leave := game.find_children("*", "Button", true, false).filter(func(b: Button) -> bool: return b.text == "Return to Platformer Menu")
check(not leave.is_empty(), route + ": the results offer a way out")
if not leave.is_empty():
(leave[0] as Button).pressed.emit()
check(not game.stage_clear.playing, route + ": leaving the results stops the stinger before the level music resumes")
await create_timer(0.6, true, false, true).timeout
paused = false
await open_level(route)
game.player.position.y = 900
game.player._physics_process(0.0)
await create_timer(0.5, true, false, true).timeout
check(not game.stage_clear.playing, route + ": a knockout does not play the stage-clear stinger")
paused = false
print("PLATFORMER STAGE CLEAR: %s (%d failures)" % ["PASS" if failures == 0 else "FAIL", failures])
change_scene_to_file("res://scenes/main_menu/main_menu.tscn")
await process_frame
await root.get_node("AudioService").settle()
quit(1 if failures else 0)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://cd5vqgnbgwu5e
Loading