Skip to content

Commit 0ef6446

Browse files
PC: make the route a decision and the boss a fight
Two complaints, one cause: nothing that happened on the way to a boss mattered, and the boss did not either. The map Sector maps were filled by column -- column 4 always a trader, column 6 always a repair bay. Every route crossed both, and the last node before the boss handed back the hull. Measured across 400 generated sectors: 1.87 repair bays each, 100% of routes crossing one, 100% with one in the column before the boss. That is not a branching map, it is a corridor with decorations. Special nodes are now placed one at a time. A trader, and less than half the time a repair bay -- and when both exist they go in the same column on different rows, so it is one or the other and the choice was really made a column earlier by taking the row that could reach the one you wanted. The approach to the boss is always a patrol or an elite. Now: 0.41 repair bays a sector, 42% of sectors offering one at all, 11% forcing it, 0% before the boss. A repair bay is worth the detour instead: 45% of the bar rather than a flat five. Attrition Chasing "why is the ship always full at the boss" turned up something worse: a patrol was worth -0.13 hull. Negative. Two leaks, both invisible without counting. Every kill had a 9% chance of dropping a hull patch, and at eighteen kills a fight that healed more than the fight cost; now 3%. NANOREPAIR healed per node per level, and the Void does not end, so neither did it -- a maxed one outhealed everything the game could do. It heals once a sector. The bosses Each already owned a mechanic; they were all too slow and too sparse to be a fight. New vocabulary shared by all five: walls with exactly one gap, rotating spirals, unaimed rain, pod bursts, beam combs that sweep the arena, and destructible guns bolted to the arena walls -- the edges were where you went to be safe, and a fight with a safe corner is one you win by sitting in it. The campaign ramp is much shallower: sector 1 used to fire at two thirds the rate of sector 5, which made the first boss a formality that taught none of the patterns the second then expected. None of it is random. Same spiral every rotation, always exactly one gap, beam fixed where it aimed when charging began -- hard and unfair are different, and the difference is whether the pattern can be read. Rhythm The first cut of this simply never stopped firing, and that was the mistake: with no window to shoot back a fight stops being something you read and becomes arithmetic. 60 seconds of dodging. They breathe now -- a storm of 3 to 4.4 seconds, then a window of 1.7 to 1.1 where boss, pods and wall guns all fall silent, marked by cyan corner ticks (a full box already means "armoured", and two states that matter cannot share a shape). That took the fight to 45 seconds and let the storm be far nastier than it could otherwise have dared. Difficulty tiers They were set mostly by enemy count -- but an enemy is also salvage, so ACE fielded 35% more of them, banked 35% more crystals, and won more often than CADET. The knob was fighting itself. Tiers now separate on rate of fire, which returns nothing to the player, over a deliberately deep hull bar: a boss that can take a third of your hull in one mistake needs a hull you can afford mistakes with, or "hard" just means "the first hit ends the run". The pilot, which had to be fixed first The test pilot was a repulsion field, and that is exactly the wrong shape for bullet hell: a wall with a gap pushes it along the wall instead of into the hole, a spiral surrounds it so the vectors cancel and it stops. Tuning against it would have meant deleting the good attacks. It now evaluates all nine stick directions by proper closest approach, position against velocity. Validated on the unchanged game: the old pilot scored 7/4/1, the new one 7/6/5 -- never worse, much better where the old one lost to patterns it could not read, which also means the previous balance figures were partly measuring the robot. Measured, twelve seeds a tier: CADET 8/12, PILOT 5/12, ACE 2/12. Boss damage 0.6 -> 4.5 hull, untouched boss fights 57% -> 18%, boss fights 30s -> 45s. Worst frame measured, WARDEN phase 3 in co-op with 102 bullets live: 0.94 ms p99 against 16.7. pc/tools/balance.py is the bench all of that comes from, and it reports the map as well as the fights, because "every route crosses a repair bay" is a fact a win rate will never show you. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AKLqS4PgnCXrzKHDH8Cnzy
1 parent d439216 commit 0ef6446

22 files changed

Lines changed: 959 additions & 191 deletions

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ le jeu. `pc/build_exe.cmd` en fait un `.exe` autonome avec icône.
3232
| La carte de secteur à embranchements | Un des cinq boss |
3333

3434
Déplacement 8 directions, tir automatique, coop 2 joueurs (clavier et/ou
35-
manette), **cinq boss** aux mécaniques distinctes — rayons, escortes, modules
36-
destructibles.
35+
manette), **cinq boss** aux mécaniques distinctes — murs à un seul trou,
36+
spirales, peignes de rayons, escortes, modules destructibles et canons sur les
37+
murs de l'arène. Les motifs sont denses mais jamais aléatoires : ils sont faits
38+
pour être lus, et chaque boss ménage une **fenêtre** où tout se tait, le temps
39+
de riposter. La carte, elle, est un vrai choix — un atelier de réparation n'est
40+
proposé que dans 42 % des secteurs, sur une seule branche, jamais avant le boss.
3741
Zoom toujours entier, arène de taille fixe quelle que soit la résolution, et
3842
20 effets sonores synthétisés au démarrage (aucun fichier audio) —
3943
[à écouter](docs/nova-sound-demo.wav). Le tir étant automatique, `M` fait le

‎docs/img/pc-01-title.png‎

98 Bytes
Loading

‎docs/img/pc-02-map.png‎

-1.14 KB
Loading

‎docs/img/pc-03-fight-crt.png‎

401 Bytes
Loading

‎docs/img/pc-03-fight.png‎

81 Bytes
Loading

‎docs/img/pc-04-boss.png‎

1.1 KB
Loading

‎docs/img/pc-05-trader.png‎

-13 Bytes
Loading

‎docs/img/pc-bosses.png‎

1.16 KB
Loading

‎pc/README.md‎

Lines changed: 145 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -196,21 +196,118 @@ maintenant sa propre mécanique.
196196

197197
| | |
198198
|---|---|
199-
| **SENTINEL** | Plateforme d'artillerie. Éventails de tirs, puis salves circulaires. Lis les trous. |
200-
| **HIVE MOTHER** | Porte-vaisseaux. Ouvre ses baies et lâche des escortes. Le canon est accessoire. |
201-
| **LANCE** | Un canon géant. Charge, **télégraphe**, puis tire un rayon vertical. Toujours esquivable. |
202-
| **BULWARK** | Un cœur derrière deux **modules destructibles**. Tant qu'un module tient, le cœur ne prend qu'un quart des dégâts — la barre de vie passe au bleu pour le dire. |
203-
| **WARDEN** | Tout à la fois, en trois phases : modules, puis rayon, puis escortes. |
199+
| **SENTINEL** | Éventails, puis des **murs avec un seul trou**, puis une spirale par-dessus. Le boss qui enseigne le vocabulaire des quatre autres. |
200+
| **HIVE MOTHER** | Ouvre ses baies et lâche des escortes, arme les **canons muraux**, et fait pleuvoir depuis le haut. |
201+
| **LANCE** | Un rayon, puis deux, puis un **peigne de quatre** qui balaie l'arène d'un bord à l'autre. La colonne sûre finit toujours par ne plus l'être. |
202+
| **BULWARK** | Un cœur derrière deux **modules destructibles** qui tirent en rafales. Tant qu'un module tient, le cœur ne prend qu'un quart des dégâts — la barre passe au bleu pour le dire. Casse-les et le cœur cesse de se retenir. |
203+
| **WARDEN** | Tout à la fois : canons muraux dès le début, rayons en phase 2, baies ouvertes en phase 3, modules toujours dans le chemin. |
204204

205205
Ils partagent un corps — dérive, points de vie, phases selon la coque restante.
206206
Ce qui change est une seule méthode, `act`, dispatchée une fois par image : le
207207
seul endroit où vit la personnalité d'un boss.
208208

209+
### Dense, mais pas injuste
210+
211+
Aucun de ces motifs n'est aléatoire. Une spirale est la même spirale à chaque
212+
tour, un mur a toujours exactement un trou, un rayon est fixé là où il visait
213+
au début de la charge. C'est la différence entre difficile et injuste : le
214+
motif est fait pour être lu et traversé, pas subi. Trois choses existent
215+
uniquement pour ça — la boîte de collision du vaisseau fait 6×9 dans un sprite
216+
de 13×17, un coup à la coque offre 1,4 seconde d'invulnérabilité, et la bombe
217+
nettoie l'écran.
218+
219+
### Le rythme : tempête, puis fenêtre
220+
221+
Le premier jet de ces boss ne s'arrêtait jamais de tirer. C'était l'erreur.
222+
Sans fenêtre pour riposter, un combat cesse d'être quelque chose qu'on lit et
223+
devient de l'arithmétique : **60 secondes** d'esquive à grignoter le boss entre
224+
deux frôlements. Mesuré.
225+
226+
Ils respirent maintenant. Chaque boss alterne une **tempête** (3 à 4,4 s selon
227+
la phase) et une **fenêtre** (1,7 à 1,1 s) pendant laquelle tout se tait —
228+
boss, modules et canons muraux compris. Quatre coins cyan apparaissent autour
229+
du boss pour la signaler ; un cadre complet voudrait dire « blindé », alors la
230+
fenêtre a sa propre forme.
231+
232+
Le résultat mesuré : le combat tombe à **45 secondes**, les dégâts encaissés
233+
passent de 7,4 à 4,5 points de coque, et la tempête peut être bien plus
234+
méchante qu'elle n'aurait osé l'être sans la pause.
235+
236+
### Des canons sur les murs
237+
238+
Le boss tient le milieu de l'écran. Sans les canons muraux, les bords sont
239+
l'endroit où l'on va pour être tranquille — et un combat avec un coin sûr est
240+
un combat qu'on gagne en s'y asseyant. Ils sont **destructibles**, donc nettoyer
241+
un côté est un vrai choix à faire avec les secondes où l'on ne tire pas sur le
242+
boss, et ils **s'arment visiblement** pendant une seconde avant leur premier
243+
tir : un projectile venu du hors-champ n'est pas un motif, c'est une embuscade.
244+
209245
Chaque combat s'ouvre sur une carte qui nomme le boss et dit ce qu'il fait : un
210246
combat qu'on n'a jamais vu ne devrait pas commencer par une surprise qu'on ne
211-
pouvait pas lire. Et leur cadence de tir dépend du secteur — le premier boss se
212-
rencontre souvent avec un vaisseau d'origine, et un motif juste à la vitesse du
213-
secteur 5 n'est pas un motif juste au secteur 1.
247+
pouvait pas lire.
248+
249+
## La carte : un choix, pas un couloir
250+
251+
Un secteur est un graphe à embranchements, et pendant un moment il n'en avait
252+
que l'apparence. Le générateur remplissait des **colonnes entières** : la
253+
colonne 4 était toujours un marchand, la colonne 6 toujours un atelier de
254+
réparation. Quelle que soit la route prise, on croisait les deux — et le
255+
dernier nœud avant le boss rendait la coque à neuf. On pouvait jouer un secteur
256+
n'importe comment et affronter le seigneur de guerre à pleine vie de toute
257+
façon. Rien de ce qui arrivait en chemin ne comptait.
258+
259+
Les nœuds spéciaux sont maintenant posés **un par un**, pas par colonne :
260+
261+
| | avant | maintenant |
262+
|---|---|---|
263+
| Ateliers par secteur | 1,87 | **0,41** |
264+
| Secteurs qui en proposent un | 100 % | **42 %** |
265+
| Toutes les routes en croisent un | 100 % | **11 %** |
266+
| Atelier juste avant le boss | 100 % | **0 %** |
267+
268+
Quand les deux sont présents, **le marchand et l'atelier sont dans la même
269+
colonne, sur deux lignes différentes** : on a l'un ou l'autre, et le choix a
270+
réellement été fait une colonne plus tôt, en prenant la ligne qui pouvait
271+
atteindre celui qu'on voulait. Cristaux ou coque.
272+
273+
La dernière colonne avant le boss est toujours une patrouille ou une patrouille
274+
d'élite. Quelle que soit la route, la dernière chose avant le boss coûte
275+
quelque chose.
276+
277+
En échange, un atelier vaut le détour : il rend **45 % de la barre** au lieu
278+
d'un forfait de 5 points, et si la coque est déjà pleine l'équipage revend le
279+
blindage en trop.
280+
281+
### L'attrition, et pourquoi elle n'existait pas
282+
283+
En cherchant pourquoi on arrivait toujours au boss à pleine vie, la mesure a
284+
donné une réponse inattendue : **une patrouille rapportait −0,13 point de
285+
coque**. Négatif. On finissait un combat en meilleur état qu'on ne l'avait
286+
commencé.
287+
288+
Deux fuites, toutes deux invisibles sans compter :
289+
290+
- chaque ennemi tué avait **9 %** de chances de lâcher un correctif de coque.
291+
À dix-huit morts par combat, cela soignait plus que le combat ne coûtait.
292+
Ramené à 3 %.
293+
- **NANOREPAIR** soignait à chaque nœud, par niveau. Le Vide n'a pas de fin,
294+
donc cette réparation non plus : au niveau 3 elle surpassait tout ce que le
295+
jeu pouvait infliger. Ce n'est pas une amélioration, c'est un interrupteur.
296+
Elle soigne maintenant une fois par secteur.
297+
298+
### Les difficultés séparent enfin
299+
300+
Autre chose que la mesure a trouvée : la difficulté se réglait surtout par le
301+
**nombre** d'ennemis — or un ennemi est aussi du butin. As envoyait 35 %
302+
d'ennemis en plus, donc 35 % de cristaux en plus, donc un meilleur vaisseau, et
303+
gagnait plus souvent que Cadet. Le curseur se battait contre lui-même.
304+
305+
Les trois niveaux se séparent maintenant sur la **cadence de tir** — qui ne
306+
rend rien au joueur — et sur la profondeur de la barre de coque, qui est
307+
volontairement large : un boss capable de prendre un tiers de la coque en une
308+
erreur a besoin d'une coque avec laquelle on peut se permettre des erreurs.
309+
Sinon « difficile » veut seulement dire « le premier coup termine la partie »,
310+
ce qui n'est pas de la difficulté, c'est de la fragilité.
214311

215312
## Structure
216313

@@ -236,6 +333,7 @@ pc/
236333
nova.spec recette PyInstaller
237334
tests/ suite headless
238335
tools/shots.py rend les captures d'écran
336+
tools/balance.py mesure la carte et les combats
239337
tools/make_icon.py dessine l'icône
240338
```
241339

@@ -245,6 +343,7 @@ Tout tourne sans écran (`SDL_VIDEODRIVER=dummy`), donc en intégration continue
245343

246344
```bash
247345
python3 tests/run_all.py # runs complets + budget d'image
346+
python3 tools/balance.py # carte et difficulté, chiffrées
248347
python3 tools/shots.py # régénère les captures
249348
```
250349

@@ -279,19 +378,50 @@ exactement là où il est déjà le plus présent. Les trois régressions (un cr
279378
qui ne coupe rien, une rotation qui saute une variante, l'anti-répétition mal
280379
indexée) ont été introduites volontairement pour vérifier que le test les voit.
281380

381+
### Le pilote automatique est un instrument, et il a fallu le réparer
382+
383+
La première version était un champ de répulsion : additionner un vecteur qui
384+
s'éloigne de chaque projectile proche, et suivre la somme. Ça marche contre du
385+
tir visé et échoue complètement contre tout ce dont un motif de bullet hell est
386+
fait. Un mur avec un trou le pousse **le long** du mur au lieu de le pousser
387+
dans le trou ; une spirale l'entoure, les vecteurs s'annulent, il s'arrête et
388+
meurt.
389+
390+
Mesurer la difficulté des boss avec ce pilote-là, c'était mesurer à quel point
391+
un motif est illisible pour une mauvaise heuristique — et la première chose
392+
qu'il aurait conseillée, c'est de supprimer les bonnes attaques.
393+
394+
Il esquive maintenant comme une personne : pour chacune des neuf directions du
395+
stick, calculer à quelle distance passerait le projectile le plus menaçant, et
396+
prendre la meilleure. Le point d'approche est calculé proprement, position
397+
relative contre vitesse relative, parce qu'un projectile proche mais qui
398+
s'éloigne n'est pas une menace et qu'un projectile lointain qui converge en est
399+
une.
400+
401+
Validation : sur le jeu **inchangé**, l'ancien pilote faisait 7/4/1 et le
402+
nouveau fait **7/6/5**. Il n'est jamais moins bon, et il est beaucoup plus fort
403+
là où l'ancien perdait contre des motifs qu'il ne savait tout simplement pas
404+
lire. Ce qui veut dire au passage que l'ancien équilibrage mesurait en partie
405+
l'incompétence du robot.
406+
407+
Il a une information parfaite, des réflexes parfaits, et **aucune mémoire** :
408+
il n'apprend jamais qu'un motif se répète. Un bon humain est moins bon sur le
409+
premier point et bien meilleur sur le second, ce qui est à peu près la bonne
410+
erreur à avoir quand les chiffres servent à régler la difficulté.
411+
282412
Mesures actuelles :
283413

284414
| | |
285415
|---|---|
286-
| Taux de victoire (bot) | Cadet 7/8 · **Pilote 4/8** · As 1/8 |
287-
| Durée d'une partie gagnée | 9 à 11 min de combat |
288-
| Coût d'une image (p99) | **1,35 ms** sur un budget de 16,7 ms |
416+
| Taux de victoire (bot, 12 graines) | Cadet 8/12 · **Pilote 5/12** · As 2/12 |
417+
| Dégâts pris par boss | **4,5** points de coque (0,6 avant) |
418+
| Combats de boss sans une égratignure | **18 %** (57 % avant) |
419+
| Durée d'un combat de boss | **45 s** (30 s avant) |
420+
| Durée d'une partie gagnée | 12 à 16 min de combat |
421+
| Coût d'une image (p99) | **0,94 ms** sur un budget de 16,7 ms |
422+
| Pire cas mesuré | WARDEN phase 3 en coop, **102 projectiles** à l'écran |
289423
| Construction des sons | 24 échantillons en **0,03 s** au démarrage |
290424

291-
Le pilote automatique a une information parfaite et des réflexes parfaits mais
292-
une stratégie naïve : un humain fera moins bien, ce qui est le bon sens de
293-
l'erreur pour un contrôle de difficulté.
294-
295425
## Licence
296426

297427
MIT, comme le reste du dépôt.

0 commit comments

Comments
 (0)