Regenerado: dos trampas nuevas en la skill y en las guias
- Un macro con el parametro sin parentesis no se puede "arreglar" al portar. Medido con el shader de Noztol: poniendole los parentesis "bien" el port se separaba 36/255 en el 72% de los pixeles; sin ellos vuelve a 0/255. - Las capturas PNG de glslViewer salen volteadas en vertical: la fila 0 es gl_FragCoord.y = 0. Invita a "arreglar" una orientacion que no esta rota. Y el arreglo del estimador de coste de check_port.py: los bucles float que cuentan hacia atras o arrancan en negativo se contaban mal o desaparecian de la cuenta, y los anidados se contaban dos veces. Generado con generar-agente-portable.py. No editar aqui. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,21 @@ Aquests són els que no es veuen venir llegint el codi:
|
||||
- **`u_x3` pot no arribar.** Si l'ajust `X3_AS_SPEED` està actiu a l'aparell, aquell
|
||||
comandament passa a controlar la velocitat. Dissenya per a tres i deixa el quart per al
|
||||
menys important.
|
||||
- **Un macro amb el parametre sense parentesis NO es pot "arreglar" en portar.** Mesurat
|
||||
el 2026-09-05 al shader de Noztol: `#define p(t,a,b,c,d) (a + b*cos(6.28318*(c*t+d)))`,
|
||||
amb `t` pelat. Se'l crida amb una suma --`sp(sin(x) + terme)`-- i en expandir queda
|
||||
`c*sin(x) + terme`, o sia que el segon sumand **no** va multiplicat per `c`. Posant els
|
||||
parentesis «be» el port se separava de l'original **36/255 en el 72% dels pixels**, i
|
||||
sense parentesis torna a 0/255. Es el mateix principi que el soroll de precisio:
|
||||
**l'artefacte es el shader**. En portar, expandir el macro a ma amb la raresa inclosa i
|
||||
dir en un comentari que es a proposit, o el seguent que ho llegeixi ho «arregla» un
|
||||
altre cop.
|
||||
- **La captura PNG de glslViewer surt capgirada en vertical.** La fila 0 del fitxer es
|
||||
`gl_FragCoord.y = 0`, o sia la part de BAIX del quadre. Comprovat amb una sonda sis
|
||||
vegades seguides: sempre igual. Importa perque convida a «arreglar» una orientacio que
|
||||
no esta trencada: si el paisatge surt del reves a la captura pero els comptes diuen que
|
||||
el terra va a la meitat de baix, la captura es la que menteix. Per mirar-la, capgirar-la;
|
||||
per comparar dues captures entre si, tant es, perque totes dues surten capgirades.
|
||||
- **Sense multipassada.** Un shader de Shadertoy amb `Buffer A` no es porta: es redissenya
|
||||
la idea aprofitant el `detour` de r_e_c_u_r, o es descarta. Digues-ho aviat, abans de
|
||||
gastar una hora traduint una cosa que no pot funcionar.
|
||||
|
||||
+79
-7
@@ -15,6 +15,7 @@ Exit codes: 0 nothing fatal, 1 something BREAKS, 2 usage error.
|
||||
|
||||
Part of <shadertoy-to-recur>. GPL-3.0.
|
||||
"""
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
|
||||
@@ -315,27 +316,98 @@ def _bloque(code, i):
|
||||
return code[j + 1:k - 1], k
|
||||
|
||||
|
||||
RE_FOR = re.compile(r'\bfor\s*\(')
|
||||
|
||||
|
||||
def _parentesis(code, i):
|
||||
"""Devuelve (interior, fin) del ( ... ) que empieza en el primer ( desde i."""
|
||||
j = code.find('(', i)
|
||||
if j < 0:
|
||||
return '', len(code)
|
||||
nivel, k = 1, j + 1
|
||||
while k < len(code) and nivel:
|
||||
if code[k] == '(':
|
||||
nivel += 1
|
||||
elif code[k] == ')':
|
||||
nivel -= 1
|
||||
k += 1
|
||||
return code[j + 1:k - 1], k
|
||||
|
||||
|
||||
def _vueltas(cabecera):
|
||||
"""Vueltas de un bucle a partir de su cabecera "arranque; condicion; paso".
|
||||
|
||||
Contar solo el limite superior era un error caro: `for(float x=-2.; x<3.; x++)`
|
||||
da CINCO vueltas, no tres, y `for(float y=0.; y>-4.; y--)` da cuatro sin que el
|
||||
limite sea siquiera positivo. Un bucle anidado de esa forma --que es la que usan
|
||||
los shaders de pinceladas-- se subestimaba por un factor de 6, o desaparecia
|
||||
entero de la cuenta si el limite era negativo, porque la expresion no encajaba.
|
||||
"""
|
||||
partes = cabecera.split(';')
|
||||
if len(partes) != 3:
|
||||
return None
|
||||
ini_txt, cond_txt, paso_txt = partes
|
||||
|
||||
m = re.search(r'=\s*([+-]?[\d.]+)\s*$', ini_txt.strip())
|
||||
# Si el arranque no se lee (declarado fuera del for), se supone 0, que es lo que
|
||||
# se hacia antes: mejor la estimacion vieja que ninguna.
|
||||
ini = float(m.group(1)) if m else 0.0
|
||||
|
||||
m = re.search(r'([<>]=?)(.*)$', cond_txt)
|
||||
if not m:
|
||||
return None
|
||||
op = m.group(1)
|
||||
# El limite puede ser una cuenta constante, no un numero suelto: el bucle de
|
||||
# antialiasing se escribe `i < AA*AA - 0.5`, y leer de ahi solo el primer numero
|
||||
# daba 2 vueltas donde hay 4. Se evalua si es aritmetica pura (los #define ya se
|
||||
# han sustituido antes de llegar aqui).
|
||||
expr = m.group(2).strip()
|
||||
if not expr or not re.fullmatch(r'[\d.+\-*/() ]+', expr):
|
||||
return None
|
||||
try:
|
||||
fin = float(eval(expr, {'__builtins__': {}}, {}))
|
||||
except (SyntaxError, ZeroDivisionError, TypeError, NameError, ValueError):
|
||||
return None
|
||||
|
||||
m = re.search(r'[+-]=\s*([\d.]+)', paso_txt)
|
||||
paso = abs(float(m.group(1))) if m else 1.0
|
||||
if paso == 0:
|
||||
return None
|
||||
|
||||
recorrido = (fin - ini) if op.startswith('<') else (ini - fin)
|
||||
n = math.ceil(recorrido / paso) + (1 if op.endswith('=') else 0)
|
||||
return int(n) if n >= 1 else None
|
||||
|
||||
|
||||
def _coste_propio(cuerpo, cuerpos, profundidad=0):
|
||||
"""Operaciones matematicas de un cuerpo, expandiendo funciones propias y bucles.
|
||||
|
||||
Los bucles se sacan aparte y se cuentan multiplicados por sus vueltas: si no, el
|
||||
cuerpo del bucle --que en un raymarch es TODO el coste-- se cuenta una sola vez y
|
||||
el shader parece barato.
|
||||
|
||||
Se recorren en orden saltando por encima del cuerpo de cada uno, para que un
|
||||
bucle anidado se cuente UNA vez (dentro de la recursion del de fuera) y no dos.
|
||||
"""
|
||||
if profundidad > 6:
|
||||
return 0
|
||||
|
||||
total = 0
|
||||
resto = cuerpo
|
||||
for m in list(re.finditer(r'\bfor\s*\([^;]*;[^<>]*[<>]=?\s*([\d.]+)\s*;', cuerpo)):
|
||||
interior, fin = _bloque(cuerpo, m.end())
|
||||
try:
|
||||
vueltas = int(float(m.group(1)))
|
||||
except ValueError:
|
||||
continue
|
||||
if 1 <= vueltas <= 4096:
|
||||
pos = 0
|
||||
while True:
|
||||
m = RE_FOR.search(cuerpo, pos)
|
||||
if not m:
|
||||
break
|
||||
cabecera, fin_cab = _parentesis(cuerpo, m.end() - 1)
|
||||
vueltas = _vueltas(cabecera)
|
||||
interior, fin = _bloque(cuerpo, fin_cab)
|
||||
if vueltas is not None and 1 <= vueltas <= 4096:
|
||||
total += vueltas * _coste_propio(interior, cuerpos, profundidad + 1)
|
||||
resto = resto.replace(cuerpo[m.start():fin], ' ')
|
||||
pos = fin # saltar el cuerpo: los de dentro ya van en la recursion
|
||||
else:
|
||||
pos = m.end()
|
||||
|
||||
total += len(re.findall(MATEMATICAS, resto))
|
||||
for nom, c in cuerpos.items():
|
||||
|
||||
@@ -239,6 +239,38 @@ artefacte.**
|
||||
|
||||
`check_port.py` avisa de qualsevol literal per sobre de 65504 justament per aixo.
|
||||
|
||||
### Un macro amb el parametre sense parentesis NO s'«arregla»
|
||||
|
||||
Mesurat el 2026-09-05 portant el shader de Noztol. Defineix la seva paleta aixi:
|
||||
|
||||
```glsl
|
||||
#define p(t, a, b, c, d) ( a + b*cos( 6.28318*(c*t+d) ) )
|
||||
```
|
||||
|
||||
`t` va pelat. Aixo es inofensiu fins que es crida el macro amb una suma, que es el que
|
||||
fa: `sp(sin(x) + terme)`. En expandir queda `c*sin(x) + terme`, o sia que el segon sumand
|
||||
**no** va multiplicat per `c`. Si li poses els parentesis «bons» al voltant de `(t)`, la
|
||||
imatge canvia: el port se'n va separar **36/255 en el 72% dels pixels**, i va tornar a
|
||||
0/255 tan bon punt es van treure.
|
||||
|
||||
Mateix principi que el soroll de precisio a proposit: **l'artefacte es el shader.** En
|
||||
portar, expandir el macro a ma amb la raresa inclosa i dir en un comentari que es volent,
|
||||
o el seguent que ho llegeixi ho «arregla» un altre cop.
|
||||
|
||||
Es tambe la rao per expandir el macro: el de dalt es diu `p`, i l'original te tres
|
||||
funcions el parametre de les quals tambe es diu `p`. No va arribar a disparar mai, perque
|
||||
un macro amb parametres nomes s'expandeix davant d'un `(`, pero es un cep parat.
|
||||
|
||||
### La captura PNG de glslViewer surt capgirada en vertical
|
||||
|
||||
La fila 0 del fitxer es `gl_FragCoord.y = 0`, o sia la part de BAIX del quadre. Comprovat
|
||||
amb una sonda sis vegades seguides: sempre igual.
|
||||
|
||||
Importa perque convida a «arreglar» una orientacio que no esta trencada. Si un paisatge
|
||||
surt del reves a la captura pero els comptes diuen que el terra va a la meitat de baix,
|
||||
la captura es la que menteix. Per mirar-la, capgirar-la; per comparar dues captures entre
|
||||
si tant es, perque totes dues surten capgirades.
|
||||
|
||||
### El raymarching no hi cap --- mesurat, no suposat
|
||||
|
||||
Un shader que marxa raigs no entra en aquest aparell, i la manera de saber-ho no es
|
||||
|
||||
@@ -240,6 +240,38 @@ its reference disagree, ask whether the reference is showing you an artefact.**
|
||||
|
||||
`check_port.py` flags any literal above 65504 for this reason.
|
||||
|
||||
### A macro whose parameter has no parentheses must NOT be "fixed"
|
||||
|
||||
Measured on 2026-09-05 porting Noztol's shader. It defines its palette as:
|
||||
|
||||
```glsl
|
||||
#define p(t, a, b, c, d) ( a + b*cos( 6.28318*(c*t+d) ) )
|
||||
```
|
||||
|
||||
`t` is bare. That is harmless until the macro is called with a sum, which it is:
|
||||
`sp(sin(x) + term)`. The expansion is `c*sin(x) + term`, so the second addend is **not**
|
||||
multiplied by `c`. Add the "correct" parentheses around `(t)` and the image changes: the
|
||||
port drifted **36/255 across 72% of the pixels**, and went back to 0/255 once the
|
||||
parentheses came out again.
|
||||
|
||||
Same principle as deliberate precision noise: **the artifact IS the shader.** When you
|
||||
port, expand the macro by hand keeping the quirk, and say in a comment that it is
|
||||
deliberate -- otherwise the next person to read it "fixes" it again.
|
||||
|
||||
This is also why a macro is worth expanding at all: the one above is named `p`, and the
|
||||
original has three functions whose parameter is also called `p`. It never fired, because
|
||||
function-like macros only expand before a `(`, but it is a trap lying in the code.
|
||||
|
||||
### The glslViewer PNG capture comes out vertically flipped
|
||||
|
||||
Row 0 of the file is `gl_FragCoord.y = 0`, that is the BOTTOM of the frame. Checked with
|
||||
a probe six times in a row and also through the preview wrapper: always the same.
|
||||
|
||||
It matters because it invites you to "fix" an orientation that is not broken. If a
|
||||
landscape looks upside down in the capture but the arithmetic says the ground belongs in
|
||||
the lower half, the capture is what is lying. Flip it to look at it; for comparing two
|
||||
captures against each other it makes no difference, since both come out flipped.
|
||||
|
||||
### Raymarching does not fit — measured, not guessed
|
||||
|
||||
A shader that marches rays does not belong on this device, and the way to find out is
|
||||
|
||||
Reference in New Issue
Block a user