513 lines
21 KiB
Markdown
513 lines
21 KiB
Markdown
# a-trier.md — Sujets à redistribuer plus tard
|
|
|
|
Ce fichier est volontairement un dépotoir organisé. Il contient des pratiques qui n'ont pas encore trouvé leur place dans [`coding.md`](coding.md), [`csharp.md`](csharp.md), [`unity.md`](unity.md), [`reactive-state.md`](reactive-state.md) ou [`code-review.md`](code-review.md), mais qui méritent d'être formalisées.
|
|
|
|
Quand un paragraphe devient trop gros ici, il est temps de le promouvoir dans son propre markdown.
|
|
|
|
---
|
|
|
|
## Tests : philosophie et organisation
|
|
|
|
### Ce qu'on teste, ce qu'on ne teste pas
|
|
|
|
Je teste par ordre de priorité :
|
|
|
|
1. **La logique métier pure** (Engine, Shared) — obligatoire. Chaque GameEvent a au moins un test. Chaque règle de balance non triviale a son test. Les calculs déterministes (pipeline de bonus, résolution de combat, tri topologique) ont leurs tests.
|
|
2. **Les invariants d'état** — qu'un GameState après N events reste cohérent. Tests de fixtures partagées qui valident qu'on ne peut pas finir dans un état interdit.
|
|
3. **Les formats de sérialisation** — un round-trip `Serialize → Deserialize` sur des samples représentatifs, plus des tests de rétrocompatibilité pour les Union versions.
|
|
4. **Le code de networking** — validation serveur (que les événements impossibles sont rejetés), rollback client.
|
|
|
|
Je teste **peu ou pas** :
|
|
|
|
- Les Display Unity (couverts implicitement par le fait que l'état est testé et que l'UI est passive).
|
|
- Les getters/setters triviaux.
|
|
- Le code généré (MessagePack formatters, migrations EF).
|
|
- Le code tiers.
|
|
|
|
### Structure de test
|
|
|
|
- Projet `Tests/` séparé, référence l'Engine/Shared en tant que projet C# pur.
|
|
- Lancement en `dotnet test`, jamais via Unity Test Runner pour la logique métier.
|
|
- Fixtures partagées dans une classe `Fixtures` statique : `CreateInitialState`, `TestRunes`, `ShallowPool`.
|
|
- Convention de nommage : `MethodOrEvent_Scenario_ExpectedResult`.
|
|
- Structure AAA : Arrange, Act, Assert séparés par commentaires.
|
|
|
|
```csharp
|
|
[TestFixture]
|
|
public class GameEventTests {
|
|
[Test]
|
|
public void PickLootGameEvent_AddsGoldToPlayer() {
|
|
// Arrange
|
|
var state = Fixtures.CreateInitialState(Fixtures.ShallowPool, Fixtures.TestRunes);
|
|
state.CurrentRun.CurrentPhase = Phase.Encounter;
|
|
var loot = new Loot(50, false);
|
|
state.CurrentRun.LootBox.Add(loot);
|
|
|
|
// Act
|
|
new PickLootGameEvent(loot).Apply(state, null);
|
|
|
|
// Assert
|
|
Assert.That(state.CurrentRun.Gold, Is.EqualTo(initialGold + 50));
|
|
}
|
|
}
|
|
```
|
|
|
|
### Filtres de test
|
|
|
|
Pour itérer rapidement :
|
|
|
|
```bash
|
|
dotnet test --filter "FullyQualifiedName~CombatTests"
|
|
dotnet test --filter "FullyQualifiedName~CombatTests.TestBasicCombat_HeroVsTwoEnemies"
|
|
```
|
|
|
|
Je profite de `TestCase` et `TestCaseSource` (NUnit) pour grouper les variations.
|
|
|
|
### Tests Unity Play Mode
|
|
|
|
Rares. Uniquement pour ce qui est intrinsèquement Unity (animations chainées, physics, prefabs). Un test Play Mode qui pourrait être un test NUnit est un test mal rangé.
|
|
|
|
---
|
|
|
|
## Documentation projet : README, CLAUDE.md, GDD
|
|
|
|
Je distingue trois types de docs :
|
|
|
|
### `README.md`
|
|
|
|
- Pitch du projet en 2-3 lignes.
|
|
- Prérequis techniques (.NET, Unity, Node, outils CLI).
|
|
- Comment cloner et lancer (`git config core.symlinks`, `NuGet > Restore`, `dotnet tool install`, `mpc -i ...`).
|
|
- Structure de dossiers haut niveau.
|
|
- Liens vers les docs plus détaillées.
|
|
|
|
### `CLAUDE.md` (ou équivalent agent)
|
|
|
|
Écrit pour qu'un agent (humain ou LLM) débarquant sur le projet puisse contribuer. Contient :
|
|
|
|
- **Project Overview** : une ou deux phrases sur ce que fait le projet.
|
|
- **Commands** : les commandes clés (build, test, run, generation).
|
|
- **Architecture** : la structure de dossiers annotée, les couches, les sources de vérité.
|
|
- **Core Patterns** : les patterns centraux (GameEvent, ConnectedBehaviour, pipeline de bonus, etc.).
|
|
- **Workflow diagrams** : mermaid state diagrams pour les machines d'état complexes.
|
|
- **Coding Standards** : les conventions spécifiques au projet (naming, attributs, composition).
|
|
- **Key Dependencies** : la liste des libs et pourquoi elles sont là.
|
|
|
|
Une règle : si un agent ne peut pas comprendre l'architecture en 15 minutes via ce fichier, c'est le fichier qui est à revoir.
|
|
|
|
### `GDD.md` / `GAME_DESIGN.md` (pour un jeu)
|
|
|
|
- Mécaniques de gameplay (combat, progression, économie).
|
|
- Balance (formules, courbes, constantes).
|
|
- Systèmes thématiques (arcanes, classes, factions).
|
|
- Musique / rythme / animation specs quand c'est intégré au gameplay (comme les 120 BPM ternaires de TheRuneMaker).
|
|
|
|
### Diagrammes Mermaid
|
|
|
|
J'inline les diagrammes dans le markdown. Ils sont :
|
|
|
|
- **versionés avec le code** — pas sur Miro ou LucidChart.
|
|
- **diffables** — un changement de state diagram apparaît clairement en PR.
|
|
- **mis à jour par celui qui casse l'invariant** — si tu modifies la state machine, tu modifies le diagramme.
|
|
|
|
Exemple type pour une state machine de jeu :
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> Build : StartRunGameEvent
|
|
state Build {
|
|
[*] --> Deckbuilding
|
|
Deckbuilding --> Deckbuilding : RerollShopGE / BuyFromShopGE / MoveSlotGE
|
|
Deckbuilding --> PickSpec : PendingSpecialisationLoot?
|
|
}
|
|
Build --> Encounter : StartCombatGE
|
|
```
|
|
|
|
Autre usage : flux client/serveur, pipeline de build, hiérarchie de classes.
|
|
|
|
---
|
|
|
|
## Protocoles CLI LLM-friendly
|
|
|
|
Quand un jeu / système peut tourner en headless, je l'expose via un protocole stdin/stdout structuré pour qu'un LLM (ou un test automatisé) puisse le piloter.
|
|
|
|
### Forme typique
|
|
|
|
Blocs délimités explicitement :
|
|
|
|
```
|
|
---STATE---
|
|
Phase: Build
|
|
String: 0 Encounter: 0
|
|
Lives: 3 Gold: 15
|
|
Hero: HP=100/100 ATK=10
|
|
Rune Graph:
|
|
[slot0] FireRune (action=Attack, arcane=Fire, rarity=1) -> [slot3:ActionRune]
|
|
Effects: +10 Power
|
|
Bench (2 runes, 3 empty):
|
|
[b0] EarthRune (action=Value, arcane=Earth, rarity=1)
|
|
Shop:
|
|
[0] FrostRune (cost=2, action=Value, arcane=Frost)
|
|
---END_STATE---
|
|
|
|
---ACTIONS---
|
|
[0] BUY FrostRune (cost=2)
|
|
[1] PLACE EarthRune -> slot3
|
|
[2] REROLL (cost=10)
|
|
[3] FIGHT
|
|
---END_ACTIONS---
|
|
|
|
---PROMPT---
|
|
Build action
|
|
Enter a number [0-3]:
|
|
---END_PROMPT---
|
|
```
|
|
|
|
L'agent répond par un **entier** sur stdin. Simple, testable, loggable.
|
|
|
|
### Garde-fous
|
|
|
|
Un protocole doit avoir des limites dures :
|
|
|
|
- **Timeout** : 60 secondes par run.
|
|
- **Max actions** : 2000 décisions avant exit forcé.
|
|
- **Output cap** : 500 KB pour éviter les loops qui spamment.
|
|
- **Défaut sur EOF** : si stdin ferme, l'agent prend automatiquement l'action `0`.
|
|
|
|
Documenté dans le `CLAUDE.md` du projet à côté du protocole.
|
|
|
|
### Pourquoi ça vaut le coup
|
|
|
|
- Tests de régression automatisés sur des runs complètes.
|
|
- Démo rapide sans client graphique.
|
|
- Intégration avec des bots d'évaluation (balance auto-testée).
|
|
- Forçage architectural : pour qu'un jeu soit pilotable en CLI, il faut que la logique soit déjà séparée du rendu.
|
|
|
|
---
|
|
|
|
## Networking et multijoueur
|
|
|
|
### Strategy de base
|
|
|
|
Référence que je suis : [State Synchronization — Gaffer On Games](https://gafferongames.com/post/state_synchronization/). Résumé :
|
|
|
|
- Le serveur est l'autorité ultime sur l'état.
|
|
- Les clients appliquent localement leurs actions **optimistiquement**.
|
|
- Le serveur valide et re-broadcast ; en cas de désaccord, client rollback + rejoue.
|
|
- La latence est masquée par l'optimistic update, les divergences sont corrigées discrètement.
|
|
|
|
### Types de messages
|
|
|
|
Trois familles, toutes serialisées en MessagePack, toutes implémentent `INetworkMessage` :
|
|
|
|
- **GameEvent** : mutation bidirectionnelle (peut venir du client ou du serveur), validée par le serveur, applicable optimistiquement côté client.
|
|
- **Command** : client → serveur, demande d'opération (ex. login, demande de blueprint). Serveur répond par un `AckResponse`.
|
|
- **Query** : client → serveur, demande de lecture sans side-effect. Serveur répond par la donnée.
|
|
- **Response** : serveur → client, réponse à une Query.
|
|
|
|
Tous les messages dans `Shared/Net/Messages/`. Un seul endroit, serializable, partagé entre client et serveur.
|
|
|
|
### Architecture serveur
|
|
|
|
Stack typique :
|
|
|
|
- **ASP.NET Core** en .NET 6+ (pour .NET Core Web, pour le DI, pour les middlewares).
|
|
- **EntityFramework Core** pour la DB (SQLite en dev, Postgres en prod).
|
|
- **MessagePack** côté transport.
|
|
- **Services** injectés via `IServiceScopeFactory` pour les scopes par requête / par session.
|
|
- **`UserSessionData` par utilisateur connecté**, indexé par `ushort` short ID.
|
|
- **Queues thread-safe** (`ConcurrentQueue<InputMessage>`, `ConcurrentQueue<OutputMessage>`) entre la couche réseau et la boucle de jeu.
|
|
- **PeriodicTimer** pour le tick réseau (1ms) et le backup périodique (5s).
|
|
- **ServerClock** pour la référence temporelle authoritative.
|
|
|
|
Extrait illustratif :
|
|
|
|
```csharp
|
|
public class VoxelsEngineServer {
|
|
private readonly GameState _state = new();
|
|
private readonly GameState _stateBackup = new();
|
|
private readonly ConcurrentDictionary<ushort, UserSessionData> _userSessionData = new();
|
|
private readonly ConcurrentQueue<InputMessage> _inbox = new();
|
|
private readonly ConcurrentQueue<OutputMessage> _outbox = new();
|
|
private readonly ConcurrentDictionary<ChunkKey, Chunk> _dirtyChunks = new();
|
|
private PeriodicTimer networkingTime = new(TimeSpan.FromMilliseconds(1));
|
|
private PeriodicTimer backupTimer = new(TimeSpan.FromSeconds(5));
|
|
}
|
|
```
|
|
|
|
### Versioning des messages
|
|
|
|
Obligatoire. Un client ancien doit pouvoir coexister avec un serveur récent, ou au minimum recevoir un message d'erreur clair plutôt qu'une désérialisation cassée. Voir la section MessagePack dans [`csharp.md`](csharp.md) pour le pattern Union.
|
|
|
|
---
|
|
|
|
## Docker et déploiement
|
|
|
|
### Dockerfile
|
|
|
|
Pour un serveur .NET type, un Dockerfile multi-stage (build + runtime) :
|
|
|
|
- Stage 1 : image `sdk` pour restore + build + publish.
|
|
- Stage 2 : image `aspnet` (runtime seule) avec juste les artefacts et les assets nécessaires.
|
|
|
|
Objectif : image finale légère (~100-200 MB), pas de SDK dans l'image de prod.
|
|
|
|
### docker-compose
|
|
|
|
J'ai au minimum deux fichiers séparés :
|
|
|
|
- `docker_compose_xxx.devtest.yml` : pour le staging interne (test, dev partagé).
|
|
- `docker_compose_xxx.prod.yml` : pour la prod.
|
|
|
|
Différences typiques : volumes persistants (saves, DB), variables d'environnement (connection strings, ports), replicas, healthchecks.
|
|
|
|
Les secrets ne sont **jamais dans le docker-compose** versionné — variables d'environnement, fichiers `.env` non-commit, ou secret manager.
|
|
|
|
### Déploiement client web
|
|
|
|
Un build Unity WebGPU + un script `.bat` qui rsync/scp vers le serveur de staging :
|
|
|
|
```bat
|
|
deploy_web_build.bat
|
|
```
|
|
|
|
Versionné, relu, pas un script bash à part dans le wiki. Si c'est reproductible, c'est dans le repo.
|
|
|
|
### Unity Cloud Build pour iOS
|
|
|
|
Quand iOS est une cible et que je ne veux pas maintenir un Mac de build :
|
|
|
|
- **Unity Cloud Build (Build Automation)** configure un target iOS qui tourne sur les machines mac fournies par Unity.
|
|
- Clé **App Store Connect API** (Key ID + Issuer ID + `.p8`) convertie en JSON fastlane-compatible et stockée en variable d'environnement Unity Cloud.
|
|
- Script **post-build** qui appelle `fastlane deliver` pour pousser l'IPA vers App Store Connect.
|
|
- Piège : Unity réécrit les `\n` de la clé privée en espaces à l'injection, il faut restaurer les newlines dans le script avant de passer la clé à fastlane.
|
|
- Gestion de version : un `BuildVersion` ScriptableObject dérive `Major.Minor.Patch` depuis les tags git (`v1.0`), et le build number depuis le nombre de commits.
|
|
- Coût : environ 1 €/build iOS — OK pour une cadence humaine (quelques pushs par semaine).
|
|
|
|
Voir [samuel-bouchet.fr/posts/2025-03-24-unity-automated-appstoreconnect-upload](https://samuel-bouchet.fr/posts/2025-03-24-unity-automated-appstoreconnect-upload/) pour le détail.
|
|
|
|
---
|
|
|
|
## Serialization : au-delà de MessagePack
|
|
|
|
Même si MessagePack est mon défaut, je choisis selon le contexte :
|
|
|
|
- **MessagePack binaire** : réseau, saves, AOT-compatible, versioning via Union. Défaut pour presque tout.
|
|
- **JSON** : config humaine éditable (`appsettings.json`), API externe, debug. Newtonsoft pour Unity, System.Text.Json côté .NET pur.
|
|
- **`.bytes` files** : données pré-calculées (layouts de grid) serializées en binaire, chargées via `ResourceLoader` ou `File.ReadAllBytes`. Plus rapides que JSON, moins lisibles — uniquement pour des données générées.
|
|
- **`.dblog` / `.dbconf`** : formats spécifiques à un outil (DebugView++), pas du code maison.
|
|
|
|
Règle : chaque format sérialisé a sa raison documentée dans le `CLAUDE.md` du projet.
|
|
|
|
---
|
|
|
|
## DebugView++ et logs structurés
|
|
|
|
Sur les projets serveur ou multijoueur, je configure **DebugView++** avec un `.dblog` versionné pour visualiser les logs en temps réel sur Windows. Le `.dbconf` contient la configuration des filtres et colonnes.
|
|
|
|
Les logs eux-mêmes suivent quelques règles :
|
|
|
|
- **Préfixes systématiques** : `[Server]`, `[Client]`, `[Net]`, `[GameEvent]` — permet le filtrage rapide.
|
|
- **Niveaux explicites** : `Debug`, `Info`, `Warn`, `Error` — jamais de `Console.WriteLine` anonyme en prod.
|
|
- **Contexte dans le message** : IDs, counters, valeurs observées. Un log "something went wrong" n'a aucune valeur.
|
|
|
|
Côté Unity, `Debug.Log` + un filtre custom de niveau dans la console. Je désactive le spam avec `[Conditional("UNITY_EDITOR")]` ou via niveaux runtime.
|
|
|
|
---
|
|
|
|
## Outillage : Rider, DotSettings, hot-reload
|
|
|
|
### Rider comme IDE
|
|
|
|
- `.DotSettings` versionnés : conventions de format, nommage, inspections désactivées, patterns de refactor.
|
|
- Un fichier `.sln.DotSettings` pour les prefs de la solution.
|
|
- Des `.csproj.DotSettings` par projet quand il y a des règles spécifiques.
|
|
|
|
Bénéfice : n'importe qui clone et a les bonnes règles dès le premier `Ctrl+Alt+L`. Pas de débat de formatage en PR.
|
|
|
|
### Unity Hot Reload
|
|
|
|
Quand possible. Permet de modifier du code pendant le play mode sans recompile complète. Utile pour itérer rapidement sur la balance ou l'UI.
|
|
|
|
### Commandes récurrentes
|
|
|
|
Je garde à portée dans le `README.md` / `CLAUDE.md` :
|
|
|
|
```bash
|
|
# Build et test serveur
|
|
dotnet build
|
|
dotnet test
|
|
dotnet test --filter "FullyQualifiedName~CombatTests"
|
|
|
|
# MessagePack AOT generation
|
|
mpc -i D:\projets\TheRuneMaker\Assets -o "Assets/Client/Scripts/MessagePackGenerated.cs"
|
|
|
|
# EF migrations
|
|
dotnet tool install --global dotnet-ef
|
|
dotnet ef migrations add <Name>
|
|
dotnet ef database update
|
|
dotnet ef database drop
|
|
|
|
# LLM runner
|
|
cd LlmRunner
|
|
dotnet run -- [seed]
|
|
```
|
|
|
|
---
|
|
|
|
## Patterns de performance
|
|
|
|
Dans l'ordre : **mesurer, optimiser le hot path, laisser le reste lisible**.
|
|
|
|
### Mesurer d'abord
|
|
|
|
- `Stopwatch` autour des sections suspectes avant de toucher au code.
|
|
- Unity Profiler pour les frames hiccups.
|
|
- `BenchmarkDotNet` côté .NET pur quand un algo a vraiment besoin d'être micro-optimisé.
|
|
|
|
### Anti-allocations
|
|
|
|
Quand c'est un hot path identifié :
|
|
|
|
- `for` plutôt que `foreach` sur les collections concrètes.
|
|
- `List<T>.GetEnumerator()` ne boxe pas, mais `IEnumerable<T>.GetEnumerator()` si.
|
|
- ZLinq ou LINQ manuel quand c'est critique.
|
|
- Pool d'objets pour les struct fréquentes.
|
|
- `Span<T>` / `Memory<T>` / `stackalloc` pour les buffers temporaires.
|
|
- `StringBuilder` pour les concaténations en boucle.
|
|
|
|
### Mais : ne pas optimiser avant mesure
|
|
|
|
95% du code n'est pas hot-path. La lisibilité et la testabilité priment jusqu'au moment où le profiler montre un problème réel.
|
|
|
|
---
|
|
|
|
## Cancellation et gestion de durée de vie
|
|
|
|
Pattern récurrent que je réutilise :
|
|
|
|
- `CancellationTokenSource` au niveau d'un lifecycle (GameObject, session, session réseau).
|
|
- `CancellationToken` passé à toutes les tâches async qui peuvent dépasser ce lifecycle.
|
|
- `cancellationToken.Register(disposable.Dispose)` pour chainer des disposables sur le cancel.
|
|
- `gameObject.GetCancellationTokenOnDestroy()` pour lier une tâche au destroy Unity.
|
|
- `GetCancellationTokenOnEnable()` / `GetCancellationTokenOnDisable()` pour les toggles actifs/inactifs.
|
|
|
|
Helper `DisposeOnReset(IDisposable disposable)` dans `ConnectedBehaviour` pour inscrire automatiquement un cleanup au prochain reset.
|
|
|
|
---
|
|
|
|
## Snippets utiles que je réutilise
|
|
|
|
### Throttle
|
|
|
|
```csharp
|
|
public static Action CreateThrottled(Action action, UniTask delayTask) {
|
|
bool throttling = false;
|
|
async void ThrottledEventHandler() {
|
|
if (throttling) return;
|
|
action();
|
|
throttling = true;
|
|
await delayTask.SuppressCancellationThrow();
|
|
throttling = false;
|
|
}
|
|
return ThrottledEventHandler;
|
|
}
|
|
```
|
|
|
|
### Generic EventBus dispatcher
|
|
|
|
```csharp
|
|
public EventDispatcher<T> For<T>() {
|
|
if (_all.TryGetValue(typeof(T), out EventDispatcher obs))
|
|
return (EventDispatcher<T>) obs;
|
|
var newObs = new EventDispatcher<T>();
|
|
_all.Add(typeof(T), newObs);
|
|
return newObs;
|
|
}
|
|
```
|
|
|
|
### Broadcast filtré (côté serveur)
|
|
|
|
```csharp
|
|
public void BroadcastBut<T>(Func<T, bool> shouldSkip, T message)
|
|
where T : INetworkMessage {
|
|
foreach (var session in _userSessionData.Values) {
|
|
if (shouldSkip(message)) continue;
|
|
_outbox.Enqueue(new OutputMessage(session.ShortId, message));
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## VFX fullscreen URP : distortion, secondary camera, RenderTexture
|
|
|
|
Pattern utilisé sur *Powered by Geometry* (GMTK 2024) pour un effet de distortion de projectiles, et réutilisable pour tout effet fullscreen en URP (Unity 6).
|
|
|
|
### Le problème
|
|
|
|
Depuis URP, `OnRenderImage` n'existe plus. Les effets fullscreen passent par un **FullScreenPassRendererFeature** configuré dans l'asset Renderer (`Renderer2DData` en 2D, équivalent en 3D).
|
|
|
|
### Setup
|
|
|
|
- Ajouter un `FullScreenPassRendererFeature` sur le Renderer asset.
|
|
- **Injection Point** : `Before Rendering Post Processing` — en amont du bloom, sinon la distortion produit des artefacts dans les zones bloomées.
|
|
- **Fetch Color Buffer** : activé pour accéder aux pixels déjà rendus.
|
|
- **Pass Material** : un material shader qui consomme le `URP Sample Buffer` (node `BlitSource` comme entrée).
|
|
|
|
### Pattern « Secondary Camera + RenderTexture »
|
|
|
|
Pour localiser l'effet (distortion seulement autour des projectiles, pas sur toute l'image) :
|
|
|
|
1. Créer un **layer** `Secondary`.
|
|
2. Une **caméra secondaire** configurée avec un Renderer dédié sans post-processing, qui ne voit que le layer `Secondary`, et qui sort vers un `RenderTexture` asset.
|
|
3. La main camera exclut `Secondary` de son culling mask.
|
|
4. Le shader de distortion consomme ce `RenderTexture` en paramètre `_Secondary`.
|
|
|
|
### Encodage de la distortion
|
|
|
|
Chaque projectile sur le layer `Secondary` a deux éléments :
|
|
|
|
- **ProjectileMask** : sprite noir de la taille du projectile (→ pas de distortion là-dessus).
|
|
- **Shrink** : sprite de distortion, ~3x plus large que le projectile, qui encode les offsets UV dans les canaux R/G :
|
|
- `R = 0.5` et `G = 0.5` = pas de distortion.
|
|
- `< 0.5` ou `> 0.5` = offset UV négatif / positif.
|
|
- Le shader soustrait 0.5 pour normaliser en `[-0.5 ; 0.5]`, multiplie par un `DistortionStrength` paramétrable.
|
|
|
|
Particle emitters avec couleurs inversées pour les effets de « push » d'explosion. Article détaillé : [samuel-bouchet.fr/posts/unity-6-wave-distortion-fullscreen-vfx](https://samuel-bouchet.fr/posts/unity-6-wave-distortion-fullscreen-vfx/).
|
|
|
|
---
|
|
|
|
## Orthogonal design : combinaisons > additions
|
|
|
|
Concept repris de vidéos de game design (Dishonored, Left 4 Dead) : au lieu d'empiler des features indépendantes, cibler des mécaniques **orthogonales** qui se combinent entre elles pour générer de la diversité émergente. N mécaniques orthogonales → N² situations ; N mécaniques additives → N situations.
|
|
|
|
Règle pragma : quand je conçois un nouveau système, je me demande « avec quelles autres mécaniques déjà en place va-t-il interagir, et ces interactions produiront-elles des situations intéressantes ? ». Si la réponse est « aucune », soit la mécanique est prématurée, soit l'architecture est trop silotée.
|
|
|
|
---
|
|
|
|
## Déterminisme en physique 2D (Box2D)
|
|
|
|
Pour les jeux multijoueurs ou replays où la physique doit être identique sur toutes les machines :
|
|
|
|
- **Désactiver fast-math** dans les options du compilateur.
|
|
- **Désactiver les instructions FMA** (Fused Multiply-Add) — elles diffèrent entre CPU et cassent le déterminisme cross-platform.
|
|
- Fixer le timestep, éviter les `deltaTime` variables dans les équations de physique.
|
|
|
|
Ce n'est pas gratuit (~ perte de perf mesurable), à activer uniquement sur les builds où c'est nécessaire.
|
|
|
|
---
|
|
|
|
## Pistes à formaliser plus tard
|
|
|
|
Quand ces sujets auront accumulé suffisamment de matière, ils mériteront leur propre fichier :
|
|
|
|
- **`testing.md`** — tests, fixtures, test data builders, philosophy TDD/BDD.
|
|
- **`networking.md`** — INetworkMessage, protocole serveur, state sync, latency handling.
|
|
- **`llm-workflow.md`** — comment je designe pour l'accessibilité LLM, CLAUDE.md templates, protocoles CLI, garde-fous.
|
|
- **`serialization.md`** — MessagePack détaillé, Union versioning, AOT, migrations.
|
|
- **`deployment.md`** — Docker, EF migrations, déploiement client web, secrets.
|
|
- **`documentation.md`** — structure README/CLAUDE.md/GDD, mermaid, commits liés à la doc.
|
|
- **`debugging.md`** — DebugView++, Unity Profiler, logs structurés, reproductibilité.
|
|
- **`performance.md`** — profiling, allocations, hot paths, quand optimiser.
|
|
- **`editor-tooling.md`** — Odin, Artificetoolkit, custom editors, validators, Rider DotSettings.
|