Skip to content

Mesure la fréquentation de www.aidesktopstudio.com, avec consentement - #5

Merged
pasquelin merged 16 commits into
developfrom
feat/mesure-audience
Sep 23, 2026
Merged

pasquelin merged 16 commits into
developfrom
feat/mesure-audience

Conversation

@pasquelin

Copy link
Copy Markdown
Owner

Une ligne dans le gabarit, donc dans les quinze langues à la fois.

Le bandeau de consentement et le chargement de Google Analytics vivent dans un seul fichier partagé, servi par www.pasquelin.com et commun aux quatre sites ; son code, ses traductions et ses tests sont dans le dépôt pasquelin/site, sous public/shared/. Une correction s'y fait une fois et non quatre.

Rien n'est demandé à Google avant un accord explicite du visiteur : le mode Consentement est armé sur denied et gtag.js n'est même pas téléchargé. Le panneau emprunte les couleurs, la police et les arrondis de la page plutôt que d'imposer les siens — vérifié sur le rendu construit de ce site.

Propriété GA4 : G-YRFNHZLMJH, dans le compte Analytics « Site perso ». Un identifiant de mesure n'est pas un secret : toute page qui le porte le montre à qui lit sa source.

⚠️ À fusionner après pasquelin/site#1, qui publie le fichier partagé. Avant, la page chargerait une 404 : pas de bandeau ni de mesure, mais rien de cassé.

…elles scènes

Étape 1 du chantier C6 (moteur de rendu).

- `RenderPolicy.csm`, désactivé par défaut, et `csm.ts` qui enveloppe l'addon
  `CSM` : trois lumières de cascade, le soleil du document retiré du casting,
  les matériaux habillés une seule fois et remis en état à la libération.
  Réglage exposé dans l'espace 3D, dépendant de « Ombres portées ».
- Les textures sont lues avec `capabilities.getMaxAnisotropy()` : le filtrage
  anisotrope coûte de la bande passante d'échantillonnage, pas un octet de
  mémoire de texture, donc plafonner sous ce que la carte offre ne rend rien.
- `agx` rejoint `ToneMapping` et devient la courbe des scènes CRÉÉES, via
  `NEW_SCENE_WORLD` ; `DEFAULT_WORLD` reste sur `none`, donc un projet relu
  s'ouvre exactement comme il a été écrit.

Le point 1.1 du spec n'est pas appliqué et c'est mesuré, pas un arbitrage :
`WebGLProgram.shadowMapTypeDefines` de three 0.185 ne nomme que PCF et VSM,
donc `PCFSoftShadowMap` retombe sur `SHADOWMAP_TYPE_BASIC` — une comparaison
non filtrée, `shadow.radius` ignoré. `PCFShadowMap` EST le mode doux (disque
de Vogel à cinq taps). Le commentaire de `MAP_TYPES` porte la vérification.
…/ Avancé

Étape 2 du chantier C6 : l'interface, pas encore de contenu GPU.

`RenderDriver` (engines/render/) couvre les quatre points qui dépendent de
l'API graphique et rien d'autre : construire le renderer, relire ses pixels,
préfiltrer l'environnement, patcher le matériau standard. Même forme que
`game/ports/` : l'interface d'un côté, chaque implémentation dans son fichier.

- `glDriver` enveloppe le code existant tel quel — `readRenderPixels`,
  `environment.ts` et le patch de `materialShader` passent derrière sans que
  leur logique bouge ; un projet `gl` dessine exactement ce qu'il dessinait.
- `gpuDriver` refuse les quatre appels sur la même phrase localisée.
- `mountRenderer` retombe sur Compatible quand Avancé lève ou qu'aucun
  adaptateur n'a répondu : jamais d'écran noir, une trace `render.fallback`
  qui ne remonte pas en toast — l'image est juste, rien n'est perdu.
- `gpuAdapter` interroge `navigator.gpu?.requestAdapter()` une seule fois.
  C'est le SEUL signal qui décide ; `adapter.info` n'entre pas dans le choix.
- `PostEffectMeta.engines` : les trente effets existants sont `['gl']`. Les
  slots et la règle d'exclusivité ne bougent pas.

`readPixels` rend une promesse des deux côtés bien que WebGL réponde tout de
suite : la lecture GPU mappe un buffer et résout une frame plus tard, et les
trois appelants étaient déjà dans un chemin asynchrone.

Écart assumé sur le MUST 7 : `RenderPolicy` vit dans `Settings.three`, un
réglage d'application, et la création de projet est un choix de dossier sans
dialogue. Le sélecteur est donc dans l'espace 3D des préférences, et le
verrou « pas de switch en direct » tient par construction — le moteur est lu
au montage du viewport, ce que dit son texte d'aide.
…ises d'aplomb

Ce que `pnpm check` puis `pnpm validate` ont trouvé sur les deux étapes.

- `postParamSpec.ts` sort du registre les six fabriques de spec et leurs types :
  520 lignes pour une limite à 500 une fois `engines` posé sur trente fiches.
- `mountRenderer.ts` sort de `renderDriver.ts` : les implémentations importent
  l'interface, un chooseur posé à côté d'elle fermait le graphe en cycle.
- `cascadesMoved` sort de `configure`, qui dépassait la complexité autorisée.
- Zod ignorait `three.engine` et `three.csm` : un réglage absent du schéma est
  effacé silencieusement à l'écriture, et `validation.test.ts` le disait.
- `fakeRenderer()` rejoint les fixtures viewport : neuf points stubaient `gl`
  par `{}`, ce qui ne répond plus depuis que le cache lit `getMaxAnisotropy`.
- Le banc 48.4 vérifiait « une courbe est active », désormais vrai du décor
  lui-même : il vérifie aussi l'APPEL. `sceneWorld` citait `agx` comme mot
  inconnu du build — remplacé par un mot qui l'est encore.
- Typographie française des deux nouveaux textes de réglage.

`RAPPORT-C6.md` : `docs/fr/audits/moteur-rendu-c6/RAPPORT.md`. L'étape 3 n'est
pas livrée et le rapport dit pourquoi, mesures à l'appui.
…lles n'éclairaient

Cinq relectures indépendantes (réutilisation, simplification, efficacité,
altitude, code-review). Ce qui a été retenu, du plus grave au moins :

- **Les cascades sur-éclairaient la scène d'environ dix fois.** `CSM` ajoute
  trois lumières à 3 d'intensité chacune, et le soleil du document continuait
  d'éclairer à côté. Les bandes REMPLACENT désormais le soleil : sa couleur et
  son intensité passent sur elles, il cesse d'éclairer et de projeter, et
  `release` lui rend les deux. Pas de division par trois : le chunk n'éclaire
  un fragment que depuis la bande où tombe sa profondeur.
- **Le terrain perdait ses deux ombres.** `dress` sautait tout matériau portant
  un `onBeforeCompile`, c'est-à-dire le splat de relief — donc pas de cascades
  ET plus de carte du soleil, sur le seul cas que les cascades servent. Les
  deux crochets se composent maintenant, et `release` ne rend le sien qu'à un
  matériau que personne n'a réattaché depuis.
- **Un orbite affichait des ombres périmées** : `follow` répond si les bandes
  ont bougé, et `dressPane` remonte cette réponse — c'est ce qui dit à la frame
  que la passe d'ombres vaut d'être jouée.
- **Les rendus hors écran** (film, capture de vol, validation) ne recadraient
  jamais les bandes : elles restaient ajustées à la vue de l'éditeur.
- **L'export jeu ignorait `csm`** : `webRender` construit les mêmes cascades.
- Le garde de `aim` comparait x et z d'un vecteur brut contre un normalisé :
  un soleil qui ne bougeait qu'en y ne recadrait jamais.
- Habillage limité aux nœuds changés plus les lots instanciés, au lieu d'un
  `traverse` complet à chaque `applyState` — y compris par frame de jeu.
- `follow` recadrait par identité de caméra : quatre recadrages par frame en
  quadrant, chacun parcourant tous les matériaux habillés. C'est la PROJECTION
  qui est comparée.
- `materialsOf` remonte dans `shadows.ts`, partagé par les deux passes qui
  marquent les mêmes matériaux ; `NEW_SCENE_WORLD` retrouve son gel ; zod
  ignorait deux réglages ; l'anisotropie ne descend plus sous 1 (three répond
  0 sans l'extension) ; `driverFor` n'est plus exporté.
- L'aide du sélecteur dit désormais que le moteur Avancé n'est pas encore
  construit et que le choisir dessine en Compatible.

Refusés, avec le motif : retirer `engines` du registre et le réglage `engine`
(deux revues le demandent — le spec les exige à cette étape), et retirer la
sonde d'adaptateur (MUST 5, avec son test).
…n nœuds

Étape 3 du chantier C6. Le moteur Avancé dessine réellement, mesuré sur cette
machine (M2 Max, adaptateur WebGPU présent).

- `gpuModule` charge `three/webgpu`, TSL et les nœuds d'affichage à la demande,
  une seule fois, et seulement là où un adaptateur a répondu : deux mégaoctets
  de renderer ne sont jamais cherchés sur une machine qui ne peut pas s'en
  servir. `three/webgpu` et `three` partagent `three.core.js`, donc le renderer
  de nœuds dessine les scènes que le studio construit déjà.
- `materialNodes` réécrit le patch matériau en TSL. Deux écarts assumés : plus
  aucune recompilation quand un canal se remplit (un uniforme remplace le
  `#ifdef`), et la cavité tombe sur la couleur diffuse faute de couture sur
  `reflectedLight` — identique sur un diélectrique, un peu plus sombre sur un
  métal. Les uniformes sont ceux du moteur, partagés, pas copiés.
- `gpuComposer` bâtit un `RenderPipeline` : passe de scène en MRT avec ses
  normales, puis `ao()` natif multiplié dedans. `gtao.engines` passe à
  `['gl','gpu']`. Aucune fusion façon `fuseShader` : le pipeline partage déjà
  profondeur et normales.
- `gpuPostQuality` est DÉRIVÉ de `postQuality`, jamais une seconde table : le
  même réglage achète la même chose des deux côtés.
- Lecture de pixels par `readRenderTargetPixelsAsync`, sous la signature
  promise dès l'étape 2. Les trois appelants n'ont pas bougé.
- `StudioRenderer` élargit le type du renderer sur toute la chaîne viewport.
  Les différences réelles sont nommées une par une : pas de porte globale sur
  la passe d'ombres, pas de minuterie GPU, pas de `forceContextLoss`,
  l'anisotropie lue à deux endroits, les échantillons MSAA à zéro.

Trois pannes trouvées en faisant tourner le banc, pas en relisant le code : le
montage demandait son contexte WebGL2 au renderer de nœuds avant son init, la
scène préfiltrait la salle neutre avant l'init, et les images n'étaient pas
retenues tant que le backend n'était pas là.

Banc : `pnpm engines:bench`, harnais navigateur piloté par CDP, comme
`world:validate`. Chiffres dans le rapport, y compris celui qui dérange :
sur ces deux profils l'Avancé coûte plus cher côté CPU (0,12 ms contre 0,045
à 0,080) et n'est plus rapide sur rien. Il ne se dégrade pas avec la scène,
lui, mais aucun seuil de gain n'est atteint et le rapport le dit tel quel.

TRAA écarté, motif écrit : pas de jumeau GL, donc pas de place dans un
catalogue partagé sans une UI par moteur, que le spec met hors périmètre.
…lée et à l'envers

Revue de correction (neuf défauts, tous dans le chemin GPU) et revue de
simplification. Du plus grave au moins :

- **La lecture de pixels rendait des lignes paddées et à l'endroit.** WebGPU
  copie une texture vers un buffer en alignant chaque ligne sur 256 octets, et
  les rend de haut en bas ; le Compatible les rend serrées et de bas en haut,
  et l'encodeur de film retourne sans condition. Toute capture Avancé sortait
  donc retournée, et cisaillée dès que la largeur n'était pas un multiple de
  64 pixels. `sameShapeAsGl` rend une seule forme aux deux moteurs.
- **La chaîne gelait sa caméra.** Le GL réattache scène et caméra à chaque
  dessin ; un chaînage de nœuds les cuit dans le graphe. Un film dont la liste
  de plans change de caméra en cours continuait sur la première. La caméra
  entre dans la chaîne et la fait reconstruire quand elle bouge.
- **`RenderPipeline.dispose` ne libère que son quad** : chaque chaîne évincée
  fuyait un G-buffer plein écran. La passe est libérée à la main avec elle.
- **`colorNode` écrasait la carte de couleur de base** : tout matériau texturé
  aurait rendu plat. C'est `materialColor` qu'il faut multiplier, pas la teinte.
- **Les deux cartes remappées lisaient sans leur matrice** : une rugosité tuilée
  lisait non tuilée pendant que le masque de cavité à côté tuilait — exactement
  le décalage que `syncEdgeTransform` existe pour empêcher.
- **La cible de SORTIE manquait** : une passe de nœuds se dimensionne sur le
  drawing buffer faute de mieux, donc un film 1920×1080 se composait dans un
  G-buffer de la taille du canevas. Et rien ne restaurait viewport ni ciseaux.
- Le montage pouvait armer un renderer sur la réponse du précédent ; `settled()`
  résout aussi sur un refus, donc l'éclairage attend `canDraw`, pas la promesse.

Revue de simplification, retenu : une seule déclaration de `ComposerJob` pour
les deux chaînes (elle était copiée champ par champ), `ROOM_SIGMA` et
`BOTH_ENGINES` déduplitués, les quatre capacités qui différaient entre moteurs
(`maxSamples`, `maxAnisotropy`, `frameTimer`, `releaseContext`) passent dans
`RenderDriver` — cinq `'capabilities' in renderer` disparaissent, dont un qui
vivait dans le fichier d'interface lui-même. TRAA est retiré jusqu'à son lot :
son module de nœuds n'était plus chargé pour rien, ni son champ de budget lu
par personne.

Le banc moyenne désormais dix images fixes : un échantillon unique variait du
simple au double. Chiffres du rapport remesurés sur cette révision, et une
affirmation du tour précédent corrigée — l'Avancé monte moins vite avec la
scène, mais il monte.
`pnpm engines:parity` rend la même scène de référence sur le Compatible et sur
l'Avancé et compare pixel à pixel, dans le harnais que `world:validate` utilise
déjà entre deux représentations. Six cas, chacun par la couture que le studio
emprunte lui-même : la scène nue, la même sous GTAO, la capture d'export, une
image de film, le patch matériau et la règle temporelle.

Mesuré le 11 septembre 2026, fenêtre au premier plan : scène nue 0 % de pixels
différents, film 642x362 0 %, export 0,22 %, GTAO 2,7 %. Le patch matériau
diffère de 24,75 % — l'écart connu, la cavité tombant sur la couleur diffuse
côté nœuds. Le harnais refuse de mesurer sans image d'animation : une fenêtre
derrière une autre n'en reçoit aucune, et les deux moteurs y dessinent autrement
— 58 % d'écart sur une scène identique, mesuré.

Le moteur vit désormais dans le DOCUMENT (`SceneWorld.engine`), choisi à la
création et relu à chaque montage ; la préférence ne fait plus que pré-remplir le
champ. L'export jeu lit celui de la scène d'entrée. TRAA est porté, sans
paramètre — three 0.185 n'expose aucun nombre d'échantillons — et la
bibliothèque d'effets ne propose plus ce que le moteur du document ne sait pas
bâtir.

Ce que la comparaison a trouvé, que le banc ne pouvait pas voir parce qu'il
mesurait ce que la chaîne coûte et non ce qu'elle dessine :

- l'occlusion sortait ROUGE : `GTAONode` rend dans une cible `RedFormat`, donc
  `getTextureNode()` donne (ao, 0, 0, 1) et multiplier ça tuait le vert et le
  bleu ;
- `blend` ne faisait rien côté Avancé, alors que le `GTAOPass` GL l'applique ;
- un effet temporel donnait une image PLATE à l'export : bâtie, dessinée puis
  libérée, la chaîne ne lui laisse aucun historique. `PostEffectMeta.temporal`
  le déclare et le composeur laisse ces effets hors d'une image unique.

Et ce que la revue a trouvé ensuite, dont un défaut haut : un document en Avancé
n'obtenait jamais le moteur Avancé dans la session qui l'ouvre — le viewport
lançait le chargement du bundle puis demandait s'il était là, sur la ligne
suivante. `useRenderEngineReady` retient le montage jusqu'à ce que ce chargement
aboutisse. Suivent la cible de l'occlusion qui fuyait, la chaîne abandonnée
quand une pile change de forme, `maxSamples` qui répondait à deux questions et
faisait perdre son anticrénelage à une capture, l'applicateur manquant qui
aurait décalé les paramètres d'un effet sur la passe d'un autre, et un soleil
remplacé qui laissait la scène éclairée deux fois.

Rapport : docs/fr/audits/moteur-rendu-c6/RAPPORT.md
La porte a viré au rouge sur `develop` : `check-as-const` lit `git ls-files`, donc
il ne voit pas un fichier neuf tant qu'il n'est pas indexé. La validate lancée
avant le premier `git add` était aveugle à ce test, qui figeait son paramètre avec
`as const` et le rerendait avec un `as unknown as`.

Annoter l'argument de `renderHook` supprime les deux d'un coup.
…ort ce qu'il affirmait sans le tenir

Relecture du chantier C6. Six énoncés du rapport ne tenaient pas devant le code :
un fichier de test qui n'existe pas (`renderDriver.test.ts`), une colonne de banc qui
n'existe pas (`readbackMs`), trois nombres de tests gonflés, des plafonds de porte
présentés comme des mesures arrondies alors que ce sont des marges, un critère
d'acceptation (« un projet existant est identique ») démenti par l'anisotropie maximale
de sa propre étape, et l'export jeu présenté comme honorant le moteur du document alors
que `createWebRender` construit un `WebGLRenderer` sans jamais lire `policy.engine`.

Un défaut, corrigé : les cascades se construisaient sur le moteur Avancé. `CSM` passe par
`onBeforeCompile`, que seul `WebGLRenderer` appelle — three 0.185 ne le nomme nulle part
sous `renderers/common` ni `renderers/webgpu`. Le patch n'atteignait aucun programme
pendant que `dress` éteignait le soleil du document et posait trois bandes à son
intensité : scène éclairée trois fois, ombre perdue. `cascadesWanted(policy, engine)`
porte désormais la règle, et le port de jeu la lit au lieu de la réécrire.

/simplify, quatre angles :
- `gpuComposer` tenait deux maps pour une relation une-à-une ; la clé contenant déjà la
  surface, les deux gardes `[...bound.values()].includes(...)` protégeaient un cas
  impossible. Une `Map<surface, {shape, chain}>` suffit.
- `gpuSamplesOf` fabriquait un `divisor` que `samplesOf` ne lit jamais : `samplesOf`
  s'élargit à `Pick<PostBudget, 'samples'>` et la fonction disparaît.
- `drawOverlay` rejoint `RenderDriver` : `'capabilities' in renderer` au point d'appel
  était l'expression que l'en-tête du driver cite comme ce qu'il existe pour empêcher.
- `asNodeTarget` était écrit deux fois, JSDoc comprise ; il rejoint `drawInto`.
- Le banc réutilise `mountedScene`, `offScreenHost` et l'attente d'images du harnais de
  parité au lieu de les réécrire ; `messageOf` remplace deux copies.
- `mountRenderer` perd un troisième état que seul le test exerçait.

Cinq coûts par image relevés et NON MESURÉS sont écrits dans le rapport plutôt que
corrigés à l'aveugle : ils demandent `pnpm engines:parity` sur une machine WebGPU.

`pnpm validate` verte en 174 s.
La map unique du commit précédent réécrivait son entrée à chaque image : en régime
établi, `{ shape, chain }` reconstruit était identique à celui qu'il remplaçait. La
réponse se donne maintenant avant toute écriture — un objet et un `Map.set` de moins
par surface et par image, sur le chemin à 60 Hz.

Constat de la revue d'efficacité, introduit par la simplification elle-même.
…uteur

`policy.engine` voyage dans le manifeste et `webRender` ne le lit nulle part : un jeu
fait sur le moteur Avancé joue en WebGL. Le champ était rempli par
`gameExportCompiler`, relu par `readRenderPolicy`, et ignoré en silence.

L'honorer demande de porter le bundle de nœuds dans une page exportée — un chantier à
part. Ce qui pouvait être corrigé ici l'est : le jeu le DIT au chargement, par le port
de journal que `webRender` ouvre déjà pour la chaîne d'effets qui ne bâtit pas, et sur
la même doctrine — un jeu qui joue sans ce que son auteur a demandé le dit au lieu de
jouer quand même.

Le rapport note au passage un critère d'acceptation de l'étape 2 qui n'a jamais été
mesuré : « un projet `'gl'` est bit-identique à avant ce chantier ». Aucun banc du dépôt
ne compare une révision à la précédente ; `engines:parity` compare les deux moteurs
entre eux.

`pnpm validate` verte en 167 s.
…t de refuser

Six constats de `/code-review`, tous retenus.

Le seul qui coûtait une image : en passant `'gl'` en dur à `cascadesWanted`, le port de
jeu bâtissait les cascades d'un document Avancé pendant que l'éditeur les lui refusait —
même scène éclairée de deux façons, ce que le commentaire d'`exportRequestOf` désigne
comme l'accident à ne pas avoir. C'est le moteur du DOCUMENT qui décide des deux côtés.

Le refus était par ailleurs muet : « Ombres en cascade » reste une préférence offerte, et
sur un document Avancé elle ne changeait plus rien sans le dire. Elle le dit maintenant
au journal, par la même trace que le repli de moteur, dans les quinze langues.

Quatre suivis de texte : un test de `gpuPostQuality` devenu tautologique après la
suppression de `gpuSamplesOf` (il n'assertait plus que ce que son voisin assertait déjà),
deux références du rapport fausses depuis leur propre commit (`sayEngineIgnored` renommé
`policyOf`, « cinq cas » devenus quatre), et `run-engine-parity.mjs` qui gardait sur ses
plafonds l'affirmation que le rapport venait de corriger.

`pnpm validate` verte en 185 s, 0 lien caché.
Une ligne dans le gabarit, donc dans les quinze langues à la fois : le bandeau de
choix et le chargement de Google Analytics vivent dans un seul fichier partagé,
servi par www.pasquelin.com et commun aux quatre sites. Une correction s'y fait
une fois et non quatre ; son code et ses tests sont dans le dépôt `pasquelin/site`,
sous `public/shared/`.

Rien n'est demandé à Google avant un accord explicite du visiteur, et le panneau
emprunte les couleurs, la police et les arrondis de cette page plutôt que
d'imposer les siens. Propriété GA4 : G-YRFNHZLMJH — un identifiant de mesure
n'est pas un secret, toute page qui le porte le montre à qui lit sa source.
@pasquelin
pasquelin merged commit 9b3c094 into develop Sep 23, 2026
3 checks passed
@pasquelin
pasquelin deleted the feat/mesure-audience branch September 23, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant