fletchtime 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {fletchtime-0.2.2/src/fletchtime.egg-info → fletchtime-0.2.4}/PKG-INFO +20 -8
  2. {fletchtime-0.2.2 → fletchtime-0.2.4}/README.md +19 -7
  3. fletchtime-0.2.4/REMERCIEMENTS.md +30 -0
  4. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/architecture.md +47 -1
  5. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/dev-guide/index.md +34 -0
  6. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/index.md +3 -1
  7. fletchtime-0.2.4/docs/premier-club.md +113 -0
  8. fletchtime-0.2.4/docs/remerciements.md +5 -0
  9. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/roadmap.md +17 -7
  10. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/__main__.py +97 -14
  11. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/_version.py +3 -3
  12. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/models.py +9 -9
  13. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/sequence.py +3 -2
  14. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/logging_setup.py +28 -3
  15. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/runtime.py +3 -1
  16. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/server/http_static.py +22 -4
  17. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/control.html +19 -2
  18. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/display.html +38 -11
  19. {fletchtime-0.2.2 → fletchtime-0.2.4/src/fletchtime.egg-info}/PKG-INFO +20 -8
  20. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime.egg-info/SOURCES.txt +4 -1
  21. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime.egg-info/scm_file_list.json +4 -1
  22. fletchtime-0.2.4/src/fletchtime.egg-info/scm_version.json +8 -0
  23. fletchtime-0.2.4/tests/test_main_cli.py +152 -0
  24. {fletchtime-0.2.2 → fletchtime-0.2.4}/tests/test_match_server.py +16 -1
  25. {fletchtime-0.2.2 → fletchtime-0.2.4}/tests/test_runtime.py +13 -0
  26. fletchtime-0.2.2/src/fletchtime/web/_defaults/gui/theme.json +0 -64
  27. fletchtime-0.2.2/src/fletchtime.egg-info/scm_version.json +0 -8
  28. {fletchtime-0.2.2 → fletchtime-0.2.4}/.github/workflows/build.yml +0 -0
  29. {fletchtime-0.2.2 → fletchtime-0.2.4}/.github/workflows/docs.yml +0 -0
  30. {fletchtime-0.2.2 → fletchtime-0.2.4}/.github/workflows/test.yml +0 -0
  31. {fletchtime-0.2.2 → fletchtime-0.2.4}/.gitignore +0 -0
  32. {fletchtime-0.2.2 → fletchtime-0.2.4}/CONTRIBUTING.md +0 -0
  33. {fletchtime-0.2.2 → fletchtime-0.2.4}/LICENSE +0 -0
  34. {fletchtime-0.2.2 → fletchtime-0.2.4}/config/app.toml +0 -0
  35. {fletchtime-0.2.2 → fletchtime-0.2.4}/config/flint.toml +0 -0
  36. {fletchtime-0.2.2 → fletchtime-0.2.4}/config/indoor.toml +0 -0
  37. {fletchtime-0.2.2 → fletchtime-0.2.4}/demo.py +0 -0
  38. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/_static/logo.svg +0 -0
  39. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/api-reference.md +0 -0
  40. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/conf.py +0 -0
  41. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/requirements.txt +0 -0
  42. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/specifications.md +0 -0
  43. {fletchtime-0.2.2 → fletchtime-0.2.4}/docs/user-guide/index.md +0 -0
  44. {fletchtime-0.2.2 → fletchtime-0.2.4}/fletchtime.spec +0 -0
  45. {fletchtime-0.2.2 → fletchtime-0.2.4}/pyproject.toml +0 -0
  46. {fletchtime-0.2.2 → fletchtime-0.2.4}/run_server.py +0 -0
  47. {fletchtime-0.2.2 → fletchtime-0.2.4}/run_tests.py +0 -0
  48. {fletchtime-0.2.2 → fletchtime-0.2.4}/scripts/generate_classic_sounds.py +0 -0
  49. {fletchtime-0.2.2 → fletchtime-0.2.4}/setup.cfg +0 -0
  50. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/__init__.py +0 -0
  51. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/__init__.py +0 -0
  52. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/engine.py +0 -0
  53. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/modes/__init__.py +0 -0
  54. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/modes/base.py +0 -0
  55. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/modes/flint.py +0 -0
  56. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/modes/indoor.py +0 -0
  57. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/engine/turn_modes.py +0 -0
  58. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/gui.py +0 -0
  59. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/server/__init__.py +0 -0
  60. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/server/config_store.py +0 -0
  61. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/server/match_server.py +0 -0
  62. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/server/ws_server.py +0 -0
  63. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/__init__.py +0 -0
  64. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/banners/README.md +0 -0
  65. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/club/README.md +0 -0
  66. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/README.md +0 -0
  67. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/countdown_tick.wav +0 -0
  68. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/emergency_end.wav +0 -0
  69. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/emergency_start.wav +0 -0
  70. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/end_of_match.wav +0 -0
  71. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/end_of_volee.wav +0 -0
  72. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/pause_end.wav +0 -0
  73. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/pause_start.wav +0 -0
  74. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/prep_start.wav +0 -0
  75. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/shoot_start.wav +0 -0
  76. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/sounds/packs/classic/warning_orange.wav +0 -0
  77. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/targets/flint_20cm_4spot.jpg +0 -0
  78. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/targets/flint_35cm_1spot.jpg +0 -0
  79. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/targets/indoor_compound.jpg +0 -0
  80. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/_defaults/targets/indoor_recurve.jpg +0 -0
  81. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/config.html +0 -0
  82. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/i18n.js +0 -0
  83. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/index.html +0 -0
  84. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/logo.svg +0 -0
  85. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/manual.html +0 -0
  86. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/screenshots/config.png +0 -0
  87. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/screenshots/control.png +0 -0
  88. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/screenshots/display.png +0 -0
  89. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/screenshots/index.png +0 -0
  90. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime/web/theme.js +0 -0
  91. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime.egg-info/dependency_links.txt +0 -0
  92. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime.egg-info/entry_points.txt +0 -0
  93. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime.egg-info/requires.txt +0 -0
  94. {fletchtime-0.2.2 → fletchtime-0.2.4}/src/fletchtime.egg-info/top_level.txt +0 -0
  95. {fletchtime-0.2.2 → fletchtime-0.2.4}/tests/test_config_store.py +0 -0
  96. {fletchtime-0.2.2 → fletchtime-0.2.4}/tests/test_engine.py +0 -0
  97. {fletchtime-0.2.2 → fletchtime-0.2.4}/tests/test_flint_mode.py +0 -0
  98. {fletchtime-0.2.2 → fletchtime-0.2.4}/tests/test_indoor_mode.py +0 -0
  99. {fletchtime-0.2.2 → fletchtime-0.2.4}/web/assets/banners/README.md +0 -0
  100. {fletchtime-0.2.2 → fletchtime-0.2.4}/web/assets/sounds/packs/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fletchtime
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Chronométrage de compétitions d'archerie FFTL (Indoor, Flint) -- serveur + interfaces web incluses
5
5
  License: GPL-3.0-or-later
6
6
  Requires-Python: >=3.11
@@ -21,7 +21,7 @@ Dynamic: license-file
21
21
 
22
22
  <img src="https://mrfanghodev.github.io/fletchtime/_static/logo.svg" width="80" height="80" alt="FletchTime logo">
23
23
 
24
- [![Python](https://img.shields.io/badge/python-version-from-pep-621-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FMrFanghoDev%2Ffletchtime%2Fmaster%2Fpyproject.toml)](pyproject.toml)
24
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](pyproject.toml)
25
25
  [![Licence](https://img.shields.io/github/license/MrFanghoDev/fletchtime)](LICENSE)
26
26
  [![Tests](https://github.com/MrFanghoDev/fletchtime/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/MrFanghoDev/fletchtime/actions/workflows/test.yml)
27
27
  [![PyPI](https://img.shields.io/pypi/v/fletchtime)](https://pypi.org/project/fletchtime/)
@@ -38,6 +38,13 @@ compris Pydroid 3. Ajoute `--headless` pour retrouver l'ancien mode
38
38
  terminal (aussi utilisé automatiquement si la fenêtre ne peut pas se
39
39
  charger, ex. `customtkinter` absent).
40
40
 
41
+ En mode terminal, `fletchtime --headless --help` liste toutes les
42
+ options -- notamment `-v`/`--verbose` et `-d`/`--debug` (plus de détail
43
+ dans le journal), et `--http-port`/`--ws-port` pour remplacer les ports
44
+ configurés le temps d'un seul lancement (ex. plusieurs salles de
45
+ compétition sur un même PC via un script, sans dossier séparé par salle
46
+ -- voir plus bas pour l'approche par copie de dossier).
47
+
41
48
  ## Installation
42
49
 
43
50
  Trois façons d'obtenir et faire tourner FletchTime, selon ton matériel --
@@ -105,12 +112,14 @@ Autoriser une application via le pare-feu* -- coche `FletchTime.exe` pour
105
112
  les réseaux privés.
106
113
 
107
114
  **Ports réseau utilisés** : **8000** en HTTP (pages web) et **8765** en
108
- WebSocket (synchronisation temps réel) -- deux ports séparés, tous deux
109
- nécessaires. Si le serveur tourne dans un conteneur/VM (Docker, WSL2...),
110
- les deux doivent être redirigés vers l'hôte, pas seulement le 8000 : sans
111
- le 8765, les pages se chargent normalement mais restent bloquées sur "en
112
- attente de connexion" indéfiniment (la synchronisation temps réel ne peut
113
- jamais s'établir).
115
+ WebSocket (synchronisation temps réel) par défaut -- deux ports séparés,
116
+ tous deux nécessaires, modifiables (fenêtre graphique, `config/gui.toml`,
117
+ ou `--http-port`/`--ws-port` en ligne de commande). Si le serveur tourne
118
+ dans un conteneur/VM (Docker, WSL2...), les deux doivent être redirigés
119
+ vers l'hôte, pas seulement le port HTTP : sans le port WebSocket, les
120
+ pages se chargent normalement mais restent bloquées sur "en attente de
121
+ connexion" indéfiniment (la synchronisation temps réel ne peut jamais
122
+ s'établir).
114
123
 
115
124
  Pour construire ces exécutables toi-même :
116
125
  voir `.github/workflows/build.yml` et `fletchtime.spec` (PyInstaller). Un
@@ -156,6 +165,9 @@ Envie de proposer un correctif, une idée, ou juste signaler un bug ? Voir
156
165
  [CONTRIBUTING.md](CONTRIBUTING.md) (français/anglais) -- processus
157
166
  volontairement simple, flux classique fork/branche/Pull Request.
158
167
 
168
+ Merci aux membres du club qui ont testé l'outil et proposé des idées au
169
+ fil du développement -- voir [REMERCIEMENTS.md](REMERCIEMENTS.md).
170
+
159
171
  ## Qualité continue
160
172
 
161
173
  Chaque push et pull request déclenche `.github/workflows/test.yml` :
@@ -1,6 +1,6 @@
1
1
  <img src="https://mrfanghodev.github.io/fletchtime/_static/logo.svg" width="80" height="80" alt="FletchTime logo">
2
2
 
3
- [![Python](https://img.shields.io/badge/python-version-from-pep-621-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FMrFanghoDev%2Ffletchtime%2Fmaster%2Fpyproject.toml)](pyproject.toml)
3
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](pyproject.toml)
4
4
  [![Licence](https://img.shields.io/github/license/MrFanghoDev/fletchtime)](LICENSE)
5
5
  [![Tests](https://github.com/MrFanghoDev/fletchtime/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/MrFanghoDev/fletchtime/actions/workflows/test.yml)
6
6
  [![PyPI](https://img.shields.io/pypi/v/fletchtime)](https://pypi.org/project/fletchtime/)
@@ -17,6 +17,13 @@ compris Pydroid 3. Ajoute `--headless` pour retrouver l'ancien mode
17
17
  terminal (aussi utilisé automatiquement si la fenêtre ne peut pas se
18
18
  charger, ex. `customtkinter` absent).
19
19
 
20
+ En mode terminal, `fletchtime --headless --help` liste toutes les
21
+ options -- notamment `-v`/`--verbose` et `-d`/`--debug` (plus de détail
22
+ dans le journal), et `--http-port`/`--ws-port` pour remplacer les ports
23
+ configurés le temps d'un seul lancement (ex. plusieurs salles de
24
+ compétition sur un même PC via un script, sans dossier séparé par salle
25
+ -- voir plus bas pour l'approche par copie de dossier).
26
+
20
27
  ## Installation
21
28
 
22
29
  Trois façons d'obtenir et faire tourner FletchTime, selon ton matériel --
@@ -84,12 +91,14 @@ Autoriser une application via le pare-feu* -- coche `FletchTime.exe` pour
84
91
  les réseaux privés.
85
92
 
86
93
  **Ports réseau utilisés** : **8000** en HTTP (pages web) et **8765** en
87
- WebSocket (synchronisation temps réel) -- deux ports séparés, tous deux
88
- nécessaires. Si le serveur tourne dans un conteneur/VM (Docker, WSL2...),
89
- les deux doivent être redirigés vers l'hôte, pas seulement le 8000 : sans
90
- le 8765, les pages se chargent normalement mais restent bloquées sur "en
91
- attente de connexion" indéfiniment (la synchronisation temps réel ne peut
92
- jamais s'établir).
94
+ WebSocket (synchronisation temps réel) par défaut -- deux ports séparés,
95
+ tous deux nécessaires, modifiables (fenêtre graphique, `config/gui.toml`,
96
+ ou `--http-port`/`--ws-port` en ligne de commande). Si le serveur tourne
97
+ dans un conteneur/VM (Docker, WSL2...), les deux doivent être redirigés
98
+ vers l'hôte, pas seulement le port HTTP : sans le port WebSocket, les
99
+ pages se chargent normalement mais restent bloquées sur "en attente de
100
+ connexion" indéfiniment (la synchronisation temps réel ne peut jamais
101
+ s'établir).
93
102
 
94
103
  Pour construire ces exécutables toi-même :
95
104
  voir `.github/workflows/build.yml` et `fletchtime.spec` (PyInstaller). Un
@@ -135,6 +144,9 @@ Envie de proposer un correctif, une idée, ou juste signaler un bug ? Voir
135
144
  [CONTRIBUTING.md](CONTRIBUTING.md) (français/anglais) -- processus
136
145
  volontairement simple, flux classique fork/branche/Pull Request.
137
146
 
147
+ Merci aux membres du club qui ont testé l'outil et proposé des idées au
148
+ fil du développement -- voir [REMERCIEMENTS.md](REMERCIEMENTS.md).
149
+
138
150
  ## Qualité continue
139
151
 
140
152
  Chaque push et pull request déclenche `.github/workflows/test.yml` :
@@ -0,0 +1,30 @@
1
+ # Remerciements
2
+
3
+ <!-- sphinx-include-start -->
4
+
5
+ FletchTime doit beaucoup aux membres du club (Les Archers
6
+ Libres de Fontaine le Port) qui ont testé l'outil en conditions réelles,
7
+ remonté des bugs, et proposé des idées qui ont façonné le projet au fil
8
+ de son développement.
9
+
10
+ Un grand merci à :
11
+
12
+ - **Fangho** -- Intention initiale
13
+ - **Xavier** -- Primo testeur
14
+ - **Fabrice** -- Concours multi-serveurs
15
+ - **Caro** -- Ne pas oublier les Remerciements!
16
+
17
+ <!--
18
+ Modèle à remplir : une ligne par personne, avec ce qu'elle a apporté si
19
+ tu veux être précis·e (pas obligatoire -- juste le nom suffit aussi).
20
+ Pas d'ordre particulier attendu (alphabétique, chronologique, ou
21
+ simplement dans l'ordre où ça te vient -- comme tu préfères).
22
+ -->
23
+
24
+ ---
25
+
26
+ *Tu as testé FletchTime ou proposé une idée et tu ne te retrouves pas
27
+ dans cette liste ? Fais-le savoir pour qu'on corrige ça.*
28
+
29
+ *Envie de contribuer plus directement (code, documentation) ? Voir
30
+ [CONTRIBUTING.md](CONTRIBUTING.md).*
@@ -28,7 +28,7 @@ installation logicielle sur les tablettes d'affichage.
28
28
  | Composant | Choix | Justification |
29
29
  |---|---|---|
30
30
  | Backend WebSocket | Paquet `websockets` (asyncio) | Pas de FastAPI/Pydantic (dépendance Rust `pydantic-core` à risque sur Android/Pydroid) ni d'uvicorn `[standard]` (extensions C `uvloop`/`httptools`) ; `websockets` a un fallback pur Python si son extension C optionnelle ne compile pas — installable de façon fiable sur Pydroid 3. |
31
- | Serveur HTTP statique | `http.server` (stdlib), port **8000** | Sert les pages et assets sur un port séparé du WebSocket (port **8765**) -- évite l'API instable de combinaison HTTP+WS selon les versions de `websockets` ; utilisé aussi pour la découverte de fichiers (bannières, packs de sons) via le listing de répertoire natif. **Les deux ports doivent être accessibles** depuis les écrans/postes clients (pare-feu, redirection de port si le serveur tourne dans un conteneur/VM -- ex. Docker, WSL2) : rediriger seulement le 8000 charge les pages mais laisse la synchronisation temps réel bloquée ("en attente de connexion" indéfiniment). |
31
+ | Serveur HTTP statique | `http.server` (stdlib), port **8000** par défaut | Sert les pages et assets sur un port séparé du WebSocket (port **8765** par défaut) -- évite l'API instable de combinaison HTTP+WS selon les versions de `websockets` ; utilisé aussi pour la découverte de fichiers (bannières, packs de sons) via le listing de répertoire natif. Les deux ports sont modifiables (fenêtre graphique, `config/gui.toml`, ou `--http-port`/`--ws-port` en ligne de commande -- voir {doc}`dev-guide/index`), notamment pour plusieurs salles de compétition sur un même PC. **Les deux ports doivent être accessibles** depuis les écrans/postes clients (pare-feu, redirection de port si le serveur tourne dans un conteneur/VM -- ex. Docker, WSL2) : rediriger seulement le port HTTP charge les pages mais laisse la synchronisation temps réel bloquée ("en attente de connexion" indéfiniment). |
32
32
  | Communication temps réel | WebSocket | Évite la dérive du polling, tous les écrans restent synchronisés à la seconde près. |
33
33
  | Frontend | HTML/CSS/JS vanilla | Pas de build, une tablette ouvre juste une URL. |
34
34
  | Config des modes (Indoor/Flint) | Fichiers **TOML** (`config/*.toml`) | Lu via `tomllib`, stdlib depuis Python 3.11 (donc Pydroid) -- zéro dépendance. Écriture via un petit sérialiseur maison (pas de support d'écriture en stdlib). |
@@ -502,6 +502,52 @@ Le poste de contrôle, à l'inverse, affiche une bannière large et alarmante
502
502
  (pas discrète) en cas de coupure -- c'est le responsable du chronométrage
503
503
  qui doit être alerté clairement, pas les archers.
504
504
 
505
+ ## Synchronisation du diaporama de l'écran neutre
506
+
507
+ L'écran neutre (hors concours, ou après sa fin) alterne logo/horloge et
508
+ bannières sponsors -- voir `display.html`, `showSlideshowStep`. Aucune
509
+ coordination serveur pour ça : chaque écran calcule sa slide actuelle en
510
+ divisant l'horloge murale (`Date.now()`) par la durée d'une slide, plutôt
511
+ que d'incrémenter un compteur local à partir de 0 à son propre démarrage.
512
+
513
+ ```{important}
514
+ Une première version utilisait un compteur local (`slideshowStep`,
515
+ incrémenté par `setInterval`) -- deux écrans qui chargeaient ou se
516
+ reconnectaient à des instants différents affichaient alors des slides
517
+ différentes au même moment, chacun étant reparti de 0 à son propre
518
+ démarrage. Corrigé en dérivant la slide actuelle de l'horloge murale
519
+ (`Math.floor(Date.now() / SLIDE_DURATION_MS) % totalSlides`) : deux
520
+ écrans avec des horloges système raisonnablement synchronisées (le cas
521
+ normal sur un même réseau local) calculent alors la même slide,
522
+ indépendamment de quand chacun a démarré. Vérifié avec un vrai navigateur
523
+ (Chromium via Playwright) : deux pages chargées à 3 secondes d'écart
524
+ affichent bien la même slide.
525
+ ```
526
+
527
+ ## Découverte du port WebSocket côté client
528
+
529
+ `display.html` et `control.html` ne connaissent pas à l'avance le port
530
+ WebSocket à utiliser : depuis que les ports sont devenus modifiables
531
+ (voir la fenêtre graphique et `config/gui.toml`, pensé pour plusieurs
532
+ salles de compétition sur un même PC), le coder en dur côté client
533
+ casserait silencieusement toute page si le port avait été changé.
534
+
535
+ Chaque page interroge `/api/version` (servi par le même serveur HTTP qui
536
+ vient de la servir, donc forcément sur le bon port) avant d'ouvrir sa
537
+ connexion WebSocket -- la réponse inclut `ws_port`, le port réellement
538
+ configuré (`ServerRuntime.ws_port`, plombé jusqu'à
539
+ `http_static.start_http_server`). Un échec de cette requête (réseau,
540
+ serveur non démarré) se rabat silencieusement sur `8765` -- l'ancien
541
+ port fixe, qui reste une valeur par défaut raisonnable, jamais une
542
+ erreur bloquante pour l'utilisateur.
543
+
544
+ ```{note}
545
+ Vérifié avec un vrai navigateur (Chromium via Playwright), pas seulement
546
+ en théorie : les deux pages utilisent bien le port récupéré
547
+ dynamiquement, et se rabattent proprement sur 8765 sans planter quand
548
+ `/api/version` échoue.
549
+ ```
550
+
505
551
  ## Multi-écrans et ciblage
506
552
 
507
553
  Chaque écran se connecte au WebSocket et s'enregistre avec son numéro de lane
@@ -133,6 +133,40 @@ dernier commit de `main`, seulement la dernière version taguée -- voir
133
133
  la note correspondante sur {doc}`../index`.
134
134
  ```
135
135
 
136
+ ## Options de la ligne de commande (mode terminal)
137
+
138
+ `fletchtime --headless --help` (ou `python -m fletchtime --headless
139
+ --help`) affiche la liste complète, mais résumé ici pour référence
140
+ rapide -- voir `fletchtime/__main__.py`, `_build_arg_parser`.
141
+
142
+ | Option | Effet |
143
+ |---|---|
144
+ | `-h`, `--help` | Affiche l'aide et quitte. |
145
+ | `-V`, `--version` | Affiche la version et quitte. |
146
+ | `--headless`, `--no-gui` | Mode terminal, sans fenêtre graphique. |
147
+ | `-v`, `--verbose` | Affiche les journaux applicatifs (commandes reçues, (dé)connexions...) dans le terminal, pas seulement dans le fichier. |
148
+ | `-d`, `--debug` | Journalisation la plus détaillée possible, fichier compris -- implique `--verbose`. |
149
+ | `--http-port PORT` | Remplace le port HTTP configuré, pour cette exécution seulement. |
150
+ | `--ws-port PORT` | Remplace le port WebSocket configuré, pour cette exécution seulement. |
151
+
152
+ ```{note}
153
+ `--http-port`/`--ws-port` **ne modifient jamais** `config/gui.toml` --
154
+ un remplacement ponctuel (utile pour un lancement scripté/CI, ou
155
+ plusieurs salles de compétition sur un même PC sans dossier séparé par
156
+ salle), pas un changement persistant. Pour un réglage durable, voir la
157
+ fenêtre graphique (section Ports) ou éditer `config/gui.toml`
158
+ directement.
159
+ ```
160
+
161
+ **Niveaux de journalisation, fichier et terminal indépendants l'un de
162
+ l'autre** (voir `fletchtime.logging_setup.configure_logging`) : le
163
+ fichier de journal (`logs/fletchtime.log`) reste toujours à INFO par
164
+ défaut, quelle que soit la commande utilisée pour lancer FletchTime --
165
+ le diagnostic après-coup d'un concours ne doit pas dépendre de si
166
+ quelqu'un a pensé à ajouter `-v`. Seul le terminal respecte
167
+ `-v`/`--verbose` (silencieux par défaut, WARNING). `--debug` élève les
168
+ deux au niveau DEBUG.
169
+
136
170
  ## Architecture générale
137
171
 
138
172
  Voir {doc}`../architecture` pour le détail complet. En résumé :
@@ -4,7 +4,7 @@
4
4
  :align: center
5
5
  ```
6
6
 
7
- [![Python](https://img.shields.io/badge/python-version-from-pep-621-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FMrFanghoDev%2Ffletchtime%2Fmaster%2Fpyproject.toml)](https://github.com/MrFanghoDev/fletchtime/blob/master/pyproject.toml)
7
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://github.com/MrFanghoDev/fletchtime/blob/master/pyproject.toml)
8
8
  [![Licence](https://img.shields.io/github/license/MrFanghoDev/fletchtime)](https://github.com/MrFanghoDev/fletchtime/blob/master/LICENSE)
9
9
  [![Tests](https://github.com/MrFanghoDev/fletchtime/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/MrFanghoDev/fletchtime/actions/workflows/test.yml)
10
10
  [![PyPI](https://img.shields.io/pypi/v/fletchtime)](https://pypi.org/project/fletchtime/)
@@ -27,9 +27,11 @@ page d'accueil, pas ici) -- voir {doc}`user-guide/index`.
27
27
  :maxdepth: 2
28
28
  :caption: Contenu
29
29
 
30
+ premier-club
30
31
  specifications
31
32
  architecture
32
33
  roadmap
34
+ remerciements
33
35
  user-guide/index
34
36
  dev-guide/index
35
37
  api-reference
@@ -0,0 +1,113 @@
1
+ # Découvrir FletchTime pour son club
2
+
3
+ Cette page s'adresse à quelqu'un qui découvre FletchTime pour la première
4
+ fois -- un⋅e archer⋅ère, un⋅e responsable de club. Pas besoin de
5
+ connaître quoi que ce soit du projet au préalable.
6
+
7
+ ## C'est quoi
8
+
9
+ FletchTime est un logiciel de chronométrage pour les compétitions
10
+ d'archerie FFTL (Indoor et Flint) : il gère le décompte du temps de tir,
11
+ les phases de préparation et de repos, les sons qui rythment le
12
+ concours, sur autant d'écrans que nécessaire. Gratuit, open source, né
13
+ de l'usage réel d'un club.
14
+
15
+ ## Pourquoi s'en servir
16
+
17
+ ::::{grid} 2
18
+ :gutter: 3
19
+
20
+ :::{grid-item-card} 📺 Un écran par pas de tir
21
+ Pas juste un chrono près du starter -- chaque archer voit le temps
22
+ restant sans avoir à se retourner.
23
+ :::
24
+
25
+ :::{grid-item-card} 🔊 Les sons rythment le concours tout seuls
26
+ Début de préparation, passage à l'orange, fin de volée... plus besoin
27
+ qu'une personne déclenche chaque signal à la main.
28
+ :::
29
+
30
+ :::{grid-item-card} 💻 Du matériel qu'un club a probablement déjà
31
+ Un vieux PC, des tablettes ou téléphones pour les écrans -- pas de
32
+ matériel spécialisé à acheter.
33
+ :::
34
+
35
+ :::{grid-item-card} 🔌 Résiste aux coupures et aux plantages
36
+ Le chrono ne perd pas le fil, la reprise se fait sans intervention
37
+ manuelle -- voir {doc}`architecture` pour le détail technique.
38
+ :::
39
+
40
+ ::::
41
+
42
+ ## Ce qu'il faut avant de commencer
43
+
44
+ - Un appareil pour héberger le serveur (PC ou téléphone selon la méthode
45
+ d'installation choisie) -- pas besoin d'être puissant.
46
+ - Un réseau WiFi local reliant cet appareil et les écrans.
47
+ - Des écrans pour l'affichage : tablettes, téléphones, ou même un vieux
48
+ moniteur relié à un PC secondaire.
49
+ - Aucune compétence technique nécessaire pour l'usage courant -- voir
50
+ plus bas si l'installation elle-même te semble intimidante.
51
+
52
+ ## Premiers pas
53
+
54
+ 1. **Installer** : plusieurs façons possibles selon le matériel -- voir le
55
+ [README](https://github.com/MrFanghoDev/fletchtime#installation)
56
+ pour le détail de chacune.
57
+ 2. **Premier lancement** : une fenêtre s'ouvre avec les adresses à
58
+ utiliser depuis les autres appareils du réseau.
59
+ 3. **Ouvrir la page de contrôle** depuis l'appareil hôte ou n'importe
60
+ quel appareil du réseau, choisir le mode (Indoor ou Flint), ajuster
61
+ les réglages si besoin (temps de tir, nombre de volées...).
62
+ 4. **Ouvrir la page d'affichage** sur chaque écran destiné aux archers.
63
+ 5. **Faire un match d'essai** avant le premier vrai concours -- le
64
+ temps de se familiariser avec les boutons (démarrer, pause, urgence)
65
+ sans pression.
66
+
67
+ Une fois ces cinq étapes passées une fois, le pilotage d'un vrai
68
+ concours se résume à quelques clics : voir le manuel utilisateur
69
+ intégré à l'application (accessible depuis sa page d'accueil) pour le
70
+ détail de chaque réglage et bouton.
71
+
72
+ ## Peut-on lui faire confiance
73
+
74
+ Question légitime avant de l'utiliser en compétition officielle :
75
+
76
+ - **Open source** : le code est public, inspectable par qui veut --
77
+ rien de caché.
78
+ - **Testé en conditions réelles de concours**, pas seulement "ça
79
+ compile" -- voir {doc}`roadmap` pour l'historique des versions et ce
80
+ qui a été vérifié en pratique.
81
+ - **Récupère après un plantage ou un redémarrage du serveur** sans
82
+ perdre la progression du match en cours.
83
+ - Reste un projet de club, sans obligation de résultat ni support
84
+ garanti -- voir la
85
+ [licence](https://github.com/MrFanghoDev/fletchtime/blob/master/LICENSE)
86
+ et le ton du
87
+ [guide de contribution](https://github.com/MrFanghoDev/fletchtime/blob/master/CONTRIBUTING.md)
88
+ pour ce que ça implique concrètement.
89
+
90
+ ## Où chercher de l'aide
91
+
92
+ ::::{grid} 2
93
+ :gutter: 3
94
+
95
+ :::{grid-item-card} 📖 Usage au quotidien
96
+ Réglages, pilotage d'un match : le manuel utilisateur intégré à
97
+ l'application, accessible depuis sa page d'accueil une fois installée.
98
+ :::
99
+
100
+ :::{grid-item-card} 🐛 Un bug, une question
101
+ Les [Issues GitHub](https://github.com/MrFanghoDev/fletchtime/issues).
102
+ :::
103
+
104
+ :::{grid-item-card} 🤝 Envie de contribuer
105
+ Code, documentation, idée -- voir
106
+ [CONTRIBUTING.md](https://github.com/MrFanghoDev/fletchtime/blob/master/CONTRIBUTING.md).
107
+ :::
108
+
109
+ :::{grid-item-card} 🔧 Fonctionnement technique
110
+ Le reste de cette documentation : {doc}`specifications`, {doc}`architecture`.
111
+ :::
112
+
113
+ ::::
@@ -0,0 +1,5 @@
1
+ # Remerciements
2
+
3
+ ```{include} ../REMERCIEMENTS.md
4
+ :start-after: sphinx-include-start
5
+ ```
@@ -56,23 +56,33 @@ dépendent d'extensions compilées (Rust/C) peu fiables sur Pydroid 3, voir
56
56
  (`pip install fletchtime` + commande `fletchtime`), exécutables
57
57
  autoporteurs Windows/Linux, CI (lint + tests à chaque push).
58
58
 
59
- ## Étape 7 — Partage FFTL
59
+ ## Étape 7 — Partage FFTL (hors contact fédération, démarche humaine)
60
60
 
61
61
  - ~~Licence open source claire~~ -- déjà en place : `LICENSE`
62
62
  (GPL-3.0-or-later), cohérent avec `pyproject.toml`.
63
63
  - ~~Guide de contribution~~ -- fait : `CONTRIBUTING.md` (français/anglais),
64
64
  flux classique fork/branche/Pull Request, volontairement simple.
65
- - Nettoyage : vérifier qu'aucun contenu spécifique au club (nom, logo,
66
- détails propres à Les Aigles 77) ne traîne en dur dans le code ou la
67
- doc, en dehors de `web/assets/` c'est normal et attendu.
68
- - Un vrai guide "premier club" : point d'entrée pour quelqu'un qui
69
- découvre l'outil sans le contexte déjà connu (c'est quoi, pourquoi
70
- l'utiliser, comment démarrer).
65
+ - ~~Nettoyage~~ -- fait : balayage complet du dépôt, aucun contenu
66
+ spécifique au club trouvé en dehors de `web/assets/` (normal et
67
+ attendu là). Un vrai souci trouvé au passage, mais côté livraison
68
+ plutôt que dans le dépôt lui-même : un logo de club réel se
69
+ retrouvait dans les archives livrées malgré son exclusion de git --
70
+ corrigé.
71
+ - ~~Guide "premier club"~~ -- fait : {doc}`premier-club`, point d'entrée
72
+ pour quelqu'un qui découvre l'outil sans contexte préalable (c'est
73
+ quoi, pourquoi s'en servir, prérequis, premiers pas, peut-on lui faire
74
+ confiance, où chercher de l'aide).
71
75
  - Contact fédération pour retour d'expérience / adoption éventuelle par
72
76
  d'autres clubs -- démarche humaine, hors du champ du dépôt lui-même.
73
77
 
74
78
  ## Backlog — à discuter / non encore programmé dans une étape précise
75
79
 
80
+ - ~~**Remerciements**~~ -- structure prête, à compléter : `REMERCIEMENTS.md`
81
+ (modèle à remplir avec les noms), lié depuis le README et intégré à la
82
+ doc Sphinx ({doc}`remerciements` -- même contenu, pas dupliqué, via une
83
+ inclusion). Les membres du club ont aidé à tester et proposé des idées
84
+ au fil du développement -- reste à toi d'ajouter les noms.
85
+
76
86
  - ~~**Journal applicatif persistant**~~ -- fait : `MatchServer` journalise
77
87
  désormais chaque commande reçue, (dé)connexion, perte de connexion
78
88
  réseau et message malformé dans un fichier avec rotation
@@ -19,6 +19,8 @@ Deux notions de dossier bien distinctes ici, à ne pas confondre :
19
19
 
20
20
  from __future__ import annotations
21
21
 
22
+ import argparse
23
+ import logging
22
24
  import shutil
23
25
  import signal
24
26
  import socket
@@ -150,7 +152,85 @@ def _print_banner(ip: str, data_root: Path, http_port: int) -> None:
150
152
  print("=" * 60)
151
153
 
152
154
 
153
- def _run_headless() -> None:
155
+ def _build_arg_parser() -> argparse.ArgumentParser:
156
+ parser = argparse.ArgumentParser(
157
+ prog="fletchtime",
158
+ description=(
159
+ "Serveur de chronométrage pour compétitions d'archerie FFTL " "(Indoor et Flint)."
160
+ ),
161
+ )
162
+ parser.add_argument(
163
+ "-V",
164
+ "--version",
165
+ action="version",
166
+ version=f"FletchTime {__version__}",
167
+ )
168
+ parser.add_argument(
169
+ "--headless",
170
+ "--no-gui",
171
+ dest="headless",
172
+ action="store_true",
173
+ help="Mode terminal, sans fenêtre graphique.",
174
+ )
175
+ parser.add_argument(
176
+ "-v",
177
+ "--verbose",
178
+ action="store_true",
179
+ help="Affiche les journaux applicatifs (commandes reçues, "
180
+ "(dé)connexions...) dans le terminal, pas seulement dans le "
181
+ "fichier de journal.",
182
+ )
183
+ parser.add_argument(
184
+ "-d",
185
+ "--debug",
186
+ action="store_true",
187
+ help="Journalisation la plus détaillée possible, fichier compris " "-- implique --verbose.",
188
+ )
189
+ parser.add_argument(
190
+ "--http-port",
191
+ type=int,
192
+ metavar="PORT",
193
+ help="Port HTTP -- remplace la valeur de config/gui.toml pour "
194
+ "cette exécution seulement, sans la modifier (voir aussi la "
195
+ "fenêtre graphique pour un réglage persistant).",
196
+ )
197
+ parser.add_argument(
198
+ "--ws-port",
199
+ type=int,
200
+ metavar="PORT",
201
+ help="Port WebSocket -- remplace la valeur de config/gui.toml "
202
+ "pour cette exécution seulement, sans la modifier.",
203
+ )
204
+ return parser
205
+
206
+
207
+ def _resolve_console_log_level(args: argparse.Namespace) -> int:
208
+ if args.debug:
209
+ return logging.DEBUG
210
+ if args.verbose:
211
+ return logging.INFO
212
+ return logging.WARNING
213
+
214
+
215
+ def _resolve_ports(args: argparse.Namespace, parser: argparse.ArgumentParser) -> tuple[int, int]:
216
+ """Priorité aux options de la ligne de commande sur config/gui.toml,
217
+ sans jamais modifier ce fichier -- une exécution scriptée/CI ne doit
218
+ pas laisser de trace persistante par accident. Revalide les deux
219
+ ports ensemble (même règle que config_store.save_gui_config) même
220
+ si un seul des deux vient de la ligne de commande, pour ne jamais se
221
+ retrouver avec une combinaison invalide (identiques, hors bornes)."""
222
+ gui_config = config_store.load_gui_config()
223
+ http_port = args.http_port if args.http_port is not None else gui_config["http_port"]
224
+ ws_port = args.ws_port if args.ws_port is not None else gui_config["ws_port"]
225
+ for name, port in (("--http-port", http_port), ("--ws-port", ws_port)):
226
+ if not (1 <= port <= 65535):
227
+ parser.error(f"{name} : le port doit être entre 1 et 65535 (reçu {port}).")
228
+ if http_port == ws_port:
229
+ parser.error("--http-port et --ws-port doivent être différents.")
230
+ return http_port, ws_port
231
+
232
+
233
+ def _run_headless(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
154
234
  """Mode terminal classique -- utilisé si l'interface graphique n'a pas
155
235
  pu être chargée (ex. `customtkinter` absent), ou explicitement demandé
156
236
  via `--headless`/`--no-gui`."""
@@ -159,18 +239,18 @@ def _run_headless() -> None:
159
239
  ensure_directories(data_root, app_web_dir)
160
240
  assets_dir = data_root / "web" / "assets"
161
241
 
162
- log_file = configure_logging(data_root / "logs")
242
+ console_level = _resolve_console_log_level(args)
243
+ file_level = logging.DEBUG if args.debug else logging.INFO
244
+ log_file = configure_logging(data_root / "logs", console_level, file_level)
163
245
  print(f"Journal détaillé / Detailed log: {log_file}")
164
246
 
165
- # Ports lus depuis config/gui.toml (mêmes préférences que la fenêtre
166
- # graphique, voir config_store.load_gui_config) -- permet de faire
167
- # tourner plusieurs salles de compétition sur le même PC : une copie
168
- # de dossier par salle, chacune avec son propre gui.toml donnant des
169
- # ports différents. "gui.toml" malgré le nom concerne aussi ce mode
170
- # terminal, pour que les deux se comportent de façon cohérente.
171
- gui_config = config_store.load_gui_config()
172
- http_port = gui_config["http_port"]
173
- ws_port = gui_config["ws_port"]
247
+ # Ports lus depuis config/gui.toml par défaut (mêmes préférences que
248
+ # la fenêtre graphique, voir config_store.load_gui_config), sauf
249
+ # remplacement explicite via --http-port/--ws-port pour cette seule
250
+ # exécution -- permet de faire tourner plusieurs salles de
251
+ # compétition sur un même PC sans dossier séparé par salle, utile
252
+ # pour un lancement scripté/CI par exemple.
253
+ http_port, ws_port = _resolve_ports(args, parser)
174
254
 
175
255
  _print_banner(local_ip(), data_root, http_port)
176
256
 
@@ -193,8 +273,11 @@ def _run_headless() -> None:
193
273
 
194
274
 
195
275
  def main() -> None:
196
- if "--headless" in sys.argv or "--no-gui" in sys.argv:
197
- _run_headless()
276
+ parser = _build_arg_parser()
277
+ args = parser.parse_args()
278
+
279
+ if args.headless:
280
+ _run_headless(args, parser)
198
281
  return
199
282
 
200
283
  try:
@@ -212,7 +295,7 @@ def main() -> None:
212
295
  # repli ne risque pas un conflit de port avec _run_headless().
213
296
  print(f"Interface graphique indisponible ({exc}) -- mode terminal.")
214
297
  print(f"Graphical interface unavailable ({exc}) -- terminal mode.")
215
- _run_headless()
298
+ _run_headless(args, parser)
216
299
 
217
300
 
218
301
  if __name__ == "__main__":
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.4'
22
+ __version_tuple__ = version_tuple = (0, 2, 4)
23
23
 
24
- __commit_id__ = commit_id = 'gceb71f360'
24
+ __commit_id__ = commit_id = 'g5efc3a10b'
@@ -14,15 +14,15 @@ from enum import StrEnum
14
14
  class Phase(StrEnum):
15
15
  """Visual/safety phase of the current step.
16
16
 
17
- WAIT -- before the match starts, or between matches. No countdown.
18
- RED -- preparation time (archers approach / take position).
19
- GREEN -- main shooting time.
20
- ORANGE -- warning period near the end of shooting time.
21
- PAUSE -- end of a volée: archers retrieve arrows, no countdown.
22
- The engine waits here indefinitely until the DOS manually
23
- starts the next volée (``MatchEngine.next()``).
24
- EMERGENCY -- danger signal, clock frozen, must be explicitly resumed.
25
- FINISHED -- sequence exhausted, nothing left to shoot.
17
+ - ``WAIT`` -- before the match starts, or between matches. No countdown.
18
+ - ``RED`` -- preparation time (archers approach / take position).
19
+ - ``GREEN`` -- main shooting time.
20
+ - ``ORANGE`` -- warning period near the end of shooting time.
21
+ - ``PAUSE`` -- end of a volée: archers retrieve arrows, no countdown.
22
+ The engine waits here indefinitely until the DOS manually starts
23
+ the next volée (``MatchEngine.next()``).
24
+ - ``EMERGENCY`` -- danger signal, clock frozen, must be explicitly resumed.
25
+ - ``FINISHED`` -- sequence exhausted, nothing left to shoot.
26
26
  """
27
27
 
28
28
  WAIT = "wait"
@@ -1,7 +1,8 @@
1
1
  """A ``Step`` is one timed segment of a competition (e.g. "red light, 10s,
2
2
  end 3 of 12, turn A-B, distance 18m"). A shooting mode's job is only to
3
- produce an ordered list of ``Step``s up front; the engine then plays that
4
- list back, handling ticking, manual advance and emergency stop.
3
+ produce an ordered list of ``Step`` objects up front; the engine then
4
+ plays that list back, handling ticking, manual advance and emergency
5
+ stop.
5
6
 
6
7
  This keeps modes simple, declarative, and trivial to unit test: you can
7
8
  assert on the exact list of steps a config produces without running any