Commit Graph
83 Commits
Author SHA1 Message Date
Sansan b510aa47f2 Split settings into a quiz card and an application Settings popup
Build / test (push) Successful in 44s
Build / build (push) Successful in 1m13s
- A Quiz settings button under the player bar opens a small card above
  it with the quiz settings, including Auto repeat, which moves there
  from under the player bar. The card does not dim the quiz, so it can
  be changed during a song, and grows to fit its rows.
- A Settings button in the top bar opens the application settings:
  server, fullscreen and songs to download ahead. Quick sync is gone,
  as the server no longer has it, and the server list is in Settings.
- Labels use sentence case, and seconds to hide show as whole numbers.
- Debug builds switch to the local server and a low winning score in
  the Settings autoload, before any scene asks the server for anything.
  The DEBUG label moves to the top bar, so it shows on every tab.
- Search moves from the top bar back to the quiz toolbar, as the
  Statistics tab has its own search.
2026-10-07 16:05:13 +02:00
Sansan 8d21b0617d Fix and tidy the Character select, Shortcuts, Log and QR popups
Build / test (push) Successful in 41s
Build / build (push) Successful in 1m13s
Character select
- A card only picks its character when it is clicked. Before, every card
  watched every click, so clicking below the panel could pick a card
  scrolled out of view there.
- Six columns of fixed-size cards that scroll for any number of
  characters, with a gap between the cards and the scrollbar.
- Pictures are read as PNG, JPG or WebP by their first bytes, so JPGs no
  longer print an error. Names are split into words ("Paper Mario") and
  the card under the mouse is highlighted.

Shortcuts
- Alt+Z restarts the song; it was listed as "Reset".
- Grouped by what the keys do, in sentence case, and the panel fits the
  list. A test checks that every button shortcut is listed.

Log
- Scrolls to the newest row, alternates row colours and says "Nothing
  has happened yet" when empty.

QR
- A title, and the code is set up in one place.
2026-10-07 15:24:43 +02:00
Sansan b2d547b650 Centre every popup in the window
Build / test (push) Successful in 40s
Build / build (push) Successful in 1m13s
Most popups sat where they had been placed by hand, up to 328 px off.
They are now all centred, and test_popups.gd checks it for every popup.
2026-10-07 14:53:51 +02:00
Sansan 845d08c260 Rework the Search popup and highlight what matched
- The search field is a single-line field, so Enter no longer adds a
  line break that made every search come up empty.
- The field, button and list use real font sizes from the theme instead
  of being drawn at half size and scaled up, so the text is sharp, and
  everything fits inside the panel.
- Results have alternating row backgrounds like the other lists, a count
  ("3 of 1045 soundtracks") and a message when nothing matches.
- The letters that matched are highlighted.
- Searches shorter than Settings.LETTERS_FOR_LOOSE_SEARCH (3) only find
  names that contain the text, instead of names with the letters far
  apart, which matched most of the library. The Statistics tab uses the
  same search.
2026-10-07 14:53:51 +02:00
Sansan 13fdb3f68c Show the server's version history in the About popup
Build / test (push) Successful in 38s
Build / build (push) Successful in 1m13s
The About popup is twice as wide and shows the player's and the server's
version history side by side, each in its own scrolling column. The
server's comes from GET /api/v1/version/history every time the popup
opens. The description, which is about both, is centred above the
columns, and each history starts right under its version.
2026-10-07 14:32:31 +02:00
Sansan e1e05f3904 Show a graph of the game on the Winner popup
Build / test (push) Successful in 38s
Build / build (push) Successful in 1m12s
The Winner popup now shows every player's score after each song as a
step line, with the winning score marked, and three facts under it: the
longest streak, the longest standstill and the song most players scored
on. The data comes from the points each song already records, through
the new ScoreHistory. The popup grows to 1600x920 with the graph across
the full width, so a 60-song game with five players stays readable.

Reset points now starts a new game: it clears the points on the songs
and the graph starts after the song that was playing. Songs already on
the server at startup count as an earlier game.

The screenshot tour has a long five-player game, and only the Winner
popup differs from before.
2026-10-07 10:40:46 +02:00
Sansan b40b160fde Show Japanese text and close the gap in the top bar
Build / test (push) Successful in 35s
Build / build (push) Successful in 1m12s
Godot's built-in font has no Japanese, so kanji in song names showed as
boxes. The theme now sets Godot's own Open Sans SemiBold as its default
font, with Noto Sans JP Medium as fallback. The Noto file's line metrics
are set to Open Sans's, so line heights stay the same and every other
screen is pixel-identical; fonts/README.md explains how to redo it.

Search and Sync move right to where the Statistics button was.
2026-10-06 23:57:37 +02:00
Sansan a21b6e9b22 Add a Statistics tab with a card for every soundtrack
Build / test (push) Successful in 40s
Build / build (push) Successful in 1m16s
Part of #41. The top bar has Quiz and Statistics tabs instead of the
Statistics button. The Statistics tab replaces the quiz below the top bar
(the song keeps playing) and shows every soundtrack as a card with its
plays, last played date and most and least played songs, loaded from the
new GET /api/v1/statistics/soundtracks. The cards can be searched (with
the Search popup's matching, now in SoundtrackSearch), filtered on played
or never played, and sorted A-Z, Z-A, most or least played. Clicking a
card opens a popup with all its songs.

Screenshots of the other states only differ in the top bar, and the
layout baseline is taken again with the new statistics states.
2026-10-06 23:33:00 +02:00
Sansan f7619baf34 Use /api/v1 with token authentication and typed responses
Closes #38, #39 and #40; adds the client side of #41.

- Every request goes to /api/v1. MusicServerClient gets a token with
  Settings.api_password the first time one is needed, reuses it, and on a
  401 gets a new one and sends the request once more. Switching server gets
  a new token too. Callers never see the token.
- Sync is started with POST, as MusicServer 5.0 requires.
- The client has every endpoint in the server's OpenAPI spec, including
  statistics, soundtracks and songs by UUID, version, downloads and tokens.
- tools/generate_api_types.gd generates a typed class per schema in
  server/openapi.json into server/generated/. Song info, statistics and
  versions are passed to callers as these classes. A list the server sends
  as null is an empty list.
- FakeHttpTransport hands out tokens and answers 401 without one, so every
  test goes through the token flow. New tests cover the token handling,
  every endpoint and each generated class.
2026-10-06 22:45:28 +02:00
Sansan 8b71723a34 Put the whole look into one project theme
Build / test (pull_request) Successful in 31s
Build / build (pull_request) Skipped
Build / test (push) Successful in 31s
Build / build (push) Successful in 1m13s
themes/main_theme.tres is the project theme. It holds Godot's default look for
every control type the game uses, plus named styles for the game's own look
(popups, the background, spacers, list rows, title and text sizes, highlights)
and a palette for the popup dimming. Scenes and scripts name these styles
instead of setting colours, sizes or style boxes, and the five old theme files
are gone. themes/README.md explains the styles and how to make a new theme.

The screenshot tour now uses the real character pictures, so the layout
baseline is taken again. Screenshots are pixel-identical to before, and
test_theme.gd checks that the look stays in the theme.
2026-10-06 13:33:35 +02:00
Sansan a7bf38c5f9 Version 2.0.0
Build / test (pull_request) Successful in 31s
Build / build (pull_request) Skipped
2026-10-06 12:04:42 +02:00
Sansan 5519afb5f6 Add what's new in 2.0.0 to the About popup
The layout test lets the changelog grow in height with its text, and still
checks its position and width.
2026-10-06 12:02:55 +02:00
Sansan d77b72159d Sort the scripts and scenes into folders
main/ holds the main window and top bar, quiz/ the quiz and its parts,
player/ the music player, popups/ every popup and what they share, data/ the
songs, players, playlist and settings, server/ the MusicServer client,
common/ the list row and GameLog, and themes/ the themes and style boxes.
2026-10-06 11:57:29 +02:00
Sansan a0428e3475 Follow the GDScript style guide in every script
- Types on every parameter, return value and variable, and snake_case names
  (the player name limit is the constant MAX_NAME_LENGTH).
- Spelling fixes: *_triggered, character_selected, separator, caught_errors.
  The server's catched_errors key stays as the server sends it.
- Signals are connected with signal.connect() instead of by name.
- Code is ordered signals, constants, exported vars, vars, @onready vars,
  functions, with @onready on one line and two blank lines between functions.
- Every script starts with a ## description. Template comments, old LOC notes
  and commented-out code are gone, apart from the local play code.
- Lines are at most 100 characters, apart from the local play path.
- The screenshot tour turns off the blinking text cursor so pictures match.
2026-10-06 11:53:26 +02:00
Sansan 674604425f Reach nodes by unique name instead of long paths
Nodes a script uses through a path of several nodes are marked unique in their
scene and found with %Name. The search list is renamed SearchResults, and the
player row's nodes get their real types.
2026-10-06 11:44:47 +02:00
Sansan 390da577d7 Name popups *Popup and give scripts, scenes and classes matching names
Every popup is now called *Popup instead of *Window in its file, root node,
node name, class and variable, and PopupWindow becomes BasePopup. Player is
PlayerRow, SongListObject is SongRow and Character is CharacterCard. Script
files are snake_case and scene files PascalCase, each matching its class or
root node, and LightGrayTheme.tres is light_gray_theme.tres.
2026-10-06 11:22:39 +02:00
Sansan b7d67b573e Give every script a class name and use it for typed references
The popups, the volume slider, the player name field and the main window get
a class_name, so the main window and the quiz hold them by their real type
instead of Control. The classes already use the names the files will get.
2026-10-06 11:16:09 +02:00
Sansan 5831e1e73d Log through a GameLog autoload instead of print()
GameLog.debug() prints only in debug builds, and GameLog.warning() reports
problems such as unexpected server data or failed downloads.
2026-10-06 11:15:03 +02:00
Sansan 5cdec5cc8f Move the log popup to the main window
The quiz reports point changes with an event_logged signal, and the main
window adds them to the log. Alt+L is handled by the main window.
2026-10-06 11:13:11 +02:00
Sansan e94c75e84d Split now playing, the quiz buttons and song loading out of the quiz
NowPlaying, QuizToolbar and SongControls become their own scenes, the auto
repeat switch moves into the music player scene, and downloading songs moves
into a SongLoader node. The quiz only wires the parts together. Every node
keeps its place on screen.

Screenshots of every UI state are pixel-identical to before, the layout test
still matches the original baseline, and each new part has its own tests.
2026-10-06 11:11:18 +02:00
Sansan bad4b3ad8b Indent every script with tabs, like the Godot editor does 2026-10-06 10:28:12 +02:00
Sansan 8c1987807c Make the music list, player list and inspiration list their own scenes
MusicListPanel, PlayersPanel (was Players) and InspirationList (was
ScrollContainer) each get their own scene and script. They report what
happens through signals, and the quiz wires them to the playlist, the
music player, the log and the popups. Every node keeps its place on screen.

Screenshots of every UI state are pixel-identical to before, the layout
test still matches the original baseline, and each part is tested on its
own as well as through the quiz.
2026-10-06 10:27:35 +02:00
Sansan 3c7b4e5c9d Split the main window into TopBar and Quiz scenes
The quiz (song, playlist, players, music player and the popups opened from
it) moves into Quiz.tscn and the top panel into TopBar.tscn. The main window
keeps only the top bar, the quiz and the popups opened from the top bar, so
other views can be added next to the quiz later. Every node keeps its place
on screen.

Screenshots of every UI state before and after are pixel-identical, and a
layout test checks each node against a baseline taken before the split.
2026-10-06 10:02:22 +02:00
Sansan 75c59eb223 Move Sync to the top row and Settings next to the reset buttons 2026-10-06 08:43:05 +02:00
Sansan 10e9e7b2a4 New app icon, packaged with a menu entry for Linux
Build / test (pull_request) Successful in 22s
Build / build (pull_request) Skipped
The icon is a plate wearing headphones with a red Nordic house cookie
on it. It replaces the default Godot icon, so the game window, Windows
and macOS builds use it.

Linux programs can't carry their own icon, so the build now also makes
MusicPlayer_<version>_linux.tar.gz with the game, the icon as a PNG, a
.desktop menu entry and install.sh, which installs them for the current
user. The bare Linux binary is still published.
2026-10-06 08:32:56 +02:00
Sansan 29b11560b5 Show where each item starts in the inspiration and sync lists
Items in the inspiration list and the sync result columns now each have
their own background box, alternating between two shades, with a small
gap between them. When a long name wraps over several lines it is
still clear which lines belong together. The sync columns keep their
alternating white and orange text.
2026-10-06 08:32:56 +02:00
Sansan 2a332c2d19 Make the sync result look right with any number of columns
- Visible columns share the width equally, whether one or five are shown
- Long soundtrack names and errors wrap inside their column instead of
  widening the window, which pushed the title and Restart button out of
  view and cut off text
- Separators only go between columns; the extra one before the first
  column is gone
- Wider, centred panel with inner margins, and spacing above the
  Restart button
- The Re-added header no longer reads ReAdded
2026-10-06 08:32:56 +02:00
Sansan 6cbbfee6a1 Tidy up popup layouts
- Settings: labels in one column and switches and number buttons lined
  up in another, with margins inside the panel
- Log: title and margins; text no longer touches the panel edge
- Sync result: error list centred under its header, which now reads
  Errors instead of CatchedErrors
- About: text wraps inside the panel instead of running under the
  scrollbar
- Character select: wider panel, so the right column of characters is
  no longer right next to the scrollbar
2026-10-06 08:32:56 +02:00
Sansan c7d49bc5cc Rework popups and fix blurry text and player list layout
Popups:
- All nine popups now extend a shared PopupWindow class with open(),
  close() and toggle(), instead of each copying its own close-on-click
  code. Popups that need cleanup override close().
- A click outside a popup closed it and also pressed whatever was
  underneath, e.g. Reset playlist. The closing click is now consumed.
- Popups used Godot's see-through default panel, so the song list and
  titles showed through. They now use an opaque popup theme, and a
  backdrop dims the window behind them.
- The main window root now fills the screen, so the backdrop covers it.

Text and images:
- Many labels and buttons forced nearest filtering, which made text and
  icons jagged when the window is not 1920x1080. They now use the
  default smooth filtering. Only the pixel-art "no character" picture
  keeps nearest filtering when the winner window enlarges it.

Player list:
- A long player name pushed that row's character and buttons to the
  right. Names now take the free space and shorten with an ellipsis, so
  all rows line up and use the full width of the panel.
2026-10-06 08:32:56 +02:00
Sansan 6288739de0 Use the Compatibility renderer
The game is a 2D interface without 3D, shaders or lights, so it does
not need Forward+. With Compatibility it uses about 25% less CPU and
30 MB less memory while running, with no visible difference.
2026-10-06 08:32:56 +02:00
Sansan d79f5f92c5 Remove unused code
- The old character select scene, which was never shown but loaded
  every character image at startup
- Unused SyncResponse and ProgressResponse classes
- Playlist.set_last_played_answered and select/local-loading code in
  the new character select, none of which were called
- Settings.delete_player_children, identical to delete_children
- An unused variable holding a local file path
2026-10-06 08:32:56 +02:00
Sansan 14c166614d Add one log row at a time and pad log times
The log rebuilt every row each time a point was given or taken. It now
adds just the new row, and shows times as 09:05:03 instead of 9:5:3.
2026-10-06 08:32:56 +02:00
Sansan 428decf73a Turn statistics back on for a new game and fix low winning scores
Winning turned statistics off and nothing turned them back on, while
the Settings checkbox still showed them as on. Resetting points now
turns them back on if the win turned them off, and the checkbox shows
the current state when Settings is opened.

A winning score of 1 could never be won and with 2 the first point was
not announced as match point, because the first-point check ran first.
The winning score also could not be kept from going below 1.
2026-10-06 08:32:56 +02:00
Sansan 739d0f7af6 Preload songs only after the song list has loaded
At startup and after resetting the playlist, loading the song list and
preloading the first song started at the same time. When the list
answered last, it cleared the playlist and dropped the preloaded song.
Preloading now starts once the list has loaded, and loading the list is
retried if it fails.
2026-10-06 08:32:56 +02:00
Sansan ed292877ef Ignore Show answer when no song is playing
Before the first song played, currently_playing_song is -1, so pressing
Show answer marked the last song in the list as answered. That is the
preloaded song, so its title was shown as soon as it started playing,
and played?song=-1 was sent to the server. With an empty playlist it
crashed.
2026-10-06 08:32:56 +02:00
Sansan 5a979fca48 Time out requests the server never answers
HTTPRequest has no timeout by default, so a server that accepts the
connection but never responds left song downloads waiting forever.
Requests now fail after 120 seconds, which triggers the normal retry.
2026-10-06 08:32:56 +02:00
Sansan 1acc757981 Fill the song cache when the settings menu is closed
Raising the cache size used to have no effect until the next song was
played. Downloads now start when the settings window closes, so clicking
the number up and down while it is open does not start any downloads.
2026-10-06 08:32:56 +02:00
Sansan 16724922ed Test that the song cache fills up to the cache size setting
Covers a cache size above 1: songs are preloaded until the setting is
reached, playing a song tops the cache up again, and the cache keeps
filling after a failed download once the server answers again.
2026-10-06 08:32:56 +02:00
Sansan c1fc24adf9 Release from the version in project.godot
The version now lives in project.godot (application/config/version)
and Settings.version reads it from there. The Build workflow tests and
exports on every push, and on main creates the tag, uploads the builds
and creates the Gitea release when that version has no tag yet. This
replaces gitea-release.yaml, which released when a branch named after
the version was pushed.

Godot setup moves into a local .gitea/actions/setup-godot action shared
by the test and build jobs, and Godot is updated to 4.7.2 to match the
editor the project is made with.
2026-10-06 08:32:56 +02:00
Sansan f67dccbf52 Retry failed song downloads instead of getting stuck
MusicServerClient functions now take an optional on_failed callback,
called when a request fails or returns invalid JSON. Before, a failed
request was silently dropped, so a failure while preloading a song left
Playlist.is_song_downloading set forever and no more songs were fetched.

Song fetching retries the step that failed (download, queue or info)
every song_fetch_retry_seconds, so the server and the game keep the same
playlist.
2026-10-06 08:32:56 +02:00
Sansan c985a6ce5b Replace the broken test suite with working GdUnit4 tests
The previous tests never compiled: they used GUT's assert functions,
Godot 3 APIs and reassigned autoload functions, so no test ran.

- 90 tests: unit tests for the playlist, songs, player scoring, time
  formatting, search and the server client, plus integration tests
  that run MainWindow and SyncWindow against a fake server
- Keybind tests check that Alt+<number> and Ctrl+Alt+<number> keep
  following the player rows while players are added and removed
- run_tests.sh finds Godot and runs the suite headless (~10 seconds)
- CI runs the tests before the build job in gitea-build.yaml; the
  separate test.yml used a GitHub action that does not exist
- GdUnit4 is committed instead of ignored, since the project enables
  it, and .gitea/ is no longer ignored
- Exported builds leave out tests/ and addons/gdUnit4/
2026-10-06 08:32:56 +02:00
Sansan 56e9fe66d3 Move server calls to MusicServerClient and give players stable ids
Server requests move out of Settings into a new MusicServerClient
autoload with one function per endpoint. It sends requests through a
swappable HttpTransport so tests can fake the server, and it frees its
HTTPRequest nodes after use.

Players now have a UUID that never changes. Their position in
Settings.player_array is only used for the Alt+<number> keybinds.
Previously the position doubled as the id, so removing a player made
points and wins land on the wrong player, and could delete another
player's point from a song.

Also fixes:
- format_time showed 59.6 seconds as "00:60"
- searches containing regex characters such as "Q*bert" broke search
- SongObject and PlayerObject were Nodes that were never freed; they
  are now RefCounted
- search matching is split out of the UI code into find_matches()
2026-10-06 08:32:55 +02:00
Sansan ab338b0696 Add GdUnit4 testing framework with comprehensive test coverage
- Add GdUnit4 v6.2.1 framework in addons/gdUnit4/
- Create test directory structure (tests/unit/, tests/integration/, tests/mocks/)
- Add unit tests for MusicPlayer.gd (8 tests)
- Add unit tests for MainWindow.gd (15+ tests)
- Add unit tests for Settings.gd (5 tests)
- Add unit tests for Playlist.gd (11 tests)
- Add integration tests for full flow (10+ tests)
- Add MockHTTPRequest for HTTP request mocking
- Add TestHelpers with utility functions
- Add Gitea Actions CI workflow (.gitea/workflows/test.yml)
- Add .runsettings for GdUnit4 CLI configuration
- Add run_tests.sh for convenient test execution
- Add TESTING.md with comprehensive documentation
- Add TESTING_PLAN.md with implementation details
- Update .gitignore for test artifacts
- Enable GdUnit4 plugin in project.godot

Features:
- Test button functionality (play, pause, search, sync, etc.)
- Mock REST API requests to server
- Test complete application flows
- Run tests in CI (headless mode)
- Run tests in GUI for visual feedback
- JUnit XML and HTML test reports

Generated by OpenCode AI assistant.
2026-10-06 08:32:55 +02:00
Sansan 9a91d969bd Merge pull request 'Made improvements to the Search window, Made small changes to the inspiration list scrolling, Made small changes to the player list, Changed the term game to soundtrack, #28: Made changes to the sync window' (#37) from develop into main
Build / build (push) Successful in 1m6s
Reviewed-on: #37
2026-10-05 22:53:08 +02:00
Sansan 8da6fc442f Made improvements to the Search window, Made small changes to the inspiration list scrolling, Made small changes to the player list, Changed the term game to soundtrack, #28: Made changes to the sync window
Build / build (push) Successful in 1m14s
Publish / build (push) Successful in 1m10s
2026-09-26 16:21:02 +02:00
Sansan 235bdcbcb5 #34: Fixed bug in Character Select when clicking or scrolling
Build / build (push) Successful in 1m6s
1.9.0
2026-05-17 12:14:07 +02:00
Sansan 6989b53dda Merge pull request 'Fixed workflow' (#35) from 1.8.0 into main
Build / build (push) Successful in 1m5s
Publish / build (push) Successful in 1m13s
Reviewed-on: #35
1.8.5
2026-05-16 17:50:05 +02:00
Sansan 899e99864f #9 #32 #33 : New winner screen, new setting for hide beginning and fixed bug
Build / build (push) Successful in 1m25s
2026-05-16 17:46:09 +02:00
Sansan 84608c95c2 Fixed workflow
Publish / build (push) Successful in 1m13s
2026-05-15 11:03:20 +02:00
Sansan acd8d65b95 #29 Small fixes
Build / build (push) Successful in 1m6s
Publish / build (push) Failing after 1m16s
1.8.0
2026-05-15 10:52:26 +02:00