1// Live office â Etappe 3. Same contract as GET /api/backlog/buero. 2// Headless Nachweis has no adminFetch, so this module stays dark and the 3// first frame stays the literal Belegschaft from belegschaft.js. 4// 5// After the first poll the figures do what the 2D office does: sit at 6// their own desk when they work, walk the hall to a rest spot when they 7// don't, and take a seat at hausMeetingTisch when runden[0] has a voice. 8// Arms swing on the walk and type at the desk; rest pose is 0. 9 10import { setzeFigurStand } from './belegschaft.js'; 11import { 12 standVon, imGespraechVon, sitzplaetzeBesprechung, wegVon, 13 wegLaenge, wegPunkt, laufDauer, blickNach, SITZ_ZAHL, 14 sitzpose, hoeheVon, laufHoehe, etageVon, armWinkel, STATION_NAME, 15} from './ort.js'; 16 17const POLL_MS = 8000; 18const AM_ZIEL = 0.05; 19 20function rolleVonDaten(daten, name) { 21 return ((daten && daten.rollen) || []).find((r) => r && r.name === name) || null; 22} 23 24function rundeAmTisch(daten) { 25 const runden = Array.isArray(daten && daten.runden) ? daten.runden : []; 26 return runden.length ? runden[0] : null; 27} 28 29const HALTUNG_TEXT = { dafuer: 'dafür', dagegen: 'dagegen', unentschieden: 'unentschieden' }; 30 31function vorgangDerRolle(daten, name) { 32 const eigene = ((daten && daten.vorgaenge) || []).filter((v) => v.zustaendig === name); 33 return eigene.find((x) => Number(x.zeigen) === 1) 34 || eigene.find((x) => Number(x.in_arbeit)) 35 || eigene[0] 36 || null; 37} 38 39function stationText(key) { 40 if (!key) return ''; 41 return STATION_NAME[key] || key; 42} 43 44function taetigkeit(daten, name, stand) { 45 if (stand === 'besprechung') { 46 const runde = rundeAmTisch(daten); 47 const stimme = ((runde && runde.stimmen) || []).find((s) => s && s.rolle === name); 48 if (stimme && stimme.haltung && HALTUNG_TEXT[stimme.haltung]) { 49 return { art: 'haltung', text: HALTUNG_TEXT[stimme.haltung] }; 50 } 51 return { art: 'besprechung', text: (runde && runde.titel) || 'am Besprechungstisch' }; 52 } 53 const rolle = rolleVonDaten(daten, name); 54 if (!rolle || (rolle.an_arbeit_seit_min === undefined && rolle.arbeitet_seit_min === undefined)) { 55 return null; 56 } 57 const v = vorgangDerRolle(daten, name); 58 if (rolle.an_arbeit_seit_min !== undefined) { 59 const station = stationText(v && v.station); 60 const titel = (v && v.titel) || 'arbeitet'; 61 return { 62 art: 'arbeit', 63 text: station ? (station + ' · ' + titel) : titel, 64 station: (v && v.station) || '', 65 seit_min: Number(rolle.an_arbeit_seit_min), 66 }; 67 } 68 return { art: 'geliefert', text: 'zuletzt geliefert' }; 69} 70 71// Same words as 2D standText / blaseInhalt. Name is always on; work 72// text only when there is a real stand (never invent a sentence). 73function etikett(daten, name, stand) { 74 const t = taetigkeit(daten, name, stand); 75 if (t) return t; 76 if (stand === 'besprechung') return { art: 'besprechung', text: 'am Besprechungstisch' }; 77 if (stand === 'arbeit') return { art: 'arbeit', text: 'arbeitet' }; 78 return { art: 'name', text: '' }; 79} 80 81function wenigBewegung() { 82 return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); 83} 84 85// Steht der Besprechungstisch in dieser Szene, und ist er an? Mehr fragt 86// dieses Modul das Mesh nicht mehr. Bis zum 17.08.2026 hat es hier die 87// Position von 'hausMeetingTischTop' gelesen â das Mesh des alten Hauses, 88// das eineVilla() abschaltet und das seine Koordinate danach trotzdem 89// weiter herausgibt: getMeshByName kennt kein isEnabled. Damit sassen die 90// sechs Sitze bei z=-10,8, also 19 m vor der Suedwand im Freien, und die 91// Flurlinie gleich mit. 92// 93// Neu: der Name ist der des Hanghaus-Tisches, ein abgeschaltetes Mesh 94// zaehlt als nicht vorhanden, und die Koordinaten kommen ohnehin nicht vom 95// Mesh, sondern aus BESPRECHUNG â der Stelle, aus der auch der Tisch 96// gebaut wird. Ohne Tisch gibt es keine Besprechungssitze und die Figuren 97// bleiben, wo sie sind. Das ist der richtige Ausgang, nicht der Notausgang.
98const TISCH_MESH = 'hangBespPlatte'; 99 100function tischOrt(scene) { 101 const platte = scene.getMeshByName(TISCH_MESH); 102 if (!platte) return null; 103 if (typeof platte.isEnabled === 'function' && !platte.isEnabled()) return null; 104 return { vorhanden: true }; 105} 106 107export function starteBetrieb(scene) { 108 const zustand = { 109 live: false, 110 daten: null, 111 figuren: new Map(), 112 phase: new Map(), 113 labels: new Map(), 114 wege: new Map(), 115 // Auf welcher Etage jede Figur gerade steht. Aus der Welthoehe ist 116 // das nicht zurueckzurechnen â wer im Obergeschoss am Tisch sitzt, 117 // ist bei 3,49, und das ist keine Etage. Also mitgefuehrt. 118 etage: new Map(), 119 sitze: [], 120 // Namen der Figuren, deren metadata.home ohne Etage geschrieben 121 // wurde. Leer, solange die drei Schreibstellen ihr y setzen; siehe 122 // heimVon(). Der Waechter liest die Liste aus. 123 heimatOhneEtage: [], 124 }; 125 126 function registriere(m) { 127 const rolle = m && m.metadata && m.metadata.rolle; 128 if (!rolle || typeof rolle !== 'string') return; 129 if (!m.name || m.name !== 'figur_' + rolle.toLowerCase()) return; 130 zustand.figuren.set(rolle, m); 131 zustand.phase.set(m, Math.random() * Math.PI * 2); 132 } 133 scene.meshes.forEach(registriere); 134 if (scene.transformNodes) scene.transformNodes.forEach(registriere); 135 136 // Keine Flurlinie mehr, die hier einmal eingefroren wird â sie haengt am 137 // Stockwerk der Figur und wird bei jedem Lauf neu geholt. 138 // 139 // Und die Sitze werden nachgezogen, solange es keine gibt: scene.js ruft 140 // starteBetrieb() auf, BEVOR es veredleSzene() aufruft, und erst 141 // veredleSzene() baut das Hanghaus. Hier stand bis zum 18.08.2026 eine 142 // Zeilennummer statt der beiden Funktionsnamen; sie war nach einem Tag 143 // falsch (1689 -> 1717). Die Reihenfolge ist die Aussage, die Zeile war 144 // nur ihr Fundort. Ein einmaliger Griff hier faende den 145 // Besprechungstisch nie und die Besprechung fiele still aus. Das Umstellen 146 // der beiden Aufrufe waere die andere Loesung â Sigrun hat am 17.08.2026 147 // gemessen, dass sie den eigentlichen Fehler nicht behebt, und eine 148 // Verdrahtung, die von der Zeilenreihenfolge in einer fremden Datei 149 // abhaengt, ist ohnehin keine. 150 function sitzeHolen() { 151 if (!zustand.sitze.length && tischOrt(scene)) { 152 zustand.sitze = sitzplaetzeBesprechung(); 153 } 154 return zustand.sitze; 155 } 156 sitzeHolen(); 157 158 function schicht() { 159 let el = document.getElementById('b3d-blasen'); 160 if (!el && scene.getEngine().getRenderingCanvas()) { 161 const canvas = scene.getEngine().getRenderingCanvas(); 162 if (!canvas.parentElement) return null; 163 el = document.createElement('div'); 164 el.id = 'b3d-blasen'; 165 el.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:6;overflow:hidden;'; 166 canvas.parentElement.appendChild(el); 167 } 168 return el; 169 } 170 171 function blaseFuer(name, inhalt) { 172 const layer = schicht(); 173 if (!layer) return; 174 let node = zustand.labels.get(name); 175 if (!inhalt) { 176 if (node) { node.remove(); zustand.labels.delete(name); } 177 return; 178 } 179 if (!node) { 180 node = document.createElement('div'); 181 node.style.cssText = 'position:absolute;max-width:200px;padding:3px 8px 4px;' + 182 'border-radius:10px 10px 10px 4px;font:11px/1.3 ui-sans-serif,system-ui,sans-serif;' + 183 'background:rgba(10,14,22,0.9);color:#F4F7FF;white-space:nowrap;' + 184 'overflow:hidden;text-overflow:ellipsis;transform:translate(-50%,-110%);'; 185 layer.appendChild(node); 186 zustand.labels.set(name, node); 187 } 188 const farbe = inhalt.art === 'arbeit' ? '#6FE1FF' 189 : (inhalt.art === 'besprechung' ? '#FFD76B' 190 : (inhalt.art === 'geliefert' ? '#FFD76B' : 'rgba(232,237,246,0.35)')); 191 node.style.border = '1px solid ' + farbe; 192 node.textContent = inhalt.text ? (name + ' · ' + inhalt.text) : name; 193 } 194 195 function dauerMinText(min) {
196 const n = Number(min); 197 if (!isFinite(n) || n < 0) return ''; 198 if (n < 1) return 'gerade eben'; 199 if (n === 1) return '1 min'; 200 return Math.round(n) + ' min'; 201 } 202 203 function jetztLeiste(daten) { 204 const canvas = scene.getEngine().getRenderingCanvas(); 205 if (!canvas || !canvas.parentElement) return; 206 let el = document.getElementById('b3d-jetzt'); 207 if (!el) { 208 el = document.createElement('div'); 209 el.id = 'b3d-jetzt'; 210 el.setAttribute('role', 'region'); 211 el.setAttribute('aria-label', 'Jetzt im Haus â wer gerade an etwas sitzt'); 212 el.style.cssText = 'position:absolute;left:10px;right:10px;top:10px;z-index:8;' + 213 'padding:7px 10px 8px;border-radius:8px;pointer-events:none;' + 214 'font:12px/1.35 ui-sans-serif,system-ui,sans-serif;' + 215 'background:rgba(8,12,18,0.82);color:#E8EDF6;' + 216 'border:1px solid rgba(111,225,255,0.28);'; 217 canvas.parentElement.appendChild(el); 218 } 219 const sitzend = []; 220 zustand.figuren.forEach((mesh, name) => { 221 const t = taetigkeit(daten, name, 'arbeit'); 222 if (t && t.art === 'arbeit') sitzend.push({ name, mesh, t }); 223 }); 224 sitzend.sort((a, b) => (a.t.seit_min || 0) - (b.t.seit_min || 0)); 225 const arbeit = window.__belegschaft && window.__belegschaft.arbeit || 0; 226 const besprechung = window.__belegschaft && window.__belegschaft.besprechung || 0; 227 const ruhe = window.__belegschaft && window.__belegschaft.ruhe || 0; 228 const kopf = 'Jetzt im Haus · ' + 229 (arbeit === 1 ? '1 arbeitet' : arbeit + ' arbeiten') + 230 ' · ' + (besprechung ? besprechung + ' in Besprechung · ' : '') + 231 (ruhe === 1 ? '1 in Ruhe' : ruhe + ' in Ruhe'); 232 el.textContent = ''; 233 const titel = document.createElement('div'); 234 titel.style.cssText = 'color:#6FE1FF;font-size:11px;letter-spacing:0.08em;text-transform:uppercase;margin-bottom:5px;'; 235 titel.textContent = kopf; 236 el.appendChild(titel); 237 if (!sitzend.length) { 238 const leer = document.createElement('div'); 239 leer.style.color = '#9aa3b5'; 240 leer.textContent = 'Niemand sitzt gerade an etwas. Figuren in Ruhe stehen im Flur.'; 241 el.appendChild(leer); 242 return; 243 } 244 const liste = document.createElement('div'); 245 liste.style.cssText = 'display:flex;flex-wrap:wrap;gap:5px;'; 246 sitzend.forEach((eintrag) => { 247 const chip = document.createElement('button'); 248 chip.type = 'button'; 249 chip.style.cssText = 'pointer-events:auto;cursor:pointer;max-width:240px;' + 250 'padding:3px 8px;border-radius:6px;text-align:left;color:#F4F7FF;' + 251 'background:rgba(111,225,255,0.10);border:1px solid rgba(111,225,255,0.4);' + 252 'font:11px/1.3 ui-sans-serif,system-ui,sans-serif;'; 253 chip.textContent = eintrag.name + ' · ' + eintrag.t.text; 254 const seit = dauerMinText(eintrag.t.seit_min); 255 chip.title = seit ? eintrag.name + ' sitzt seit ' + seit : eintrag.name; 256 chip.addEventListener('click', () => { 257 const cam = scene.activeCamera; 258 if (!cam || !cam.setTarget) return; 259 const p = eintrag.mesh.getAbsolutePosition(); 260 cam.setTarget(new BABYLON.Vector3(p.x, p.y + 1.1, p.z)); 261 }); 262 liste.appendChild(chip); 263 }); 264 el.appendChild(liste); 265 } 266 267 function projektion(mesh) { 268 const cam = scene.activeCamera; 269 const engine = scene.getEngine(); 270 if (!cam) return null; 271 const welt = mesh.getAbsolutePosition().add(new BABYLON.Vector3(0, 1.85, 0)); 272 const p = BABYLON.Vector3.Project( 273 welt, 274 BABYLON.Matrix.Identity(), 275 scene.getTransformMatrix(), 276 cam.viewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight()), 277 ); 278 if (p.z < 0 || p.z > 1) return null; 279 const canvas = engine.getRenderingCanvas(); 280 const box = canvas.getBoundingClientRect(); 281 const parent = canvas.parentElement.getBoundingClientRect(); 282 return { 283 x: (p.x / engine.getRenderWidth()) * box.width + (box.left - parent.left), 284 y: (p.y / engine.getRenderHeight()) * box.height + (box.top - parent.top), 285 }; 286 } 287 288 function heimVon(mesh) { 289 const home = mesh.metadata && mesh.metadata.home; 290 if (home && typeof home.y === 'number') return home; 291 if (home) { 292 // Hier stand bis zum 18.08.2026 `{ y: 0, ...home }`. Das war 293 // kein Vorgabewert, das war eine Zudeckung: zwei der drei 294 // Schreibstellen (optik.js, belegschaft.js) liessen die Etage 295 // weg, und die elf Figuren im Obergeschoss standen nur deshalb 296 // richtig, weil veredleSzene() eineVilla() VOR bauHanghaus() 297 // ruft und hanghaus.js als letzter schrieb. Ein Tausch der 298 // beiden Aufrufe haette sie ein Stockwerk fallen lassen, ohne 299 // dass irgendetwas rot geworden waere. Eine Heimat ohne Etage 300 // ist jetzt ein gemeldeter Fehler, kein Erdgeschoss: die 301 // uebrigen Angaben bleiben, die Etage wird an der Figur 302 // gemessen statt geraten, und der Name steht in 303 // zustand.heimatOhneEtage â dort holt ihn der Waechter ab.
304 if (zustand.heimatOhneEtage.indexOf(mesh.name) < 0) { 305 zustand.heimatOhneEtage.push(mesh.name); 306 } 307 return { ...home, y: etageVon(mesh.position.y) }; 308 } 309 return { 310 x: mesh.position.x, 311 z: mesh.position.z, 312 // Ohne Heimatangabe ist der Platz, auf dem die Figur steht, ihre 313 // Heimat â und die Etage die, auf der sie steht. 314 y: etageVon(mesh.position.y), 315 rotationY: mesh.rotation.y, 316 doorX: mesh.position.x, 317 }; 318 } 319 320 function etageDerFigur(name, mesh) { 321 if (zustand.etage.has(name)) return zustand.etage.get(name); 322 return heimVon(mesh).y || 0; 323 } 324 325 // Jedes Ziel traegt seine Etage mit. `ruhe` fuehrt seit dem 17.08.2026 326 // an den eigenen Platz zurueck und nicht mehr in eine Reihe vor dem 327 // abgerissenen Haus; wer schon dort steht, geht also keinen Schritt. 328 function zielVon(mesh, name, stand, sitz) { 329 if (stand === 'besprechung' && sitz) { 330 return { 331 x: sitz.x, z: sitz.z, y: sitz.y || 0, 332 rotationY: sitz.rotationY, alignX: sitz.alignX, 333 }; 334 } 335 const home = heimVon(mesh); 336 if (stand === 'arbeit' && home.sitzX !== undefined && home.sitzZ !== undefined) { 337 return { 338 x: home.sitzX, z: home.sitzZ, y: home.y, 339 rotationY: home.rotationY, alignX: home.doorX, 340 }; 341 } 342 return { 343 x: home.x, z: home.z, y: home.y, 344 rotationY: home.rotationY, alignX: home.doorX, 345 }; 346 } 347 348 function setzeArme(mesh, art, t, phase) { 349 const arme = mesh && mesh.metadata && mesh.metadata.arme; 350 if (!arme || arme.length < 2) return; 351 const w = armWinkel(art, t, phase); 352 arme[0].rotation.x = w.links; 353 arme[1].rotation.x = w.rechts; 354 } 355 356 // y ist Etage PLUS Pose, nie die Pose allein. Vorher stand hier 357 // `mesh.position.y = pose.y`, also 0,34 fuer jeden Sitzenden â die elf 358 // Figuren des Obergeschosses fielen damit beim ersten Poll ins 359 // Erdgeschoss, sichtbar durch den Boden. 360 function setzen(name, mesh, ziel, stand) { 361 mesh.position.x = ziel.x; 362 mesh.position.z = ziel.z; 363 mesh.rotation.y = ziel.rotationY; 364 const pose = sitzpose(stand, false); 365 mesh.position.y = hoeheVon(ziel.y, stand, false); 366 mesh.rotation.x = pose.rotationX; 367 zustand.etage.set(name, ziel.y || 0); 368 setzeArme(mesh, stand === 'arbeit' ? 'arbeit' : (stand === 'besprechung' ? 'besprechung' : 'ruhe'), 0, 0); 369 } 370 371 function starteLauf(name, mesh, ziel, stand) { 372 const vonEtage = etageDerFigur(name, mesh); 373 const von = { x: mesh.position.x, z: mesh.position.z, y: vonEtage }; 374 if (Math.hypot(von.x - ziel.x, von.z - ziel.z) < AM_ZIEL) { 375 zustand.wege.delete(name); 376 setzen(name, mesh, ziel, stand); 377 return; 378 } 379 const home = heimVon(mesh); 380 // wegVon holt die Flurlinie des Stockwerks, auf dem die Figur gerade 381 // steht â nicht die eine Linie, die frueher bei Init aus dem Tisch 382 // des alten Hauses gelesen und fuer alle neunzehn festgehalten wurde. 383 const punkte = wegVon(von, ziel, home.doorX); 384 const strecke = wegLaenge(punkte); 385 if (wenigBewegung() || strecke < AM_ZIEL) { 386 zustand.wege.delete(name); 387 setzen(name, mesh, ziel, stand); 388 return; 389 } 390 zustand.wege.set(name, { 391 punkte, 392 start: performance.now(), 393 dauer: laufDauer(strecke), 394 ziel, 395 stand, 396 vonEtage, 397 strecke, 398 }); 399 } 400 401 function anwenden(daten) { 402 zustand.daten = daten; 403 zustand.live = true; 404 const gespraech = imGespraechVon(daten, sitzeHolen().length || SITZ_ZAHL); 405 let arbeit = 0; 406 let besprechung = 0; 407 zustand.figuren.forEach((mesh, name) => { 408 const stand = standVon(rolleVonDaten(daten, name), gespraech, daten); 409 const sitzIndex = gespraech.has(name) ? gespraech.get(name) : -1; 410 const sitz = sitzIndex >= 0 ? zustand.sitze[sitzIndex] : null; 411 setzeFigurStand(mesh, stand); 412 blaseFuer(name, etikett(daten, name, stand)); 413 starteLauf(name, mesh, zielVon(mesh, name, stand, sitz), stand); 414 if (stand === 'arbeit') arbeit += 1; 415 else if (stand === 'besprechung') besprechung += 1; 416 }); 417 window.__belegschaft = window.__belegschaft || {}; 418 window.__belegschaft.live = true; 419 window.__belegschaft.arbeit = arbeit; 420 window.__belegschaft.besprechung = besprechung; 421 window.__belegschaft.ruhe = zustand.figuren.size - arbeit - besprechung; 422 jetztLeiste(daten); 423 } 424 425 async function abrufen() { 426 if (typeof window.adminFetch !== 'function') return; 427 if (document.hidden) return; 428 try { 429 const res = await window.adminFetch('/backlog/buero'); 430 if (res && res.data && res.data.datenbank !== false) anwenden(res.data); 431 } catch (_) { 432 // Stay on the last good stand. A failed poll is not a black office. 433 } 434 } 435 436 scene.onBeforeRenderObservable.add(() => { 437 const jetzt = performance.now(); 438 const t = jetzt / 180; 439 const gespraech = zustand.live 440 ? imGespraechVon(zustand.daten, zustand.sitze.length || SITZ_ZAHL) 441 : null;
442 zustand.figuren.forEach((mesh, name) => { 443 if (zustand.live) { 444 const weg = zustand.wege.get(name); 445 const stand = standVon(rolleVonDaten(zustand.daten, name), gespraech, zustand.daten); 446 const phase = zustand.phase.get(mesh) || 0; 447 if (weg) { 448 const u = (jetzt - weg.start) / weg.dauer; 449 if (u >= 1) { 450 setzen(name, mesh, weg.ziel, weg.stand || stand); 451 zustand.wege.delete(name); 452 } else { 453 const p = wegPunkt(weg.punkte, u); 454 mesh.position.x = p.x; 455 mesh.position.z = p.z; 456 mesh.rotation.x = 0; 457 // Der Schritt federt um die Etage, auf der gegangen 458 // wird, nicht um Null. 459 mesh.position.y = laufHoehe(weg.vonEtage, weg.ziel.y, u) 460 + 0.03 * Math.abs(Math.sin(t * 2 + phase)); 461 if (p.dx !== 0 || p.dz !== 0) mesh.rotation.y = blickNach(p.dx, p.dz); 462 setzeArme(mesh, 'gehen', t, phase); 463 } 464 } else { 465 const pose = sitzpose(stand, false); 466 mesh.rotation.x = pose.rotationX; 467 mesh.position.y = hoeheVon(etageDerFigur(name, mesh), stand, false) 468 + (stand === 'arbeit' ? 0.018 * Math.sin(t + phase) : 0); 469 const rolle = rolleVonDaten(zustand.daten, name); 470 const tippt = stand === 'arbeit' && rolle && rolle.an_arbeit_seit_min !== undefined; 471 setzeArme(mesh, tippt ? 'arbeit' : (stand === 'besprechung' ? 'besprechung' : 'ruhe'), t, phase); 472 } 473 } 474 }); 475 }); 476 477 // Die Schilder werden NACH dem Bild gesetzt, nicht davor. In 478 // onBeforeRender traegt scene.getTransformMatrix() noch die Matrix des 479 // VORIGEN Bildes â Babylon schreibt sie erst in _renderForCamera, also 480 // nach diesem Observable. Das Schild hing der Figur damit bei jedem 481 // Kameraschwenk um genau ein Bild hinterher (gemessen 17.08.2026: 1,8 px 482 // bei 0,02 rad/Bild, 7,9 px beim Reissen, 17 px bei 50 m Abstand zum 483 // Kameraziel). 484 // 485 // Die Figurenanimation bleibt bewusst oben in onBeforeRender. Wandert sie 486 // mit hierher, laufen die Figuren ein Bild zu spaet und der Versatz ist 487 // nur umgezogen, statt behoben zu sein. 488 scene.onAfterRenderObservable.add(() => { 489 zustand.figuren.forEach((mesh, name) => { 490 const label = zustand.labels.get(name); 491 if (!label) return; 492 const xy = projektion(mesh); 493 if (!xy) { label.style.display = 'none'; return; } 494 label.style.display = 'block'; 495 label.style.left = Math.round(xy.x) + 'px'; 496 label.style.top = Math.round(xy.y) + 'px'; 497 }); 498 }); 499 500 // Names from the first frame (literal roster). Live poll overwrites 501 // the text; headless has no overlay parent so this stays a no-op. 502 (function seedLabels() { 503 const liste = (window.__belegschaft && window.__belegschaft.stand) || []; 504 zustand.figuren.forEach((mesh, name) => { 505 const eintrag = liste.find((s) => s && s.name === name); 506 blaseFuer(name, etikett(null, name, (eintrag && eintrag.stand) || 'ruhe')); 507 }); 508 }()); 509 510 // First poll after the Nachweis screenshot has already been taken 511 // (headless has no adminFetch; interactive waits a beat so __sceneReady 512 // resolves on the literal frame). 513 setTimeout(abrufen, 1200); 514 setInterval(abrufen, POLL_MS); 515 516 zustand.anwenden = anwenden; 517 window.__betrieb = zustand; 518 return zustand; 519}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.