[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
// La géométrie d'une table (§ 7.1, § 7.7), en centimètres de dessin, l'axe y
|
|
|
|
|
|
// vers le bas : plateau tiré du nombre de places, chaises numérotées depuis le
|
|
|
|
|
|
// haut dans le sens horaire, emprise, poignée et origine du bloc de noms. Le
|
|
|
|
|
|
// dessin pose les chaises par geometrieTable et la désignation d'un siège
|
|
|
|
|
|
// (designation.js) lit les mêmes chaises : une seule implémentation, que le
|
|
|
|
|
|
// dessin et le dépôt partagent. Aucune table ne tourne : la géométrie ne
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
// porte aucun angle. La grille où se posent les tables des démonstrations et
|
|
|
|
|
|
// celles que l'opérateur crée a un seul pas, ecartsGrille.
|
|
|
|
|
|
import { constante } from './constantes.js';
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @typedef {Object} GeometrieTable en cm
|
|
|
|
|
|
* @property {number} id
|
|
|
|
|
|
* @property {number} numero
|
|
|
|
|
|
* @property {'ronde'|'carree'} forme
|
|
|
|
|
|
* @property {{x: number, y: number}} centre
|
|
|
|
|
|
* @property {number} demiTaille rayon du plateau (ronde) ou demi-côté (carrée)
|
|
|
|
|
|
* @property {number} pas distance entre deux sièges voisins, au bord du plateau
|
|
|
|
|
|
* @property {Array<{siege: number, x: number, y: number, rayon: number}>} chaises sièges 1 à places
|
|
|
|
|
|
* @property {{x: number, y: number}} poignee centre du disque de la poignée
|
|
|
|
|
|
* @property {number} rayonEmprise demiTaille + 2 × RAYON_CHAISE : rayon du disque d'emprise
|
|
|
|
|
|
* (ronde) ou demi-côté du carré d'emprise (carrée)
|
|
|
|
|
|
* @property {{x: number, y: number}} origineListe coin haut gauche du bloc de noms
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/** Diamètre minimal d'une table ronde, côté minimal d'une carrée, en cm :
|
|
|
|
|
|
* réglage par défaut, non norme (§ 7.7). */
|
|
|
|
|
|
export const PLANCHER_DIAMETRE = 70;
|
|
|
|
|
|
/** Longueur de bord par convive, en cm (§ 7.7). */
|
|
|
|
|
|
export const LARGEUR_PAR_PLACE = 60;
|
|
|
|
|
|
/** Rayon d'une chaise, en cm. */
|
|
|
|
|
|
export const RAYON_CHAISE = 22;
|
|
|
|
|
|
/** Rayon du disque de la poignée, en cm. */
|
|
|
|
|
|
export const RAYON_POIGNEE = 15;
|
|
|
|
|
|
/** Distance entre l'emprise et le bord de la poignée, en cm. */
|
|
|
|
|
|
export const ECART_POIGNEE = 8;
|
[ADD] interface: cross-screen rendering checks, frame marks, badge plate
A screen walker reaches every screen of the iteration by gestures; on each,
in both themes, text holds 4.5:1 and marks 3:1 against what is painted
under them, every text passes through the translation tables, the conflict
hue paints only conflict marks, and no state differs by colour alone. Mode
frames now carry their mark, the count badge sits on a plate over the
conflict hatch, the name list clears the focus square, the participant form
is novalidate. Demonstrations space columns so a name list never runs over
a neighbour's chairs. Checked: 2676 node, 489 browser, 60 long tests.
--- FR ---
[ADD] interface : rendu transversal, marques des cadres, socle, pastille
Un parcours atteint par gestes chaque écran de l'itération ; sur chacun,
dans les deux thèmes, le texte tient 4,5:1 et les marques 3:1 sur ce qui
est peint dessous, tout texte passe par les tables de libellés, la teinte
de conflit ne peint que les marques de conflit, et aucun état ne diffère par
la seule couleur. Les cadres de mode portent leur marque, la pastille pose
sur un socle au-dessus de la trame, la liste des noms s'écarte du focus, le
formulaire est novalidate. Les démonstrations espacent leurs colonnes : une
liste ne court plus sur les chaises voisines. Vérifié : 2676, 489, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 14:06:56 -04:00
|
|
|
|
/** Distance entre l'emprise et le bloc de noms, en cm : le carré du focus
|
|
|
|
|
|
* que l'interface pose autour de l'emprise, ou d'une chaise, s'étend à
|
|
|
|
|
|
* 26 cm d'elle, plus la moitié de son trait ; le bloc commence au-delà, et
|
|
|
|
|
|
* aucune marque ne couvre la liste. */
|
|
|
|
|
|
export const ECART_LISTE = 36;
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
/** Écart minimal entre les centres de deux rangées de la grille des tables,
|
|
|
|
|
|
* en cm. */
|
|
|
|
|
|
export const ECART_RANGEES = 300;
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
|
|
|
|
|
|
// Accroissement du rayon d'une ronde pour une place de plus, au-dessus du
|
|
|
|
|
|
// plancher : le périmètre croît de LARGEUR_PAR_PLACE, le rayon de 60 / 2π.
|
|
|
|
|
|
const RAYON_PAR_PLACE = LARGEUR_PAR_PLACE / (2 * Math.PI);
|
|
|
|
|
|
|
|
|
|
|
|
// Les quatre côtés d'une carrée, dans l'ordre où ils reçoivent les places —
|
|
|
|
|
|
// haut, droit, bas, gauche — : le coin où commence le côté dans le sens
|
|
|
|
|
|
// horaire, en demi-côtés depuis le centre, la direction de parcours et la
|
|
|
|
|
|
// normale sortante.
|
|
|
|
|
|
const COTES = [
|
|
|
|
|
|
{ depart: { x: -1, y: -1 }, direction: { x: 1, y: 0 }, normale: { x: 0, y: -1 } },
|
|
|
|
|
|
{ depart: { x: 1, y: -1 }, direction: { x: 0, y: 1 }, normale: { x: 1, y: 0 } },
|
|
|
|
|
|
{ depart: { x: 1, y: 1 }, direction: { x: -1, y: 0 }, normale: { x: 0, y: 1 } },
|
|
|
|
|
|
{ depart: { x: -1, y: 1 }, direction: { x: 0, y: -1 }, normale: { x: -1, y: 0 } },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Lève RangeError sur un nombre de places qui n'est pas un entier ≥ 1, ou un
|
|
|
|
|
|
// diamètre ou côté saisi qui n'est pas un nombre fini > 0 : la géométrie
|
|
|
|
|
|
// d'une table se calcule sans valeur de repli.
|
|
|
|
|
|
function exigerPlaces(places, saisi) {
|
|
|
|
|
|
if (!Number.isInteger(places) || places < 1) {
|
|
|
|
|
|
throw new RangeError(`nombre de places entier ≥ 1 attendu, reçu ${JSON.stringify(places)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (saisi !== null && !(Number.isFinite(saisi) && saisi > 0)) {
|
|
|
|
|
|
throw new RangeError(`dimension saisie > 0 attendue, reçu ${JSON.stringify(saisi)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Diamètre d'une table ronde (§ 7.7) : max(70, places × 60 / π), ou le
|
|
|
|
|
|
* diamètre saisi, qui l'emporte.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {number} places entier ≥ 1
|
|
|
|
|
|
* @param {{saisi?: number|null}} [options]
|
|
|
|
|
|
* @returns {number} cm
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function diametre(places, { saisi = null } = {}) {
|
|
|
|
|
|
exigerPlaces(places, saisi);
|
|
|
|
|
|
return saisi ?? Math.max(PLANCHER_DIAMETRE, (places * LARGEUR_PAR_PLACE) / Math.PI);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Côté d'une table carrée : max(70, ⌈places / 4⌉ × 60) — le côté le plus
|
|
|
|
|
|
* garni porte ⌈places / 4⌉ sièges —, ou le côté saisi, qui l'emporte.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {number} places entier ≥ 1
|
|
|
|
|
|
* @param {{saisi?: number|null}} [options]
|
|
|
|
|
|
* @returns {number} cm
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function cote(places, { saisi = null } = {}) {
|
|
|
|
|
|
exigerPlaces(places, saisi);
|
|
|
|
|
|
return saisi ?? Math.max(PLANCHER_DIAMETRE, Math.ceil(places / 4) * LARGEUR_PAR_PLACE);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Chaises d'une ronde : sur le cercle de rayon demiTaille + RAYON_CHAISE,
|
|
|
|
|
|
// siège s à l'angle −π/2 + 2π(s − 1)/places, le siège 1 en haut et la
|
|
|
|
|
|
// numérotation dans le sens horaire à l'écran (y vers le bas).
|
|
|
|
|
|
function chaisesRonde(centre, demiTaille, places) {
|
|
|
|
|
|
const rayon = demiTaille + RAYON_CHAISE;
|
|
|
|
|
|
return Array.from({ length: places }, (_, i) => {
|
|
|
|
|
|
const angle = -Math.PI / 2 + (2 * Math.PI * i) / places;
|
|
|
|
|
|
return { siege: i + 1, x: centre.x + rayon * Math.cos(angle), y: centre.y + rayon * Math.sin(angle), rayon: RAYON_CHAISE };
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Nombre de sièges de chaque côté d'une carrée, dans l'ordre de COTES :
|
|
|
|
|
|
// ⌊places / 4⌋ chacun, et une place en surplus à chacun des premiers côtés.
|
|
|
|
|
|
const siegesParCote = (places) => COTES.map((_, i) => Math.floor(places / 4) + (i < places % 4 ? 1 : 0));
|
|
|
|
|
|
|
|
|
|
|
|
// Chaises d'une carrée : sur chaque côté, n sièges espacés de côté / n, le
|
|
|
|
|
|
// premier à côté / 2n du coin de départ, leur centre à RAYON_CHAISE au-delà
|
|
|
|
|
|
// du bord ; le siège 1 est le premier du côté haut.
|
|
|
|
|
|
function chaisesCarree(centre, demiTaille, places) {
|
|
|
|
|
|
const chaises = [];
|
|
|
|
|
|
siegesParCote(places).forEach((nombre, i) => {
|
|
|
|
|
|
const { depart, direction, normale } = COTES[i];
|
|
|
|
|
|
for (let j = 0; j < nombre; j += 1) {
|
|
|
|
|
|
const avance = ((j + 0.5) * 2 * demiTaille) / nombre;
|
|
|
|
|
|
chaises.push({
|
|
|
|
|
|
siege: chaises.length + 1,
|
|
|
|
|
|
x: centre.x + depart.x * demiTaille + direction.x * avance + normale.x * RAYON_CHAISE,
|
|
|
|
|
|
y: centre.y + depart.y * demiTaille + direction.y * avance + normale.y * RAYON_CHAISE,
|
|
|
|
|
|
rayon: RAYON_CHAISE,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return chaises;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Géométrie d'une table, en cm (contrat de données, GeometrieTable).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Le pas d'une ronde est π × diamètre / places, calculé sous la forme
|
|
|
|
|
|
* max(60, 70π / places) qui lui est égale : elle rend 60 exactement dès que
|
|
|
|
|
|
* le périmètre borne la table, là où le produit rend 59,999… pour certains
|
|
|
|
|
|
* nombres de places et ferait basculer le régime des cibles (designation.js)
|
|
|
|
|
|
* sous le seuil. Le pas d'une carrée est son côté divisé par le nombre de
|
|
|
|
|
|
* sièges du côté le plus garni.
|
|
|
|
|
|
*
|
|
|
|
|
|
* La poignée est un disque de rayon RAYON_POIGNEE sur la diagonale à +45°
|
|
|
|
|
|
* (vers le bas à droite), son bord à ECART_POIGNEE de l'emprise : son centre
|
|
|
|
|
|
* est à rayonEmprise + 23 cm du centre d'une ronde, à rayonEmprise × √2 + 23
|
|
|
|
|
|
* cm de celui d'une carrée, au-delà du coin de son emprise.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{id: number, numero: number, forme: 'ronde'|'carree', position: {x: number, y: number}}} table
|
|
|
|
|
|
* l'enregistrement de charge.tables ; position est le centre
|
|
|
|
|
|
* @param {number} places la capacité courante, entier ≥ 1
|
|
|
|
|
|
* @returns {GeometrieTable}
|
|
|
|
|
|
* @throws {RangeError} pour une forme inconnue ou un nombre de places invalide
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function geometrieTable(table, places) {
|
|
|
|
|
|
const { id, numero, forme } = table;
|
|
|
|
|
|
if (forme !== 'ronde' && forme !== 'carree') {
|
|
|
|
|
|
throw new RangeError(`forme de table inconnue : ${JSON.stringify(forme)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const centre = { x: table.position.x, y: table.position.y };
|
|
|
|
|
|
const ronde = forme === 'ronde';
|
|
|
|
|
|
const demiTaille = (ronde ? diametre(places) : cote(places)) / 2;
|
|
|
|
|
|
const pas = ronde
|
|
|
|
|
|
? Math.max(LARGEUR_PAR_PLACE, (PLANCHER_DIAMETRE * Math.PI) / places)
|
|
|
|
|
|
: (2 * demiTaille) / Math.ceil(places / 4);
|
|
|
|
|
|
const chaises = ronde ? chaisesRonde(centre, demiTaille, places) : chaisesCarree(centre, demiTaille, places);
|
|
|
|
|
|
const rayonEmprise = demiTaille + 2 * RAYON_CHAISE;
|
|
|
|
|
|
const distancePoignee = (ronde ? rayonEmprise : rayonEmprise * Math.SQRT2) + ECART_POIGNEE + RAYON_POIGNEE;
|
|
|
|
|
|
const decalage = distancePoignee / Math.SQRT2;
|
|
|
|
|
|
return {
|
|
|
|
|
|
id,
|
|
|
|
|
|
numero,
|
|
|
|
|
|
forme,
|
|
|
|
|
|
centre,
|
|
|
|
|
|
demiTaille,
|
|
|
|
|
|
pas,
|
|
|
|
|
|
chaises,
|
|
|
|
|
|
poignee: { x: centre.x + decalage, y: centre.y + decalage },
|
|
|
|
|
|
rayonEmprise,
|
|
|
|
|
|
origineListe: { x: centre.x + rayonEmprise + ECART_LISTE, y: centre.y - demiTaille },
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Rectangle englobant des emprises : pour chaque table, le carré de
|
|
|
|
|
|
* demi-côté rayonEmprise centré sur elle, qui circonscrit le disque d'une
|
|
|
|
|
|
* ronde et égale l'emprise d'une carrée.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {GeometrieTable[]} geometries
|
|
|
|
|
|
* @returns {{minX: number, minY: number, maxX: number, maxY: number}|null} null sans table
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function englobant(geometries) {
|
|
|
|
|
|
if (geometries.length === 0) return null;
|
|
|
|
|
|
let minX = Infinity;
|
|
|
|
|
|
let minY = Infinity;
|
|
|
|
|
|
let maxX = -Infinity;
|
|
|
|
|
|
let maxY = -Infinity;
|
|
|
|
|
|
for (const { centre, rayonEmprise } of geometries) {
|
|
|
|
|
|
minX = Math.min(minX, centre.x - rayonEmprise);
|
|
|
|
|
|
minY = Math.min(minY, centre.y - rayonEmprise);
|
|
|
|
|
|
maxX = Math.max(maxX, centre.x + rayonEmprise);
|
|
|
|
|
|
maxY = Math.max(maxY, centre.y + rayonEmprise);
|
|
|
|
|
|
}
|
|
|
|
|
|
return { minX, minY, maxX, maxY };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
/**
|
|
|
|
|
|
* Le pas de la grille des tables, en cm entiers, pour la plus grande emprise
|
|
|
|
|
|
* E des tables données, chacune de sa forme et de son nombre de places :
|
|
|
|
|
|
* entre deux colonnes, ⌈2E + 2 × ECART_LISTE + LARGEUR_BLOC_NOMS_CM⌉ — le bloc
|
|
|
|
|
|
* de noms d'une table, à ECART_LISTE de son emprise et large de
|
|
|
|
|
|
* LARGEUR_BLOC_NOMS_CM, où toute ligne s'abrège, s'achève à ECART_LISTE de
|
|
|
|
|
|
* l'emprise de la colonne suivante : aucune liste ne passe sur une autre
|
|
|
|
|
|
* table de la grille, d'aucune rangée ; entre deux rangées,
|
|
|
|
|
|
* max(ECART_RANGEES, ⌈2E + ECART_LISTE⌉) — deux emprises voisines restent à
|
|
|
|
|
|
* ECART_LISTE l'une de l'autre, la marge du carré de focus.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Array<{forme: 'ronde'|'carree', places: number}>} tables au moins une
|
|
|
|
|
|
* @returns {{colonnes: number, rangees: number}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function ecartsGrille(tables) {
|
|
|
|
|
|
const emprise = Math.max(
|
|
|
|
|
|
...tables.map(({ forme, places }) => geometrieTable({ id: 0, numero: 0, forme, position: { x: 0, y: 0 } }, places).rayonEmprise),
|
|
|
|
|
|
);
|
|
|
|
|
|
return {
|
|
|
|
|
|
colonnes: Math.ceil(2 * emprise + 2 * ECART_LISTE + constante('LARGEUR_BLOC_NOMS_CM')),
|
|
|
|
|
|
rangees: Math.max(ECART_RANGEES, Math.ceil(2 * emprise + ECART_LISTE)),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
/**
|
|
|
|
|
|
* Position d'une table créée : la première case libre, ligne par ligne, d'une
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
* grille à ⌈√(T + 1)⌉ colonnes, T = tables.length, partant de (0, 0), au pas
|
|
|
|
|
|
* d'ecartsGrille des tables posées et de la nouvelle — le pas des
|
|
|
|
|
|
* démonstrations (scripts/engendrer_livrables.js). Une case est prise quand
|
|
|
|
|
|
* une table posée est à moins d'un pas de son centre sur chacun des deux
|
|
|
|
|
|
* axes, bornes exclues. Une table à un pas de colonne ou plus a son emprise
|
|
|
|
|
|
* hors de la bande du bloc de noms de la table nouvelle, et la sienne
|
|
|
|
|
|
* s'achève avant l'emprise nouvelle, à ECART_LISTE au moins, quelle que soit
|
|
|
|
|
|
* la rangée : c'est le calcul d'ecartsGrille, fait sur la plus grande
|
|
|
|
|
|
* emprise des deux. Une table à un pas de rangée ou plus a son emprise à
|
|
|
|
|
|
* ECART_LISTE au moins de l'emprise nouvelle. Ainsi une table déplacée hors
|
|
|
|
|
|
* de la grille, ou une grille dont le pas a crû depuis qu'elle a été posée,
|
|
|
|
|
|
* ne reçoit pas la liste de la table nouvelle et ne lui porte pas la sienne
|
|
|
|
|
|
* à travers une colonne.
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
*
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
* La recherche s'arrête : un intervalle ouvert de largeur 2 × pas contient
|
|
|
|
|
|
* au plus deux multiples du pas, et une table prend donc au plus quatre
|
|
|
|
|
|
* cases ; l'une des 4T + 1 premières cases est libre.
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
*
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
* @param {Array<{position: {x: number, y: number}, forme: 'ronde'|'carree', places: number}>} tables
|
|
|
|
|
|
* les tables posées, chacune à sa capacité courante
|
|
|
|
|
|
* @param {{forme: 'ronde'|'carree', places: number}} nouvelle la table à poser
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
* @returns {{x: number, y: number}}
|
|
|
|
|
|
*/
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
export function caseLibre(tables, nouvelle) {
|
|
|
|
|
|
const { colonnes: pasX, rangees: pasY } = ecartsGrille([...tables, nouvelle]);
|
|
|
|
|
|
const colonnes = Math.ceil(Math.sqrt(tables.length + 1));
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
for (let rang = 0; ; rang += 1) {
|
[FIX] plan: named warning, one places state, highlight, table spacing
The unheld-reservation warning names the person and the table number
instead of their identifiers. The places state is computed once per change
and handed to the side panels, not once per panel. A person can be
highlighted from their menu or from the grid, and the view bar names them.
A created table takes a cell a full pitch away from every table, its own
capacity included, so no name list runs over a neighbour's chairs. The
rendering guards fail on an empty walk and also check focus and selection.
Checked: 2692 node, 496 browser, 60 long tests, from the index alone.
--- FR ---
[FIX] plan : avertissement nommé, état des places unique, évidence, pas
L'avertissement de réservation non honorée nomme la personne et le numéro
de la table plutôt que leurs identifiants. L'état des places se calcule une
fois par changement et passe aux panneaux, non une fois par panneau. Une
personne se met en évidence par son menu ou depuis la grille, et la barre
de vue la nomme. Une table créée prend une case à un pas plein de chaque
table, sa capacité comprise : aucune liste ne court sur les chaises
voisines. Les gardes du rendu échouent sur un parcours vide et éprouvent
aussi le focus et la sélection. Vérifié : 2692 node, 496 navigateur, 60.
Assisted-by: Claude Opus 5.5
2026-10-07 15:05:01 -04:00
|
|
|
|
const candidate = { x: (rang % colonnes) * pasX, y: Math.floor(rang / colonnes) * pasY };
|
|
|
|
|
|
const prise = tables.some(
|
|
|
|
|
|
({ position: { x, y } }) => Math.abs(x - candidate.x) < pasX && Math.abs(y - candidate.y) < pasY,
|
|
|
|
|
|
);
|
[ADD] geometry: tables, their chairs, designating a seat
A table's tabletop, chairs, footprint and handle derive from its number of
places, chairs numbered clockwise from the top, in drawing centimetres.
The seat a pointer designates is read from the same chairs the drawing
places, through one implementation, so what is drawn and where a drop
lands cannot disagree. No table rotates.
Checked: 1681 node tests.
--- FR ---
[ADD] géométrie : les tables, leurs chaises, la désignation d'un siège
Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son
nombre de places, chaises numérotées depuis le haut dans le sens horaire,
en centimètres de dessin. Le siège que désigne un pointeur se lit sur les
chaises mêmes que le dessin pose, par une seule implémentation : ce qui est
dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne
tourne.
Vérifié : 1681 épreuves node.
Assisted-by: Claude Opus 5.5
2026-10-07 02:32:47 -04:00
|
|
|
|
if (!prise) return candidate;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Nombre de places sous la poignée : placesInitiales + round(ecartRadial ×
|
|
|
|
|
|
* 2π / 60), jamais sous 2. L'écart se compte depuis le début du geste
|
|
|
|
|
|
* (§ 7.2) ; un demi-pas exact s'arrondit vers le haut (Math.round).
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {number} placesInitiales les places au début du geste
|
|
|
|
|
|
* @param {number} ecartRadial cm, projeté sur l'axe de la poignée, positif en s'éloignant du centre
|
|
|
|
|
|
* @returns {number} entier ≥ 2
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function placesSousPoignee(placesInitiales, ecartRadial) {
|
|
|
|
|
|
return Math.max(2, placesInitiales + Math.round(ecartRadial / RAYON_PAR_PLACE));
|
|
|
|
|
|
}
|