# R6A — Informe técnico de construcción y verificación

**Fecha:** 1 de octubre de 2026. **Vehículo declarado:** Passat 3BG AWX, caja 01A, EDC15P+ 0281011201 / 038906019KE / 1037366297 / 4898.

## 1. Resultado y alcance real

Se construye `038906019KE_AWX_R6A.bin`, de **524288 bytes**, desde la ROM actual identificada por hash. SHA-256 final:

`e17badb7aff3fd70cf8f0b3712b150822cdec3642f23476dbd1ca07aac1381d5`

**La R6A es una candidata conservadora que corrige la progresión de una demanda interna. No se ha demostrado un incremento de trabajo útil, potencia o par respecto a la actual.** La única modificación de calibración es Driver Wish a 1491 rpm y 100 % de pedal, de 55 a 66 nominales. Se recalcula su checksum. No se aplican incrementos globales ni se añaden avances, combustible, presión o cambios de arbitraje basados en objetivos físicos no identificados.

La revisión de aire, inyección, turbo y ASR no ha identificado otra modificación cuya mejora física quede defendida por las evidencias disponibles. Los documentos y la geometría no permiten resolver un máximo de trabajo físico: faltan la transferencia hidráulica de los inyectores, la identificación de combustión y la respuesta real del sistema de aire. Se conserva la calibración heredada donde esa incertidumbre afecta al subsistema; conservarla no la certifica como óptima ni convierte sus valores nominales en medidas.

El resultado está construido y verificado offline. No se ha escrito en la ECU ni obtenido un log o ensayo nuevo. No se presupone instalado el turbo híbrido ni el FMIC proyectados. La anulación física de EGR se toma de la descripción del vehículo; su anulación electrónica no está demostrada.

## 2. Fuentes, identidad y contradicciones resueltas

**Padre exclusivo:** SHA-256 `65fd9c4000fbb1b17216c9951f06c367f40612691abef1177cc6a09463d087ed`, 524288 bytes. Se leyó directamente de `/home/trollencio/Documents/VW-PASSAT_DOCUMENTS/R5A/R5A_V6.bin`. Las dos copias descargadas con nombre `038906019KE_AWX_R5A_V5_AIR_OPT_R4K_TAIL_CANDIDATE.bin`, incluida la copia «(1)», son idénticas por hash. Esto satisface la identidad de `01-R5A_V6.bin` solicitada, sin depender del nombre.

**Referencia histórica KE:** `/home/trollencio/Documents/Codex/2026-09-28/pu/outputs/ke.bin`, SHA-256 `96094e90cfa028fe7e962168d4414d1729fa2758e63498db7ff13f2c8e2b85ec`. También se comprobó su existencia dentro de los paquetes históricos. Se usa para contrastar código, diagnóstico, escalas y superficies; no se dispone de firma de autenticidad OEM.

El `.md` contiguo al padre, `R5A_V6.md`, SHA-256 `66d30f5bc0c534cb4c6b483626da13a2dd1cdbb4d0a81a081d757a161fe1425f`, describe una construcción de hash `6cbcb7d95a2a757c779ea9d82007028a393a078e3b7cfdfc46f96780ccfa588d`. **Ese informe no describe los bytes actuales.** Sus estadísticas de mallas, avances, remuestreo N75 y ASR, checksums y replay no se trasladan a R6A. La candidata anterior no se utiliza como padre ni donante.

Se auditó por separado el experimento V4.1 disponible, SHA-256 `30eba56defa597552e0169bb0d6cf36bf214dba484611cb3dec677e5b5a4dfca`. En su código cambian exactamente los bytes `0x16BF2` y `0x16EA4`, de `2D` a `0D`. El padre y R6A conservan `2D` en ambos, igual a KE. No se reincorporan los parches retirados.

La ROM `0281010940_038906019GS_AWX_338hp_579Nm` queda excluida como donante. No se utilizan sus valores ni su nombre para inferir potencia o par.

Se buscaron y deduplicaron fuentes en las dos carpetas indicadas por el usuario, incluidas las entradas de ZIP y los ZIP anidados. No aparecieron los paquetes R5A ZERO BASE V1, CLEAN PERFORMANCE V2, V4/V4.2, `REVISION_V6_CON_ROM_ACTUAL.txt`, `REVISION_V6_EVIDENCIAS.zip` ni `PAQUETE_BASE_CODEX_R4K_V13_ALTAS_RPM.zip` con esos nombres o miembros. La búsqueda adicional de R5A y REVISION_V6 en Google Drive no devolvió archivos. **No se afirma haber leído paquetes ausentes.** Se recuperaron las cadenas runtime, herramientas y evidencias equivalentes disponibles en los paquetes V6/V9/V10/V13 locales; la identidad del padre se resolvió mediante los binarios presentes.

Fuentes determinantes, además de los binarios:

| Fuente | SHA-256 | Utilización |
|---|---|---|
| `RESULTADOS_CODEX_R4I_V6.zip` | `ef82f05a5a32ee83db3e49937774c13520dd1b41f67bda3d3bd77575209b1c96` | Lectura de miembros runtime y herramientas comprimidas. |
| `IQ_RUNTIME_CHAIN_V6.md` | `3883799f166724a102d6c8cf7b3ed7c7a570271eda3e2f46b5fae41a6df530a6` | Cascada de cantidad y consumidor hasta scheduler. |
| `DURATION_RUNTIME_CHAIN_V6.md` | `5852abd0d4bf4dce9427c3f661c43622af71dc7ac9df2bf42c76193b3aacd3f6` | Selector, mezcla y DD9E. |
| `BOOST_N75_RUNTIME_CHAIN_V6.md`, versión corregida local | `113bd5c0e97a13e2d005542c32435784791d4f2b3ecec8db46248a6e1d9a9e0a` | Target, límites, feed-forward, estados y corrección entre bancos. |
| `EGR_AIRMASS_RUNTIME_CHAIN.md` | `7c913aa064dfbafd2fe1e3e97f7223509fa581b0e2277f89f96f43e4e5d0840b` | Ruta de aire y EGR. |
| `CALCULATION_METHODS_V6.md`, versión corregida local | `b32568556adcbc2a9dc5eabc5103bb9f934b896d20525f227f39ea27ef87c0e5` | Contratos y límites de cálculo. |
| `EDC15P_AWX_R4K_ASR_evidencia_V12_siguiente_ROM.md` | `18415f3d4922362cee20fbb043c7509f4a3bdd714dea7462926c967baa500bf6` | Arbitraje, conversión de par y distinción de mediciones. |
| `ESTUDIO_TECNICO_PAPERS_R4K_V13_COMPLETO.md` | `08a98c89ab3c4ad476dfb03108e9ee4069429a188b1c853c8d520dcd12b60a88` | Límites de transferencia de la literatura; no aporta mapas óptimos AWX. |
| `checksum_readonly.py` histórico | `efc48bb5af9466252ae7bb8e8a5b57eec662ad5ce1b764d840a597bc1ea6234a` | Especificación de los ocho checksums conocidos. |

Las versiones corregidas de métodos y turbo están en `/home/trollencio/Documents/Codex/2026-09-25/he-subido-el-paquete-completo-del/outputs/`. Se contrastaron con los miembros comprimidos: las diferencias añaden la corrección de comparación entre bancos; no se confundieron dos textos diferentes por compartir nombre. Los documentos ASR y papers están, respectivamente, en `VW-PASSAT_DOCUMENTS/codex_r4/` y `/home/trollencio/`. El corpus de papers se utiliza como evidencia secundaria de sus límites, sin afirmar una nueva lectura o verificación de todos los PDF originales.

## 3. Modificación y justificación matemática

| Mapa / dirección | Coordenadas | Actual → R6A | Justificación |
|---|---|---|---|
| Driver Wish, cabecera `0x6D1EA`, matriz `0x6D222`, celda `0x6D2C0` | 1491 rpm; pedal raw 10000 = 100 % | raw 5500 → 6600; nominal 55 → 66 | Elimina el descenso de demanda al pasar de 80 % a 100 %, manteniendo todas las columnas hasta 80 %. |
| Checksum `0x6FFFC` | Región `0x6C000–0x6FFFB` | `EAC22771` → `6C50012D` | Consecuencia de la modificación de calibración. |

La cifra 66 procede de la celda contigua a 80 % de pedal: no es un porcentaje global ni un objetivo físico de inyección. Igualar el extremo al valor contiguo es la elevación mínima que elimina el descenso sin modificar la respuesta hasta 80 %. La tabla mantiene 16×8, sus ejes y direcciones. La escala nominal es raw/100; no mide masa hidráulica.

El soporte de interpolación de la celda es 1218–1995 rpm y pedal 80–100 %. La nueva superficie es continua y no decrece con el pedal en todo el rectángulo operativo 0–5200 rpm. La inversión del nudo 5355 rpm se conserva: empieza aproximadamente en 5242,628 rpm y TL ya es cero desde 5200. No se altera ese cierre para imponer una propiedad arbitraria fuera de la zona operativa.

Se comprobó la **envolvente estática** `q_est=min(DW,TL,Smoke)` en continuo, no sólo en una malla. Smoke no decrece con aire y TL no decrece con barometría; sus máximos del dominio afectado están en aire interno 1275 y barometría 1000. La unión de nudos y los cruces entre ramas lineales divide el soporte en 12 puntos de comprobación exactos. En cada tramo, el mínimo de DW respecto al techo se reduce a los extremos de régimen y pedal. El margen mínimo del padre sobre ese techo es:

\[
\min\{DW_{actual}-\min(TL_{1000},Smoke_{1275})\}
=\frac{139625}{644}\;raw
=2{,}168090062\;nominales.
\]

Se alcanza a `n=40100/23 rpm≈1743,478261 rpm`, pedal 100 %. Como la demanda nueva nunca disminuye y el techo ya estaba por debajo de ambas demandas, **la envolvente de tres mapas permanece idéntica** en aire 300–1275, barometría 600–1000 y todo el soporte del cambio. Fuera de ese soporte las tablas son iguales.

No debe confundirse esa prueba con `min(DW,TL)` sin Smoke: esta última puede aumentar hasta **0,3136320203 nominales** cerca de 1958,3302 rpm y 87,83754 % de pedal. A WOT, incluso la envolvente de dos mapas se conserva, con margen mínimo `24630/347 raw≈0,709798 nominales`.

El firmware utiliza DW antes de estados, correcciones y solicitudes externas. La identidad de la envolvente no demuestra igualdad de cada estado dinámico ni de la entrega real. Esta reparación ordena una demanda interna; no se presenta como ganancia de rendimiento físico ni como solución del máximo trabajo útil.

## 4. Cantidad: consumidores y límites del modelo

Los offsets son de fichero; la correspondencia CPU histórica es fichero+`0x80000`. El código `[0,0x4C000)` del padre y R6A es idéntico a KE. El slot 1378 consulta DW con régimen F962 y coordenada de pedal C41A/CC3C; publica C414→C0B6 en `0x1F370`, con variantes y reducción CC7C. No se identifica la tabla aislada con la demanda final.

La función IQ recoge F94A→F964, F946→F962 y F944→F966. C01C participa en aire medido, corregido por 1356. La configuración `slot1000+0x1C=0`, fichero `0x71CA0`, mantiene la ruta Smoke. El selector 13CE tiene eje `[2531,3131,3531]` y valores cero; respalda familia0 en el modelo dentro del dominio, sin certificar el helper fuera de él. La publicación Smoke incluye `C064=max(0,CBC8+C19A−CBB6)`, con estados y memoria de transición.

En `0x17082–0x1709E`, CBA0 es `max_signed([11AE+0x30],CB9E)`: la constante cero no impone CBA0=0. En `0x17180–0x17190`, CBAC se combina mediante mínimo. La rama `0x17516–0x175A2` añade un floor mediante máximo signed, después de lo cual siguen factores, techos y rampas; no es un mínimo final universal. Las operaciones y saturaciones de caller comprobadas no cierran el redondeo de todos los helpers.

Cadena comprobada hasta orden del inyector: `F946 → F96E → C4D6 → C4D2 → C4D4 → C086`; después `C086 → C4CE`, correcciones a C4D2/C5B8, lookup y mezcla Duration, `C4D0 → C5FA → C670` y programación temporal. C66E procede de la programación SOI. Por ello `min(DW,TL,Smoke)` es una envolvente de tablas, **no Final IQ, combustible hidráulico ni un emulador completo**.

La coherencia de los mapas altos se evalúa con el contexto DPP documentado para ese banco. La búsqueda de codeblock se conserva; el historial EEPROM registra palabras selector2, pero no se obtuvo una lectura nueva de la EEPROM del vehículo. La elección efectiva del banco y el estado de las tareas siguen siendo una condición externa. Se conserva íntegro el banco de reserva.

## 5. Aire, mezcla, turbo, diagnóstico y EGR

| Tabla alta | Cabecera / matriz | Dimensiones y dominio | Resultado / decisión |
|---|---|---|---|
| Smoke | `0x6DC26 / 0x6DC78` | 16×13; 861–5355 rpm; aire interno 300–1275 | raw 1550–9744; cero descensos en aire; conservada. |
| Corrección aire1356 | `0x6E1F6 / 0x6E232` | 16×10; aire raw 0–15000; térmico raw 2531–3431 | Identidad nodal exacta e interpolada en dominio; igual a KE. |
| Boost | `0x76B42 / 0x76B7E` | 16×10; 0–4746 rpm; q nominal 0–80 | Máximo tabular 2580 mbar absolutos (258 kPa), escala convencional; conservado. |
| BoostLimit | `0x77144 / 0x77174` | 10×10; barometría raw 600–1100; 1500–4700 rpm | Máximo 2620 mbar (262 kPa); conservado. |
| N75 | `0x76E48 / 0x76E8A` | 16×13; 760–4242 rpm; q nominal 0–58 | raw 2500–8000; región completa idéntica a KE; conservada. |
| SVBL | `0x71D18` | Palabras iniciales 0 y 2620 | Conservado; no aumento de techo. |
| EGR/airmass | `0x6C0D4 / 0x6C116` | 16×13; 0–5355 rpm; segunda coordenada raw 0–5100 | Región `[0x6C0D4,0x6C2B6)` idéntica a KE. |

La ley interna de mezcla se revisó como hipótesis, sin declararla óptima. A aire interno 1100, los cocientes aire/q son aproximadamente 13,627354 a 3007;13,085891 a 3500;13,431833 a 4100;13,682065 a 4500;14,041956 a 4750;14,474636 a 5000 rpm. La superficie es más permisiva en la banda central y reduce la cantidad a altas rpm. Esa geometría no demuestra la mezcla que maximiza trabajo útil, ni lambda física, ni ausencia de humo. Se conserva porque no hay identificación independiente que justifique desplazarla.

El target turbo atraviesa correcciones, estados, limitación y SVBL antes de publicar C038. N75 es feed-forward y pasa por dinámica, límites y driver; no se identifica su convención `100−raw/100 %` con posición física de VNT o vacío. En el dominio común 1500–4700 rpm, barometría 1000 y q80, la unión de 17 nudos da `BoostLimit−Boost` entre 40 y 70,245902 mbar: no hay target por encima del límite en esa sección. Esto no es un ensayo de sobrepresión, velocidad de turbo ni temperatura. Las entradas fuera del eje en Boost, N75 o BoostLimit se señalan como contrato de endpoint pendiente, no como fallo demostrado. Ampliar una meseta retirando nudos introduce remuestreo sin probar una mejora; se conservan superficies, auxiliares, PID, límites y rollback.

Los descriptores diagnósticos actuales permanecen iguales a KE en ambos bancos. MAF actual `0x73294`, bytes `31c4300f`, A196 y gain200/10000, da convencionalmente `display=4,9·B`; MAF solicitado `0x732C8`, A200, da `display=5·B`. Régimen usa `display=21·B` y coordenada nominal `n≈10000·B/480`. IQ008 conserva A70/gain364/10000; su representación puede saturar en 70 aunque los mapas internos alcancen 110. Esa saturación de display no es un límite hidráulico demostrado.

Los factores 5,7/5,8 de los logs históricos pertenecen a descriptores R4I diferentes, A228/A232. No son la representación diagnóstica de R6A. No se ha presentado un replay histórico como medición de esta candidata; los bytesB255 y la cuantización tampoco autorizan inferir aire por encima de la representación disponible.

EGR mantiene tabla, consigna C022, regulación y correcciones. Sus clamps 8500/0 y límites de regulador 8000/1500 siguen activos en la calibración. La selección de segunda coordenada C418←C048 no se etiqueta sin prueba como Final IQ. **El binario conserva la funcionalidad electrónica heredada: no se certifica EGR-off.**

## 6. Duration, SOI y contrato de selección

La revisión usa el padre real de SHA-256 `65fd9c4000fbb1b17216c9951f06c367f40612691abef1177cc6a09463d087ed`. La referencia KE de SHA-256 `96094e90cfa028fe7e962168d4414d1729fa2758e63498db7ff13f2c8e2b85ec` se recuperó también en `/home/trollencio/Documents/Codex/2026-09-28/pu/outputs/ke.bin`. Sus bytes de código en `[0x00000,0x4C000)` coinciden exactamente con el padre. Las 497 instrucciones documentadas de `Duration.asm` y las 561 de `SOI.asm` coinciden con el padre, sin discrepancias. Esto permite aplicar los consumidores documentados; no reconstruye el planificador completo ni la implementación de los helpers internos.

El caller de Duration carga `C5F8→C4CA` en `0x46690`. C5F8 es una salida de la cadena SOI, publicada en `0x38FB6` después de correcciones y límites; no se identifica con régimen ni cantidad por sí sola. C5F4 alimenta el lookup slot182E. Su salida se guarda en DD9E y `0x46702..0x4670A` calcula `DDA0=C4CA−DD9E` como operación de máquina de 16 bits. La tabla de corrección, cabecera alta `0x745BE`, tiene eje signed16 `[-384,-213,-43,0,43,213,384]` y siete valores cero en `0x745D0`. Es idéntica a KE. El cero nodal y el caller están comprobados; el comportamiento del helper fuera del eje no lo está.

El selector slot1818, cabecera alta `0x7538A`, conserva eje `[2176,2432,2688,2944,3157,3328]` y salidas `[0,256,512,768,1024,1280]`. H0CB2 recibe DDA0. El retorno se guarda en DDB4 en `0x46902`; `0x4690E` ceroextiende DDB5, el byte alto que contiene el índice. El byte bajo contiene la fracción. El índice selecciona uno de seis punteros DDA8..DDB2. Si la fracción es cero sólo se consulta esa familia; si es distinta de cero se consulta también la siguiente. `0x46918` pasa cantidad efectiva C4D2 y régimen F96C al lookup. `0x4694A..0x46962` implementa la estructura `D0 + H10F2((D1−D0) mod 65536,fracción,256)`. Esta estructura es compatible con una mezcla lineal, pero signedness, división, redondeo y saturación del helper IROM permanecen pendientes. El caller no ofrece un clamp explícito de índice5 con fracción no nula; depende de un endpoint válido del selector. En el modelo habitual,3328 devuelve1280: familia 05 y fracción0. No existe una familia 06 demostrada.

Los datos altos actuales son:

| Familia | Cabecera / matriz | Dimensiones | Eje cantidad nominal | Eje régimen | Rango Z signed16 |
|---|---|---:|---:|---:|---:|
|00|`0x745DE / 0x7460E`|10×10|10–110|800–5000|14–2384|
|01|`0x746D6 / 0x7471C`|15×16|0,5–110|100–5000|−188–2017|
|02|`0x7495C / 0x749A2`|15×16|0,5–110|100–5000|−152–1884|
|03|`0x74BE2 / 0x74C28`|15×16|0,5–110|100–4500|−137–1731|
|04|`0x74E68 / 0x74EAE`|15×16|0,5–110|100–4000|−164–1710|
|05|`0x750EE / 0x7511E`|9×11|0,5–30|200–2500|41–553|

La conversión de cantidad raw/100 es una coordenada nominal; no demuestra masa hidráulica. Duration `3/128 °/raw` y SOI `78−raw·3/128 °` son escalas de orden inferidas de las cadenas documentadas. Los words FFxx de baja carga se interpretan como signed16 en el modelo; tratarlos como ángulos unsigned daría extremos espurios. Las familias00–04 ya alcanzan110: no se amplían por rutina. La salida C5FA se publica en `0x46AB2`, se copia a C670 en `0x48DEC` y participa en el scheduler. Las etiquetas de Group004 indican órdenes especificadas; no aportan medida hidráulica del inyector.

El padre conserva la configuración SOI `00 01` en `0x74224`: la compuerta de las cinco primeras matrices principales está desactivada por ese byte estático; las ramas térmicas y el override tienen condiciones adicionales, entre ellas C314. La geometría SOI de alta carga presenta entradas al selector inferiores a 2176 en los escenarios recalculados del apartado siguiente. Se describe como consulta fuera del eje, no como fallo runtime demostrado. La matriz SOIlimit alta, cabecera `0x7B490` y datos `0x7B4CA`, es 14×11; su coordenada térmica termina en 3521. El cálculo nuevo consulta exactamente ese extremo, dentro del dominio.

La malla de 41700 consultas y los máximos 41,392°/46,247° publicados en el informe de hash6cbc corresponden a otra candidata, no al padre actual ni a R6A. Tampoco la conservación geométrica del fin de orden KE a q55 acredita CA50, trabajo máximo, fin hidráulico o combustión óptima. R6A conserva SOI, Duration, DD9E y selector; no añade avance ni codifica una saturación IROM no demostrada.

## 7. ASR136C: conversión compartida y arbitraje

Se cotejaron635 instrucciones del listado `asr_coordinator_current.asm` y593 de `SLOT136C_CALLERS.asm` con el padre: todas coinciden. Los offsets son de fichero. La resolución de punteros supone el contexto DPP del banco correspondiente; no equivale a observar qué banco ejecutó el vehículo ni cierra toda su propagación por helpers y despacho.

El slot 136C alto `0x7136C` resuelve a cabecera `0x6CCEA`, Z `0x6CD18`,8×11. El eje de régimen es `[1000,1500,1900,2500,3000,3500,4000,4500]`; el eje de cantidad corregida raw es `[400,1200,1600,2000,2500,3000,3500,4000,4500,5600,11000]`; Z ocupa700..1114. El banco de reserva conserva la tabla KE de eje final5600 y rango700..925. La interpretación Nm/IQ no está demostrada.

Se identifican siete cargas directas del slot: `0x147B0`, `0x14826`, `0x14868`, `0x1494E`, `0x1574E`, `0x182BC` y `0x2244A`. Los cinco primeros alimentan conversiones con estructura convencional cantidad×500/L; `0x2244A` aplica L×C4DC/500 en acondicionamiento de demanda. El consumidor ASR `0x182BC..0x182C8` consulta con `F962←F94A` y `C43A←C12E`. C12E procede de cantidad corregida, publicada desde C48A en `0x14732..0x1473A`; no se sustituye por DW ni TL. H0E20/slot1334 filtra la salida con estadoC7D4 y devuelve C7D6 a C448/C0D6 en `0x182DE..0x182E6`; C448 no es instantáneamente una celda.

La rama ASR es `0x19122..0x19298`, con peticiónC324 y estadosC0A2/C0A4. Sus compuertas requieren `(C0A2&0x20)!=0`, C324!=FF, `(C0A4&0x20)==0`, FD02.7=0 y ausencia de bits0800/0080 en C0A2. `0x191EE` realiza MOVBS, pero el helper flash `0x181BC` recupera y ceroextiende el byte, por lo que no prueba una petición negativa. Bajo el contrato convencional H10F2, la conversión es `Traw≈byte·4000/255`, `Qraw≈Traw·C448/500`, seguida de `C7B2=max_signed(0,word16(Qraw−C090))`. El mínimo limita C414/C460 y se publica por C08E/C054 en `0x1973C..0x19748`; C0B0 conserva otra demanda base. Otros arbitrajes y compuertas siguen interviniendo. La resta de16bits no contiene saturación por overflow explícita en ese tramo.

El normalizador 4000 permanece en `0x7227E`, el neutral 10000 en `0x71D80` y la restitución 24 en `0x71D86`. Sólo en la rama inactiva, C324=FE asigna neutral directamente; FE activo se convierte numéricamente. El paso 24 no es una pendiente por segundo conocida. Neutral y normalizador son compartidos con otras funciones.

Frente al último extremo KE, los coeficientes actuales a q110 son `[900,966,906,879,987,1072,1106,1114]`, con incrementos de 0–23,58 %. En el modelo algebraico, aumentarL permite mayorcantidad para una peticiónraw fija y reduce el valor de publicación para cantidad fija; aplanarlo modifica ambos sentidos. En el dominio antiguo el error máximo de interpolación respecto a KE es 5 raw a 4000 rpm/q800 raw, debido al nodo 800 retirado. Ninguna de esas diferencias constituye calibración física validada. Restaurar un extremo constante, extrapolar de nuevo o alterar normalización también necesitaría justificación propia. Se conserva la cadena completa y su tabla heredada; no se declara óptima, ineficaz ni medida en Nm.


## 8. Comparación calculada SOI–Duration con la actual

Lectura nueva de ambos binarios y KE. Aire interno 1100; barometría 1000; pedal 100 %; rama SOI14 (matriz `0x79F00`); DD9E=0. SOIlimit consultado exactamente en su último nudo térmico 3521, dentro del dominio. Interpolación y mezcla convencionales; contrato IROM pendiente. Todos los puntos del padre/R6A quedan bajo el mínimo 2176 del selector: familia 00/fracción 0 sólo bajo esa hipótesis. EOI es Duration−SOI como proxy geométrico de orden.

| rpm | q actual = R6A | SOI orden ° | Duration orden ° | EOI proxy ° | EOI KE q55 ° | ΔEOI proxy vs. KE ° |
|---:|---:|---:|---:|---:|---:|---:|
|3000|80.69032|28.53744|37.98275|9.44531|6.24811|3.19720|
|3500|84.06000|33.45753|39.84107|6.38353|4.78720|1.59633|
|4000|82.25583|34.92363|41.18600|6.26237|4.76953|1.49284|
|4250|81.33333|35.10664|41.50156|6.39492|4.87779|1.51713|
|4500|80.39722|35.27323|41.79086|6.51763|5.05005|1.46758|
|4750|74.33333|33.25937|39.70703|6.44766|5.17767|1.26998|
|5000|68.00000|31.05547|37.44034|6.38487|5.34375|1.04112|

Delta actual/R6A: cero en todas las cantidades y ángulos calculados. La discrepancia frente al ancla KE pertenece al padre y se conserva; no justifica por sí sola adelantar SOI. Estos valores no acreditan CA50, final hidráulico ni potencia.

## 9. Cierre TL y comparación de cantidad

La fila barométrica 1000 conserva sus valores exactos:

| rpm | q TL nominal |
|---:|---:|
|4000|90|
|4250|90|
|4500|82|
|4800|72,8|
|5000|68|
|5200|0|

El nudo de 5000 rpm ya está presente en el padre. Los interpolados son `4750→223/3=74,333333…` y `5100→34`. En particular, `4750: 82+(72,8−82)·250/300`, y `5100: 68+(0−68)·100/200`. No se reemplaza el último tramo por un cierre suavizado. Las tres filas, ejes, dimensiones 3×21 y bytes de TL quedan idénticos.

A aire interno 1100 y WOT, la tabla del apartado 8 muestra igualdad cuantitativa de q entre actual y R6A. A 4750 rpm Smoke permitiría 78,336667, pero TL cierra en 74,333333; a 5000 Smoke da 75,995 y TL fija 68. En la banda 3000–4500 se mantienen la permisividad actual y los límites actuales. El máximo nodal Smoke es 97,44 nominales, no 97,43 de la candidata diferente del informe contiguo.

Frente a KE, tanto el padre como R6A difieren en 4088 posiciones de byte; esta igualdad de conteos no implica que sus bytes sean iguales entre sí. Frente al padre, R6A difiere exactamente en seis bytes, una celda de calibración y un checksum. No hay un aumento físico medido ni una conversión de esas cifras a CV o Nm.

## 10. Verificación del archivo final

El archivo entregable se volvió a leer después de su última escritura. Resultado: **INDEPENDENT_BINARY_AND_STATIC_PROPERTY_PASS**. El verificador no importa el constructor ni su manifiesto: localiza la intención por coordenadas, calcula su posición, reconstruye el resultado esperado y comprueba propiedades de las tablas. Constructor y verificador usan rutas distintas de cálculo del checksum; además se obtuvo acuerdo exacto con `checksum_readonly.py` histórico. Las implementaciones comparten la especificación heredada, no constituyen una firma OEM ni dos especificaciones Bosch independientes.

| Propiedad | Resultado |
|---|---|
|Tamaño / identidad|524288 bytes; SHA-256 final indicado; cuatro identificaciones presentes.|
|Ocho checksums|8/8 correctos en padre, KE y R6A; acuerdo con el checker histórico sobre el entregable.|
|Diferencia exacta|6 bytes; ningún cambio fuera de la celda DW y su checksum.|
|Código, arranque, diagnóstico y reserva|Iguales al padre; código `[0,0x4C000)` también igual a KE; banco `[0x4C000,0x5C000)` preservado íntegro.|
|Ejes, dimensiones y matrices|17 tablas altas principales, incluidos ASR y SOIlimit, comprobadas; metadatos y resto de matrices preservados.|
|SOI y selección|30 matrices SOI de ambos bancos, ejes compartidos 16×14, curvas DD9E signed16 y selector de seis familias comprobados y conservados.|
|Límites numéricos|Duration leído signed16; otras Z como uint16; cantidades nominales y ejes registrados; máximo Smoke 97,44 ≤110 del dominio principal de cantidad.|
|Continuidad / progresión|Prueba por segmentos: DW no decrece con pedal en 0–5200 rpm; soporte del cambio continuo; Smoke no decrece en aire.|
|TL / extremos|Seis nudos solicitados e interpolados exactos; TL se cierra en 5200; dominios exteriores de otras tablas señalados sin inventar contrato IROM.|
|Envolvente de cantidad|Identidad continua de `min(DW,TL,Smoke)` en el soporte y dominio declarados; igualdad trivial fuera del soporte.|
|Reproducción|Nueva construcción en archivo separado, comparación de todos sus bytes y reconstrucción independiente: idénticas al entregable.|
|Lecturas finales|Padre, KE y candidata vuelven a leerse sin cambios durante la auditoría.|

Los ocho controles negativos detectaron: un byte alterado de Smoke por checksum; eje DW duplicado; cierre TL cambiado; demanda DW decreciente; código modificado fuera de la calibración; eje SOI duplicado; orden/signo incorrecto en DD9E; y selector con familia 6 inválida. Estas pruebas verifican detección de errores estructurales y propiedades funcionales del modelo, no sólo los valores escritos por el constructor.

Identificación del entregable: `0281011201` en `0x53519`, `038906019KE` en `0x534EE`, `1037366297` en `0x10046` y `4898` en `0x5350B`. Son cadenas presentes y conservadas; no autenticación criptográfica OEM.

Checksums finales, palabras uint32 little-endian:

| Dirección de almacenamiento | Actual | R6A | Estado |
|---|---|---|---|
|`0xFFFC`|`86A2C5EA`|`86A2C5EA`|PASS|
|`0x13FFC`|`4B5F0324`|`4B5F0324`|PASS|
|`0x4FFFC`|`CAC08B42`|`CAC08B42`|PASS|
|`0x50B7C`|`E70DBA47`|`E70DBA47`|PASS|
|`0x5BFFC`|`ED7C4AE0`|`ED7C4AE0`|PASS|
|`0x6FFFC`|`EAC22771`|`6C50012D`|PASS|
|`0x70B7C`|`E70DBA47`|`E70DBA47`|PASS|
|`0x7BFFC`|`A8027B30`|`A8027B30`|PASS|

Parche exacto reproducible, sin cambios ocultos:

| Dirección | Actual, hex | R6A, hex |
|---|---|---|
|`0x6D2C0–0x6D2C1`|`7C 15`|`C8 19`|
|`0x6FFFC–0x6FFFF`|`71 27 C2 EA`|`2D 01 50 6C`|

Regiones completas preservadas: `[0,0x6C000)` y `[0x70000,0x80000)`; dentro del resto sólo cambian las dos regiones del parche. Duration, SOI, selector, DD9E, ASR, Smoke, EGR y todas las superficies turbo están fuera de esas regiones y quedan idénticos.

Se cotejaron contra el padre 497 instrucciones del listado Duration, 561 de SOI, 635 de coordinación ASR y 593 de consumidores136C; ninguna discrepancia de bytes. El cotejo valida que esos listados describen este ejecutable, no redecodifica independientemente todo el CFG ni los cuerpos IROM.

## 11. Supuestos pendientes y conclusión técnica

Permanecen abiertos el contrato exacto de interpolación y extremos de IROM, signedness y redondeo de mezclas; el origen y rango runtime completo de C5F4/C5F8; la selección efectiva de banco sin EEPROM nueva; el orden de tareas y todas las correcciones a la cantidad; el caudal hidráulico frente a la coordenada nominal; la correlación ASR con par físico; y combustión, límites térmicos y dinámica VNT del hardware real. No se estima ninguna de esas magnitudes mediante el nombre de una ROM.

La corrección propuesta elimina una incoherencia demostrada de demanda con una intervención mínima, mantiene el cierre solicitado y conserva la envolvente de combustible del modelo estático. **No se ha encontrado evidencia suficiente para presentar una calibración más agresiva como mejora de trabajo útil.** La R6A conserva las incertidumbres heredadas expresamente descritas y no obtiene certificación de combustión, tracción, humo ni comportamiento del motor a partir de los checksums.

## 12. Reproducción autosuficiente

Los tres programas completos están incluidos a continuación. Guardar sus bloques como `build_r6a.py`, `independent_verify.py` y `runtime_evaluate.py`, usando Python 3.10 o posterior y su biblioteca estándar, sin `-O`. No hace falta descargar herramientas ni usar la GS. El constructor exige rutas nuevas para evitar sobreescritura.

Ejemplo, sustituyendo las rutas del padre y de KE por las copias de esos mismos hashes:

```bash
python3 -B build_r6a.py R5A_V6.bin R6A_reproducida.bin --manifest R6A_reproducida.json
python3 -B independent_verify.py R6A_reproducida.bin --parent R5A_V6.bin --ke ke.bin --out R6A_verificacion.json
python3 -B runtime_evaluate.py R5A_V6.bin R6A_reproducida.bin ke.bin R6A_timing.json R6A_timing.md
```

El SHA-256 de la reproducción debe ser `e17badb7aff3fd70cf8f0b3712b150822cdec3642f23476dbd1ca07aac1381d5`. El informe conserva los programas utilizados, la intención y la comparación, de modo que no depende de una herramienta no entregada para reconstruir esta candidata. Los valores de la tabla de timing son un escenario condicional y se reproducen por separado de las propiedades binarias.

Hashes de los programas incluidos, bytes UTF-8 de los archivos originales con LF:

| Programa | SHA-256 |
|---|---|
|`build_r6a.py`|`0fb8d73059605f1203ebf1fe384d81dcfd33e324331bd8c9b59336e48d0c5690`|
|`independent_verify.py`|`2f89bb7abcf5dab6f464f8e69447c767d39d401c434bb20ff0592309c4ad7925`|
|`runtime_evaluate.py`|`27200ab299f8fdf840444b523ddb06771145749f2d043e146f6f3cd1becda136`|

Hashes de listados de código cotejados:

| Listado | SHA-256 | Instrucciones coincidentes |
|---|---|---:|
|`Duration.asm`|`f43c8b4df9373b7caa0b322bcaf36b6929c77dcdda979c161c741a4791e63ba2`|497|
|`SOI.asm`|`3b38beea39e7dfa84a2d2852aabe9cb8e0ad4053425d6fa4bd50cbc03f1a550a`|561|
|`asr_coordinator_current.asm`|`19d0e04234305b335f6e040bd4dead09ac9ca723e8c7736520787e30a17e4a84`|635|
|`SLOT136C_CALLERS.asm`|`662662bd245b1e40ad7dece0b1ce625b1758877e376ce736e4fac7a5b3cf0033`|593|

### 12.1. build_r6a.py

```python
#!/usr/bin/env python3
"""Construct R6A only from the exact R5A parent. Standard library only."""
import argparse, hashlib, json, struct
from pathlib import Path
PARENT_SHA = "65fd9c4000fbb1b17216c9951f06c367f40612691abef1177cc6a09463d087ed"
sha = lambda b: hashlib.sha256(b).hexdigest()
def require(ok, label):
    if not ok: raise ValueError(label)
def rol16(v,n):
    n &= 15
    return ((v<<n)|(v>>(16-n)))&65535 if n else v
def ror16(v,n):
    n &= 15
    return ((v>>n)|(v<<(16-n)))&65535 if n else v
def calc(buf,start,end,a,b,c=0,d=0,seed=False):
    words=struct.unpack_from("<"+"H"*(len(buf)//2),buf)
    pos=start//2; last=end//2
    if start==0x8000: a^=0xd565; b=(b+0x308a)&65535
    while pos<=last:
        a^=words[pos]; pos+=1; n=b&15; a=rol16(a,n)
        carry=(a&1) if n else 0
        b=((b-words[pos]-carry)&65535)^a; pos+=1
        if pos>last: break
        a=(a-words[pos]+0xdaac)&65535; pos+=1
        b^=words[pos]; pos+=1; b=ror16(b,a&15)
    if start==0: a=(a-0x79cf)&65535; b=(b-0x1033)&65535
    if seed: return a|(b<<16)
    return ((a-c+0xdaac)&65535)|((b^rol16(d,c&15))<<16)
def jobs(buf):
    s1=calc(buf,0x14000,0x4bffe,0x8631,0xefcd,seed=True)
    s2=calc(buf,0,0x7ffe,0,0,seed=True)
    out=[(0x8000,0xfffb,0xfffc,s2&65535,s2>>16,0x4531,0x3550),
         (0x10000,0x13ffb,0x13ffc,0,0,0x8631,0xefcd)]
    for bank in (0x4c000,0x6c000):
        require(buf[bank+0x4008:bank+0x400c]==b"V4.1","checksum-layout marker")
        out += [(bank,bank+0x3ffb,bank+0x3ffc,s1&65535,s1>>16,s1&65535,s1>>16),
                (bank+0x4000,bank+0x4b7b,bank+0x4b7c,s1&65535,s1>>16,s1&65535,s1>>16),
                (bank+0x4b80,bank+0xfffb,bank+0xfffc,s1&65535,s1>>16,s1&65535,s1>>16)]
    return out
def checks(buf):
    return [dict(first=hex(first),last=hex(last),store=hex(store),
        calculated=calc(buf,first,last,a,b,c,d),stored=struct.unpack_from("<I",buf,store)[0])
        for first,last,store,a,b,c,d in jobs(buf)]
def build(parent):
    require(len(parent)==524288 and sha(parent)==PARENT_SHA,"parent identity")
    require(all(c["calculated"]==c["stored"] for c in checks(parent)),"parent checksums")
    b=bytearray(parent); h,z=0x6d1ea,0x6d222
    nx=struct.unpack_from("<H",b,h+2)[0]; xh=h+4
    x=list(struct.unpack_from("<"+"H"*nx,b,xh)); yh=xh+2*nx
    ny=struct.unpack_from("<H",b,yh+2)[0]
    y=list(struct.unpack_from("<"+"H"*ny,b,yh+4))
    require(nx==16 and ny==8 and all(a<c for t in (x,y) for a,c in zip(t,t[1:])),"DW topology")
    off=z+2*(x.index(1491)*ny+y.index(10000))
    require(off==0x6d2c0 and struct.unpack_from("<H",b,off)[0]==5500,"DW cell precondition")
    struct.pack_into("<H",b,off,6600)
    for first,last,store,a,c,d,e in jobs(b):
        struct.pack_into("<I",b,store,calc(b,first,last,a,c,d,e))
    require(all(c["calculated"]==c["stored"] for c in checks(b)),"output checksums")
    changed=[i for i,(a,c) in enumerate(zip(parent,b)) if a!=c]
    require(set(changed)<=set(range(off,off+2))|set(range(0x6fffc,0x70000)),"change whitelist")
    return bytes(b),dict(parent_sha256=sha(parent),sha256=sha(b),bytes=len(b),
        changes=[dict(offset=hex(i),before=parent[i],after=b[i]) for i in changed],
        map_change=dict(map="DriverWish",data=hex(z),offset=hex(off),rpm=1491,pedal_percent=100,
                        old_raw=5500,new_raw=6600,nominal_scale=0.01),checksums=checks(b))
def main():
    ap=argparse.ArgumentParser(); ap.add_argument("parent",type=Path); ap.add_argument("output",type=Path)
    ap.add_argument("--manifest",type=Path,required=True); a=ap.parse_args()
    before=a.parent.read_bytes(); b,manifest=build(before)
    require(not a.output.exists() and not a.manifest.exists(),"new output paths required")
    with a.output.open("xb") as f: f.write(b)
    readback=a.output.read_bytes(); require(readback==b,"final readback")
    require(a.parent.read_bytes()==before,"parent changed during build")
    require(build(before)[0]==readback,"in-memory reproducibility")
    a.manifest.write_text(json.dumps(manifest,indent=2)+"\n")
    print(json.dumps({k:manifest[k] for k in ("sha256","bytes","changes")}))
if __name__=="__main__": main()
```

### 12.2. independent_verify.py

```python
#!/usr/bin/env python3
"""Independent R6A binary/property verifier. Standard library; never writes ROM.

R6A intended change is one DW word at the literal raw coordinates (1491,10000),
5500 -> 6600. This program derives its byte position independently from table
axes and rebuilds the entire expected candidate, including eight known checksums.
Fraction arithmetic proves the whole affected continuous static envelope.
The model uses bilinear interpolation and endpoint clamping; these are declared
static-model assumptions, not a complete IROM emulator or physical calibration.
"""
import argparse, bisect, hashlib, json, struct
from fractions import Fraction as F
from decimal import Decimal, localcontext
from pathlib import Path

PARENT_SHA = '65fd9c4000fbb1b17216c9951f06c367f40612691abef1177cc6a09463d087ed'
KE_SHA = '96094e90cfa028fe7e962168d4414d1729fa2758e63498db7ff13f2c8e2b85ec'
PARENT = Path('/home/trollencio/Documents/VW-PASSAT_DOCUMENTS/R5A/R5A_V6.bin')
KE = Path('/home/trollencio/Documents/Codex/2026-09-28/pu/outputs/ke.bin')
SPECS = {'EGR':(0x4c0d4,0x4c116),'DW':(0x4d1ea,0x4d222),
'TL':(0x4d8fc,0x4d934),'Smoke':(0x4dc26,0x4dc78),
'Boost':(0x56b42,0x56b7e),'BoostLimit':(0x57144,0x57174),
'N75':(0x56e48,0x56e8a),'FloorBase':(0x4d672,0x4d69a),
'FloorFactor':(0x4d8ba,0x4d8d4),'Duration00':(0x545de,0x5460e),
'Duration01':(0x546d6,0x5471c),'Duration02':(0x5495c,0x549a2),
'Duration03':(0x54be2,0x54c28),'Duration04':(0x54e68,0x54eae),
'Duration05':(0x550ee,0x5511e),'ASR136C':(0x4ccea,0x4cd18),
'SOILimit':(0x5b490,0x5b4ca)}

def sha(b): return hashlib.sha256(b).hexdigest()
def require(ok, message):
    if not ok: raise ValueError(message)
def u16(b, o): return struct.unpack_from('<H', b, o)[0]
def words(b, o, n): return list(struct.unpack_from('<'+'H'*n,b,o))
def rotate(v,n,right=False):
    n &= 15
    if not n: return v
    return ((v>>n)|(v<<(16-n)))&65535 if right else ((v<<n)|(v>>(16-n)))&65535

def calc(b,start,end,a,bv,c=0,d=0,seed=False):
    if start==0x8000: a ^= 0xd565; bv=(bv+0x308a)&65535
    o=start
    while o<=end:
        a ^= u16(b,o); o+=2; n=bv&15
        a=rotate(a,n); carry=(a&1) if n else 0
        bv=((bv-u16(b,o)-carry)&65535)^a; o+=2
        if o>end: break
        a=(a-u16(b,o)+0xdaac)&65535; o+=2
        bv ^= u16(b,o); o+=2; bv=rotate(bv,a&15,True)
    if start==0: a=(a-0x79cf)&65535; bv=(bv-0x1033)&65535
    if seed: return a|(bv<<16)
    return ((a-c+0xdaac)&65535)|((bv^rotate(d,c&15))<<16)

def checksums(b):
    s1=calc(b,0x14000,0x4bffe,0x8631,0xefcd,seed=True)
    s2=calc(b,0,0x7ffe,0,0,seed=True)
    jobs=[(0x8000,0xfffb,0xfffc,s2&65535,s2>>16,0x4531,0x3550),
          (0x10000,0x13ffb,0x13ffc,0,0,0x8631,0xefcd)]
    for bank in (0x4c000,0x6c000):
        require(b[bank+0x4008:bank+0x400c]==b'V4.1','checksum bank marker')
        for start,end,store in [(bank,bank+0x3ffb,bank+0x3ffc),
          (bank+0x4000,bank+0x4b7b,bank+0x4b7c),
          (bank+0x4b80,bank+0xfffb,bank+0xfffc)]:
            jobs.append((start,end,store,s1&65535,s1>>16,s1&65535,s1>>16))
    return [dict(store=o,stored=struct.unpack_from('<I',b,o)[0],calculated=calc(b,s,e,a,v,c,d))
            for s,e,o,a,v,c,d in jobs]

def table(b,name,mirror=0x20000):
    h,z=SPECS[name]; h+=mirror; z+=mirror
    nx=u16(b,h+2); require(1<nx<=50,'invalid nx '+name)
    yh=h+4+2*nx; ny=u16(b,yh+2); require(1<ny<=50,'invalid ny '+name)
    x,y=words(b,h+4,nx),words(b,yh+4,ny)
    require(all(a<c for axis in (x,y) for a,c in zip(axis,axis[1:])), 'nonascending axes '+name)
    require(yh+4+2*ny<=z and z+2*nx*ny<=len(b),'storage contract '+name)
    raw=words(b,z,nx*ny)
    return dict(h=h,z=z,nx=nx,ny=ny,x=x,y=y,zv=[raw[i*ny:(i+1)*ny] for i in range(nx)],
                x_id=u16(b,h),y_id=u16(b,yh),signed_range=[min(v if v<32768 else v-65536 for v in raw),max(v if v<32768 else v-65536 for v in raw)])

def at(t,x,y):
    xs,ys,z=t['x'],t['y'],t['zv']
    x=max(xs[0],min(xs[-1],F(x))); y=max(ys[0],min(ys[-1],F(y)))
    i=max(0,min(len(xs)-2,bisect.bisect_right(xs,x)-1)); j=max(0,min(len(ys)-2,bisect.bisect_right(ys,y)-1))
    a=(x-xs[i])/F(xs[i+1]-xs[i]); c=(y-ys[j])/F(ys[j+1]-ys[j])
    return (1-a)*((1-c)*z[i][j]+c*z[i][j+1])+a*((1-c)*z[i+1][j]+c*z[i+1][j+1])

def monotonic_demand(t):
    # For each rpm segment, slopes along pedal are convex combinations of its
    # endpoint slopes. Nonnegative slopes at all breakpoints prove continuity
    # and monotonicity over the complete operational rectangle.
    knots=sorted(set([F(0),F(5200)]+[F(r) for r in t['x'] if 0<=r<=5200]))
    return all(at(t,r,a)<=at(t,r,c) for r in knots for a,c in zip(t['y'],t['y'][1:]))

def tl_contract(t):
    nodes={4000:9000,4250:9000,4500:8200,4800:7280,5000:6800,5200:0}
    require(all(at(t,1000,n)==v for n,v in nodes.items()),'requested TL nodes')
    require(at(t,1000,4750)==F(22300,3),'TL 4750 interpolation')
    require(at(t,1000,5100)==3400,'TL 5100 interpolation')

def continuous_envelope(parent,candidate):
    old,new=table(parent,'DW'),table(candidate,'DW')
    tl,sm=table(parent,'TL'),table(parent,'Smoke')
    require(all(all(a<=c for a,c in zip(row,row[1:])) for row in sm['zv']),'Smoke monotonic air')
    require(all(all(a<=c for a,c in zip(col,col[1:])) for col in zip(*tl['zv'])),'TL monotonic barometry')
    require(old['x'][8:11]==[1218,1491,1995] and old['y'][-2:]==[8000,10000], 'changed support')
    knots=sorted(set(F(x) for x in old['x']+tl['y']+sm['x']+[1218,1995] if 1218<=x<=1995))
    # Insert the intersections at which the controlling linear branch changes.
    for a,z in list(zip(knots,knots[1:])):
        for fn in (lambda n:at(tl,1000,n)-at(sm,n,12750),lambda n:at(old,n,8000)-at(old,n,10000)):
            fa,fz=fn(a),fn(z)
            if fa*fz<0: knots.append(a+(z-a)*fa/(fa-fz))
    knots.sort()
    margins=[(n,p,at(old,n,p)-min(at(tl,1000,n),at(sm,n,12750))) for n in knots for p in (8000,10000)]
    minimum=min(v for n,p,v in margins)
    require(minimum>=0, 'old demand does not cover maximal static cap')
    require(all(at(new,n,p)>=at(old,n,p) for n in knots for p in (8000,10000)), 'new demand decreases')
    # On every partition, the differences are linear in rpm. DW is affine in
    # pedal; its minimum is at an endpoint. Smaller air/barometry lowers cap.
    # Therefore min(DW,TL,Smoke) is identical over all affected continuous inputs.
    wot_knots=sorted(set(F(x) for x in old['x']+tl['y']+[1218,1995] if 1218<=x<=1995))
    wot_margin=min(at(old,n,10000)-at(tl,1000,n) for n in wot_knots)
    require(wot_margin>=0,'WOT two-map envelope not preserved')
    with localcontext() as ctx:
        ctx.prec=40
        a=F(403200,347)+400; b=F(400)+F(327200,347)
        dec=lambda v:Decimal(v.numerator)/Decimal(v.denominator)
        t=(dec(b)/dec(a)).sqrt(); max_delta=Decimal(1100)/Decimal(290)*(dec(a)+dec(b)-2*(dec(a)*dec(b)).sqrt())/100
        rpm=Decimal(1491)+504*t
    return dict(static_three_map_envelope_identical=True,model='bilinear interpolation and endpoint clamp',
        affected_domain=dict(rpm=[1218,1995],pedal_percent=[80,100],barometry_raw=[600,1000],air_internal=[300,1275]),
        proof_partition_exact=[str(n) for n in knots],minimum_cover_raw_exact=str(minimum),minimum_cover_nominal=float(minimum/100),
        minimum_at=[dict(rpm_exact=str(n),pedal_raw=p) for n,p,v in margins if v==minimum],
        WOT_two_map_minimum_cover_raw_exact=str(wot_margin),two_map_partial_max_increase_nominal=str(max_delta),
        two_map_partial_max_rpm=str(rpm),physical_runtime_claim=False)

def ancillary_geometry(parent,candidate):
    result={'SOI_matrices':[],'DD9E_curves':[],'Duration_selectors':[]}
    for mirror in (0,0x20000):
        base=0x50000+mirror
        xh,yh=0x585fc+mirror,0x58620+mirror
        require(u16(candidate,xh+2)==16 and u16(candidate,yh+2)==14,'SOI shared dimensions')
        x,y=words(candidate,xh+4,16),words(candidate,yh+4,14)
        require(all(a<c for axis in (x,y) for a,c in zip(axis,axis[1:])),'SOI shared ascending axes')
        require(candidate[xh:yh+32]==parent[xh:yh+32],'SOI shared metadata preservation')
        starts=list(range(0x58668,0x58f28,0x1c0))+list(range(0x58f40,0x5a0c0,0x1c0))
        require(len(starts)==15,'SOI family count')
        for family,low in enumerate(starts):
            z=low+mirror; raw=words(candidate,z,224)
            require(candidate[z:z+448]==parent[z:z+448],'SOI matrix preservation')
            require(all(0<=v<=65535 for v in raw),'SOI unsigned16 bounds')
            result['SOI_matrices'].append(dict(bank='high' if mirror else 'reserve',family=family,
                shared_headers=[hex(xh),hex(yh)],data=hex(z),end_exclusive=hex(z+448),dimensions=[16,14],
                ascending_axes=True,z_unsigned16_range=[min(raw),max(raw)],geometry_and_cells_preserved=True))
        dh=0x545be+mirror; da=dh+4; dz=0x545d0+mirror
        require(u16(candidate,dh+2)==7,'DD9E curve dimension')
        axis=list(struct.unpack_from('<7h',candidate,da)); raw=words(candidate,dz,7)
        require(axis==[-384,-213,-43,0,43,213,384] and all(a<c for a,c in zip(axis,axis[1:])),'DD9E signed axis')
        require(raw==[0]*7,'DD9E nodal zero values')
        require(candidate[dh:dz+14]==parent[dh:dz+14],'DD9E curve preservation')
        result['DD9E_curves'].append(dict(header=hex(dh),data=hex(dz),dimensions=[7],signed16_axis=axis,
            values_raw=raw,preserved=True,outside_axis_helper_behavior='not proved'))
        sh=0x5538a+mirror; sa=sh+4; sz=0x5539a+mirror
        require(u16(candidate,sh+2)==6,'Duration selector dimension')
        axis=words(candidate,sa,6); raw=words(candidate,sz,6)
        require(all(a<c for a,c in zip(axis,axis[1:])),'Duration selector ascending axis')
        require(raw==[0,256,512,768,1024,1280] and all(0<=v<=5*256 for v in raw),'Duration selector family bounds')
        require(candidate[sh:sz+12]==parent[sh:sz+12],'Duration selector preservation')
        result['Duration_selectors'].append(dict(header=hex(sh),data=hex(sz),axis_raw=axis,values_raw=raw,
            last_valid_family=5,preserved=True,outside_axis_helper_behavior='not proved'))
        config=0x54224+mirror
        require(candidate[config:config+2]==parent[config:config+2],'SOI static config preservation')
    result['SOI_static_config_high']=dict(offset='0x74224',bytes_hex=candidate[0x74224:0x74226].hex(),
        first_byte=candidate[0x74224],preserved=True,other_runtime_conditions='not exhausted')
    result['scope']='Geometry, numeric representation and exact preservation; no SOI monotonicity or combustion optimum assertion'
    return result

def negative_controls(parent,candidate):
    results={}
    def bad_checksum():
        b=bytearray(candidate); b[0x6dc78]^=1
        return any(r['stored']!=r['calculated'] for r in checksums(b))
    results['single_Smoke_byte_checksum_detected']=bad_checksum()
    b=bytearray(candidate); t=table(b,'DW'); struct.pack_into('<H',b,t['h']+6,t['x'][0])
    try: table(b,'DW'); results['duplicate_axis_detected']=False
    except ValueError: results['duplicate_axis_detected']=True
    b=bytearray(candidate); t=table(b,'TL'); o=t['z']+2*(t['x'].index(1000)*t['ny']+t['y'].index(4800)); struct.pack_into('<H',b,o,7200)
    try: tl_contract(table(b,'TL')); results['TL_shape_mutation_detected']=False
    except ValueError: results['TL_shape_mutation_detected']=True
    b=bytearray(candidate); struct.pack_into('<H',b,0x6d2c0,5000)
    results['decreasing_pedal_DW_detected']=not monotonic_demand(table(b,'DW'))
    b=bytearray(candidate); b[0x20000]^=1
    results['unexpected_code_diff_detected']=b[0x14000:0x4c000]!=parent[0x14000:0x4c000]
    b=bytearray(candidate); struct.pack_into('<H',b,0x78626,u16(b,0x78624))
    try: ancillary_geometry(parent,b); results['SOI_axis_mutation_detected']=False
    except ValueError: results['SOI_axis_mutation_detected']=True
    b=bytearray(candidate); struct.pack_into('<h',b,0x745c2,384)
    try: ancillary_geometry(parent,b); results['DD9E_signed_axis_mutation_detected']=False
    except ValueError: results['DD9E_signed_axis_mutation_detected']=True
    b=bytearray(candidate); struct.pack_into('<H',b,0x753a4,1536)
    try: ancillary_geometry(parent,b); results['selector_invalid_family_detected']=False
    except ValueError: results['selector_invalid_family_detected']=True
    require(all(results.values()),'negative control failure')
    return results

def verify(candidate,parent,ke):
    require((len(parent),sha(parent))==(524288,PARENT_SHA),'parent identity')
    require((len(ke),sha(ke))==(524288,KE_SHA),'KE identity')
    require(len(candidate)==524288,'candidate size')
    expected=bytearray(parent); t=table(parent,'DW'); require(t['x'][9]==1491 and t['y'][7]==10000,'literal coordinate')
    offset=t['z']+2*(9*t['ny']+7); require(offset==0x6d2c0 and u16(parent,offset)==5500,'literal parent DW value')
    struct.pack_into('<H',expected,offset,6600)
    for row in checksums(expected): struct.pack_into('<I',expected,row['store'],row['calculated'])
    require(bytes(expected)==candidate,'candidate differs from independent intended rebuild')
    checks={name:checksums(b) for name,b in [('parent',parent),('KE',ke),('candidate',candidate)]}
    require(all(len(rows)==8 and all(r['stored']==r['calculated'] for r in rows) for rows in checks.values()),'eight checksums')
    diff=[i for i,(a,c) in enumerate(zip(parent,candidate)) if a!=c]
    require(set(diff)<={offset,offset+1,*range(0x6fffc,0x70000)},'difference outside intended DW/checksum')
    require(candidate[:0x6c000]==parent[:0x6c000] and candidate[0x70000:]==parent[0x70000:],'preserved regions')
    require(candidate[:0x6c000]==ke[:0x6c000] and candidate[0x7c000:]==ke[0x7c000:],'original code/reserve metadata')
    tl_contract(table(candidate,'TL')); require(monotonic_demand(table(candidate,'DW')),'DW operational monotonicity')
    maps=[]
    for name in SPECS:
        p,c=table(parent,name),table(candidate,name)
        if name=='ASR136C': require((c['nx'],c['ny'])==(8,11),'ASR136C dimensions')
        if name=='SOILimit': require((c['nx'],c['ny'])==(14,11),'SOILimit dimensions')
        require((p['nx'],p['ny'],p['x'],p['y'],p['x_id'],p['y_id'])==(c['nx'],c['ny'],c['x'],c['y'],c['x_id'],c['y_id']),'map metadata '+name)
        end=c['z']+2*c['nx']*c['ny']
        require(parent[p['h']:p['z']]==candidate[c['h']:c['z']],'map header/gap '+name)
        changes=sum(a!=z for prow,crow in zip(p['zv'],c['zv']) for a,z in zip(prow,crow))
        require(changes==(1 if name=='DW' else 0),'unexpected map matrix change '+name)
        maps.append(dict(name=name,header=hex(c['h']),data=hex(c['z']),end_exclusive=hex(end),dimensions=[c['nx'],c['ny']],changed_cells=changes,z_unsigned16_range=[min(v for row in c['zv'] for v in row),max(v for row in c['zv'] for v in row)],signed16_range=c['signed_range'] if name.startswith('Duration') else None,ascending_axes=True))
    ranges=sorted((m['header'],m['end_exclusive'],m['name']) for m in maps)
    require(all(int(a[1],16)<=int(z[0],16) for a,z in zip(ranges,ranges[1:])), 'maps overlap')
    report=dict(status='INDEPENDENT_BINARY_AND_STATIC_PROPERTY_PASS',size_bytes=len(candidate),sha256=sha(candidate),parent_sha256=sha(parent),KE_sha256=sha(ke),
        identification={s:hex(candidate.find(s.encode())) for s in ('0281011201','038906019KE','1037366297','4898')},
        changed_bytes=len(diff),exact_byte_diff=[dict(offset=hex(o),before=parent[o],after=candidate[o]) for o in diff],
        intended_change=dict(map='DW',offset=hex(offset),rpm=1491,pedal_raw=10000,before_raw=5500,after_raw=6600),
        independent_reproduction_identical=True,code_and_reserve_bank_identical=True,metadata_axes_and_remaining_maps_identical=True,
        operational_DW_monotonicity_proved=True,tl_4750_nominal_exact='223/3',tl_5100_nominal_exact='34',
        preserved_V41_removed_patch_sites=all(candidate[a:a+8]==ke[a:a+8] for a in (0x16bf2,0x16ea4)),
        checksums={name:[{**r,'store':hex(r['store']),'stored':f"{r['stored']:08X}",'calculated':f"{r['calculated']:08X}"} for r in rows] for name,rows in checks.items()},
        checksum_specification='Eight known historical checksums; not OEM signature or independent Bosch specification',
        maps=maps,ancillary_geometry=ancillary_geometry(parent,candidate),continuous_envelope=continuous_envelope(parent,candidate),negative_controls=negative_controls(parent,candidate),physical_validation=False)
    require(all(int(o,16)>=0 for o in report['identification'].values()), 'missing identification')
    return report

def main():
    ap=argparse.ArgumentParser(); ap.add_argument('candidate',type=Path); ap.add_argument('--parent',type=Path,default=PARENT); ap.add_argument('--ke',type=Path,default=KE); ap.add_argument('--out',type=Path)
    args=ap.parse_args(); parent=args.parent.read_bytes(); candidate=args.candidate.read_bytes(); ke=args.ke.read_bytes()
    report=verify(candidate,parent,ke)
    require(args.parent.read_bytes()==parent and args.candidate.read_bytes()==candidate and args.ke.read_bytes()==ke,'input changed during verification')
    report['fresh_reads_unchanged']=True; report['verifier_sha256']=sha(Path(__file__).read_bytes())
    result=json.dumps(report,indent=2,ensure_ascii=False)+'\n'
    if args.out: args.out.write_text(result)
    print(json.dumps({k:report[k] for k in ('status','sha256','changed_bytes','operational_DW_monotonicity_proved','continuous_envelope','negative_controls')}))
if __name__=='__main__': main()
```

### 12.3. runtime_evaluate.py

```python
#!/usr/bin/env python3
"""Fresh-byte conditional SOI/Duration model; no ROM writes or IROM claims."""
import argparse,bisect,hashlib,json,struct
from fractions import Fraction as F
from pathlib import Path
PARENT_SHA='65fd9c4000fbb1b17216c9951f06c367f40612691abef1177cc6a09463d087ed'
KE_SHA='96094e90cfa028fe7e962168d4414d1729fa2758e63498db7ff13f2c8e2b85ec'
def u(b,a,n=1):return list(struct.unpack_from('<'+'H'*n,b,a))
def axis(b,a):return u(b,a+4,u(b,a+2)[0])
def tab(b,a,signed=False,base=None):
 x=axis(b,a);hy=a+4+2*len(x);y=axis(b,hy)
 assert all(v<w for v,w in zip(x,x[1:])) and all(v<w for v,w in zip(y,y[1:]))
 d=base if base is not None else hy+4+2*len(y);z=u(b,d,len(x)*len(y));z=[v-65536 if signed and v>=32768 else v for v in z]
 return dict(x=x,y=y,z=[z[i*len(y):(i+1)*len(y)] for i in range(len(x))])
def bracket(a,v):
 v=max(F(a[0]),min(F(a[-1]),F(v)));i=max(0,min(len(a)-2,bisect.bisect_right(a,v)-1));return i,(v-a[i])/(a[i+1]-a[i])
def at(t,x,y):
 i,s=bracket(t['x'],x);j,w=bracket(t['y'],y);z=t['z']
 return (1-s)*((1-w)*z[i][j]+w*z[i][j+1])+s*((1-w)*z[i+1][j]+w*z[i+1][j+1])
def phase(b,n,q):
 xs,ys=axis(b,0x785fc),axis(b,0x78620);z=u(b,0x79f00,len(xs)*len(ys));soitab=dict(x=xs,y=ys,z=[z[i*len(ys):(i+1)*len(ys)] for i in range(len(xs))])
 lim=tab(b,0x7b490);source=at(soitab,n,q*100);limit=at(lim,n,3521);s=max(source,limit)
 selx=axis(b,0x7538a);selz=u(b,0x7539a,6);iv,t=bracket(selx,s);sel=(1-t)*selz[iv]+t*selz[iv+1];family=int(sel//256);fr=(sel-family*256)/256
 assert family<=5 and not(family==5 and fr)
 ds=[tab(b,a,True) for a in [0x745de,0x746d6,0x7495c,0x74be2,0x74e68,0x750ee]]
 d0=at(ds[family],q*100,n);durationraw=d0 if not fr else d0+(at(ds[family+1],q*100,n)-d0)*fr;soi=78-s*F(3,128);dur=durationraw*F(3,128)
 fl=lambda v:round(float(v),8)
 return dict(q_nominal=fl(q),soi_source_raw=fl(source),soi_limit_raw=fl(limit),soi_output_selector_raw=fl(s),selector_below_axis=s<selx[0],selector_family_model=family,selector_fraction_model=fl(fr),SOI_order_degree=fl(soi),Duration_order_degree=fl(dur),EOI_order_proxy_degree=fl(dur-soi),SOI_rpm_clamp_model=n>xs[-1],limit_thermal_clamp_model=3521>lim['y'][-1],limit_thermal_endpoint_raw=lim['y'][-1],selected_Duration_rpm_clamp_model=n>ds[family]['y'][-1])
def main():
 ap=argparse.ArgumentParser();ap.add_argument('parent',type=Path);ap.add_argument('candidate',type=Path);ap.add_argument('KE',type=Path);ap.add_argument('json_out',type=Path);ap.add_argument('markdown_out',type=Path);args=ap.parse_args()
 paths=dict(parent=args.parent,candidate=args.candidate,KE=args.KE);bs={k:p.read_bytes() for k,p in paths.items()};sha=lambda b:hashlib.sha256(b).hexdigest()
 assert len(bs['parent'])==len(bs['candidate'])==len(bs['KE'])==524288
 assert sha(bs['parent'])==PARENT_SHA and sha(bs['KE'])==KE_SHA
 # This model's SOI/Duration/selector/limiter regions are byte-identical.
 for a,e in [(0x745be,0x75516),(0x785fc,0x7b5fe)]:assert bs['parent'][a:e]==bs['candidate'][a:e]
 rows=[]
 for n in [3000,3500,4000,4250,4500,4750,5000]:
  qs=[]
  for b in [bs['parent'],bs['candidate']]:
   # Smoke has a third-axis header/correction block: Z is at explicit6DC78,
   # not immediately after the first two axes. Read the actual registered Z.
   qs.append(min(at(tab(b,0x6d1ea),n,10000),at(tab(b,0x6d8fc),1000,n),at(tab(b,0x6dc26,base=0x6dc78),n,11000))/100)
  assert qs[0]==qs[1]
  a,c=phase(bs['parent'],n,qs[0]),phase(bs['candidate'],n,qs[1]);assert a==c
  k=phase(bs['KE'],n,F(55));rows.append(dict(rpm=n,parent_and_candidate=a,KE_q55_reference_model=k,EOI_delta_vs_KE_q55_degree=round(a['EOI_order_proxy_degree']-k['EOI_order_proxy_degree'],8)))
 out=dict(inputs={k:dict(path=str(paths[k]),sha256=sha(v)) for k,v in bs.items()},conditions=dict(air_internal=1100,baro_internal=1000,pedal_raw=10000,SOI_branch=14,SOI_data='0x79F00',DD9E_assumed=0,SOIlimit_thermal_raw=3521,SOIlimit_endpoint_clamp_assumed=False,Duration_selector_endpoint_clamp_assumed=True,interpolation_and_blend='exact rational conventional model; unknown IROM rounding, signedness and saturation',angle_units='order angles and geometric EOI proxy, not hydraulic injection or combustion'),rows=rows)
 args.json_out.write_text(json.dumps(out,indent=2,ensure_ascii=False)+'\n')
 md=['## Comparación calculada SOI–Duration con la actual','', 'Lectura nueva de ambos binarios y KE. Aire interno1100; barometría1000; pedal100%; rama SOI14 (matriz0x79F00); DD9E=0. SOIlimit consultado exactamente en su último nudo térmico3521, dentro del dominio. Interpolación y mezcla convencionales; contrato IROM pendiente. Todos los puntos del padre/R6A quedan bajo el mínimo2176 del selector: familia00/fracción0 sólo bajo esa hipótesis. EOI es Duration−SOI como proxy geométrico de orden.','', '| rpm | q actual = R6A | SOI orden ° | Duration orden ° | EOI proxy ° | EOI KE q55 ° | ΔEOI proxy vs. KE ° |','|---:|---:|---:|---:|---:|---:|---:|']
 for r in rows:
  a=r['parent_and_candidate'];k=r['KE_q55_reference_model'];md.append(f"|{r['rpm']}|{a['q_nominal']:.5f}|{a['SOI_order_degree']:.5f}|{a['Duration_order_degree']:.5f}|{a['EOI_order_proxy_degree']:.5f}|{k['EOI_order_proxy_degree']:.5f}|{r['EOI_delta_vs_KE_q55_degree']:.5f}|")
 md += ['', 'Delta actual/R6A: cero en todas las cantidades y ángulos calculados. La discrepancia frente al ancla KE pertenece al padre y se conserva; no justifica por sí sola adelantar SOI. Estos valores no acreditan CA50, final hidráulico ni potencia.']
 args.markdown_out.write_text('\n'.join(md)+'\n')
 print(json.dumps(dict(points=len(rows),candidate_sha256=sha(bs['candidate']),output=str(args.json_out))))
if __name__=='__main__':main()
```
