- Introduce a comprehensive test suite (cli, app, board, tasks, audio) and a new TESTING.md document describing test execution. - Correct vertical offset calculation in `BoardState` to use saturating subtraction and ceiling division. - Clamp initial `Offset` values to the provided maximum in `Offset::new`. - Export `load_audio` from the audio thread module and switch to `LoopedDecoder` for proper OGG looping. - Update several crate versions: clap, ratatui, log, rodio, and bitflags.
9.5 KiB
Mapa kodu i przepływu
Migawka: 2026-10-18. Projekt war_in_tunnels 0.1.0 to prototyp terminalowego RTS. Działają menu, prezentacja planszy, nawigacja, zaznaczanie ścieżki i obsługa audio; pełna symulacja nie jest podłączona do zegara.
Moduły i odpowiedzialność
Wszystkie ścieżki poniżej są względem katalogu głównego repozytorium.
| Obszar | Pliki | Odpowiedzialność |
|---|---|---|
| Start | src/main.rs, src/lib.rs |
CLI, kanały, wątki, terminal, eksporty biblioteki |
| Parametry | src/cli.rs, src/logs.rs |
Clap, walidacja zakresów, opcjonalny logger |
| Pętla aplikacji | src/app/app.rs |
App, inicjalizacja stanów przy pierwszym rysowaniu, wejście, resize, update |
| Zbiór stanów | src/app/state.rs, src/app/states/ |
GameStates, menu, potyczka, ustawienia, perki, umiejętności |
| Routing widoku | src/app/view.rs, src/app/views/ |
View, implementacja Widget for &mut App, layouty i ekrany |
| Wejście | src/app/keybind.rs, src/app/keybindings/ |
Routing według widoku, KEYBINDINGS, Action, Group, mapowanie zdarzeń |
| Plansza | src/app/states/skirmish_states/board.rs |
Tworzenie mapy, kamera, zoom, focus, zaznaczenia, zlecanie i szkic wykonywania zadań |
| Współrzędne | focused_cell.rs, offset.rs, marked_cells.rs, zoom_level.rs w skirmish_states/ |
Granice kursora i przewijania, ścieżka zaznaczenia, rozmiar komórek |
| Obiekty gry | skirmish_states/structures/, units/, tasks/ |
Traity Structure, Unit, Task i delegujące enumy Structures, Units, Tasks |
| Widgety | src/app/widgets/ |
Plansza, komórka, panel boczny i legenda klawiszy |
| Pomocniczy layout | src/app/helpers/ |
Obszar komórek, tytuły bloków i tekst opcji menu |
| Wątki | src/app/threads/{events,tick,audio}.rs |
Crossterm, zegar 33 ms, Rodio i komendy audio |
| Środowisko | Cargo.toml, flake.nix, default.nix, flake.lock |
Zależności, toolchain Fenix, ALSA, pakowanie przez Naersk lub buildRustPackage |
| Zasoby | soundtracks/default/, ui_draft/ |
Dwa pliki OGG i tekstowe szkice ekranów |
Ścieżki skrócone skirmish_states/ oznaczają src/app/states/skirmish_states/. Nie ma osobnego silnika gry, bazy danych ani warstwy sieciowej. Zależności Cargo: Ratatui/Crossterm, Clap, Rodio, log/simplelog i once_cell.
Uruchomienie i pętla
flowchart TD
CLI[Cli::parse / get_args] --> MAIN[main]
MAIN --> INPUT[Wątek handle_ct_events]
MAIN --> TICK[Wątek handle_tick_event: 33 ms]
MAIN --> AUDIO[Wątek handle_audio, jeśli włączony]
MAIN --> INIT[ratatui::init / App::new]
INIT --> LOOP[App::run]
INPUT -->|kanały key i resize| LOOP
TICK -->|kanał tick| LOOP
LOOP --> DRAW[terminal.draw / App::draw]
DRAW --> STATES[GameStates::new przy pierwszym rozmiarze okna]
LOOP --> KEYS[handle_keybindings]
KEYS -->|AudioCmd| AUDIO
LOOP --> UPDATE[App::update / SkirmishState::tick_update]
LOOP --> SLEEP[sleep 10 ms]
SLEEP --> LOOP
LOOP -->|exit lub błąd| RESTORE[ratatui::restore]
Kanały to standardowe std::sync::mpsc. Stan gry mutuje główny wątek. Każdy obrót pętli najpierw rysuje, następnie próbuje odebrać po jednym zdarzeniu z każdego kanału. Aktualizacja potyczki odbywa się niezależnie od wybranego ekranu. tick_update obecnie tylko zwiększa turn_counter; self.board.advance_turn() jest zakomentowane.
GameStates::new tworzy wszystkie stany jednocześnie. SettingsState przechowuje parametry z CLI; samo istnienie parametrów ekonomii, XP i trybu gry nie oznacza zaimplementowanych mechanik. View zawiera MainMenu, Skirmish, PerkDecks i SkillsConfig. Dwa ostatnie używają default_view z napisem „Work in progress”; ustawienia nie mają osobnego wariantu View.
Renderowanie i dane
flowchart LR
APP[App + GameStates] --> ROUTE[View::render]
ROUTE --> MENU[main_menu_view]
ROUTE --> SK[skirmish_view i layouty]
ROUTE --> WIP[default_view]
SK --> BOARD[BoardWidget]
SK --> SIDE[SidePanelWidget]
SK --> HELP[KeybindingsWidget]
BOARD --> CELL[CellWidget]
CELL --> STRUCT[Structures / Structure]
CELL --> UNIT[Option Units / Unit]
UNIT --> TASK[VecDeque Tasks / Task]
BoardState.cells: Vec<Vec<CellWidget>> łączy model i prezentację: komórka zawiera strukturę, opcjonalną jednostkę, pozycję i flagi UI. BoardWidget wybiera widoczne komórki przez offsety i layout; CellWidget odpowiada za ich tekst i styl. Panel boczny prezentuje dane zaznaczenia. Zoom wpływa na wielkość komórek i liczbę widocznych wierszy/kolumn; resize i przełączanie panelu zmieniają granice offsetów.
Mapa powstaje deterministycznie: kamień, baza gracza w (map_height / 2, 2), baza przeciwnika w (map_height / 2, map_width - 3), sąsiadujące złoża i po jednym górniku w bazie. CLI domyślnie wybiera mapę 42 × 11; dopuszczalne zakresy to szerokość 36–108 i wysokość 11–39. Dostępne struktury to Base, Ore, Stone, Tunnel; jedyna jednostka to Miner, jedyne zadanie to Digging.
Klawisze i zlecanie kopania
handle_ct_eventsodbiera zdarzenie Crossterm i przekazuje je kanałem doApp.handle_keybindingswybiera obsługę menu, potyczki albo domyślną.event_to_actionporównuje kod, rodzaj i modyfikatory zKEYBINDINGS, następnie obsługuje znak wildcard.- Akcje wspólne realizują wyjście, powrót do menu i komendy audio.
- W potyczce ruch zmienia focus i ewentualnie kamerę; zoom przelicza rozmiary; Tab przełącza panel.
- Spacja przełącza zaznaczanie.
start_marking_cellszapamiętuje jednostkę z komórki początkowej; ruch dopisuje współrzędne do kolejki. Backspace cofa ścieżkę, Delete ją anuluje. end_marking_cells(false)pomija pierwsze pole, tworzyDiggingTaskz pozostałej ścieżki i siły kopania oraz przekazuje zadanie jednostce.
Projektowane wykonanie: advance_turn skanuje komórki, wyjmuje jednostkę przez take(), obsługuje pierwsze zadanie i przenosi jednostkę na cel, zastępując strukturę tunelem. Ten przepływ istnieje w kodzie, ale nie jest obecnie wywoływany przez tick.
Audio i pakowanie
Główny wątek wysyła AudioCmd; osobny wątek obsługuje głośność, mute, część i soundtrack. Cache wykorzystuje Lazy, RwLock, Arc i bajty OGG. Calm ładuje calm.ogg; Buildup, Assault i Outro korzystają obecnie z test.ogg. Błędy urządzenia, odczytu i dekodowania mogą zakończyć wątek przez expect.
Rodio zapętla źródło przez Decoder::new_looped. Wątek sterujący blokuje się na odbiorniku (for cmd in rx), zamiast aktywnie sprawdzać komendy i koniec utworu. Zmiana części lub soundtracku dopisuje zapętlone źródło i pomija bieżące przez skip_one. Zamknięcie kanału kończy wątek. Test audio_repeats_after_end_of_file sprawdza przekroczenie końca rzeczywistego pliku OGG bez urządzenia audio.
flake.nix dostarcza devShell dla x86_64-linux, domyślny pakiet z default.nix i pakiet .#develop budowany przez Naersk. Obie ścieżki pakowania kopiują soundtracki do $out/bin i zmieniają nazwę binarki na war-in-tunnels. default.nix ma stały cargoHash; po zmianie zależności wymaga osobnej weryfikacji. Samo cargo test nie sprawdza pakowania.
Punkty wymagające uwagi przed dalszą implementacją
To obserwacje statyczne, nie lista napraw wykonanych w tej sesji:
- Symulacja wyłączona:
SkirmishState::tick_updatenie wywołujeadvance_turn. - Postęp zadania: naprawiono zapisywanie przyrostu i ograniczenie do 0–100; zachowanie jest objęte testami w
tests/tasks.rs. Nie oznacza to uruchomienia symulacji. - Ruch w szkicu symulacji:
advance_turnpobiera cel bez warunku osiągnięcia postępu, nie aktualizuje współrzędnych jednostki i nie usuwa zakończonego zadania z kolejki. Jednostka przeniesiona na późniejszą komórkę może zostać obsłużona ponownie w tym samym skanowaniu. Zapis celu może nadpisać inną jednostkę i strukturę. Przed aktywacją potrzebne są reguły kolizji i testy pojedynczej tury. - Zaznaczenie: jednostka startowa jest zapamiętywana oddzielnie; przy uruchomieniu ruchu trzeba sprawdzić ważność jej pozycji i zachowanie dla pustej komórki oraz jednostki przeciwnika.
- Audio: usunięto aktywne odpytywanie; pomiar CPU na docelowym urządzeniu i odsłuch pozostają do wykonania. Test dekodera nie weryfikuje urządzenia audio.
- Granice UI: małe terminale, resize i trzy poziomy zoomu wymagają interaktywnego sprawdzenia. Indeksowanie planszy zakłada poprawne współrzędne.
- Pokrycie: 23 testy CLI, sterowania, renderowania, planszy, zadań i dekodowania audio; szczegóły w TESTING.md. Brak testów pełnej symulacji i konfiguracji CI. Clippy zgłasza 51 ostrzeżeń; nie uruchamiać automatycznych poprawek poza zakresem zadania.
Gdzie zacząć konkretną zmianę
| Zadanie | Główne miejsca i powiązania |
|---|---|
| Nowa akcja | keybindings/keybindings.rs → handler widoku → lista akcji w views/ |
| Mechanika kopania | tasks/digging.rs → units/ → board.rs::advance_turn → skirmish.rs::tick_update |
| Nowy obiekt | Trait i enum w structures/ lub units/, eksporty mod.rs, tworzenie mapy, komórka i panel |
| Kamera i zoom | board.rs, focused_cell.rs, offset.rs, zoom_level.rs, helpers/cells_area.rs, layout i widget planszy |
| Nowy ekran | View, GameStates, states/, views/, routing klawiszy i menu |
| Parametr CLI | cli.rs, walidacja, GameStates::new, faktyczny konsument i README |
| Audio lub dystrybucja | threads/audio.rs, main.rs, zasoby, flake.nix, default.nix |