Modo de ejecución visual
Superficie de simulación complementaria de ejecución infinita donde los puertos de un diseño VHDL se enlazan a un lienzo de hardware virtual. Objetivo pedagógico: permitir que los estudiantes interactúen con su diseño — pulsar un botón, ver el LED — en lugar de leer las formas de onda a posteriori.
La paleta incluye actualmente doce widgets (2026-07-05): LED,
Botón, Interruptor, Multi-interruptor (DIP de 2 a 8 palancas), arreglo de
LED, matriz de LED 3×3 (monocromática), matriz RGB 3×3, display de 7
segmentos, Zumbador / Piezo (Web Audio), teclado matricial, LCD HD44780
(2×16 / 4×20 · modos de 4 bits / 8 bits / texto), y motor paso a paso
(Stepper, 4 fases). Registrados en
Kmila.Shared/Services/VisualRun/KvizWidgetCatalog.cs (PinsOf) y
expuestos por Kmila.Shared/Components/VisualRun/WidgetPalette.razor.
Barra de herramientas (2026-07-05): Atrás · ▶ Ejecutar / ⏹ Detener · Cablear · Eliminar · Deshacer / Rehacer · Guardar · Descartar cambios · Mostrar / ocultar cables · Zoom ± / Restablecer · Ajustar a la vista · Localizador · Exportar SVG · Grabar video · ⋯ Más · Pantalla completa. El localizador es una lista deslizable de todos los widgets colocados con un botón Enfocar por fila (encuadra el desplazamiento y el zoom sobre ese widget).
Exportación del lienzo — SVG · PNG · PDF, todas disponibles. La
exportación SVG está en la barra de herramientas; PNG y PDF viven
detrás del menú ⋯ Más (OnMoreExportPng / OnMoreExportPdf en
VisualPanel.razor). PNG se lanzó en la sesión 22 y PDF se lanzó el
2026-07-26 con jspdf 2.5.2 empaquetado localmente en
wwwroot/lib/jspdf/. La antigua nota "diferido hasta que se empaqueten
html2canvas/jspdf" quedó obsoleta: en lugar de html2canvas, el lienzo se
rasteriza mediante una ruta propia (kmilaCanvasExportPng /
kmilaCanvasExportPdf en wwwroot/js/kmila-canvas-export.js, respaldada
por kmilaSvgToPngBase64), y la ruta de PDF envuelve ese mapa de bits en
un documento de una sola página mediante la compilación UMD de jspdf
(window.jspdf.jsPDF).
Este documento es la referencia de usuario / desarrollador del módulo.
Para el fundamento de diseño y las decisiones fijadas, consulte
HANDOFF_visual_runtime_2026-06-24.md. Para el contexto más amplio del
pipeline del sistema, consulte ARCHITECTURE.md § Visual Runtime.
1. Abrir el lienzo visual
Dos puntos de entrada, ambos apuntando al mismo componente subyacente
(Kmila.Shared/Components/VisualRun/VisualPanel.razor):
- Pestaña de vista del editor (lanzada 2026-06-26) — abra cualquier proyecto y la tira de pestañas del banco de trabajo expone el lienzo como la 4.ª vista junto a Código · Bloques · Reproducción por pasos · Visual. Esta es la única ruta que funciona en MAUI nativo (sin barra de direcciones). El panel se monta de forma diferida en la primera visita a la pestaña y luego sobrevive a los cambios posteriores para que el estado del coordinador + el lienzo no se pierda cuando el usuario regresa a inspeccionar el código.
- URL directa
/visual/{ProjectId}— el mismo id de proyecto que usa el editor. Útil para enlaces compartidos y pruebas de Playwright. Renderiza el mismoVisualPanelconEmbeddedMode=falsepara que se muestre el encabezado completo (botón de retroceso + título del proyecto).
La página analiza el .vhd/.vhdl de punto de entrada del proyecto una
vez al montarse y de nuevo en cada clic en Ejecutar. Si edita VHDL en
el editor mientras el lienzo visual está abierto, haga clic en Ejecutar
para recoger los cambios — el lienzo no se recarga en caliente.
2. Construir un lienzo
La barra de herramientas actual es la que se lista en el encabezado
anterior (Atrás · ▶ Ejecutar / ⏹ Detener · Cablear · Eliminar · Deshacer
/ Rehacer · Guardar · Descartar cambios · Mostrar / ocultar cables · Zoom
± / Restablecer · Ajustar a la vista · Localizador · Exportar SVG · Grabar
video · ⋯ Más · Pantalla completa). Los widgets ya no se agregan
desde la barra de herramientas: provienen de la paleta de widgets
(Kmila.Shared/Components/VisualRun/WidgetPalette.razor), que expone los
doce tipos de widget (véase el encabezado). La antigua barra
minimalista "+ LED / + Botón" descrita en revisiones anteriores de este
documento ha sido reemplazada.
- Suelte un widget desde la paleta; aterriza en una posición predeterminada.
- Arrastre los widgets para distribuirlos. Solo en modo edición — el arrastre está deshabilitado durante una ejecución.
- Cablear abre un cuadro de diálogo modal que lista cada pin de widget colocado y los puertos de diseño compatibles (filtrados por dirección: las salidas de botón alimentan las entradas del diseño, las entradas de LED leen las salidas / inout del diseño).
- ▶ Ejecutar inicia el coordinador. Pulse el widget de botón (mousedown / touchstart → '1'; soltar → '0'). El widget de LED refleja el valor de su puerto enlazado en cada cuadro.
- Guardar escribe un archivo lateral
.kvizen la raíz del proyecto (uno por proyecto). El archivo lateral persiste las posiciones, las configuraciones y los enlaces.
3. Esquema .kviz (v1)
JSON, junto al .vhd/.vhdl del diseño. El simulador nunca lee este
archivo; es puramente el estado del lienzo visual.
{
"version": 1,
"designFile": "main.vhd",
"canvas": { "width": 1200, "height": 800, "background": "grid" },
"components": [
{
"id": "led-ab12cd",
"type": "led",
"position": { "x": 320, "y": 180 },
"config": { "color": "red" },
"bindings": { "anode": "led_out" }
},
{
"id": "btn-34ef56",
"type": "button",
"position": { "x": 120, "y": 180 },
"config": { "label": "GO" },
"bindings": { "out": "btn_in" }
}
],
"settings": { "ringBufferTicks": 2000, "defaultSpeed": "Normal" }
}
| Campo | Valores válidos de la Fase 0 |
|---|---|
components[].type |
led, button |
claves de components[].bindings |
LED: anode. Botón: out. |
settings.defaultSpeed |
Half / Normal / Fast / Free |
La Fase 1 extiende type a switch, led-array, seven-seg; agrega
sintaxis de rebanada a los valores de enlace (bus[3:0], bus[7]).
4. La costura en un párrafo
Kmila.Shared/Services/VisualRun/VisualRunCoordinator.cs impulsa un
Interpreter.Services.DeltaCycleEngine existente un tick a la vez. Entre
ticks vacía su InputStateMap hacia las señales de entrada del motor
estableciendo Variable.Value directamente — esto activa
SignalAttributeHandler._activeFlags[name] = true, que el motor comprueba
al inicio de cada RunTimeStepAsync y propaga como un evento normal de
estímulo externo. No se necesita ningún nuevo enganche en el motor —
la ruta existente de eventos/planificador transporta las escrituras en
vivo desde un coordinador pausado entre ticks igual de bien que desde la
lista de estímulos precompilada que StimulusBuilder produce para
ejecuciones acotadas. El HeadlessSimulator / SimulationRunner /
SimulatorBench acotados no son tocados por nada en este módulo.
4.bis. Prevención de ejecución en segundo plano + control externo (2026-08-11)
Lanzado en la entrada del CHANGELOG "Visual-sim background prevention + toolbar polish — 2026-08-11" (verificado en dispositivo Xiaomi 13T Pro):
- Enum
SimMode(Kmila.Shared/Services/SimulationParameters.cs) etiqueta a quién pertenece la ejecución activa:SimMode.Signalpara la ruta singletonProgramBuilder/DeltaCycleEngine(persistente en segundo plano) frente aSimMode.Visualpara una ejecución de lienzo visual.SimulationParameters.RunningMode(establecido porBeginRun(projectId, mode)) permite al resto de la aplicación distinguir ambas sin acceder al interior del lienzo. VisualRunRegistry(Services/VisualRun/VisualRunRegistry.cs) es una búsquedaprojectId → VisualRunCoordinatora nivel de aplicación. Una ejecución visual pertenece como campo privado alVisualPanelcon alcance de componente, de modo que, sin este registro, las superficies externas (barra lateral NavMenu / ProjectsView) no podrían alcanzarla.VisualPanelse registra enStartRuny se desregistra enStopRun/Dispose; se rastrea como máximo un coordinador por proyecto (un nuevo registro reemplaza al anterior).- Efecto: una ejecución visual ya no sigue avanzando invisiblemente después de que el usuario navega hacia otra parte — la ejecución puede pausarse/detenerse desde fuera del lienzo (por ejemplo, un Detener en la barra lateral), y los cambios de pestaña/visibilidad se manejan de modo que el lienzo no se ejecute sin supervisión en segundo plano.
5. Contrato de componente
Cada widget es un componente Razor bajo
Kmila.Shared/Components/VisualRun/ con una lista de pines tipada y una
dirección:
| Componente | Pin | Dirección | Configuración |
|---|---|---|---|
LedWidget (Fase 0) |
anode |
in (lee la salida del diseño) | color: red \| green \| blue \| yellow \| white |
ButtonWidget (Fase 0) |
out |
out (impulsa la entrada del diseño) | label: string |
SwitchWidget (Fase 1) |
out |
out (impulsa la entrada del diseño) | label: string, defaultState: 0 \| 1 |
LedArrayWidget (Fase 1) |
anodes |
in (lee la salida del diseño, multi-bit) | length: 1..64, color, orientation: h \| v |
StepperWidget (Fase 2) |
phaseA, phaseB, phaseAn, phaseBn |
in (4 entradas de un solo bit) | stepsPerRev: 4..4096 (predeterminado 200), direction: auto \| cw \| ccw |
SevenSegmentWidget (Fase 3) |
modo raw: a, b, c, d, e, f, g, dp (8 de un solo bit in) · modo decodificado: value (std_logic_vector de 4 bits in) + dp opcional |
in | mode: raw \| hex \| dec (predeterminado raw), polarity: cathode \| anode (predeterminado cathode, solo modo raw — anode = activo en bajo), color (mismo enum que el LED) |
SevenSegmentBankWidget (Anillo 5) |
raw/hex/dec: value (un solo bus empaquetado in) — ancho raw digits×7 (cada rebanada de 7 bits abcdefg), ancho decodificado digits×4 · mux (multiplexado, segmentos compartidos + selección por dígito): cableado empaquetado → seg (7 bits) + sel (digits bits); cableado explícito → a..g + an0..an{digits-1} (todos de un solo bit) |
in | digits: 2..8 (predeterminado 4), mode: raw \| hex \| dec \| mux (predeterminado raw), polarity: cathode \| anode (predeterminado cathode; líneas de segmento raw + mux), color; solo mux: wiring: packed \| explicit (predeterminado packed), selectActiveLow (bool, predeterminado true — dígito habilitado cuando su selección lee 0), persistence (bool, predeterminado true — retiene cada dígito a lo largo de los cuadros recientes frente a mostrar el estrobo instantáneo) |
El desajuste de ancho es una truncación natural por diseño (handoff §3 decisión 4). Un LED único enlazado a un bus de 8 bits lee solo el bit 0; una señal de 1 bit enlazada a un (futuro) arreglo de LED impulsa solo el LED 0.
6. Presupuesto de rendimiento
La velocidad predeterminada (Normal) apunta a ≈60 Hz: un tick cada 16 ms
con un límite de repintado de la UI a 30 Hz del lado de la página
(temporizador de 33 ms en Visual.razor). A velocidad Free el coordinador
cede el control cada 256 ticks.
El búfer circular (predeterminado 2000 entradas) es por coordinador y limita la memoria en vivo; los cuadros más antiguos se expulsan. Con ~10 puertos por cuadro y ~64 bytes por valor, eso es aproximadamente 1.2 MB a plena capacidad — bien dentro del techo de 3 GB del emulador.
7. Recorrido de aceptación de la Fase 0 — CERRADO 2026-06-24
Los 12 elementos del handoff §9 pasan en ambos objetivos (Web
completamente, MAUI estructuralmente). SimulatorBench sin cambios en
119/119 PASS. El código del motor y de la ruta acotada quedan
intactos por construcción. Registro de progreso completo, incluyendo
sorpresas, en HANDOFF_visual_runtime_2026-06-24.md § 9.
Cobertura de pruebas que se lanza con la Fase 0:
Tests/Playwright/tests/11-visual/seed.spec.ts— crea 4 proyectos de fixture (passthrough / inverter / and_gate / sr_latch) a través de la UI. Idempotente.Tests/Playwright/tests/11-visual/wire_and_save.spec.ts— fija los enlaces del fixture passthrough en.kvizpara cualquier prueba posterior que necesite un lienzo precableado.Tests/Playwright/tests/11-visual/acceptance.spec.ts— seis pruebas que cubren §9.1, §9.2, §9.4 (ratón + táctil), §9.5, §9.6/§9.7/§9.8 combinadas, §9.9, §9.10.
Observación de latencia (handoff §10): latencia del objetivo Web ≈ 100-150 ms (SignalR + límite de repintado a 30 Hz + cadencia de sondeo de Playwright). En una tableta, la pulsación táctil real → LED encendido es visiblemente el siguiente cuadro. El objetivo de 50 ms en §9.7 se mantiene como el presupuesto del lado del motor; el viaje de ida y vuelta de Web es el impuesto.
8. Fase 1A/B lanzada 2026-06-25 — Interruptor + arreglo de LED
Dos nuevos componentes, además del reloj automático del coordinador para que los diseños con reloj se ejecuten sin una fuente de reloj externa:
SwitchWidget— conmuta al tocar, el estado persiste a lo largo de los ciclos Ejecutar/Detener (diccionario_switchStatesa nivel de página; el coordinador recibe un conjuntouserDrivenPortspara que no intente aplicar reloj automático a los puertos enlazados a interruptores).LedArrayWidget— N segmentos configurables, orientación h/v, convención MSB a la izquierda que coincide con la representación en cadena destd_logic_vector(N-1 downto 0).- Reloj automático en
VisualRunCoordinator: cualquier puerto IN que coincida conStimulusBuilder.IsClockPortName(clk,clock,aclk…) obtiene una onda cuadrada enCLOCK_HALF_PERIOD_TICKS = 8(ciclo ≈256 ms a velocidad Normal). Los puertos enlazados a interruptor/botón se excluyen — el usuario los impulsa.
Restante de la Fase 1 (diferido):
- 7 segmentos único + N dígitos (raw + decodificación asistida bin/hex/BCD).
- UI de rebanada en el modal de cableado — opciones desplegables
bus[3:0],bus[7]. - Conmutador de rebote del botón + generador de traqueteo.
10. Fase 3 parcial — i18n + visualización de cables + placa de demostración (2026-06-26)
Tres entregables se lanzan juntos en esta porción — cierra 2 de los 4 elementos de la hoja de ruta de la Fase 3 + el hallazgo de la auditoría de i18n + un nuevo proyecto de muestra predeterminado que "muestra todo".
Muestra de placa de demostración (flujo Probar una muestra)
ProjectsView.razor reescribe SAMPLE_MAIN_VHD del contador de rizo de 7
etapas a una entidad demo_board que ejercita cada widget Visual a la
vez: botones (reset, nudge), interruptores (run, dir), LEDs
(heartbeat, btn_echo), arreglo de LED (count), motor paso a paso
(phase_a/b/an/bn). Una nueva constante SAMPLE_MAIN_KVIZ incluye el
.kviz precableado acompañante para que abrir la pestaña Visual sea
inmediatamente jugable — sin necesidad de recorrer el modal de cableado.
Los momentos de puertos de la simulación acotada en
SaveSampleConfigurationAsync también se actualizaron para impulsar la
nueva forma de 5 entradas (clk, reset, run, dir, nudge). Una
ejecución total de 30 µs muestra el contador avanzando, el heartbeat
conmutando, el motor paso a paso cambiando de fase, y un único pulso
tardío de nudge haciendo eco en btn_echo.
Actualizaciones del diccionario: PROJECTS_SAMPLE_NAME → "Sample · Demo
board" (EN/ES nativo, FR/DE/JP/CH/AR con prefijo ⚠). PROJECTS_SAMPLE_HINT
y PROJECTS_SAMPLE_DESCRIPTION reescritos para anunciar la superficie
visual.
i18n de la página Visual
Se agregaron ~50 claves de cadena a los 7 diccionarios bajo el espacio de
nombres VISUAL_*. EN+ES traducidos de forma nativa; FR/DE/JP/CH/AR llevan
el prefijo ⚠ según la convención de traducción pendiente.
VisualPanel.razor y VisualWiringModal.razor ya no contienen inglés
codificado en duro — todas las etiquetas, sugerencias, cadenas de error y
opciones del panel de propiedades se enrutan a través de Traductor.T().
Superposición de visualización de cables
Nuevo conmutador Mostrar cables / Ocultar cables en la barra de
herramientas. Cuando está activo, una superposición SVG (posicionada con
pointer-events: none para que la interacción con los widgets quede
intacta) dibuja líneas discontinuas de color de acento desde el ancla de
cada widget colocado hasta un chip con el nombre del puerto anclado cerca
del borde derecho del lienzo. Los chips se apilan verticalmente para que
varios cables cerca de la misma Y no se solapen. Estado por sesión (campo
_showWires en VisualPanel) — opcional cada vez que el usuario abre el
lienzo; no se persiste en .kviz para que el archivo guardado se mantenga
centrado solo en el diseño + los enlaces.
El contenido interno de la superposición SVG se construye mediante
MarkupString porque Razor reserva la etiqueta en minúsculas <text>
para su propio constructo de escape desde C# y se niega a emitir un
elemento SVG <text> directamente.
Infraestructura de pruebas
El paso de i18n significó que cada selector de barra de herramientas
:text-is(...) y :has-text(...) en phase2.spec.ts pasó a depender de
la localización (el ajuste de idioma predeterminado del servidor determina
qué texto se renderiza). Se migraron todos los selectores de la barra de
herramientas + la barra de ejecución a data-testid="visual-...", a
prueba de futuro en las 7 localizaciones:
visual-run/visual-stopvisual-wire/visual-wiring-donevisual-add-led·visual-add-button·visual-add-switch·visual-add-led-array·visual-add-stepper·visual-add-seven-segvisual-toggle-wiresvisual-pause-continuevisual-bp-addvisual-delete/visual-save
Recorrido de aceptación de la Fase 2: 5/5 pasan en localización EN + comprobación puntual en ES.
11. Fase 3+ — widget de 7 segmentos + galería de ejemplos (2026-06-26)
Widget de display de 7 segmentos
Sexto widget; dos modos de operación establecidos por la clave de
configuración mode:
raw(predeterminado) — 8 pines individuales (abcdefgdp). El estudiante cablea cada segmento a un puerto; se comporta exactamente como impulsar las salidas de segmento de un CI BCD-a-7-segmentos 7447/4511.hex— pinvaluede 4 bits, el widget decodifica 0..F internamente al patrón canónico de 7 segmentos.dec— el mismo pinvaluede 4 bits, muestra 0..9 y se apaga (sin segmentos encendidos) ante cualquier valor > 9. Coincide con lo que hace un decodificador BCD 4511 real ante entradas fuera de rango.
Ambos modos decodificados aún exponen un pin dp para que el usuario
pueda impulsar el punto decimal por separado.
Los cambios de modo en el panel de propiedades descartan los enlaces que
no aplican al nuevo conjunto de pines para que el usuario no se quede con
cables huérfanos (raw conserva a..g + dp; los modos decodificados
conservan value + dp).
La muestra de placa de demostración ahora incluye un display de 7 segmentos decodificado en hex enlazado a la salida del contador de 4 bits — lo primero que ve el estudiante después de elegir la muestra es un display de dígitos avanzando a través de 0–F junto al arreglo de LED.
Galería de ejemplos
Un nuevo botón "Ejemplos" junto a "Probar una muestra" abre un modal que
lista el catálogo precableado. Un clic crea el proyecto (con su archivo
lateral .kviz) y salta al editor. El catálogo incluye:
- Demo Board — cada widget a la vez (la muestra existente de un clic es ahora la primera entrada del catálogo).
- Counter Clock — entidad
counter_clock, dos displays de 7 segmentos decimales contando 00 → 99 con un LED de desbordamiento al dar la vuelta. Demuestra máquinas de estado estilo BCD. - Traffic Light FSM — entidad
traffic_light, máquina de Moore de tres estados (RED → GREEN → YELLOW), 3 LEDs de color + una lectura del registro de estado en arreglo de LED de 2 bits. - Registro de desplazamiento de 8 bits (agregado 2026-06-26 con la
UI de rebanada) — entidad
shift_register, 8 widgets de LED individuales, cada uno cableado a un solo bit de un puertoreg_outde 8 bits mediante enlaces de rebanadareg_out[N]. Muestra la UI de cableado por rebanada de bits.
Cada ejemplo tiene un id estable (demo / counter / traffic /
shift) para futuros enlaces compartidos + analítica. Agregar un nuevo
ejemplo = agregar una entrada ExampleDef a la lista _examples en
ProjectsView.razor.
12. Fase 3++ — UI de rebanada de bits (2026-06-26)
Cierra un elemento diferido más de la Fase 1C de HANDOFF §8.
Sintaxis del valor de enlace
Los valores de enlace en .kviz ahora aceptan un sufijo de rebanada
opcional:
| Enlace | Significa |
|---|---|
count |
puerto completo (comportamiento existente) |
count[3] |
un solo bit (numerado desde el LSB, convención std_logic_vector) |
count[7:0] |
rango de bits, MSB primero para coincidir con la sintaxis downto de VHDL |
La rebanada opera sobre la representación en cadena del puerto — el motor
emite el MSB a la izquierda para std_logic_vector(N-1 downto 0), de modo
que un 0xA de 4 bits se renderiza como "1010" (bit3 bit2 bit1 bit0) y
[2] toma la posición 2 desde la derecha = '0', [3:1] toma "101".
Las rebanadas fuera de rango devuelven vacío (el widget lo trata como no cableado / en blanco).
API del coordinador
coordinator.LatestSlicedValue("count[3]") // → "0" / "1"
coordinator.LatestSlicedValue("bus[7:4]") // → 4-char MSB-on-left
coordinator.LatestSlicedValue("count") // → whole port (passthrough)
SliceHelper.Parse(binding) analiza el sufijo; SliceHelper.Extract
devuelve la subcadena. Todos los widgets de salida (LED, arreglo de LED, 7
segmentos, motor paso a paso) llaman a LatestSlicedValue en lugar de
LatestPortValue para que cualquiera de ellos acepte de forma
transparente los enlaces con rebanada.
Modal de cableado — opciones desplegables por bit
Para puertos OUT multi-bit que se enlazan a un pin de widget de UN SOLO
BIT (anode en el LED, segmentos en el 7 segmentos en modo raw, pines de
fase en el motor paso a paso), el desplegable ahora se expande a:
port(completo, se usa el MSB)port[N](una entrada por bit, descendente)
Para pines de widget multi-bit (anodes en el arreglo de LED, value en
el 7 segmentos en modo decodificado), el desplegable ofrece rangos
naturales (port[3:0] / port[7:4]).
Rebanada del lado de escritura — diferido
Los widgets de entrada (interruptor, botón) aún impulsan puertos completos — la fusión de múltiples entradas del lado de escritura (dos interruptores reclamando cada uno un bit distinto del mismo puerto) necesita lógica de superposición a nivel de coordinador que es un esfuerzo mayor. Los diseños que necesitan control de entrada de grano fino todavía declaran puertos IN individuales de un solo bit por ahora.
13. Rebote del botón (2026-06-26)
Cierra el último elemento diferido de la Fase 1C de HANDOFF §8.
ButtonWidget gana un config.bounce: bool (predeterminado false) —
cuando está habilitado, cada pulsación + liberación genera un patrón de
traqueteo alternante de 6 ticks antes de que el puerto se asiente en la
intención estable. Los botones mecánicos reales rebotan de 5 a 20 ms; esta
exageración (≈ 96 ms a velocidad Normal) da a los ejercicios de práctica
de eliminación de rebotes algo visible que eliminar.
Cómo funciona
VisualRunCoordinator.EnqueueBounce(portName, chatter) encola una FIFO de
valores por tick. DrainInputsToPorts superpone la cola encima de
InputStateMap — durante un tick a la vez, el puerto enlazado lee el
valor encolado en lugar de la intención de estado estable. Una vez que la
cola se vacía, el valor estable se reafirma.
Volver a pulsar durante el traqueteo trunca y rearma limpiamente — igual que un botón real cuando lo vuelves a pulsar antes de que se asiente el temblor de la liberación.
Panel de propiedades
Los botones obtienen una casilla "Simular rebote de contacto" debajo de la
entrada de etiqueta. Al activarla se escribe "bounce": true en la
configuración .kviz del widget; el lienzo se guarda como sucio.
Práctica sugerida
Cablee un botón con rebote a un puerto que impulse un contador count <= count + 1 en rising_edge(btn). Sin rebote, una pulsación incrementa
una vez. Con rebote, una pulsación incrementa 3 veces. Luego escriba un
eliminador de rebotes simple basado en flip-flops (muestree a intervalos
de ~10 ms, cuente lecturas estables consecutivas) y observe cómo filtra el
traqueteo de vuelta a un solo flanco.
9. Fase 2 lanzada 2026-06-25 — controles + motor paso a paso
Cinco elementos fijados de HANDOFF §8, todos en la misma sesión:
- Deslizador de velocidad —
<input type="range">nativo sobre 0..3 mapeando Half / Normal / Fast / Free. Reemplaza los cuatro botones. - Tasa de paso personalizada — campo
<input type="number">de ms/tick que anula el preajuste del deslizador. Entrada vacía → preajuste del deslizador activo. Se establece enVisualRunCoordinator.CustomTickDelayMs. - Pausar / Continuar —
Pause()/Resume()en el coordinador. El bucle duerme en pulsos de 20 ms mientrasIsPaused; los widgets dejan de repintarse porque no se empujan nuevos cuadros. - Puntos de interrupción por tick — entradas numéricas de punto de
interrupción recopiladas en
VisualRunCoordinator.TickBreakpoints(HashSet). El bucle comprueba la pertenencia al inicio de cada tick; al acertar establece IsPaused = true+ disparaOnBreakpointHit(tick). El centinela_lastBreakTickevita el redisparo al reanudar. - Desplazamiento de cuadros — deslizador sobre
[FrameRing.OldestTick, NewestTick]. Activo solo mientras está en pausa. La página llama aFrameRing.Nearest(t)para buscar el cuadro almacenado más cercano, luego escribe susPortValuesen los puertos del motor → los widgets releen en el siguiente repintado. Reanudar → el siguiente tick en vivo sobrescribe los valores desplazados. StepperWidget— 4 entradas de fase de un solo bit decodificadas contra una tabla de código Gray (8 patrones). Rotor rotado por CSS + 4 almohadillas de bobina (N/E/S/W). Inferencia automática de dirección a partir del delta de paso envuelto; cw/ccw fuerzan el signo.- Ajustes → Apariencia → Simulación → búfer circular (ticks) — nueva
clave SQLite
KMILA_VISUAL_RING_BUFFER(256..16384, predeterminado 2000). La página Visual lee enOnInitializedy tomaMath.Max(perDoc, settings)para la capacidad del anillo del coordinador.
Diferido de la Fase 2 (ninguno — los 5 elementos lanzados).
Hoja de ruta de la Fase 3+ (sin cambios):
- Fase 3 — inspector de propiedades, visualización de cables, lienzos de ejemplo, i18n completo.