Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
f622359
[perf] add stack zone design doc and phase-0 spike benchmarks
agustincico Jul 5, 2026
a14d8ce
[perf] add deterministic differential-trace harness (phase 0 oracle)
agustincico Jul 5, 2026
0fd131b
[perf] harness: reset image-side artifacts before each run
agustincico Jul 5, 2026
2bf92d2
[perf] harness: hash only representation-independent state
agustincico Jul 6, 2026
cc6f1c2
[perf] route all operand-stack access through vm.stack
agustincico Jul 6, 2026
0f51931
[perf] design: v1 marriage protocol (snapshot + sync-at-send + flush-…
agustincico Jul 6, 2026
cfbe16c
[perf] route temp-var access through vm.temps/tempOffset
agustincico Jul 6, 2026
cad0d60
[perf] stack zone: flat frames with lazy context reification, oracle-…
agustincico Jul 6, 2026
912c283
[perf] jit: frames-mode templates — stack zone now runs jitted
agustincico Jul 6, 2026
f1fda69
[perf] stack zone: browser/node entry points, per-frame sync, class g…
agustincico Jul 6, 2026
e20bcfa
[perf] jit2 WIP: stack-to-register compiler with inline caches (opt-in)
agustincico Jul 6, 2026
b4df359
[perf] jit2: fix V3 block stack-temp aliasing — golden-identical
agustincico Jul 6, 2026
a093c17
[perf] direct JS calls between activations (frames mode)
agustincico Jul 7, 2026
573026d
[perf] jit2: frameless leaf calls — golden-identical
agustincico Jul 11, 2026
2908dd5
[perf] harness: leaf call counters in jit2 report line
agustincico Jul 11, 2026
16b8e5d
[perf] cache resolved named primitives on the method (frames mode)
agustincico Jul 11, 2026
7634082
[perf] primFn: fix stale success flag poisoning module loads
agustincico Jul 11, 2026
0b57fee
[perf] jit2: recover bails — super sends, closure temps, remote vecs,…
agustincico Jul 11, 2026
df2f6dd
[perf] uniform object shapes from birth in per-class constructors
agustincico Jul 11, 2026
50207d4
[perf] page-granular flush for married-context stores + page eviction
agustincico Jul 11, 2026
46c42f7
[perf] treat empty-string option values as enabled for stackZone/jit2
agustincico Jul 12, 2026
2b55379
[perf] harness: FREEZE_SIM reproduces browser FilePlugin freeze patte…
agustincico Jul 12, 2026
5a87afe
[perf] harness: --ui mode — offscreen display + event injection + dra…
agustincico Jul 20, 2026
4da632a
[perf] harness: UNWINDDBG coverage probe (unwind/terminate/process-sw…
agustincico Jul 20, 2026
4f59aa6
[perf] browser event recorder (#record) + harness --events replay wit…
agustincico Jul 20, 2026
2396d9e
[perf] dialogo.sh — one-command automated headless workload (compare/…
agustincico Jul 20, 2026
33d26f7
[perf] implement missing LargeInteger arithmetic primitives (21,22,29…
agustincico Jul 20, 2026
d0ad7c9
[perf] harness: HOTSEL (hot Smalltalk methods/classes), --until-ms, L…
agustincico Jul 20, 2026
5e90a50
[perf] harness: --until-stable (quiescence metric) — honest 'cost of …
agustincico Jul 20, 2026
9100964
[perf] monomorphize object representation: single shared constructor …
agustincico Jul 20, 2026
bbe296f
[perf] correct the monomorphization mechanism note (win is real, attr…
agustincico Jul 20, 2026
cdc1cb7
[perf] spike: linear-memory (WASM) vs monomorphic JS objects — kills …
agustincico Jul 20, 2026
559ed5d
[perf] cache decoded named-primitive module/function names on the met…
agustincico Jul 21, 2026
6a2068c
[perf] browser.js — drive the real browser headless (own feedback loop)
agustincico Jul 21, 2026
3c4d1e3
[perf] browser.js: --trace (capture Chrome perf trace) for self-measu…
agustincico Jul 21, 2026
c9f7231
[perf] opt-in #sliceMs=N: trade a little throughput for far fewer vis…
agustincico Jul 21, 2026
ccaab18
[perf] harness: CLEANBLK + COUNTBV probes (block marriage source anal…
agustincico Jul 21, 2026
ff87e2b
[perf] spike: run the VM in a Web Worker — UI thread stays responsive…
agustincico Jul 21, 2026
37c88b5
[perf] worker spike: full input (mouse buttons/modifiers + keyboard)
agustincico Jul 21, 2026
c58f126
[perf] worker spike: load Dialogo's project files (templates) in the …
agustincico Jul 21, 2026
6df3606
[perf] worker spike: configurable image/size via URL (#image=...&w=..…
agustincico Jul 21, 2026
57f0e27
[perf] worker spike: populate keyboard polling interface (display.key…
agustincico Jul 21, 2026
4188317
[perf] worker spike: build marker + reliable project-dir diagnostic +…
agustincico Jul 21, 2026
2b0d205
[perf] worker spike: #notemplates flag to test the no-projects state
agustincico Jul 21, 2026
b0a7776
[perf] worker spike: add pure-JS JPEGReaderPlugin (jpeg2.browser need…
agustincico Jul 21, 2026
93e410e
[perf] worker spike: worker-safe JPEG via createImageBitmap + Offscre…
agustincico Jul 21, 2026
a905fb8
[perf] worker: import full vm.display.browser.js (scanChars + real re…
agustincico Jul 21, 2026
36023f9
[perf] worker: drop periodic full-frame snapshot → render via display…
agustincico Jul 22, 2026
a76a424
[perf] implement stream primitives 65/66/67 (primitiveNext/NextPut/At…
agustincico Jul 22, 2026
77c64a6
[perf] worker: image-managed cursor (desktop parity) via native CSS c…
agustincico Jul 22, 2026
da412f0
[perf] worker: fix low-space crash (prompt shim), raise headroom, add…
agustincico Jul 22, 2026
5f9cf7b
[perf] worker: keep only the prompt shim, revert headroom+numComponen…
agustincico Jul 22, 2026
1c75e77
[perf] worker: event recorder + headless replay harness
agustincico Jul 22, 2026
dc94def
[perf] worker: raise image headRoom to 512MB alone to fix project-ope…
agustincico Jul 22, 2026
48878a5
[perf] worker: add golden event fixture (real 163s Dialogo session) f…
agustincico Jul 22, 2026
e04fff6
[perf] worker: consolidate into root squeak_worker.js + wire #worker …
agustincico Jul 22, 2026
1fa186f
[perf] worker: share the FilePlugin directory tree with the host (loa…
agustincico Jul 22, 2026
e6483fa
[perf] worker: dynamic resize + drag-drop files into the running envi…
agustincico Jul 22, 2026
004b268
[perf] worker: pin canvas to 0,0 in worker mode (remove border / over…
agustincico Jul 22, 2026
53dd4c3
[perf] worker: download-on-save — Dialogo's save lands in the user's …
agustincico Jul 22, 2026
16a028e
[perf] worker: "open file" button for mobile (and desktop) file loading
agustincico Jul 22, 2026
fb7ee32
[perf] worker: move 📂 button to the corner + make it toggleable
agustincico Jul 22, 2026
b80297d
[perf] worker: load images from the internet + default checkboxes (wo…
agustincico Jul 23, 2026
5a6da79
[perf] worker: audio output via the main thread (Web Audio is main-th…
agustincico Jul 23, 2026
5b92598
[perf] run/: modernize the launcher UI + attribution + worker-mode ex…
agustincico Jul 23, 2026
507ab9e
[perf] worker: fix changes/sources lookup for url images + drop dead …
agustincico Jul 23, 2026
9c0fe7d
[perf] worker: system clipboard copy/paste bridge
agustincico Jul 23, 2026
365198d
[perf] run/: make the lower sections coherent with the worker deploy
agustincico Jul 23, 2026
bda55ec
[perf] worker: support zip bundles (download+unzip via JSZip, prefer …
agustincico Jul 25, 2026
eb6043c
[perf] worker: fix zip loading — sequential unzip, create parent dirs…
agustincico Jul 25, 2026
431b665
[perf] run/: rebrand to Smalltalk (Squeak/Pharo/Cuis), worker by defa…
agustincico Jul 25, 2026
dc65598
[perf] run/: sync with main (zip support, rebrand, working examples)
agustincico Jul 25, 2026
2620710
VM: read 16-bit and 64-bit indexable arrays (Spur formats 9, 12-15) +…
agustincico Jul 26, 2026
e20387f
[perf] run/: drag-drop on top, internet examples below (Vanessa's ord…
agustincico Jul 26, 2026
be94fd8
[perf] run/: examples = Squeak 6.0 + Cuis 7.8 + Dialogo; note Pharo u…
agustincico Jul 26, 2026
1ab8403
worker: quitar imports de vm.stackzone/jit2 (no usados; rompían el de…
agustincico Jul 26, 2026
1342667
FilePlugin: implementar primitiveGet/SetWorkingDirectory (Cuis 7.x ar…
agustincico Jul 26, 2026
f5e60d9
worker: JIT-fallback también ante fallas de código JIT en Firefox (st…
agustincico Jul 26, 2026
ed876b8
pharo(WIP): platform=unix + prims interpreterSourceVersion/getCurrent…
agustincico Jul 26, 2026
c28111f
FileAttributesPlugin (nuevo): stat/exists/dir/masks para Pharo FileSy…
agustincico Jul 26, 2026
06c5553
plugins: stubs SDL2DisplayPlugin/MD5Plugin (report not-available) par…
agustincico Jul 26, 2026
98e94a0
worker input: corregir encoding de modificadores de teclado (era Keyb…
agustincico Jul 26, 2026
d6b528b
vm 64-bit (WIP): CompiledMethod at:-indexing usa wordSize (arregla el…
agustincico Jul 26, 2026
c50bba9
run/: agregar ejemplo Pharo 10 (experimental) + nota sobre git/FFI
agustincico Jul 26, 2026
484d96a
pharo 64-bit: wordSize hack para VirtualMachine (raíz del bug de pcs)…
agustincico Jul 26, 2026
12da35d
FileAttributesPlugin: protocolo real de dir-streams de Pharo
agustincico Jul 27, 2026
37632a4
run/: ejemplo Pharo 10 64-bit (Pharo64.zip con startup.st de compatib…
agustincico Jul 27, 2026
f2df9dc
pharo/: startup-compat64.st — restaura display/input clásicos en buil…
agustincico Jul 27, 2026
e4704ce
run/: UX de carga (overlay i18n con etapas + heartbeat, canvas oculto…
agustincico Jul 27, 2026
7c0a208
pharo/: no más debugger de FFI/git al abrir (32 y 64 arrancan limpio)
agustincico Jul 27, 2026
60dc930
pharo/: demo Morphic autocontenida (bouncing balls) para el showcase
agustincico Jul 27, 2026
1ed064b
docs: ESTADO.md (estado, runbook de deploy y TODO) + README de pharo/
agustincico Jul 27, 2026
a89d4bd
files: no dejar archivos "fantasma" cuando falla la escritura en Inde…
agustincico Jul 28, 2026
1965090
pharo/: Pharo 64-bit ahora SÍ responde al mouse (faltaba el pool en H…
agustincico Jul 28, 2026
a7dd674
docs: Pharo 64-bit pasa a funcional; documentar el fix del pool y el …
agustincico Jul 28, 2026
1854a76
pharo/: Pharo 64 dibuja en modo diferido (se acabó el titileo de los …
agustincico Jul 28, 2026
16a890a
docs: documentar el fix de dibujado diferido en Pharo 64 (titileo de …
agustincico Jul 28, 2026
114aa38
docs: UX de carga, Cuis oficial y demo ya deployados; sincronizar lau…
agustincico Jul 28, 2026
ded5671
utils/: generar la página raíz del sitio en vez de copiarla a mano
agustincico Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ node_modules/
gh-pages
dist/*.min.js*
*.code-workspace
perf/harness/golden.json
ws/client/cuis.changes
ws/client/CuisDebug-*.log
/Dialogo.32bits.image
/dialogo-fs/
243 changes: 243 additions & 0 deletions ESTADO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# Estado del proyecto — Smalltalk en el browser

> Documento de trabajo de este fork (`agustincico/SqueakJS`). No es del proyecto upstream.
> Última actualización: **2026-07-27**.

Sitio en vivo: **https://dialog.ar/SmalltalkJsVm/run/**

---

## 1. Qué está vivo hoy

| Imagen | Estado | Notas |
|---|---|---|
| **Squeak 6.0** | ✅ anda | `#zip=https://dialog.ar/Squeak.zip` |
| **Cuis 7.8** | ✅ anda | Se baja de su **repo oficial de GitHub** (manda CORS). Nota: esa imagen dispara un "JIT fault" que SqueakJS resuelve solo reiniciando sin JIT — preexistente, no afecta el uso |
| **Pharo 10 (32-bit)** | ✅ **anda bien** | Arranca **limpio** (sin debugger de FFI/git) y es **plenamente interactivo**. Es el que hay que mostrar |
| **Pharo 10 (64-bit)** | ✅ **anda** | Arranca limpio y responde (menús, listas, arrastre de ventanas). Requiere el `startup.st` de compatibilidad dentro del zip |
| **Dialogo** | ✅ anda | App de dibujo para chicos, `dialog.ar/Dialogo.zip` |
| **Demo Morphic** | ✅ anda | `dialog.ar/Pharo-demo.zip` — Pharo 32 abriendo una app propia al arrancar |

**Performance de Pharo** (medida con `sendCount` por tick, Chrome real):
- Arranque: **32-bit ≈ 5 s**, **64-bit ≈ 11 s** (el 64 pesa más por 8 bytes/oop).
- En reposo: **~23k sends/s en ambos** → la VM está casi dormida. **Pharo no es "pesado" en idle**;
no hay ningún loop desbocado. El costo real es el arranque.

---

## 2. Deploy — ⚠️ NO hay auto-deploy

El sitio **no se sirve desde este repo**. Son dos repos y la copia es **manual**:

```
agustincico/SqueakJS ProfeFuturo/profefuturo.github.io
(el código) ──────▶ └── SmalltalkJsVm/ ──────▶ dialog.ar
copia
MANUAL
```

No existe webhook ni GitHub Action (verificado: no hay `.github/workflows/` ni script de deploy).
**Pushear a `SqueakJS/main` no deploya nada por sí solo.**

### Runbook (2 pasos)

**Paso 1 — código a GitHub.** Ojo: la rama local `main` trackea `upstream` (codefrau), así que hay
que pushear explícito a `origin`:

```bash
git push origin main
```

**Paso 2 — copiar al repo del sitio.** El clone tiene que ser *disk-safe*: el historial pesa
~400 MB (binarios de Dialogo) y llena el disco.

```bash
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/ProfeFuturo/profefuturo.github.io site
cd site && git sparse-checkout set SmalltalkJsVm # aun así ocupa ~350 MB

# antes de copiar, confirmar que la copia del sitio == la versión previa de main,
# así el diff introduce SOLO el cambio buscado:
cmp <(git -C /ruta/SqueakJS show <commit-previo>:vm.interpreter.js) SmalltalkJsVm/vm.interpreter.js

cp /ruta/SqueakJS/<archivos-cambiados> SmalltalkJsVm/

# ⚠️ SI CAMBIÓ run/index.html: el sitio sirve el launcher en la RAÍZ del path
# (dialog.ar/SmalltalkJsVm/, sin /run/) desde una copia aparte con las rutas
# reescritas. Es la página que ve la gente. Regenerarla SIEMPRE, o queda una
# release atrás (ya pasó: la demo no aparecía porque sólo se actualizó run/):
python3 /ruta/SqueakJS/utils/mk-site-index.py /ruta/SqueakJS/run/index.html \
> SmalltalkJsVm/index.html

git add SmalltalkJsVm/ && git commit && git push origin HEAD
cd .. && rm -rf site # ¡borrar el clone al terminar!
```

**Paso 3 — verificar.** Pages reconstruye en ~1–2 min y hay CDN (`cache-control: max-age=600`),
así que hay que romper la caché:

```bash
curl -s "https://dialog.ar/SmalltalkJsVm/vm.interpreter.js?cb=$(date +%s)" | grep -c 'LGitLibrary'
```

Y después probar la imagen de verdad en Chrome (ver §5).

---

## 3. Ramas

| Rama | Trackea | Estado |
|---|---|---|
| `main` | `upstream` (codefrau) — pushear a `origin` explícito | Lo curado y deployable. Sincronizada con `origin/main` |
| `perf/stack-zone` | `origin` | **La rama de trabajo. 55 commits sin pushear a `origin`** |

**Los archivos del launcher (`run/index.html`, `run/squeakjs.css`, `squeak_worker.js`) ya están
sincronizados entre las dos ramas** — la UX de carga, el `first-frame` y los links nuevos se
portaron a `main` y se deployaron. `perf/stack-zone` conserva además el trabajo de perf
(stackzone/jit2) que no va a `main`.

> ⚠️ Al portar el worker a `main`, verificar siempre que sus `import` existan ahí: en su momento
> `vm.stackzone.js` / `jit2.js` (perf-only) rompieron el deploy con 404.

---

## 4. Hallazgos técnicos (ronda 9)

### 4.1 Debugger de FFI/git eliminado ✅ deployado

**Causa:** al arrancar, Iceberg llama `LGitLibrary class>>startUp:` → `initializeLibGit2` →
`libgit2_init`, un callout FFI que SqueakJS no puede resolver. `initializeLibGit2` captura el
error pero hace `ex pass` → **re-lanza** → Pharo abre un debugger post-mortem sobre un mundo
por lo demás sano. **Idéntico en 32-bit** (`Cannot locate libgit2`) **y 64-bit**
(`FFICallout subclassResponsibility`) — el 32 solo parecía sano porque el debugger se cierra.

**Fix** (`vm.interpreter.js`, commit `4565ea9` en main + `af3a99c` en el sitio): nueva operación
**`neuter`** en `hackImage`, que vuelve un método `^self` sobrescribiendo su primer bytecode con
*return-receiver* (Sista `0x58` / V3 `0x78`), solo si `methodPrimitiveIndex() === 0`:

```js
{method: "LGitLibrary class>>startUp:", neuter: true, enabled: true},
```

Pharo entonces corre **sin git**, igual que en una máquina sin libgit2. `LGitLibrary` solo existe
en Pharo → es no-op en Cuis y Squeak.

> 🔑 **Por qué no alcanzaba el mecanismo viejo:** `primitive: returnSelf (256)` hace
> `m.pointers[0] |= prim`, que funciona en imágenes V3 pero **no en Spur**, donde la primitiva se
> decodifica del primer bytecode (`callPrimitive`), no del header. De ahí el sobrescribir bytecode.

Quedó solo un aviso "VM does not support TFFI Callbacks" que se auto-cierra.

### 4.2 Pharo 64-bit ignoraba todo click ✅ resuelto

**Causa:** los métodos que `startup-compat64.st` inyecta en `HandMorph` usan variables del pool
`EventSensorConstants` (`EventTypeMouse`, `EventTypeKeyboard`, `EventKeyDown`…). El build 32-bit
declara ese pool en `HandMorph`; el 64-bit lo quitó junto con las clases clásicas de eventos, y el
script lo declaraba **sólo en las clases que crea**, nunca en `HandMorph`. Sin el pool esos nombres
compilan como **globales indefinidas — `nil` en silencio** (el compilador no falla), así que
`type = EventTypeMouse` era siempre falso, `nextEventFrom:` caía en `^ #invalid` y
`processEventsFromQueue:` retornaba **antes** de llamar a `handleEvent:`.

**Fix:** agregar el pool a `HandMorph` *antes* de compilar sus métodos.

**Cómo se localizó** (instrumentando el worker y clickeando el menú System): toda la cadena estaba
viva salvo el último eslabón.

| | antes | después |
|---|---|---|
| `doOneCycleFor:` / `processEvents` / `nextEvent` | +398 | +293 |
| `InputEventSensor>>processEvent:` | +5 | +5 |
| **`HandMorph>>handleEvent:`** | **0** ✗ | **+8** ✔ |
| redibujos | 0 | +53 |
| sends | +94k (tasa de reposo) | +897k |

> 💡 **Lección para futuros parches por script:** inyectar métodos en una clase **existente** no le
> agrega los pools/globals que esos métodos necesitan, y el compilador de Pharo **no falla** — crea
> la variable indefinida en `nil`. El síntoma aparece lejísimos del origen. Ante "compila pero se
> comporta como si todo fuera nil", sospechar del scope antes que de la lógica.

### 4.3 Carteles de Pharo 64 que "titilaban" ✅ resuelto

Los avisos semitransparentes (el de FFI al arrancar, el del atajo al abrir el browser) se
desvanecen por pasos y en 64-bit parecían reabrirse una y otra vez, mientras que en 32-bit funden
suave. **Causa:** `VMWorldRenderer` sí entra a `deferUpdatesDuring:`, pero la **primitiva 126 nunca
se ejecutaba**, porque el script no definía `DisplayScreen>>deferUpdates:` y la versión nativa del
build 64-bit (SDL2-only) no llega a la primitiva. Sin diferido, **cada BitBlt se propaga solo a la
pantalla**: el fundido redibujaba ~300 veces por segundo en vez de una vez por ciclo de Morphic.
**Fix:** portar `deferUpdates:` (instancia y class-side) del build 32-bit, agregando antes su class
var `DeferringUpdates` — misma trampa de scope que §4.2.

| mismo arranque | `deferUpdates: true` | draws | pico draws/seg |
|---|---|---|---|
| 32-bit | 21 | 21 | 3 |
| 64-bit antes | **0** | **1268** | **323** |
| 64-bit ahora | 21 | 21 | 4 |

> 🔍 **Trampa de medición que costó dos vueltas:** instrumentar contadores *después* del arranque
> (a los 40 s) mostró "0 llamadas" para métodos que **sí** se ejecutaban — los carteles ya habían
> terminado. Lo mismo con `vm.inputEventSemaIndex`, que en realidad vive en `primHandler`. Ante un
> contador en 0, verificar primero la ventana temporal y el objeto medido. Lo que destrabó el
> diagnóstico fue capturar `vm.printStack()` **dentro** del `putImageData`, que mostró la pila real.

### 4.4 Archivos fantasma en el cache del browser ✅ resuelto

`filePut` escribía la entrada de directorio (localStorage, sincrónica, nunca falla) **antes** que
el contenido (IndexedDB, asincrónico, sí falla al agotarse la cuota). Si la segunda fallaba, el
error sólo se logueaba y quedaba un **archivo fantasma**: listado con su tamaño pero ilegible para
siempre, que la imagen reportaba mucho después como `File read failed` (visto en Cuis, vía
`UniFileStream>>error:`) y que no se arreglaba solo — había que borrar los datos del sitio a mano.
Ahora `dbTransaction` acepta un callback de error opcional y `filePut` revierte la entrada, de modo
que el archivo simplemente falta y se vuelve a bajar. Además el callback de éxito ahora se dispara
igual en el camino de error: antes, `trans.onerror` no lo llamaba nunca y el `await` del flujo de
zip quedaba colgado para siempre (posible causa de cargas que "se congelaban").

### 4.5 Demo Morphic

`pharo/demo-startup.st` — ver `pharo/README.md`.

---

## 5. Cómo probar (tooling)

No hay `gh` CLI ni `puppeteer` instalados; sí **`puppeteer-core`** en `node_modules` y Chrome del
sistema. Los scripts hay que correrlos **desde la raíz del repo** (si no, no resuelve el módulo):

```js
const puppeteer = (await import("puppeteer-core")).default; // ESM: import dinámico, no require
await puppeteer.launch({
headless: "new",
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
args: ["--no-sandbox", "--use-gl=swiftshader"],
});
```

- Servidor local: `npx http-server -p 8091 -c-1`
- Probar una imagen: `http://localhost:8091/run/index.html#zip=<url-del-zip>`
- Los zips de prueba (Pharo 32/64) **no** van en el repo — bajarlos de `dialog.ar/Pharo.zip` y
`dialog.ar/Pharo64.zip`.
- Señal de arranque: en `perf/stack-zone` escuchar el postMessage `first-frame`; en `main`, esperar
por tiempo.
- Para medir performance, interceptar los mensajes `tick` del worker y leer `sends`.

---

## 6. TODO

### Media
- [ ] **Diagnosticar Pharo 11 / 12 / 13** — nunca se hizo. Son 64-bit only, así que probablemente
arrastren el mismo problema de input.

### Baja
- [ ] Rearmar `Pharo-demo.zip` si se cambia `pharo/demo-startup.st` (el zip publicado lleva una
copia como `startup.st`; no se actualiza solo).
- [ ] Bajar el tiempo de arranque del 64-bit (~11 s).
- [ ] `Character>>asByteArray` (FontSubstitutionDuringLoading) — sin diagnosticar, secundario.

---

## 7. Techo conocido: FFI

Pharo depende fuerte de FFI (`primitiveCalloutWithArgs`, `primitiveLoadSymbolFromModule`,
callbacks) para integrarse con el SO. **El FFI real no es viable en el sandbox del browser.** La
mayoría de los usos no son fatales (Pharo loguea y sigue); el que sí abría un debugger era el de
git/Iceberg, ya neutralizado.
Loading