# Patch Notes: Hytale Server

## Version 2026.01.13-50e69c385 → 2026.01.15-c04fdfe10

**Date de release** : 15 janvier 2026
**Build précédent** : `50e69c385` (13 janvier 2026)
**Build actuel** : `c04fdfe10` (15 janvier 2026)

---

### Statistiques

| Métrique | Valeur |
|----------|--------|
| Fichiers modifiés | 9 |
| Insertions | +55 |
| Suppressions | -18 |
| Delta net | +37 lignes |

---

## Table des matières

1. [Authentification & Sécurité JWT](#1-authentification--sécurité-jwt)
2. [Gestion d'échec d'authentification serveur](#2-gestion-déchec-dauthentification-serveur)
3. [Transport QUIC - Optimisations réseau](#3-transport-quic---optimisations-réseau)
4. [Timeout Setup Handler](#4-timeout-setup-handler)
5. [Physique des Items - Collisions latérales](#5-physique-des-items---collisions-latérales)
6. [Nouvelle raison d'arrêt MISSING_ASSETS](#6-nouvelle-raison-darrêt-missing_assets)
7. [Validation des Asset Packs](#7-validation-des-asset-packs)
8. [Télémétrie Sentry enrichie](#8-télémétrie-sentry-enrichie)
9. [Fix NullPointerException CraftingManager](#9-fix-nullpointerexception-craftingmanager)

---

## 1. Authentification & Sécurité JWT

**Fichier** : `com/hypixel/hytale/server/core/auth/JWTValidator.java`
**Lignes modifiées** : 31, 87-99, 293-309, 355-372

### 1.1 Nouvelle constante (non utilisée)

```java
private static final long CLOCK_SKEW_SECONDS = 300L;
```

Une constante a été ajoutée pour centraliser la valeur du clock skew, mais elle n'est **pas encore utilisée** dans le code - les valeurs `300L` sont hardcodées directement dans les méthodes de validation.

### 1.2 Augmentation du Clock Skew : 60s → 300s

Le **clock skew** (tolérance de désynchronisation d'horloge entre client et serveur) passe de **1 minute à 5 minutes**. Cette modification affecte la validation de trois types de tokens JWT :

#### Méthode `validateToken()` (lignes 87-99)

| Claim | Avant | Après | Comportement |
|-------|-------|-------|--------------|
| `exp` (expires at) | `now >= exp + 60` | `now >= exp + 300` | Token accepté jusqu'à 5 min après expiration |
| `nbf` (not before) | `now < nbf - 60` | `now < nbf - 300` | Token accepté 5 min avant validité |
| `iat` (issued at) | **Non vérifié** | `iat > now + 300` | **NOUVEAU** : Rejette tokens du futur |

**Code ajouté** (lignes 96-99) :
```java
if (claims.issuedAt != null && claims.issuedAt > nowSeconds + 300L) {
    LOGGER.at(Level.WARNING).log("Token issued in the future (iat: %d, now: %d)",
        (Object)claims.issuedAt, nowSeconds);
    return null;
}
```

#### Méthode `validateIdentityToken()` (lignes 293-309)

| Claim | Avant | Après |
|-------|-------|-------|
| `exp` | `now >= exp + 60` | `now >= exp + 300` |
| `nbf` | `now < nbf - 60` | `now < nbf - 300` |
| `iat` | `iat > now + 60` | `iat > now + 300` |

La validation `iat` existait déjà pour les identity tokens, seule la valeur du clock skew a changé.

#### Méthode `validateSessionToken()` (lignes 355-372)

| Claim | Avant | Après | Comportement |
|-------|-------|-------|--------------|
| `exp` | `now >= exp + 60` | `now >= exp + 300` | Token accepté jusqu'à 5 min après expiration |
| `nbf` | `now < nbf - 60` | `now < nbf - 300` | Token accepté 5 min avant validité |
| `iat` | **Non vérifié** | `iat > now + 300` | **NOUVEAU** : Rejette tokens du futur |

**Code ajouté** (lignes 368-371) :
```java
if (claims.issuedAt != null && claims.issuedAt > nowSeconds + 300L) {
    LOGGER.at(Level.WARNING).log("Session token issued in the future (iat: %d, now: %d)",
        (Object)claims.issuedAt, nowSeconds);
    return null;
}
```

### 1.3 Impact

| Aspect | Effet |
|--------|-------|
| **Compatibilité** | Meilleure tolérance pour clients avec horloge désynchronisée |
| **Sécurité** | Protection contre tokens pré-générés pour utilisation future |
| **Stabilité** | Réduction des erreurs de validation dues au décalage horaire |

### 1.4 Contexte technique

Le `JWTValidator` gère trois types de tokens avec des rôles distincts :

1. **Auth Token (JWTClaims)** : Authentification client, contient username, IP, certificate fingerprint
2. **Identity Token (IdentityTokenClaims)** : Identité du joueur, requiert scopes `hytale:client` ou `hytale:editor`
3. **Session Token (SessionTokenClaims)** : Sessions serveur, validation minimale

Le système utilise un cache JWKS (JSON Web Key Set) avec durée de 1 heure et mécanisme de retry intelligent en cas d'échec de vérification de signature.

---

## 2. Gestion d'échec d'authentification serveur

**Fichier** : `com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java`
**Lignes modifiées** : 291-298 (dans le callback `exceptionally`)

### 2.1 Modification du comportement

Dans la méthode `exchangeServerAuthGrant()`, le callback `.exceptionally()` (gestion des erreurs async) a été modifié. Lorsqu'une exception se produit pendant l'échange de server token (`AuthState.EXCHANGING_SERVER_TOKEN`) :

**Avant** :
```java
.exceptionally(ex -> {
    LOGGER.at(Level.WARNING).withCause(ex).log("Error exchanging server auth grant");
    this.channel.eventLoop().execute(() -> {
        if (this.authState != AuthState.EXCHANGING_SERVER_TOKEN) {
            return;
        }
        byte[] passwordChallenge = this.generatePasswordChallengeIfNeeded();
        this.completeAuthentication(passwordChallenge);
    });
    return null;
});
```

**Après** :
```java
.exceptionally(ex -> {
    LOGGER.at(Level.WARNING).withCause(ex).log("Error exchanging server auth grant");
    this.channel.eventLoop().execute(() -> {
        if (this.authState != AuthState.EXCHANGING_SERVER_TOKEN) {
            return;
        }
        this.disconnect("Server authentication failed - please try again later");
    });
    return null;
});
```

### 2.2 Impact sur la sécurité

| Aspect | Avant | Après |
|--------|-------|-------|
| Échec server token exchange | Authentification continuait silencieusement | Déconnexion immédiate |
| Bypass potentiel | Possible dans certains cas de timeout | Impossible |
| Message utilisateur | Aucun | Message explicite |

### 2.3 Contexte : Flow d'authentification mutuelle

Le `HandshakeHandler` orchestre un flow d'authentification mutuelle (mTLS) avec les états suivants :

```
REQUESTING_AUTH_GRANT (30s timeout)
    ↓
AWAITING_AUTH_TOKEN (30s timeout)
    ↓
PROCESSING_AUTH_TOKEN
    ↓
EXCHANGING_SERVER_TOKEN (15s timeout)  ← Changement ici
    ↓
AUTHENTICATED
```

**Étapes détaillées** :

1. **Validation Identity Token** : Vérifie le token du client, UUID, scopes
2. **Demande Auth Grant** : Récupère le server session token, appel async au `SessionServiceClient`
3. **Traitement Token Client** : Vérifie JWT signature, certificate binding, UUID/username
4. **Échange Server Token** : Échange auth grant contre token avec fingerprint serveur
5. **Complétion** : Crée `PlayerAuthentication`, passe au `SetupPacketHandler`

Le changement garantit qu'un échec à l'étape 4 ne permet plus de compléter l'authentification.

---

## 3. Transport QUIC - Optimisations réseau

**Fichier** : `com/hypixel/hytale/server/core/io/transport/QUICTransport.java`
**Lignes modifiées** : 30, 128

### 3.1 Nouvel import

```java
import io.netty.handler.codec.quic.QuicCongestionControlAlgorithm;
```

### 3.2 Activation de PMTU Discovery

**Ajout** : `.discoverPmtu(true)`

Le **Path MTU Discovery** permet au serveur de :

| Fonctionnalité | Description |
|----------------|-------------|
| Détection automatique | Découvre la taille maximale de paquet sur chaque chemin réseau |
| Évitement fragmentation | Prévient la fragmentation IP qui dégrade les performances |
| Optimisation throughput | Utilise des paquets de taille optimale par connexion |

### 3.3 Changement d'algorithme de congestion : BBR

**Ajout** : `.congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR)`

#### Comparaison des algorithmes

| Aspect | Cubic (implicite avant) | BBR (nouveau) |
|--------|-------------------------|---------------|
| **Approche** | Loss-based | Model-based |
| **Réaction aux pertes** | Réduit agressivement le débit | Maintient le débit estimé |
| **Latence** | Variable, peut augmenter | Minimisée activement |
| **Réseaux lossy** | Performances dégradées | Meilleures performances |
| **Bufferbloat** | Peut remplir les buffers | Évite le bufferbloat |
| **WiFi/Mobile** | Sous-optimal | Optimisé |

**BBR (Bottleneck Bandwidth and Round-trip propagation time)** est l'algorithme développé par Google qui :
- Estime continuellement la bande passante disponible
- Maintient un RTT minimal
- Offre de meilleures performances sur réseaux modernes

### 3.4 Configuration QUIC complète après modification

```java
QuicServerCodecBuilder()
    .sslContext(sslContext)
    .tokenHandler(InsecureQuicTokenHandler.INSTANCE)
    .maxIdleTimeout(playTimeout.toMillis(), TimeUnit.MILLISECONDS)
    .ackDelayExponent(3L)                                    // 8ms entre acks
    .initialMaxData(524288L)                                 // 512 KB total
    .initialMaxStreamDataUnidirectional(0L)                  // Désactivé
    .initialMaxStreamsUnidirectional(0L)                     // Désactivé
    .initialMaxStreamDataBidirectionalLocal(131072L)         // 128 KB par stream
    .initialMaxStreamDataBidirectionalRemote(131072L)        // 128 KB par stream
    .initialMaxStreamsBidirectional(1L)                      // 1 stream
    .discoverPmtu(true)                                      // NOUVEAU
    .congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR)  // NOUVEAU
```

### 3.5 Contexte technique

Le `QUICTransport` configure également :
- **mTLS obligatoire** : `ClientAuth.REQUIRE`
- **Certificat auto-signé** : Généré au démarrage
- **IPv4/IPv6 dual-stack** : Bootstraps séparés
- **Options socket** : `SO_REUSEADDR`, `IP_DONTFRAGMENT`
- **Certificate fingerprinting** : Pour validation JWT

---

## 4. Timeout Setup Handler

**Fichier** : `com/hypixel/hytale/server/core/io/handlers/SetupPacketHandler.java`
**Ligne modifiée** : 102

### 4.1 Modification du timeout

```java
// Avant
this.setTimeout("send-world-settings", () -> this.assets != null, 1L, TimeUnit.SECONDS);

// Après
this.setTimeout("send-world-settings", () -> this.assets != null, 10L, TimeUnit.SECONDS);
```

### 4.2 Contexte

Ce timeout attend que les assets soient prêts (`this.assets != null`) avant d'envoyer les paramètres du monde (`WorldSettings`) au client.

### 4.3 Raisons du changement

| Scénario | Impact avec 1s | Impact avec 10s |
|----------|----------------|-----------------|
| Connexion haute latence | Timeout fréquent | Toléré |
| Serveur avec nombreux asset packs | Timeout possible | Toléré |
| Charge serveur élevée | Timeout possible | Toléré |
| Démarrage lent des assets | Déconnexion | Attente correcte |

### 4.4 Autres timeouts dans le SetupPacketHandler

| Timeout | Durée | Condition | Usage |
|---------|-------|-----------|-------|
| `send-world-settings` | **10s** (modifié) | `this.assets != null` | Attente chargement assets |
| `receive-assets-request` | 120s | Packet `RequestAssets` reçu | Négociation d'assets |
| `send-assets` | 120s | `SendCommonAssetsEvent` terminé | Envoi assets + translations |
| `add-to-universe` | 60s | `Universe.addPlayer()` terminé | Création joueur dans monde |

### 4.5 Phases du Setup Handler

```
[Post-Auth] → registered0()
    ↓
[WorldSettings + ServerInfo envoyés]
    ↓
[Client envoie RequestAssets] (120s max)
    ↓
[Envoi CommonAssets + Translations] (120s max)
    ↓
[Client envoie PlayerOptions]
    ↓
[AddToUniverse] (60s max)
    ↓
[Joueur connecté au monde]
```

---

## 5. Physique des Items - Collisions latérales

**Fichier** : `com/hypixel/hytale/server/core/modules/entity/item/ItemPhysicsSystem.java`
**Lignes modifiées** : 84-97

### 5.1 Architecture du système

| Élément | Description |
|---------|-------------|
| **Classe parente** | `EntityTickingSystem<EntityStore>` |
| **Pattern** | Entity Component System (ECS) |
| **Tick rate** | Appelé à chaque tick du monde |

### 5.2 Composants utilisés

| Composant | Rôle |
|-----------|------|
| `ItemPhysicsComponent` | État physique spécifique aux items |
| `BoundingBox` | Boîte de collision 3D |
| `Velocity` (VelocityComponent) | Vecteur de vélocité |
| `TransformComponent` | Position et rotation |

### 5.3 Modification de la gestion des collisions

**Avant** : Seules les collisions verticales (sol) étaient gérées

```java
BlockCollisionData blockCollisionData = collisionResult.getFirstBlockCollision();
if (blockCollisionData != null && blockCollisionData.collisionNormal.equals(Vector3d.UP)) {
    velocityComponent.setZero();
    position.assign(blockCollisionData.collisionPoint);
} else {
    velocityComponent.assignVelocityTo(scaledVelocity).scale(dt);
    position.add(scaledVelocity);
}
```

**Après** : Gestion complète de toutes les directions de collision

```java
BlockCollisionData blockCollisionData = collisionResult.getFirstBlockCollision();
if (blockCollisionData != null) {
    if (blockCollisionData.collisionNormal.equals(Vector3d.UP)) {
        // Collision avec le sol : arrêt complet
        velocityComponent.setZero();
        position.assign(blockCollisionData.collisionPoint);
    } else {
        // Collision latérale : annulation de la composante perpendiculaire
        Vector3d velocity = velocityComponent.getVelocity();
        double dot = velocity.dot(blockCollisionData.collisionNormal);
        Vector3d velocityToCancel = blockCollisionData.collisionNormal.clone().scale(dot);
        velocity.subtract(velocityToCancel);
    }
} else {
    velocityComponent.assignVelocityTo(scaledVelocity).scale(dt);
    position.add(scaledVelocity);
}
```

### 5.4 Explication mathématique

Pour une collision latérale (mur, escalier, pente) :

1. **Calcul du produit scalaire** : `dot = velocity · normal`
   - Mesure la composante de vélocité perpendiculaire à la surface

2. **Calcul du vecteur à annuler** : `velocityToCancel = normal × dot`
   - Vecteur perpendiculaire à la surface avec magnitude égale à la composante perpendiculaire

3. **Soustraction** : `velocity = velocity - velocityToCancel`
   - Projette la vélocité sur le plan tangent à la surface

**Formule vectorielle** : `v' = v - (v · n) × n`

### 5.5 Impact sur le gameplay

| Comportement | Avant | Après |
|--------------|-------|-------|
| Item lancé contre un mur | Traversait ou s'arrêtait net | Glisse le long du mur |
| Item tombant sur une pente | Comportement incohérent | Roule correctement |
| Item dans un coin | Pouvait se bloquer/traverser | Réagit physiquement |

### 5.6 Méthodes de détection de collision

Le système utilise deux méthodes selon la magnitude de vitesse :

| Méthode | Condition | Usage |
|---------|-----------|-------|
| `findBlockCollisionsShortDistance()` | Mouvement faible | Optimisation pour petits mouvements |
| `findBlockCollisionsIterative()` | Mouvement important | Précision pour grandes vitesses |

---

## 6. Nouvelle raison d'arrêt MISSING_ASSETS

**Fichier** : `com/hypixel/hytale/server/core/ShutdownReason.java`
**Ligne ajoutée** : 17

### 6.1 Nouvelle constante

```java
public static final ShutdownReason MISSING_ASSETS = new ShutdownReason(7);
```

### 6.2 Liste complète des ShutdownReason

| Constante | Exit Code | Description | Utilisation |
|-----------|-----------|-------------|-------------|
| `SIGINT` | 130 | Signal d'interruption | Ctrl+C ou SIGINT reçu |
| `SHUTDOWN` | 0 | Arrêt gracieux | Commande `/stop` ou shutdown normal |
| `CRASH` | 1 | Crash serveur | Erreur non gérée pendant le boot |
| `AUTH_FAILED` | 2 | Échec authentification | Impossible de s'authentifier auprès des services |
| `WORLD_GEN` | 3 | Erreur génération monde | WorldGenLoadException |
| `CLIENT_GONE` | 4 | Client singleplayer disparu | Mode singleplayer, client déconnecté |
| `MISSING_REQUIRED_PLUGIN` | 5 | Plugin core manquant | Plugin requis non trouvé |
| `VALIDATE_ERROR` | 6 | Validation assets échouée | Erreur de validation des assets |
| **`MISSING_ASSETS`** | **7** | **Aucun asset pack** | **NOUVEAU : Aucun pack chargé** |

### 6.3 Pattern d'utilisation

```java
// Utilisation simple
HytaleServer.get().shutdownServer(ShutdownReason.MISSING_ASSETS);

// Avec message personnalisé
HytaleServer.get().shutdownServer(
    ShutdownReason.MISSING_ASSETS.withMessage("Failed to load any asset packs")
);
```

La méthode `withMessage(String)` crée une nouvelle instance de `ShutdownReason` avec le même code de sortie mais un message additionnel pour les logs.

---

## 7. Validation des Asset Packs

**Fichier** : `com/hypixel/hytale/server/core/asset/AssetModule.java`
**Lignes modifiées** : 24, 104-106

### 7.1 Nouvel import

```java
import com.hypixel.hytale.server.core.ShutdownReason;
```

### 7.2 Nouvelle vérification

Après le chargement des asset packs depuis tous les répertoires de mods :

```java
for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) {
    this.loadPacksFromDirectory(modsPath);
}

// NOUVEAU : Vérification des assets
if (this.assetPacks.isEmpty()) {
    HytaleServer.get().shutdownServer(
        ShutdownReason.MISSING_ASSETS.withMessage("Failed to load any asset packs")
    );
    return;
}
```

### 7.3 Cycle de vie de l'AssetModule

```
setup()
    ↓
[Création AssetMonitor si --disable-file-watcher absent]
    ↓
[Chargement packs CLI]
    ↓
[Chargement packs mods/]
    ↓
[NOUVEAU: Vérification assetPacks.isEmpty()] ← Shutdown si vide
    ↓
[Registration event listeners]
    ↓
LoadAssetEvent dispatch
    ↓
[AssetRegistryLoader charge tous les assets]
    ↓
BootEvent
    ↓
[Log nombre total d'assets]
```

### 7.4 Sources de chargement des asset packs

| Source | Méthode | Description |
|--------|---------|-------------|
| CLI `--assets` | `loadPacksFromDirectory()` | Chemins spécifiés en ligne de commande |
| Dossier `mods/` | `loadPacksFromDirectory()` | Packs dans le dossier mods par défaut |
| Autres `--mods-directories` | `loadPacksFromDirectory()` | Répertoires additionnels |

### 7.5 Types de packs supportés

| Type | Détection | Immutabilité |
|------|-----------|--------------|
| ZIP/JAR | Contient `manifest.json` | Toujours immutable |
| Répertoire | Contient `manifest.json` | Immutable si `CommonAssetsIndex.hashes` présent |
| Répertoire sans hash | Contient `manifest.json` | Mutable (rechargement possible) |

### 7.6 AssetMonitor (rechargement temps réel)

Si `--disable-file-watcher` n'est pas passé, un `AssetMonitor` est créé pour :
- Surveiller les modifications de fichiers dans les packs mutables
- Déclencher un rechargement automatique des assets modifiés
- Permettre le développement itératif sans redémarrage serveur

---

## 8. Télémétrie Sentry enrichie

**Fichier** : `com/hypixel/hytale/server/core/HytaleServer.java`
**Lignes modifiées** : 30, 51, 155-170

### 8.1 Nouveaux imports

```java
import com.hypixel.hytale.server.core.auth.SessionServiceClient;
import io.sentry.protocol.User;
```

### 8.2 Nouvelles données utilisateur collectées

Dans le callback `beforeSend` de Sentry, un objet `User` est maintenant créé et attaché à chaque événement :

```java
User user = new User();
HashMap<String, Object> unknown = new HashMap<String, Object>();
user.setUnknown(unknown);

// 1. Hardware UUID
UUID hardwareUUID = HardwareUtil.getUUID();
if (hardwareUUID != null) {
    unknown.put("hardware-uuid", hardwareUUID.toString());
}

// 2. Mode d'authentification
ServerAuthManager authManager = ServerAuthManager.getInstance();
unknown.put("auth-mode", authManager.getAuthMode().toString());

// 3. Profil utilisateur (si authentifié)
SessionServiceClient.GameProfile profile = authManager.getSelectedProfile();
if (profile != null) {
    user.setUsername(profile.username);
    user.setId(profile.uuid.toString());
}

// 4. Adresse IP (auto-détection Sentry)
user.setIpAddress("{{auto}}");

event.setUser(user);
```

### 8.3 Données collectées par Sentry

#### Données utilisateur (nouvelles)

| Champ Sentry | Source | Type | Description |
|--------------|--------|------|-------------|
| `user.username` | `GameProfile.username` | String | Nom d'utilisateur du compte |
| `user.id` | `GameProfile.uuid` | UUID String | Identifiant unique du compte |
| `user.ip_address` | Auto-détecté | IP | Adresse IP du serveur |
| `user.unknown.hardware-uuid` | `HardwareUtil.getUUID()` | UUID String | Identifiant matériel unique |
| `user.unknown.auth-mode` | `ServerAuthManager.getAuthMode()` | String | Mode d'auth (ONLINE/OFFLINE/etc.) |

#### Données contextuelles (existantes)

| Contexte | Données |
|----------|---------|
| **server** | Nom, max-players, listeners |
| **universe** | Chemin, nombre joueurs, liste worlds |
| **plugins** | Version et état de chaque plugin |

### 8.4 Conditions de collecte

Les données Sentry sont collectées **uniquement si** :
- Aucun early plugin avec transformer n'est chargé
- Le flag `--disable-sentry` n'est pas passé
- Une erreur se produit (beforeSend callback)

### 8.5 Filtrage des erreurs

Les erreurs provenant de plugins tiers sont filtrées avant envoi :

```java
if (PluginClassLoader.isFromThirdPartyPlugin(event.getThrowable())) {
    return null; // Ne pas envoyer
}
```

### 8.6 Configuration Sentry

| Paramètre | Valeur |
|-----------|--------|
| DSN | `https://...@sentry.hytale.com/4` |
| Environnement | `release` |
| Tags | `patchline` |
| Server name | Hostname du serveur |

---

## 9. Fix NullPointerException CraftingManager

**Fichier** : `com/hypixel/hytale/builtin/crafting/component/CraftingManager.java`
**Ligne modifiée** : 500

### 9.1 Modification

**Avant** :
```java
if (!itemResourceType.id.equals(slotCraftingMaterial.getResourceTypeId())) continue;
```

**Après** :
```java
if (!slotCraftingMaterial.getResourceTypeId().equals(itemResourceType.id)) continue;
```

### 9.2 Contexte du code

Cette ligne se trouve dans une méthode de comparaison des matériaux de craft, dans une boucle imbriquée :

```java
// Ligne ~497-502
if (slotCraftingMaterial.getResourceTypeId() == null ||
    slotItemStack.getItem().getResourceTypes() == null) continue;

for (ItemResourceType itemResourceType : slotItemStack.getItem().getResourceTypes()) {
    if (!slotCraftingMaterial.getResourceTypeId().equals(itemResourceType.id)) continue;
    return true;
}
```

### 9.3 Analyse du problème

| Variable | Peut être null ? | Raison |
|----------|------------------|--------|
| `slotCraftingMaterial.getResourceTypeId()` | Non | Vérifié 2 lignes avant |
| `itemResourceType.id` | **Oui** | Pas de vérification préalable |

**Problème** : Appeler `.equals()` sur `itemResourceType.id` quand celui-ci est `null` lance un `NullPointerException`.

**Solution** : Inverser l'ordre de comparaison pour appeler `.equals()` sur la valeur garantie non-null.

### 9.4 Architecture du CraftingManager

| Élément | Description |
|---------|-------------|
| **Interface** | `Component<EntityStore>` (ECS) |
| **Attaché à** | Joueurs |
| **Rôle** | Gestion complète du crafting |

### 9.5 Classes internes

| Classe | Rôle |
|--------|------|
| `CraftingJob` | Représente un travail de crafting en cours |
| `BenchUpgradingJob` | Représente une amélioration d'établi |

### 9.6 Flow de crafting

```
queueCraft()
    ↓
[Job ajouté à BlockingQueue]
    ↓
tick() [appelé chaque tick]
    ↓
[Vérifie matériaux disponibles] ← Fix NPE ici
    ↓
[Retire matériaux de l'inventaire]
    ↓
[Accumule temps]
    ↓
[Si terminé: giveOutput()]
    ↓
[Items craftés donnés au joueur]
```

### 9.7 Impact

| Aspect | Avant | Après |
|--------|-------|-------|
| Items avec `ResourceType.id = null` | Crash du crafting | Comparaison correcte |
| Stabilité serveur | NPE possible | Stable |
| Crafting avec ressources custom | Potentiellement cassé | Fonctionnel |

---

## Résumé des impacts

### Performance

| Changement | Impact |
|------------|--------|
| BBR congestion control | Amélioration significative du débit réseau |
| PMTU Discovery | Réduction de la fragmentation, meilleur throughput |
| Timeout setup 10s | Négligeable |

### Sécurité

| Changement | Impact |
|------------|--------|
| Clock skew 300s | Plus permissif mais toujours sécurisé |
| Validation iat | Protection contre tokens futurs |
| Échec auth → disconnect | Fermeture d'une faille potentielle |

### Stabilité

| Changement | Impact |
|------------|--------|
| MISSING_ASSETS shutdown | Erreur claire au lieu de comportement indéfini |
| Fix NPE CraftingManager | Élimination d'un crash potentiel |
| Collisions latérales items | Comportement physique cohérent |

### Télémétrie

| Changement | Impact |
|------------|--------|
| User data Sentry | Meilleur debugging des erreurs |
| Hardware UUID | Identification des problèmes matériels |
| Auth mode tracking | Corrélation erreurs/mode d'auth |

---

## Fichiers modifiés

| Fichier | Insertions | Suppressions |
|---------|------------|--------------|
| `server/core/auth/JWTValidator.java` | +11 | -6 |
| `server/core/HytaleServer.java` | +18 | 0 |
| `server/core/ShutdownReason.java` | +1 | 0 |
| `server/core/asset/AssetModule.java` | +5 | 0 |
| `server/core/io/handlers/SetupPacketHandler.java` | +1 | -1 |
| `server/core/io/handlers/login/HandshakeHandler.java` | +1 | -2 |
| `server/core/io/transport/QUICTransport.java` | +2 | -1 |
| `server/core/modules/entity/item/ItemPhysicsSystem.java` | +11 | -3 |
| `builtin/crafting/component/CraftingManager.java` | +1 | -1 |
| **Total** | **+55** | **-18** |
