Files
war-in-tunnels/docs/CODE_MAP.md
T
GarandPLG fcdbbaf74c Add tests, fix offset logic, and bump dependencies
- 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.
2026-10-18 20:29:04 +02:00

118 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```mermaid
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
```mermaid
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
1. `handle_ct_events` odbiera zdarzenie Crossterm i przekazuje je kanałem do `App`.
2. `handle_keybindings` wybiera obsługę menu, potyczki albo domyślną.
3. `event_to_action` porównuje kod, rodzaj i modyfikatory z `KEYBINDINGS`, następnie obsługuje znak wildcard.
4. Akcje wspólne realizują wyjście, powrót do menu i komendy audio.
5. W potyczce ruch zmienia focus i ewentualnie kamerę; zoom przelicza rozmiary; Tab przełącza panel.
6. Spacja przełącza zaznaczanie. `start_marking_cells` zapamiętuje jednostkę z komórki początkowej; ruch dopisuje współrzędne do kolejki. Backspace cofa ścieżkę, Delete ją anuluje.
7. `end_marking_cells(false)` pomija pierwsze pole, tworzy `DiggingTask` z 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_update` nie wywołuje `advance_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_turn` pobiera 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](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` |