Skip to content

Packs de marcos

Pablo Martínez López edited this page Oct 23, 2026 · 12 revisions

Packs de marcos

English · Español

Un pack de marcos le dice al diseñador cómo se ve una carta: dónde va cada pieza, qué colores usa y qué capas se dibujan sobre la ilustración. El pack integrado viaja dentro de la app; «Original» (marcos de imagen) y «Balatro» (pixel art) se descargan a demanda desde fuentes fijadas y comprobadas por hash; los usuarios pueden importar los suyos como carpeta o .zip. Esta página documenta el formato de pack (versión 2) tal como está implementado en pack.rs, los packs que se distribuyen, la maquinaria de descarga, el catálogo de letras libres y el modelo de seguridad.

La descripción del formato para el usuario es docs/packs-de-marcos.md (en español); esta página la vuelve a exponer contrastada con el código. Véase Motor del diseñador para saber cómo consume los packs el pintor y Composición del diseñador para los layouts del pack que usan las cartas de varias piezas.

Resumen

flowchart LR
  subgraph Sources
    B["builtin: embedded in proxy-core (include_str!)"]
    O["Original: Card Conjurer PNGs, pinned commit"]
    D["Downloadable: proxy-builder-packs repo, pinned commit"]
    U["User import: folder or .zip"]
  end
  B --> P["Pack::de_fabrica"]
  O --> L["app_data_dir/marcos/original/"]
  D --> L2["app_data_dir/marcos/id/"]
  U --> L3["app_data_dir/marcos/id/"]
  L --> R["recursos.rs: leer_packs / recargar_pack"]
  L2 --> R
  L3 --> R
  P --> R
  R --> V["Pack::cargar_con_imagenes: validate + huella"]
  V --> E["render::pintar_cara"]
Loading

Mapa del código:

Qué Dónde
Formato, validación, definiciones del integrado y de Original src-tauri/crates/proxy-core/src/diseno/pack.rs (Pack, PackDef, EstiloDef, Capa, Condicion, Hueco, PackError, VERSION_PACK, BUILTIN, ORIGINAL, ORIGINAL_IMAGENES)
Ficheros del pack integrado src-tauri/crates/proxy-core/assets/marcos/builtin/ (pack.json más SVG por estilo y layouts/)
Definición de Original y lista de PNG src-tauri/crates/proxy-core/assets/marcos/original/ (pack.json, 4 SVG, imagenes.json)
Carga/importación/borrado, PackInfo src-tauri/src/diseno/recursos.rs
Descargador de Original src-tauri/src/diseno/original.rs
Packs descargables src-tauri/src/diseno/descargables.rs, manifiesto src-tauri/assets/packs-descargables.json
Letras libres src-tauri/src/diseno/letras_libres.rs, catálogo src-tauri/assets/letras-libres.json
Comandos src-tauri/src/commands/diseno.rs: packs_list, pack_import, pack_remove, fonts_list, font_import, fonts_free_list, font_free_download, original_info, original_download, packs_downloadable_list, pack_downloadable_download, pack_extra_use
Stores de la interfaz src/features/disenar/marcos-originales.ts, src/features/disenar/packs-descargables.ts, recursos-dialog.tsx
Repositorio de creación de packs proxy-builder-packs, un repositorio aparte (véase más abajo)

El pack en disco

my-pack/
  pack.json            required
  <style>/frame.svg    SVG layers (use {{variables}})
  m15/frame{color}.png PNG layers (one per palette)
  symbols/*.png        own mana symbols (optional)
  labels/*.png         label pill backgrounds (optional)

Solo se leen los ficheros .json, .svg y .png (recursos.rs → tipo_de); los demás se ignoran. Las letras no forman parte de un pack: el pack nombra familias de letra y deben cargarse aparte (la descarga de Balatro importa su letra como parte de la descarga del pack).

Carga y validación

Pack::cargar_con_imagenes(ficheros, imagenes) devuelve un Pack { def, ficheros, imagenes, huella, simbolos } o un PackError:

PackError Causa
Json pack.json no se puede analizar; o un base de estilo que no es uno de los cuatro estilos integrados; o una capa con ambos o ninguno de svg y png; o una capa cajaReglas que no está sola con una zona; o desde/mascara en una capa que no es PNG o que lleva tramos; o un símbolo propio que no se puede analizar
Version version es 0 o mayor que VERSION_PACK (3). Una app más antigua rechaza así un pack más nuevo en lugar de pintarlo mal. El pack integrado declara la versión 1, Balatro la 2 y Original la 3 (usa desde/mascara)
FaltaFichero falta el SVG de una capa, el PNG de la mascara de una capa, un PNG para alguna paleta (se comprueba para cada paleta y, para {color1}/{color2}, para cada combinación de un solo color), un PNG de fondo de etiqueta, el PNG de lealtad, un fichero de símbolo propio o el propio pack.json
FaltaHueco a un estilo le falta uno de titulo, tipo, reglas, pie (HUECOS_OBLIGATORIOS)
FaltaEstilo falta el estilo pedido (Pack::estilo)
Color un color mal formado (se aceptan #rgb, #rrggbb, #rrggbbaa), o falta una paleta
Expresion una expresión de hueco no válida

Las nueve paletas (W U B R G gold artifact land colorless) son obligatorias. Los colores de los huecos pueden ser una {{variable}}. De los layouts de la raíz se comprueban sus capas y colores, pero no los huecos obligatorios. En el lado de la app, recursos.rs::de_pack convierte cada error en un AppError InvalidInput con motivo: "pack_invalido" y parámetros (falta, estilo, hueco, color, version, detalle, expresion), que la interfaz traduce (véase IPC y manejo de errores).

Pack.huella es un SHA-256 de todos los ficheros de texto y luego de todos los PNG (ordenados por ruta, cada uno seguido de separadores NUL). Entra en la huella de renderizado, así que cambiar cualquier fichero del pack invalida los renderizados en caché, y es la clave de las cachés de PNG en render::imagenes_png.

Dónde viven los packs y cómo se importan

  • Directorio: app_data_dir/marcos/<id>/. leer_packs carga builtin (incrustado) más todos los directorios que haya ahí al arrancar; un directorio que no supera la validación se omite en silencio.
  • Recursos::importar_pack(ruta): lee una carpeta o un .zip (leer_zip: topes por fichero de 8 MiB para json/svg y 32 MiB para png; superar uno falla con InvalidInput / motivo: "pack_grande"; las entradas con .. se omiten; si todo está en una única carpeta de primer nivel, ese prefijo se quita; los topes no se aplican a las importaciones de carpeta), valida, sanea el id a [A-Za-z0-9_-], rechaza un id vacío o builtin (motivo: "pack_id"), sustituye cualquier directorio existente con ese id, escribe los ficheros de forma atómica (escribir_atomico) y registra el pack. Importar de nuevo el mismo id lo sustituye.
  • quitar_pack(id): rechaza builtin y los ids que contengan /, \ o ..
  • PackInfo { id, nombre, importado, estilos, estilos_propios, origen } va a la interfaz; importado es falso solo para builtin. estilos_propios lista los estilos propios con sus nombres por código de idioma (EstiloPropio.nombres), base y variantes (VariantePack.nombres), tal como los trae el pack; la interfaz elige el del idioma de la interfaz, si no el inglés, si no el id (nombreEnIdioma en src/features/disenar/estilos.ts).

Unidades, expresiones y variables

Toda la geometría está en milímetros sobre una carta de 63 x 88; un estilo o layout puede declarar otro tamaño con tamano: [w, h]. El motor escala a los píxeles de salida, de modo que la vista previa, los PDF a 300/600/900 PPP y la exportación PNG salen del mismo renderizado.

Expresiones (pack::evaluar, Valor): cualquier hueco o coordenada de zona de capa es un número o una cadena que es una suma/resta de números y nombres de variable ("yTipo+1.6", "80-yTipo"). Sin paréntesis, sin multiplicación. Variables disponibles, solo cuando el estilo tiene caja de reglas dinámica (caja): yReglas (parte superior del texto de reglas), altoReglas (altura del texto de reglas), yTipo (parte superior de la barra de tipo). Si falta una variable (caja fija), la coordenada se evalúa a 0: un hueco con ancho/alto cero no se dibuja, y una capa PNG cuya zona no se puede evaluar se omite.

Variables SVG (rellenar; los nombres desconocidos quedan vacíos): se sustituyen en los SVG de las capas antes de renderizar.

Variable Significado
{{defs}} definiciones de degradado: url(#marco), #brillo, #panel, #panelfr, #crema, #lateral, #vetas, #bisel, #barra (ponerlas dentro de <defs>; véase render::defs)
{{claro}} {{medio}} {{oscuro}} {{tinte}} {{caja}} colores de la paleta (o la mezcla de dos paletas)
{{fondoCaja}} {{opacidadCaja}} {{tintaCaja}} relleno de la caja de reglas, opacidad y la tinta del texto que se lee sobre ella
{{refuerzoCaja}} 0..1, cuánto más opaca se pidió la caja (para marcos de imagen con la caja ya cocida: dibujar debajo un rectángulo negro con esa opacidad)
{{tintaCrema}} {{tintaFr}} tinta sobre crema y sobre la caja de F/R
{{yReglas}} {{altoReglas}} {{yTipo}} {{yTipo04}} geometría de la caja dinámica. En los SVG, yReglas/altoReglas son la caja entera (parte superior y altura total); en las expresiones de hueco son el área de texto. yTipo04 es yTipo + 0.4
{{extensionArte}} Cuántos mm sigue la ilustración por debajo de su ventana (la extensión del difuminado de los ajustes finos, o la extensionDifuminado del estilo; 0 de fábrica). El negro de debajo del arte tiene que empezar ese tanto más abajo.
{{subeCaja}} Cuántos mm más alta se ha pedido la caja de reglas en los ajustes finos (negativo, más baja; 0 de fábrica).
{{= 56.25 - subeCaja}} Una cuenta (sumas y restas) con las variables que son números. Así un marco fijo mueve su barra de tipo y el borde de arriba de su caja.

Los huecos pueden usar yReglas, altoReglas, yTipo y subeCaja en sumas y restas: "y": "yTipo+0.4", "h": "52.5 - subeCaja". Un pack que no use subeCaja no cambia con ese ajuste; en la caja dinámica, lo pedido simplemente se suma a su altura. El ajuste fino rulesBoxDynamic (dinámica / fija / como el marco) vale para todos los marcos: ver rules_box_dynamic en Motor-del-diseñador.

Referencia de pack.json (versión 3)

Raíz (PackDef)

Campo Tipo Significado
id, nombre, version string, string, int identidad; version 1 o 2
paletas mapa las nueve paletas obligatorias (abajo)
letras mapa de rol a letra roles titulo tipo reglas fr pie cinta (abajo)
estilos mapa de id a estilo ids integrados borderless extended classic fullArt más ids propios
layouts mapa de nombre a estilo completo layouts completos válidos para cualquier estilo (abajo)
colores mapa de paleta a letra lo que sustituye a {color} en las rutas PNG; por defecto W U B R G, gold a M, artifact a A, land a L, colorless a C (letras de Card Conjurer). Balatro las asigna a palabras (white, blue, …)
origen string de dónde vienen las imágenes, se muestra en la interfaz
escalado suave / pixel escalado por defecto de los PNG (abajo)
simbolos mapa de código a fichero símbolos de maná propios (SVG o PNG), que tapan a los de Scryfall; los códigos ausentes pasan a Scryfall
etiquetas mapa de valor a etiqueta etiqueta por valor (common uncommon rare mythic special bonus legendary plains island swamp mountain forest wastes)
insignias {fondo, borde, texto, pngLealtad} colores de las insignias de sección y un PNG opcional para la insignia de lealtad
coloresTexto {resalte, habilidad} color de los tramos [[highlight]] y de las palabras de habilidad en las reglas. Sin él, los marcadores se quitan y todo sale del color normal
sustituir mapa de carácter a carácter caracteres que la letra del pack no tiene y sus reemplazos, aplicados solo con este pack (se pasan a Maquetador.sustituir)
plantillaPie mapa de código de idioma a plantilla el pie por defecto del pack por idioma de diseño (recurre a en). Se usa solo cuando la plantilla de la colección es la de fábrica (pie_del_pack). Véase la sección «Textos por idioma»
separacionCoste float hueco entre los símbolos del coste como fracción del lado del símbolo (por defecto 0,05)

Paletas (PaletaDef)

Cada una de W U B R G gold artifact land colorless: claro, medio, oscuro (luz del marco, cuerpo, sombra; el degradado vertical), tinte (caja de reglas translúcida), caja, tinta (caja de reglas clara y su tinta). Elección de color: un color da su paleta; dos dan un degradado de las dos paletas; tres o más dan gold; ninguno da artifact, land o colorless. El usuario puede forzar otro (FrameColor).

Letras (LetraDef)

familia, peso (por defecto 400), mayuscula: altura de las mayúsculas dividida por el cuerpo del em. El texto se dimensiona por la altura de las mayúsculas, que es lo que ve el ojo: tamaño de letra = hueco.mayuscula(mm) / letra.mayuscula. La familia debe estar cargada en el motor.

Una letra de un pack no tiene por qué cubrir todos los idiomas. Lo que le falte del latín («ș», «ł»…) o del cirílico sale de Spectral o Montserrat, registradas como respaldo por escritura (Letras::respaldos), en vez de pintar cuadrados. El japonés, el coreano y el chino salen de Noto Sans JP, KR, SC o TC, que la app baja la primera vez que se elige uno de esos idiomas de diseño (Letras::cargar_cjk). Véase Motor del diseñador.

Textos por idioma

nombre y variantes de los estilos, el texto de las etiquetas y la plantillaPie son mapas código de idioma → texto (BTreeMap<String, String> en pack.rs). Los códigos son los de la app (los de Scryfall): es, en, fr, de, it, pt (portugués de Brasil), ro, pl, nl, ru, uk, ja, ko, zhs (chino simplificado) y zht (chino tradicional). Un pack puede traer los que quiera:

  • nombre y variantes se enseñan en el idioma de la interfaz; sin él, en inglés; sin inglés, el id.
  • etiquetas y plantillaPie se pintan en el idioma de diseño de la carta; sin él, en inglés.
  • Una clave que no es de ningún idioma de la app nunca se consulta, así que en la práctica se ignora.
  • {lang} en la plantilla del pie es el código que llevan las cartas impresas en ese idioma (textos_motor::codigo_pie: FR, DE, JP, KR, CS, CT…; el español sigue como ES).

La guía de autores docs/packs-de-marcos.md (sección «Textos por idioma») recoge las mismas reglas para quien hace packs.

Estilos (EstiloDef)

Campo Significado
tamano lienzo [w, h] en mm, por defecto 63 x 88
arte ventana de la ilustración (un hueco; acepta variables de la caja)
caja caja de reglas dinámica (CajaDinamica, abajo)
cajaPorDefecto tinte (translúcida, del color de la carta, 0,80 de opacidad) o clara (clara, opaca)
capas capas que se pintan sobre la ilustración, en orden
fondo capas que se pintan bajo la ilustración (el papel de una carta cuya ilustración no llena la ventana)
huecos huecos de texto (abajo)
layouts sobrescrituras de layout a nivel de estilo (ficha, alto, o los ids de las variantes)
radio radio de esquina en pantalla en mm (por defecto 3); la impresión es recta salvo que se fije el radio de los ajustes finos
coronaLegendaria las cartas legendarias reciben la corona automáticamente; por defecto solo en borderless
ventanaArte máscara de la ventana de la ilustración: {x, y, w, h, radio, soloAbajo, repintar, repintarTras}
difuminadoArte mm del borde de abajo de la ilustración que se funden con negro para que no acabe en seco (el extendido de la casa: 12). Los ajustes finos lo pueden cambiar
extensionDifuminado mm que sigue la ilustración por debajo de su ventana, perdiéndose; va con difuminadoArte
nombre nombre para mostrar por idioma (estilos propios)
base el estilo integrado del que este estilo propio hereda el comportamiento (y al que recurre si falta el pack); por defecto classic; debe ser uno de los cuatro
variantes variantes elegibles por el usuario: id a nombres por idioma
layoutSi [{ "si": {...}, "layout": "id" }]: layouts elegidos según propiedades de la carta; gana la primera coincidencia

CajaDinamica: fondo (borde inferior fijo, mm), altoMin, altoMax, barraTipo (altura de la barra de tipo sobre la caja), padArriba, padAbajo, altoComodo (altura hasta la que crece la caja antes de que la letra empiece a reducirse, hasta el 75 %), fijaDeSerie (booleano, falso de serie: la caja mide siempre altoMax salvo que el usuario active «Altura según el texto»; los tres estilos del Original lo ponen). Cuando el texto no cabe ni siquiera con altoMax, la letra baja en pasos de 0,1 pt hasta 6 pt y entonces se lanza un aviso TextoNoCabe.

ventanaArte: negro fuera de la ventana con las esquinas redondeadas a radio (o FineTuning.art_radius); soloAbajo redondea solo las esquinas inferiores; repintar es una lista de zonas [x, y, w, h] donde una capa de imagen lleva la curva ya cocida, y repintarTras el número de capas tras las cuales esas zonas se repintan con ilustración o negro cuando el usuario fija un radio. Las zonas se recortan para que nunca empiecen por encima del final de la barra de tipo (yReglas si la caja es dinámica): con una caja corta la barra baja, y repintar sus extremos los cortaría. La ventana se pinta sobre la ilustración y bajo las capas.

Capas (Capa)

Campo Significado
svg ruta de un SVG con {{variables}}, dibujado sobre todo el lienzo
png ruta de un PNG; {color} se convierte en la letra de la paleta, {color1} / {color2} en el primer y el segundo color de la carta en orden WUBRG (la letra de incoloro si no hay; ambos iguales con un solo color), usados para los cuadrados de color cerca del título
en zona {x, y, w, h} (cada una puede ser una expresión) a la que se estira el PNG; si falta, todo el lienzo
tramos [arriba, abajo] mm: esas franjas (a la escala del ancho de la zona) se dibujan sin deformación y solo se estira la del medio (cajas que crecen con esquinas)
desde (v3) zona {x, y, w, h} en mm (admite expresiones) medida como si el PNG cubriera la carta entera: solo se dibuja ese trozo. Sin en va en el mismo sitio de la carta; con en, estirado a esa zona. Sirve para tomar una pieza de otro marco (el despejado de Original toma el pergamino de la caja de reglas del clásico)
mascara (v3) ruta de un PNG del tamaño de la carta cuyo alfa multiplica el de la capa (máscaras de Card Conjurer como m15MaskType.png: solo la barra del tipo). Con desde, se usa el mismo trozo de la máscara. Sin {color}: una máscara para todos los colores. Ninguno de los dos se combina con tramos ni con capas SVG
cajaReglas true con en y sin png/svg: pinta ahí el tinte de caja de reglas pedido cuando el diseño pide una caja oscura, clara o personalizada (los marcos de imagen llevan la caja cocida). El texto de reglas toma entonces la tinta que se lee sobre ese tinte
colores sobrescrituras de paleta a letra por capa
si condición (abajo)
escalado suave o pixel para esta capa
mezcla normal, oscurecer (Darken, p. ej. líneas de barrido) o multiplicar
opacidad 0..1

Una capa es SVG o PNG (exactamente uno) o un marcador cajaReglas. Un fichero de capa que falta en un pack recurre al fichero de la misma ruta del pack integrado al pintar.

Condiciones (Condicion, el campo si)

Deben cumplirse todas las claves presentes (las ausentes se ignoran). Los hechos los calculan render::hechos_de y pintar_cara.

Clave Verdadera cuando
corona la corona está activada (regla de legendaria o forzada)
fr la cara tiene fuerza y resistencia
cinta el nombre original va en la cinta
emblema hay un emblema fijado
tipo alguno de estos (en inglés, minúsculas) aparece entre las palabras de la línea de tipo en inglés: land, creature, planeswalker, artifact, enchantment, instant, sorcery, battle, token…
basica tierra básica (la línea de tipo contiene basic) o no
legendaria legendaria (línea de tipo o efecto de marco legendary)
colores [min, max] número de colores de la cara (una tierra incolora usa su identidad)
simbolos [min, max] número de símbolos del coste de maná ([3, 3] significa exactamente tres)
variante el usuario eligió esa variante
caja (v3) la caja de reglas elegida en el diseño es una de estas: auto, dark, light, tint (Hechos.caja, de FrameOptions.rules_box)

Huecos de texto (Hueco)

Obligatorios en todo estilo: titulo, tipo, reglas, pie. Se usan cuando están: coste, fr, cinta, emblema, etiqueta (etiqueta de pastilla), simbolo (símbolo de color de fullArt), mano y vida (Vanguard).

Campo Significado
x y w h rectángulo en mm (se permiten expresiones)
rol rol de la letra
mayuscula altura de las mayúsculas en mm (por defecto 1,78)
color #rrggbb[aa] o una variable como {{tintaCaja}}
alinear inicio, centro, fin; en coste, centro centra los símbolos
sombra sombra suave detrás del texto
versalitas versalitas (títulos de fichas)
mayusculas pone el texto en mayúsculas (los símbolos no se tocan)
alinearV colocación vertical en una caja fija: inicio, centro (por defecto), fin
tabs, interlinea solo el pie: tabuladores (mm) y paso de línea (por defecto 1,6)
contenido en etiqueta: rareza (por defecto), supertipo («Legendary»), subtipoTierra (tipo de tierra básica)

El título y la línea de tipo se estrechan horizontalmente antes de reducirse; véase Motor del diseñador. Los pies usan \n para las líneas, \t para las columnas (con tabs cada columna se estrecha hasta su tabulador; si no, las columnas se siguen unas a otras y toda la línea se estrecha hasta la mitad) y todo lo que va después de {>} queda pegado a la derecha. Si la plantilla no tiene ninguno de {credit}, {universe}, {year}, se añaden después de {>} en la última línea (p. ej. «Universe · (c) 2026 Credit»).

Layouts

Dos tipos:

  • Layouts de estilo (EstiloDef.layouts[name], un LayoutDef): arte, caja, capas, fondo, huecos. Los campos presentes sustituyen a los del estilo en bloque (una lista capas sustituye la lista entera; un hueco sustituye ese hueco, los demás se quedan).
    • ficha: fichas, emblemas, fichas de dos caras (título centrado en versalitas…).
    • alto: caja de reglas más alta para los cuerpos con secciones (planeswalker, clase, caso, leveler, Station). Opcional.
    • el id de una variante de usuario, que se aplica automáticamente cuando se elige.
  • Layouts de pack (PackDef.layouts[name], EstiloDefs completos válidos para cualquier estilo): saga, mazmorra, secuelaArriba (63 x 51), secuelaAbajo (33 x 63), fusionar (88 x 6), flip, batalla (88 x 63), plano (127 x 88,9), plan y vanguard (88,9 x 127), pie (solo el pie). Las mitades de Split, Fuse y Room no necesitan layout propio. Dos de ellos merecen una nota, y el pack integrado, el Original y Balatro los definen así:
    • mazmorra: como las mazmorras impresas, solo el título (centrado) y el plano. arte mide 0 x 0, no hay hueco tipo ni emblema, y el hueco reglas ocupa todo, de debajo del título a encima del pie; el motor pinta dentro el plano (salas, paredes, puertas), encima de lo que pinten ahí las capas del layout (véase Composición del diseñador). Quitar la línea de tipo funciona porque un layout de pack sustituye al estilo entero: no se recurre a los huecos del estilo.
    • secuelaAbajo: título, una ventana de arte, línea de tipo y reglas, de arriba abajo; la pieza se pega girada a la derecha, así que su título queda a lo largo del borde derecho de la carta, como en las impresas. El motor pinta en esa ventana la ilustración de la segunda cara del diseño, como en las cartas Aftermath impresas. Un pack cuyo secuelaAbajo siga con el arte a 0 x 0 deja esa mitad sin ilustración.

Orden de resolución (render::estilo_para): estilo propio del pack (si no, aviso EstiloNoEnElPack y el estilo integrado), si no la entrada del pack para el estilo integrado, si no la del pack integrado, si no el borderless del pack. Prioridad de la petición de layout: la petición propia de la carta (ficha, saga…), luego la variante elegida si el estilo tiene un layout con ese id, luego el primer layoutSi que coincida. Un layout de ficha que falta recurre al estilo del pack integrado en silencio; cualquier otro layout que falte recurre al layout completo del pack integrado y, si no existe, se informa PackSinLayout (no para alto).

Escalado (Escalado)

suave (por defecto): Lanczos3, para marcos pintados o fotografiados. pixel: al ampliar, vecino más próximo, de modo que cada píxel se repite; al reducir, promedio con filtro de caja (render::reescalar_pixel, mediante fast_image_resize). Se fija a nivel de pack o por capa; los símbolos propios siguen el indicador del pack. Es lo que mantiene nítido el pixel art a 300 hasta 900 PPP.

El pack integrado

Ubicación: src-tauri/crates/proxy-core/assets/marcos/builtin/, incrustado con include_str! mediante la macro fichero! (lista BUILTIN en pack.rs), de modo que forma parte del binario. Pack::de_fabrica() lo construye; Pack::de_fabrica_compartido() guarda en caché una copia para los recursos de reserva.

  • Id builtin, versión 1, nueve paletas, letras Spectral (títulos, tipo, reglas, F/R, cinta) y Montserrat (pie).
  • Cuatro estilos: borderless (ilustración de 63 x 83 con una caja de reglas que crece fondo 83, altoMin 12, altoMax 27.4, barraTipo 6, ventana de la ilustración con radio de 6 mm redondeada abajo, corona, cinta, SVG de barra de título y de F/R; layouts ficha, alto), extended y classic (SVG de marco marco.svg / marco-alto.svg; layouts ficha, alto), fullArt (tierras básicas apaisadas: franja más símbolo de color, sin layouts).
  • Once layouts de pack: saga, mazmorra, secuelaArriba, secuelaAbajo, fusionar, pie, flip, batalla, plano, plan, vanguard.
  • Como todos los demás packs recurren a él, debe seguir funcionando como la implementación de referencia del formato.
  • Tests que lo protegen: dorados.rs (imágenes doradas), layouts.rs y los tests unitarios de pack.rs y render.rs (un_pack_de_antes_sin_el_layout_usa_el_de_la_casa, un_estilo_que_falta_sale_del_pack_de_la_casa, etc.).

Marcos «Original» (Card Conjurer)

Decisión: los marcos de imagen M15 del proyecto Card Conjurer no se distribuyen en el repositorio ni en los instaladores. Se descargan la primera vez que se usan y se verifican.

  • Definición: assets/marcos/original/pack.json (id original, versión 3, origen = Card Conjurer), cuatro SVG (cinta.svg, cubierta-corona.svg, refuerzo-caja.svg, sombra-pie.svg) e imagenes.json: { commit, base, imagenes: { path: sha256 } }. Actualmente 53 PNG (unos 37 MB) bajo m15/regular (8 marcos, 8 de F/R, máscaras m15MaskTitle.png y m15MaskType.png), m15/new/extended (8), m15/borderless (9), m15/crowns (18), fijados a un commit de 40 hex del repositorio público, base img/frames/.
  • Tres estilos que comparten las barras de mármol y el pergamino del clásico, para que un mazo se vea coherente use cada carta el estilo que use. Decisión: los marcos extendido y despejado propios de Card Conjurer son más lisos (barra de tipo negra translúcida, placas translúcidas sin grano), así que las piezas que llevan la textura se toman del marco normal con mascara y desde:
    • classic: marco M15 normal (m15/regular/m15Frame{color}.png), cajaPorDefecto: clara.
    • extended: m15/new/extended/{color}.png (letras w u b r g m a l; el incoloro usa a, como el clásico usa A), ilustración desde y 7,4 mm hasta debajo de la barra del tipo, a todo el ancho; encima, el marco normal con la máscara m15MaskType.png para una barra de tipo de mármol; la corona flotante (no hay bordes laterales bajo el título donde apoyar la normal); la caja de F/R y los colores de texto del clásico. Con la corona de legendaria (si.corona) la pieza de arriba de ese PNG (el borde del marco) asomaría alrededor de la corona, así que los 12 mm de arriba se pintan en trozos: los brillos de los lados (x 0–2,3 y 60,7–63), una columna de cada brillo estirada sobre el hueco junto a la corona (desde de 0,1 mm, en de 1,1 mm, para que el brillo siga sin el borde) y el centro (3,4–59,6); la misma capa sin corona (si.corona: false) se pinta entera. Sin esto, junto a la corona de las legendarias (sobre todo en los planeswalkers) salían finas rayas verticales y una columna tenue.
    • borderless: el marco «showcase» genérico de Card Conjurer (ventanaArte con zonas repintar y repintarTras: 4), con el marco normal recortado por m15MaskTitle.png y m15MaskType.png para las barras, el interior de la caja de reglas del marco normal puesto sobre la caja del showcase solo con desde (el hueco entre los bordes de la caja está en el mismo sitio en las dos imágenes, x 4,72–58,38 mm e y 55,2–81,4 mm, así que el trozo va en su sitio sin estirar; un en sacado de la zona de cajaReglas quedaba 0,2 mm desplazado y dejaba un hueco en un lado), la caja de F/R del clásico, texto oscuro y cajaPorDefecto: clara. La capa del pergamino lleva "si": {"caja": ["auto", "light"]}: con una caja oscura o con tinte no se pinta, así que queda la caja translúcida del propio marco con el tinte de cajaReglas encima, se sigue viendo la ilustración a través de la caja y el ajuste fino «caja de reglas más opaca» tiene sentido. El borde de la caja sale siempre del marco.
    • Los tres estilos definen una caja con fijaDeSerie, así que la caja de reglas mide altoMax de serie y sigue la altura del texto, como el despejado de la casa, solo si el usuario activa «Altura según el texto». La caja viene cocida en el PNG con un alto fijo, por lo que el pack la rehace en tres trozos con desde + en y expresiones sobre yTipo: el de arriba (con la barra del tipo) y el de abajo se copian tal cual, y el de en medio se estira entre ellos (despejado: barra 48.5–55.4, medio 55.4–80 puesto en yTipo+6.9 con alto 73.1 - yTipo, abajo 80–88, más tiras de esquina para que los lados no se estiren). cajaReglas es en {4.91, "yTipo+6.55", 53.43, "74.34 - yTipo"} y refuerzo-caja.svg usa {{yReglas}} / {{altoReglas}}; ventanaArte.repintarTras vale 9 en el despejado. Las tierras básicas usan el estilo fullArt propio del pack: la barra de mármol del título del despejado (el trozo del título del showcase y 3,0–10,3 más el marco normal con m15MaskTitle.png, movida a y 72,7) —la del título y no la del tipo, porque el trozo de la barra del tipo queda abierto por abajo, donde se une la caja de reglas— sobre una ventana de ilustración a sangre hasta 81 mm con las esquinas de abajo redondeadas a 3 mm (ventanaArte, sin repintar ni tiras de esquina, que son de la caja de reglas), con el nombre centrado en la barra y el símbolo de maná dentro de ella, a la izquierda; sin fullArt propio, un pack recurre al de la casa (franja oscura). El pack define todos sus propios layouts de pack (saga, clase, mazmorra, flip, secuelaArriba/secuelaAbajo, fusionar, pie, batalla, plano, plan, vanguard), hechos con trozos de sus PNG (barras en tres trozos a partir de la barra del título, cajas de reglas en nueve trozos del marco normal, con el mismo mármol y los bordes de color), y una variante alto en cada estilo (caja de reglas hasta 34,6 mm) para que quepan planeswalkers, casos y leveler (ninguno de los layouts especiales recurre al aspecto de la casa); las clases usan un layout clase (arte a la izquierda, niveles a la derecha) y, en packs sin él, alto. Balatro hace lo mismo con las piezas de su Joker (balatro/source/layouts.py, que llama build.py). El layout prepare de Scryfall (criatura preparada con su hechizo, p. ej. Emérita del conflicto) es Maquetacion::Preparar: el cuerpo de la aventura con la columna del hechizo a la derecha (Contenido::Aventura.libro_derecha).
  • Descarga (original.rs): URL = https://raw.githubusercontent.com/Investigamer/cardconjurer/<commit>/<base><path>. descargar reescribe siempre los ficheros de definición con la versión incrustada en la app, lista las imágenes cuyo SHA-256 en disco no coincide (pendientes/bueno), las baja una a una, rechaza cualquier fichero cuyo SHA-256 difiera (ErrorCode::Network con fichero), escribe de forma atómica en app_data_dir/marcos/original/, informa del progreso y llama a recargar_pack("original"). Evento de progreso: original://progress con { done, total }. info() devuelve OriginalInfo { listo, faltan, total }.
  • leer_packs llama a original::definicion_al_dia en cada arranque para que la definición del pack en disco coincida siempre con la versión de la app aunque las imágenes se bajaran hace mucho.
  • Una app más nueva puede pedir imágenes que una anterior no bajó (las del extendido). leer_packs y recargar_pack cargan este pack con original::cargar: si el pack entero falla, se queda solo con los estilos que cargan por sí solos, así que los que faltan caen al pack integrado en vez de desaparecer todo el pack Original. Al arrancar, lib.rs lanza original::completar, que baja lo pendiente cuando la carpeta ya tiene un pack.json (nunca en una instalación nueva: eso sigue siendo a petición). Sin red solo registra un aviso y lo vuelve a intentar en el siguiente arranque o cuando el usuario elige el pack.
  • Tests (original.rs): la lista está bien formada (commit de 40 caracteres, base con barra final, más de 40 imágenes, rutas .png sin .., hashes de 64 caracteres); se rechaza un fichero con otro hash; se reescribe una definición antigua; si no se ha descargado nada, todo está pendiente; sin las imágenes nuevas el pack carga sin el estilo extendido (sin_las_imagenes_nuevas_carga_sin_ese_estilo). en_vivo (ignorado) descarga de verdad. En proxy-core, el_pack_original_es_valido_con_sus_imagenes comprueba que la lista de imágenes es justo la que piden las capas, máscaras incluidas.

Packs descargables

descargables.rs + assets/packs-descargables.json entregan packs alojados en el repositorio aparte de packs. A diferencia de Original, todo (incluido pack.json) viene de ese repositorio.

Forma del manifiesto (Manifiesto { repo, packs: Vec<Descargable> }):

Campo Significado
repo https://raw.githubusercontent.com/PoolFlamingo/proxy-builder-packs
packs[].id, nombre id del pack (también el directorio de instalación) y nombre para mostrar
commit commit de 40 hex del repositorio de packs; cada URL es <repo>/<commit>/<carpeta><path>
carpeta carpeta del pack dentro del repositorio (balatro/)
ficheros mapa de ruta relativa a SHA-256: pack.json, layers/, symbols/, labels/ (128 ficheros para Balatro)
letras letras: ruta, sha256, licencia, licencia_sha256
extras imágenes extra: ruta (con prefijo extras/), sha256, tipo (reverso, ficha, tierra, carta), nombre (137 para Balatro: 82 cartas listas, 50 tierras, 3 fichas, 2 reversos)

Comportamiento:

  • lista(state) devuelve DescargableInfo { id, nombre, listo, faltan, total, bytes, extras } para cada pack que esté disponible: su commit debe ser un hash real de 40 hex (publicado) o, solo en compilaciones de depuración (cfg!(debug_assertions)), debe estar definida PB_PACKS_LOCAL. listo significa que todo lo que hay en disco coincide con su hash y el pack está cargado; los extras solo se listan cuando está listo.
  • descargar(state, id, progreso): calcula las tareas pendientes (ficheros más extras cuyo hash en disco difiere) y las letras que faltan (el id de la letra es el hash del fichero, comparado con las letras importadas), baja cada una mediante state.externo.bajar(url, MAX_IMAGEN) (tope de 15 MiB), verifica el hash (comprobar; un desajuste es ErrorCode::Network con fichero), escribe de forma atómica, importa las letras con importar_letra_datos y guarda cada licencia en fuentes/licencias/<stem>-LICENCIA.txt, luego quitar_lo_que_sobra borra todo lo que haya en las carpetas del pack y de los extras que el nuevo manifiesto ya no liste (rutas antiguas, restos de .part) y, por último, recargar_pack. Una segunda ejecución sin cambios no baja nada (total == 0), así que actualizar a una versión nueva del pack baja solo los ficheros que han cambiado.
  • Ubicaciones: el pack en app_data_dir/marcos/<id>/, los extras en app_data_dir/packs-extras/<id>/ (separados para que no se carguen con el pack).
  • Extras: extra_por_indice (bytes que mostrar, hash vuelto a comprobar), usar_extra / ruta_extra (al almacén de blobs, para que el extra pueda ser un reverso de carta o una carta propia; el comando pack_extra_use).
  • Sobrescritura para desarrollo: con PB_PACKS_LOCAL=<carpeta del repositorio de packs> (solo compilaciones de depuración), los ficheros se leen de esa carpeta en lugar de la red; los hashes se siguen comprobando.
  • Eventos: packs://progress con { id, done, total }. Interfaz: src/features/disenar/packs-descargables.ts (usePacksDescargables).
  • Tests: el_manifiesto_se_entiende (forma de la URL del repositorio, pack.json presente, hashes de 64 caracteres, y cada URL de fichero supera la lista de permitidos url_permitida), una_huella_que_no_cuadra_no_vale, lo_de_una_version_anterior_se_quita; el ignorado balatro_en_vivo descarga y comprueba 7 estilos propios.

Balatro

El único pack descargable hasta ahora (id: balatro, versión 2, escalado: pixel). Marcos de pixel art con la temática del juego, construidos a partir de siete plantillas PSD, cada una expuesta como estilo propio: joker, planeswalker, espectral, tarot, planeta, vale, tierra (los ids siguen en español porque los diseños guardados se refieren a ellos; los nombres para mostrar salen de los mapas nombre por idioma del pack). Seis heredan de classic, tierra de fullArt. Usa:

  • layouts: todos los estilos tienen un layout ficha; tierra tiene además basica, elegido por layoutSi cuando la carta es una tierra básica.
  • 31 símbolos propios (W U B R G C T, 0 a 20, X Y Z) dibujados como fichas de póker, con separacionCoste 0,122.
  • 13 etiquetas de pastilla (rarezas, legendary, tipos de tierra básica) con el texto escrito por el motor en el idioma del diseño.
  • coloresTexto (colores de resaltado y de palabra de habilidad), insignias (incluida pngLealtad), sustituir (8 caracteres tipográficos asignados a ASCII para la letra pixel), plantillaPie (pie de una sola línea) y una caja de reglas dinámica con altoComodo en seis de los siete estilos (planeswalker usa una caja fija).
  • La letra m6x11plus.ttf (de uso libre con atribución; el fichero de licencia se distribuye y se guarda en la máquina del usuario).
  • El formato admite variantes, pero el pack.json actual no declara ninguna.
  • Extras: dos reversos de carta, fichas, tierras básicas y cartas ya hechas del mazo de ejemplo.

Cuando el manifiesto pasa a un commit nuevo del repositorio de packs, un pack instalado muestra los ficheros cambiados como pendientes (faltan) hasta que el usuario lo vuelve a descargar desde «Letras, marcos y símbolos»; un cambio que solo toca los layouts es un único fichero, pack.json. Deducido: mientras tanto la copia instalada sigue cargando con sus layouts anteriores, porque los packs se leen del disco diga lo que diga el manifiesto.

Los créditos están en el CREDITS.md del pack (plantillas de una artista de la comunidad redistribuidas con permiso; Balatro pertenece a su editor; las cartas son proxies para jugar, no para vender).

El repositorio de creación de packs

El contenido de los packs se construye y se versiona en un repositorio aparte, clonado junto a este (proxy-builder-packs, publicado como PoolFlamingo/proxy-builder-packs). Tiene sus propias sesiones de trabajo e historial, y la app solo lo ve a través del manifiesto. Estructura del repositorio:

proxy-builder-packs/
  README.md
  balatro/
    pack.json         the pack definition
    layers/<template>/  PNG layers cropped to the card area (common, joker, land, planeswalker, planet, spectral, tarot, voucher)
    symbols/          mana chips
    labels/           rarity, legendary and land-type pills
    fonts/            m6x11plus.ttf and its licence
    extras/{cards,tokens,backs,lands}/   ready-made images
    extras.json       inventory of extras (type and display name)
    CREDITS.md
    source/           convert.py, build.py, manifest.py, inventory.json/.md, references.json, views/

Las carpetas y los nombres de fichero están en inglés; los ids de estilo y las claves de JSON siguen en español porque los leen la app y los mazos guardados.

Flujo de trabajo (del README del repositorio, ejecutado dentro de balatro/source/ en un virtualenv con psd-tools, pillow y numpy; siempre python -I):

  1. convert.py <carpeta con los PSD> ..: exporta todas las capas de imagen (excepto la ilustración de muestra, el texto y las muestras de color) recortadas al área de carta de un lienzo de 1365 x 1855, con la zona de cada capa en mm, y escribe inventory.json/inventory.md. Las capas de texto se registran con caja, tamaño, color y alineación. Las capas visibles se recomponen y se comparan con el compuesto del propio PSD como comprobación.
  2. build.py .. <carpeta con los extras originales> <m6x11plus.ttf>: genera símbolos, etiquetas, las cajas de color de layers/common, copia las letras y los extras (recortados sin los 3 mm de sangrado) y escribe pack.json. Borra las capas que no usa, así que hay que volver a ejecutar convert.py antes de repetir este paso.
  3. manifest.py .. <commit>: imprime la entrada del manifiesto de la app para el pack (el SHA-256 de cada fichero, la lista de letras con licencias, los extras con prefijo extras/ y hashes). Omite source/, extras/, fonts/, golden/, extras.json, README.md, CREDITS.md de ficheros.

Publicar una versión nueva del pack:

  1. Hacer commit y push de los cambios del pack al repositorio de packs (el commit debe existir en el remoto porque la app descarga por commit).
  2. Ejecutar manifest.py con ese hash completo de commit y sustituir la entrada del pack en src-tauri/assets/packs-descargables.json.
  3. Ejecutar los tests de la app (el_manifiesto_se_entiende) y, opcionalmente, el ignorado balatro_en_vivo.
  4. Sacar una release de la app; los jugadores reciben los ficheros nuevos la próxima vez que abran la descarga del pack (solo se bajan los ficheros que han cambiado).

Durante el desarrollo, usa PB_PACKS_LOCAL con un commit de relleno (que no sea de 40 hex) en el manifiesto para probar antes de hacer push. Los tests del lado del motor que necesitan el contenido del pack (balatro_con_sus_imagenes, balatro_matriz, balatro_contra_el_autor) reciben la carpeta del pack mediante PB_BALATRO (véase Motor del diseñador).

Catálogo de letras libres

src-tauri/assets/letras-libres.json lista 22 familias con licencia OFL del repositorio de Google Fonts, fijadas a un commit, con el SHA-256 y el tamaño de cada fichero y de cada licencia. Se descargan con un botón del diálogo de recursos, no están en el repositorio ni en los instaladores y se importan como cualquier letra del usuario.

Entrada (Familia): id, familia (el nombre usado en colecciones y packs), uso (UsoLetra: titulo, reglas, ambos), cursiva (tiene cursiva, usada para el texto recordatorio y de ambientación), ficheros: [{ ruta, sha256, bytes }], licencia: { ruta, sha256 }. Raíz: commit y base (ofl/).

Criterio de selección: legibles a tamaño de carta, sin trazos finos. Familias: Cinzel, Almendra, Uncial Antiqua, Grenze Gotisch, Eczar, Amarante, Metamorphous, Inknut Antiqua, Bitter, Domine (títulos); Grenze, Caudex, Cardo, Vollkorn, Libre Baskerville, Literata, Merriweather, Lora, Pixelify Sans (ambos; Pixelify Sans es la alternativa libre a la letra de Balatro); Crimson Pro, Source Serif 4, PT Serif (reglas). Las imágenes de muestra están en src/assets/letras/<id>.png.

Flujo (letras_libres.rs):

  • lista(state): LetraLibre { id, familia, uso, cursiva, descargada, bytes }; descargada cuando el SHA-256 de todos los ficheros ya está entre las letras importadas (el id de la letra es el hash del fichero).
  • descargar(state, id): para cada fichero que aún no está, baja https://raw.githubusercontent.com/google/fonts/<commit>/ofl/<ruta>, exige el SHA-256 fijado, lo importa con importar_letra_datos(datos, "ttf") (guardado como fuentes/<sha256>.ttf), luego baja y verifica la licencia y la escribe en fuentes/licencias/<id>-OFL.txt. Cualquier desajuste aborta con ErrorCode::Network.
  • Tests: el_catalogo_esta_bien_escrito (commit de 40 caracteres, al menos 10 familias, ids únicos, cada ruta empieza por su id y termina en .ttf sin .., hashes de 64 caracteres, la licencia termina en OFL.txt), sin_descargar_ninguna_esta; los ignorados los_ficheros_son_los_del_catalogo (necesita PB_GF, una copia local del repositorio de Google Fonts; comprueba los hashes y que cada fichero registra la familia declarada) y en_vivo.
  • Añadir una familia: copiar sus ficheros y su licencia del repositorio de Google Fonts en el commit (el fijado o uno nuevo), calcular el SHA-256 y el tamaño, añadir una entrada, añadir un PNG de muestra, ejecutar el test de hashes ignorado con PB_GF. Si el commit cambia, todas las entradas deben seguir coincidiendo con ese commit.

Modelo de seguridad

  • Contenido fijado. Todo el contenido remoto de los packs se direcciona por un hash de commit completo y se verifica con un SHA-256 guardado en el binario de la app (imagenes.json, packs-descargables.json, letras-libres.json). Se rechaza una rama sobrescrita a la fuerza, un fichero cambiado o una respuesta manipulada (ErrorCode::Network), así que un host comprometido no puede inyectar imágenes ni letras.
  • Escrituras atómicas. Los ficheros se escriben con escribir_atomico (temporal y luego renombrado), de modo que un fallo no deja ningún fichero a medias que pueda pasar por completo; la siguiente ejecución vuelve a comprobar los hashes.
  • Lista de permitidos para el repositorio de packs. descargables.rs pasa por state.externo (fuentes::Externo), que solo acepta la lista cerrada PERMITIDOS de src-tauri/src/fuentes/mod.rs (la entrada del repositorio de packs es raw.githubusercontent.com con el prefijo de ruta /PoolFlamingo/proxy-builder-packs/), vuelve a comprobar cada redirección y limita cada descarga a MAX_IMAGEN (15 MiB). Un test unitario comprueba que todas las URL del manifiesto superan la lista.
  • Original y letras. original.rs, letras_libres.rs y letras_cjk.rs (las letras CJK, catálogo src-tauri/assets/letras-cjk.json, véase Capa de aplicación del diseñador) usan su propio cliente reqwest con URL de repositorio fijadas en tiempo de compilación (Card Conjurer, Google Fonts) y los hashes fijados, no la lista de permitidos de Externo. Sus prefijos de URL no los puede controlar el usuario.
  • Vista web. El contenido del pack lo baja Rust; la vista web nunca sale a la red (su CSP en tauri.conf.json solo permite connect-src a los endpoints de IPC), solo muestra imágenes renderizadas mediante pbimg://. Véanse Arquitectura y Fuentes externas.
  • Packs de usuario. Solo se leen .json, .svg, .png; los topes de tamaño se aplican a los zip; el id se sanea y builtin está reservado; las rutas que contienen .. se omiten. Un pack de usuario es de confianza en la medida en que resvg rasteriza sus SVG; Deducido: un SVG hostil, como mucho, puede producir píxeles incorrectos o malgastar CPU, ya que no interviene ningún motor de scripts.
  • Licencias. Las letras descargadas conservan el texto de su licencia junto a ellas en fuentes/licencias/.

Notas sobre la documentación existente

docs/packs-de-marcos.md es la guía de creación mantenida, en español. Afirmaciones contrastadas con el código en esta página: reglas de versión del pack (1 o 2), paletas y huecos obligatorios, claves de si, nombres y tamaños de los layouts, PB_PACKS_LOCAL. Pequeña divergencia detectada: la guía dice que el pack Original distribuye «tres SVG propios» mientras que ORIGINAL en pack.rs lista cuatro; el comentario de módulo de descargables.rs menciona fuente/manifiesto.py mientras que el script del repositorio de packs es source/manifest.py.

Por dónde empezar para ampliarlo

Objetivo Empezar por
Un campo nuevo en pack.json pack.rs (añadirlo con #[serde(default, skip_serializing_if…)], subir VERSION_PACK solo si una app antigua lo pintaría mal), consumirlo en render.rs, documentarlo en docs/packs-de-marcos.md y aquí
Una condición si nueva Condicion, Hechos y Condicion::cumple en pack.rs, rellenar Hechos en render::hechos_de
Un pack descargable nuevo construirlo en el repositorio de packs, añadir su entrada de manifiesto, asegurarse de que su prefijo de URL está permitido en PERMITIDOS
Un layout de pack nuevo añadirlo al pack.json integrado y su SVG, añadir un caso en tests/layouts.rs (Composición del diseñador)

Véase también

Documentation · Documentación

Language: English · Español

Use and build · Uso y desarrollo

Architecture · Arquitectura

Data · Datos

Getting cards · Conseguir cartas

Layout, images, PDF · Maquetación, imagen y PDF

Card designer · Diseñador de cartas

Game table · Mesa de juego

Frontend · Frontend

Reference · Referencia

Clone this wiki locally