[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves du travailleur de génération (§ 5.10) : brancherTravailleur sur
// une portée factice, qui garde chaque message posté. Les configurations
// viennent des démonstrations livrées et d'une charge aux noms inventés.
import assert from 'node:assert/strict' ;
import { describe , test } from '../../test/lanceur.js' ;
import { texteLivre } from '../demo/livrees.js' ;
import { rechercher } from '../moteur/recherche.js' ;
import { analyser , configurationDepuisCharge } from '../stockage/document.js' ;
import { CADENCE _AVANCEMENT , brancherTravailleur } from './generation_travailleur.js' ;
const configurationDe = ( cle ) => configurationDepuisCharge ( analyser ( texteLivre ( cle ) ) . charge ) ;
// Une portée de travailleur factice : ce que le travailleur poste, et
// l'envoi d'un message au travailleur.
function porteeFactice ( ) {
const postes = [ ] ;
const portee = {
onmessage : null ,
postMessage : ( message ) => postes . push ( structuredClone ( message ) ) ,
} ;
brancherTravailleur ( portee ) ;
return { postes , envoyer : ( data ) => portee . onmessage ( { data } ) } ;
}
describe ( 'travailleur de génération (§ 5.10)' , ( ) => {
test ( "après lancer, des avancements à fait croissant, au plus un tous les CADENCE_AVANCEMENT, puis le résultat de rechercher" , ( ) => {
assert . equal ( CADENCE _AVANCEMENT , 50 ) ;
const configuration = configurationDe ( 'petite-conflit' ) ;
const reglages = { graine : 9 , nombre : 2 , arret : 60000 , historique : 1000 } ;
const { postes , envoyer } = porteeFactice ( ) ;
envoyer ( { type : 'lancer' , configuration , reglages } ) ;
const avancements = postes . filter ( ( { type } ) => type === 'avancement' ) ;
assert . ok ( avancements . length > 0 , 'aucun avancement' ) ;
assert . ok ( avancements . length <= Math . ceil ( ( reglages . nombre * reglages . arret ) / 1000 / CADENCE _AVANCEMENT ) ) ;
assert . deepEqual ( avancements , [
{ type : 'avancement' , fait : 50000 , total : 120000 } ,
{ type : 'avancement' , fait : 100000 , total : 120000 } ,
] ) ;
assert . deepEqual ( postes . at ( - 1 ) , { type : 'resultat' , propositions : rechercher ( configuration , reglages ) } ) ;
assert . equal ( postes . length , avancements . length + 1 ) ;
} ) ;
test ( 'des places manquantes postent erreur PLACES_MANQUANTES, les détails du moteur' , ( ) => {
const configuration = configurationDe ( 'petite' ) ;
configuration . tables = configuration . tables . slice ( 1 ) ;
const { postes , envoyer } = porteeFactice ( ) ;
envoyer ( { type : 'lancer' , configuration , reglages : { graine : 1 , nombre : 1 , arret : 10 } } ) ;
assert . deepEqual ( postes , [ { type : 'erreur' , code : 'PLACES_MANQUANTES' , details : { placesManquantes : 3 } } ] ) ;
} ) ;
test ( 'une graine de 2^32 poste erreur REGLAGE_HORS_DOMAINE, qui nomme le réglage, sans le message du moteur' , ( ) => {
const { postes , envoyer } = porteeFactice ( ) ;
envoyer ( { type : 'lancer' , configuration : configurationDe ( 'petite' ) , reglages : { graine : 2 * * 32 , nombre : 1 , arret : 10 } } ) ;
assert . deepEqual ( postes , [ { type : 'erreur' , code : 'REGLAGE_HORS_DOMAINE' , details : { reglage : 'graine' , remede : null } } ] ) ;
} ) ;
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
test ( 'une faute que versErreurGeneration ne convertit pas poste { type: faute, message, pile }, sans lever : le canal d’ erreur du travailleur reste au script qui ne se charge pas' , ( ) => {
const { postes , envoyer } = porteeFactice ( ) ;
assert . doesNotThrow ( ( ) => envoyer ( { type : 'lancer' , configuration : null , reglages : { graine : 1 , nombre : 1 , arret : 10 } } ) ) ;
assert . equal ( postes . length , 1 ) ;
const [ faute ] = postes ;
assert . deepEqual ( Object . keys ( faute ) . sort ( ) , [ 'message' , 'pile' , 'type' ] ) ;
assert . equal ( faute . type , 'faute' ) ;
assert . match ( faute . message , /^TypeError: / ) ;
assert . equal ( typeof faute . pile , 'string' ) ;
assert . match ( faute . pile , /TypeError/ ) ;
} ) ;
[ADD] application: generation controller, worker, evaluation table
Generating must never block the screen or record a plan that breaks a
rule. The controller refuses before launching, in a fixed order: an
out-of-range setting named by the engine's own check, the session's
refusals, then what makes the run useless. It runs the search in a worker
or in the thread, records only propositions whose invariants hold, skips
plans already present, and keeps the last duration for the session only.
The evaluation table ranks propositions on the minimum of encounters and
names the reservations a proposition does not honour.
Checked: 2112 node and 60 node-long tests; coverage floors kept.
--- FR ---
[ADD] application : génération, travailleur, table d'évaluation
Générer ne doit jamais bloquer l'écran ni enregistrer un plan qui enfreint
une règle. Le contrôleur refuse avant tout lancement, dans un ordre fixe :
un réglage hors de son domaine, nommé par la règle même du moteur, les
refus de la séance, puis ce qui rend le calcul vain. Il lance la recherche
dans un travailleur ou dans le fil, n'enregistre que les propositions dont
les invariants tiennent, écarte les plans déjà présents, et garde la
dernière durée pour la seule séance. La table d'évaluation classe les
propositions sur le minimum de rencontres et nomme les réservations
qu'une proposition ne tient pas.
Vérifié : 2112 node et 60 node-long ; seuils de couverture tenus.
Assisted-by: Claude Opus 5.5
2026-10-07 06:09:45 -04:00
test ( "un message d'un autre type ne lance rien" , ( ) => {
const { postes , envoyer } = porteeFactice ( ) ;
envoyer ( { type : 'inconnu' } ) ;
envoyer ( null ) ;
assert . deepEqual ( postes , [ ] ) ;
} ) ;
} ) ;