Bloc 102 : le panneau ne répond plus à sa propre écriture - #128
Merged
Merged
Conversation
Second chemin d'écrasement des paramètres du joueur, celui que le Bloc 99 avait laissé debout. Cause exacte. La persistance passe par un effet passif : React le lance après le commit qui l'a programmé, et donc — quand une mise à jour plus récente arrive d'abord — après ce rendu plus récent aussi. L'effet écrit alors, et annonce, l'instantané qu'il avait capturé, que le panneau a déjà dépassé. syncFromStorage relisait le stockage sur cette annonce, y trouvait l'instantané antérieur, le jugeait différent de son état courant et l'adoptait : le niveau que l'utilisateur venait de taper revenait à sa valeur précédente (5 attendu, 10 reçu). Le Bloc 99 empêchait le panneau de répondre à sa propre écriture en rendant la comparaison égale ; cela ne tient que tant que le stockage et le panneau sont d'accord, ce qui est faux précisément quand l'écriture est en retard. Le garde est donc structurel maintenant : `broadcasting` dit si l'événement vient du panneau lui-même, la comparaison de contenu dit si les réglages stockés diffèrent vraiment. Mesure A/B, scénario rejoué sous contention CPU (4 processus, 4 cœurs) : 7 échecs sur 600 avant, 0 sur 600 après. Tests. Un test qui force l'entrelacement au lieu de l'attendre — l'écriture suivante du panneau dépose l'instantané précédent, exactement ce que fait un effet passif en retard — et échoue avec « attendu 5, reçu 10 » sans le correctif. Plus un test sur l'autre moitié du garde, que rien ne fixait : sans la comparaison de contenu, deux copies montées du panneau se répondraient sans fin. Le correctif du Bloc 99 reste en place et couvert : le remettre en cause fait toujours rougir son test unitaire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJgsDSfsCbbn8cochFGwe2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second chemin d'écrasement des paramètres du joueur — celui que le Bloc 99 avait laissé debout.
1. Reproduction
Harnais qui rejoue le scénario du test
reflects an external equipment-skills transfer liveen boucle dans un seul processus, instrumenté (chaque écriture du stockage, chaque diffusion, chaque passage desyncFromStorageaveccurrent.level/next.level/equal, chaque rendu duNumberStepperdu niveau).Niveau du joueurattendu5, reçu10.2. Cause exacte
Trace saine, puis trace en échec, au même point :
La persistance passe par un effet passif. React le lance après le commit qui l'a programmé — et, quand une mise à jour plus récente arrive d'abord, React vide les effets passifs en attente avant de traiter cette mise à jour. L'effet écrit alors, et annonce, l'instantané qu'il avait capturé, que le panneau a déjà dépassé.
syncFromStoragerelisait le stockage sur cette annonce, y trouvait l'instantané antérieur, le jugeait différent de son état courant, et l'adoptait. Les deux mises à jour étant appliquées dans le même rendu (setLevelpuis lesync), le niveau 5 n'a jamais été committé : leNumberSteppera alors correctement lâché son brouillon (value 10 !== lastReported 5) et réaffiché 10.Le Bloc 99 empêchait le panneau de répondre à sa propre écriture en rendant la comparaison égale — ce qui ne tient que tant que le stockage et le panneau sont d'accord. C'est précisément faux quand l'écriture est en retard.
3. Correctif
Le garde devient structurel plutôt que dépendant d'une égalité octet à octet :
broadcastingdit si l'événement vient du panneau lui-même, et la comparaison de contenu dit si les réglages stockés diffèrent vraiment.dispatchEventétant synchrone, tous les écouteurs —syncFromStoragecompris — tournent dans cette fenêtre.Après le correctif, le stockage se remet à jour tout seul : l'écriture périmée est ignorée, le rendu du niveau 5 est committé, et son effet de persistance écrit 5.
Mesure A/B
Même harnais non instrumenté, mêmes conditions (4 processus × 150 tours) :
4. Portée du défaut
Les deux autres appelants de
usePersistedState(Combat, Expédition) ne s'abonnent à aucun événement et sont seuls sur leur clé : ils ne peuvent pas subir ce défaut.usePlayerSettingslit le stockage viauseSyncExternalStore— il n'a aucun état local à perdre, il reflète le stockage et se corrige à la diffusion suivante.5. Tests
Bloc102: ignores its own broadcast, which can announce a snapshot it has moved past— force l'entrelacement au lieu de l'attendre : l'écriture suivante du panneau dépose l'instantané précédent, ce que fait exactement un effet passif en retard. Déterministe, et échoue avec « attendu 5, reçu 10 » — le symptôme de production mot pour mot — dès que le garde est retiré.Bloc102: an outside announcement that changes nothing leaves the panel alone— l'autre moitié du garde, que rien ne fixait : en supprimant la comparaison de contenu, aucun test ne rougissait, alors que sans elle deux copies montées du panneau se répondraient sans fin.Bloc 99 vérifié intact : en remettant le
vdans les réglages retournés, son test unitaire (hands back the settings alone) rougit toujours. À noter honnêtement — son test comportemental (settles in a single save) passe désormais même sans lui, parce que le garde du Bloc 102 coupe la réponse à sa propre diffusion en amont. Levreste néanmoins porteur : il est ce qui empêche deux panneaux montés de se juger perpétuellement différents.Validation
pnpm lint·prettier --check·tsc --noEmit·pnpm test(147 fichiers, 1342 tests) ·pnpm build·playwright test(88/88).🤖 Generated with Claude Code
https://claude.ai/code/session_01HJgsDSfsCbbn8cochFGwe2
Generated by Claude Code