{"id":2759,"date":"2021-01-16T20:48:03","date_gmt":"2021-01-16T20:48:03","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=2759"},"modified":"2023-12-25T19:48:34","modified_gmt":"2023-12-25T19:48:34","slug":"pintando-en-el-spectrum-5","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=2759","title":{"rendered":"Pintando en el Spectrum (5)"},"content":{"rendered":"\n<p>Editado: el c\u00f3digo completo del ejemplo de esta entrada est\u00e1 disponible en un enlace al final.<\/p>\n\n\n\n<p>Llega la hora de pintar cosas en pantalla. La idea consiste en modificar los valores de la memoria RAM de v\u00eddeo para que, cuando la ULA (que es el chip encargado de generar la imagen) lea esos bytes, \u00e9stos muestren la imagen que queremos. Hay dos maneras de hacerlo: <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Vector_monitor\" target=\"_blank\">vectorial<\/a>, y por <em><a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Sprite_(computer_graphics)\" target=\"_blank\">sprites<\/a><\/em>. En el caso de gr\u00e1ficos vectoriales, se tiene almacenada en memoria una lista de segmentos y v\u00e9rtices que deben pintarse sobre la pantalla para generar la imagen. Son ideales para gr\u00e1ficos 3D, y son los utilizados en juegos como <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Elite_(video_game)\" target=\"_blank\">Elite<\/a> o <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Freescape\" target=\"_blank\">Driller<\/a>, sin embargo, en una m\u00e1quina de 8 bits son muy limitados, por lo que de momento lo dejaremos de lado. Es verdad que hace tiempo estuve trabajando en una rutina para pintar tri\u00e1ngulos vectoriales a alta velocidad en Spectrum, por lo que puede que retome el tema alg\u00fan d\u00eda.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/elite.png\" rel=\"lightbox-0\"><img loading=\"lazy\" decoding=\"async\" width=\"492\" height=\"396\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/elite.png\" alt=\"\" class=\"wp-image-2764\" srcset=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/elite.png 492w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/elite-300x241.png 300w\" sizes=\"auto, (max-width: 492px) 100vw, 492px\" \/><\/a><\/figure><\/div>\n\n\n\n<p>Los <em>sprites<\/em>, por su parte, son mucho m\u00e1s sencillos, pues consisten en tener los bytes necesarios para pintar algo en pantalla ya preparados en otra zona de memoria, y lo \u00fanico que necesitaremos ser\u00e1 copiarlos en la direcci\u00f3n adecuada de pantalla. Adem\u00e1s, cambiando la direcci\u00f3n de memoria en la que empezamos a pintar podremos cambiar la posici\u00f3n, con lo que podemos colocarlos en cualquier punto de la pantalla o, incluso, moverlos y animarlos.<\/p>\n\n\n\n<p>Para pintarlo en pantalla, primero necesitamos encontrar la direcci\u00f3n de memoria en la que ir\u00e1 el primer byte, que, como recordaremos de la entrada anterior, se pod\u00eda hacer de manera sencilla:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scanlines2-1.png\" rel=\"lightbox-1\"><img loading=\"lazy\" decoding=\"async\" width=\"464\" height=\"168\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scanlines2-1.png\" alt=\"\" class=\"wp-image-2751\" srcset=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scanlines2-1.png 464w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scanlines2-1-300x109.png 300w\" sizes=\"auto, (max-width: 464px) 100vw, 464px\" \/><\/a><figcaption>Obtenci\u00f3n de la direcci\u00f3n en pantalla a partir de las coordenadas de un car\u00e1cter trabajando con el buffer lineal. En verde va la coordenada Y, y en azul la coordenada X. En magenta va el n\u00famero de scanline dentro del car\u00e1cter.<\/figcaption><\/figure>\n\n\n\n<p>As\u00ed, si recordamos, la pantalla del Spectrum est\u00e1 dividida en una matriz de 32&#215;24 caracteres, con ocho scanlines cada uno, y si utilizamos la rutina de copia de buffer que vimos en las tres primeras entradas, obtener la direcci\u00f3n de un car\u00e1cter a partir de sus coordenadas X e Y es tan sencillo como multiplicar la Y por 256, sumarle la X, y ese valor sumarlo a la direcci\u00f3n inicial del buffer. Alguno dir\u00e1 que las multiplicaciones en ensamblador son muy costosas, pero es aqu\u00ed donde viene lo divertido: digamos que queremos tener en el par de registros HL la direcci\u00f3n inicial del car\u00e1cter situado en las coordenadas X, Y. Pues s\u00f3lo tenemos que copiar el valor de Y en el registro H, el de X en el registro L, y sumarle a HL la direcci\u00f3n base del buffer. Pero si hemos sido cuidadosos y hemos puesto el buffer en la direcci\u00f3n 0x8000, ni siquiera necesitaremos una suma, sino simplemente poner a 1 el \u00faltimo bit del registro H.<\/p>\n\n\n\n<figure class=\"wp-block-video aligncenter meme\"><video height=\"288\" style=\"aspect-ratio: 288 \/ 288;\" width=\"288\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/06\/tadah.mp4\"><\/video><\/figure>\n\n\n\n<p>Por supuesto, esto nos da la direcci\u00f3n del primer scanline del car\u00e1cter, pero movernos al siguiente scanline es tan sencillo como sumar 32 a la direcci\u00f3n. Esto nos permite pintar sprites del tama\u00f1o de un car\u00e1cter en pantalla, pero \u00bfcomo hacemos para pintar sprites de mayor tama\u00f1o?<\/p>\n\n\n\n<p>La soluci\u00f3n m\u00e1s inmediata consiste en dividir el sprite en bloques de 8&#215;8 p\u00edxeles, almacenar cada bloque por separado, y luego pintarlos uno a uno en las posiciones correspondientes. As\u00ed, si tuvi\u00e9semos por ejemplo el siguiente sprite de 16&#215;16 p\u00edxeles:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/sprite_circulo.png\" rel=\"lightbox-2\"><img loading=\"lazy\" decoding=\"async\" width=\"317\" height=\"316\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/sprite_circulo.png\" alt=\"\" class=\"wp-image-2767\" srcset=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/sprite_circulo.png 317w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/sprite_circulo-300x300.png 300w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/sprite_circulo-150x150.png 150w\" sizes=\"auto, (max-width: 317px) 100vw, 317px\" \/><\/a><\/figure><\/div>\n\n\n\n<p>Lo que har\u00edamos ser\u00eda dividirlo en bloques de 8&#215;8 p\u00edxeles, de manera que cada uno se podr\u00eda pintar como un car\u00e1cter en pantalla, y almacenar\u00edamos los bytes en este orden en memoria:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan1.png\" rel=\"lightbox-3\"><img loading=\"lazy\" decoding=\"async\" width=\"328\" height=\"418\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan1.png\" alt=\"\" class=\"wp-image-2739\" srcset=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan1.png 328w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan1-235x300.png 235w\" sizes=\"auto, (max-width: 328px) 100vw, 328px\" \/><\/a><\/figure><\/div>\n\n\n\n<p>Vemos que primero viene el primer byte del car\u00e1cter superior izquierdo, luego el inmediatamente inferior, y as\u00ed hasta hacer ocho bytes del primer car\u00e1cter. A continuaci\u00f3n vendr\u00edan los ocho bytes del car\u00e1cter de la parte superior derecha, luego los de la parte inferior izquierda, y por \u00faltimo los de la parte inferior derecha.<\/p>\n\n\n\n<p>Digamos que queremos pintarlo en las coordenadas X=5 e Y=12. Lo primero ser\u00eda calcular la direcci\u00f3n de 5,12, la cual vamos a almacenarla en el par de registros DE (para tener la regla mnemot\u00e9ctica de DE = DEstino), y eso lo haremos escribiendo 12 en D y 5 en E, y sum\u00e1ndole 128 a D para activar el bit 7 y que apunte al inicio de nuestro buffer. Ahora ya tenemos la direcci\u00f3n de memoria donde tenemos que empezar a copiar los datos. Digamos, adem\u00e1s, que en HL tenemos la direcci\u00f3n de memoria del primer byte de nuestro sprite. Bien, ahora s\u00f3lo tenemos que leer el byte de la direcci\u00f3n de memoria apuntada por HL, y escribirlo en la direcci\u00f3n de memoria apuntada por DE. El siguiente paso es saltar al siguiente byte, y para ello s\u00f3lo tenemos que incrementar HL en 1, y sumar 32 a DE para pasar al siguiente scanline, con lo que ya podemos copiar el segundo byte. Este proceso lo repetimos ocho veces para copiar los ocho bytes del car\u00e1cter.<\/p>\n\n\n\n<figure class=\"wp-block-video aligncenter meme\"><video height=\"206\" style=\"aspect-ratio: 288 \/ 206;\" width=\"288\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2020\/07\/calculating.mp4\"><\/video><\/figure>\n\n\n\n<p>Ahora que ya tenemos el primer car\u00e1cter copiado, pasamos al siguiente. Para ello tenemos que calcular la direcci\u00f3n de las coordenadas 6,12 y meter el resultado en DE. HL no debemos tocarlo pues ya est\u00e1 apuntando al segundo car\u00e1cter. Una vez hecho esto, copiamos los bytes igual que hicimos arriba y volvemos a repetirlo para las coordenadas 5, 13 y 6, 13.<\/p>\n\n\n\n<p>El m\u00e9todo es directo, pero tiene el problema de que no es demasiado eficiente: tenemos que mantener en alg\u00fan lado las coordenadas del car\u00e1cter actual, as\u00ed como el ancho y el alto para saber cuando hemos terminado, y recalcular la direcci\u00f3n de memoria de cada car\u00e1cter. Todo esto consume mucho tiempo de proceso, y por eso pocos juegos lo usan (uno que s\u00ed lo hace, por ejemplo, es <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/The_Trap_Door_(video_game)\" target=\"_blank\">The trapdoor<\/a>). Es por esto que, normalmente, se prefiere almacenar los sprites en formato <em>scanline<\/em>. La diferencia est\u00e1 en que, en lugar de almacenarlo car\u00e1cter a car\u00e1cter, se almacena por filas completas. As\u00ed, el orden para scanline en el ejemplo anterior ser\u00eda el siguiente:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan2.png\" rel=\"lightbox-4\"><img loading=\"lazy\" decoding=\"async\" width=\"328\" height=\"418\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan2.png\" alt=\"\" class=\"wp-image-2742\" srcset=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan2.png 328w, https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/scan2-235x300.png 235w\" sizes=\"auto, (max-width: 328px) 100vw, 328px\" \/><\/a><\/figure><\/div>\n\n\n\n<p>En este caso, la manera de trabajar es ligeramente diferente: partimos una vez m\u00e1s de las coordenadas X e Y en las que queremos pintar nuestro sprite, y procedemos a calcular la direcci\u00f3n de memoria de dichas coordenadas. Ahora s\u00f3lo necesitamos hacer dos bucles, uno que cuente de cero a ANCHO-1, que ser\u00e1 el interno, y otro que cuente de 0 a (ALTO*8)-1. En el bucle interno leeremos un byte del sprite y lo copiaremos en la direcci\u00f3n de pantalla, tras lo cual incrementaremos ambas direcciones en 1 y repetiremos la operaci\u00f3n tantas veces como ancho sea el sprite. Con eso ya hemos copiado un scanline. Ahora s\u00f3lo tenemos que sumar a la direcci\u00f3n de destino, la que apunta al buffer de pantalla, 32 &#8211; ANCHO para pasar al siguiente scanline de la pantalla y volver a repetir la copia anterior, y as\u00ed tantas veces como scanlines tenga el sprite, que ser\u00e1 ALTO * 8. Adem\u00e1s, el valor de 32 &#8211; ANCHO lo podemos tener almacenado en un registro, por lo que s\u00f3lo lo tendremos que calcular una vez, fuera de los bucles. Un ejemplo de c\u00f3digo ser\u00eda \u00e9ste:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted mycode\">; B contiene el alto, C el ancho, ambos en caract\u00e9res\n; HL contiene la direcci\u00f3n del primer byte del sprite\n; D contiene la coordenada Y, E la coordenada X\n; Asumimos que el buffer lineal de pantalla est\u00e1 en 0x8000\n\npintar_sprite:\n    set 7, D ; DE ya casi tiene la direcci\u00f3n, le falta el bit 7 de D\n    sla B\n    sla B ; rotamos tres veces B, que es igual que multiplicar por 8\n    sla B ; as\u00ed tenemos en B la altura en scanlines\n    ld A, 32\n    sub C     ; calculamos cuanto tenemos que sumar para pasar al\n    ld IXl, A ; siguiente scanline (32-ANCHO), y lo guardamos en IXl\nbucle:\n    push BC ; guardamos B (pues es el contador de scanlines) y C\n    ld B, 0 ; preparamos el bucle interno\n    ldir ; copiamos C bytes (C contiene el ancho)\n    ld C, IXl ; sumamos IXl a DE para pasar al siguiente scanline\n    ex DE, HL ; funciona porque B vale cero despu\u00e9s de LDIR\n    add HL, BC\n    ex DE, HL \n    pop BC ; recuperamos el n\u00famero de scanlines que nos quedan\n    djnz bucle2 ; y repetimos por cada scanline\n\n    call paint_screen ; llamamos a la funci\u00f3n que vuelca el buffer\n                      ; en la pantalla (si hemos pintado todos los\n                      ; sprites)<\/pre>\n\n\n\n<p>Con esta funci\u00f3n podemos pintar f\u00e1cilmente un sprite de cualquier tama\u00f1o en cualquier parte de la pantalla (a nivel de car\u00e1cter). Sin embargo, hay una condici\u00f3n inexcusable: el sprite tiene que entrar completamente en la pantalla, no puede \u00abestar medio fuera\u00bb. Si no lo tenemos en cuenta ocurrir\u00e1n \u00abcosas raras\u00bb. As\u00ed, si se sale por un lado aparecer\u00e1 por el opuesto (parecido a Pacman), lo cual es err\u00f3neo pero no peligros; pero si se sale \u00abpor arriba\u00bb o \u00abpor abajo\u00bb escribiremos en zonas de memoria fuera del buffer de pantalla, por lo que lo m\u00e1s probable es que nuestro c\u00f3digo se cuelgue. Afortunadamente este problema se puede solucionar.<\/p>\n\n\n\n<p>En el caso vertical (que el sprite \u00abse salga por arriba o por abajo\u00bb) resolverlo es muy sencillo: supongamos que se sale \u00abpor abajo\u00bb porque la coordenada Y es tan grande que Y + ALTURA es mayor que la m\u00e1xima coordenada \u00abpintable\u00bb (24 en el caso del Spectrum). En ese caso simplemente tenemos que cambiar la altura del sprite (la que le pasamos a la funci\u00f3n) por ALTURA &#8211; Y, con lo que el bucle externo pintar\u00e1 justo hasta el final de la pantalla, pero ni un solo byte m\u00e1s. Algo as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted mycode\">y_no_negativa:\n    ld A, D\n    cp 24\n    ret NC ; si est\u00e1 completamente fuera de la pantalla, no pintamos nada\n    add A, B ; comprobamos Y + ALTURA\n    cp 24\n    ; si hay acarreo, el sprite est\u00e1 completamente dentro de la pantalla\n    jr C, pintar_sprite ; lo pintamos normalmente\n    ld A, 24\n    sub D\n    ld B, A ; sustituimos la altura del sprite por 24 - Y\n\npintar_sprite:\n    ...<\/pre>\n\n\n\n<p>Y con esto, colocando este c\u00f3digo justo antes de la funci\u00f3n de pintar sprites, nuestro sprite ya se puede salir \u00abpor abajo\u00bb que no pasar\u00e1 nada. Para permitir que se pueda salir \u00abpor arriba\u00bb la soluci\u00f3n es la misma, pero a mayores tenemos que cambiar la direcci\u00f3n de inicio del sprite, salt\u00e1ndonos tantos scanlines como se queden fuera. Dado que Y ser\u00e1 negativa en este caso, el n\u00famero de scanlines que nos tenemos que saltar es de (-Y * 8) (ojo al signo menos), y como cada scanline tiene tantos bytes como ancho sea el sprite, tenemos que hacer una multiplicaci\u00f3n. Este ser\u00e1 el c\u00f3digo:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted mycode\">    bit 7, D ; comprueba si Y es negativa\n    jr Z, y_no_negativa\n    ld A, D\n    add A, B; sumando Y al alto nos da la nueva altura (no olvidar que Y es negativo, por lo que realmente es una resta)\n    ret Z\n    ret NC ; si no hay acarreo, o es cero, significa que el sprite est\u00e1 completamente fuera de la pantalla (B &lt; -Y)\n    ld B, A ; sustituimos la altura\n    push BC\n    push DE\n    ld A, C  ; necesitamos calcular (-Y) * ANCHO * 8 para saber\n    add A, A ; cuantos bytes saltar\n    add A, A\n    add A, A ; multiplicamos ANCHO por 8\n    ld E, A\n    ld A, D\n    neg ; A contiene -Y, que es el n\u00famero de filas a saltar (en caracteres)\n    ld B, A\n    ld D, 0\nbucle1:\n    add HL, DE ; HL + 8 * ANCHO * (-Y)\n    djnz bucle1\n    pop DE\n    pop BC\n    ld D, 0 ; pintamos a partir de la coordenada 0\n    jr pintar_sprite ; pintamos el sprite\n\ny_no_negativa:<\/pre>\n\n\n\n<p>As\u00ed, colocando este c\u00f3digo justo antes del anterior, ya podr\u00e1 salirse tambi\u00e9n \u00abpor arriba\u00bb sin que pase nada.<\/p>\n\n\n\n<p>Para tener en cuenta el que se salga \u00abpor los lados\u00bb el proceso es el mismo, pero con la diferencia de que hay que modificar la funci\u00f3n de pintado para que, al terminar de pintar un scanline del sprite (que ser\u00e1 m\u00e1s corto que el ancho real), se salte tantos bytes como haya de diferencia.<\/p>\n\n\n\n<p>As\u00ed se ve este ejemplo corriendo en un emulador:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"480\" style=\"aspect-ratio: 640 \/ 480;\" width=\"640\" controls src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2021\/01\/ejemplo_sprite1-1.mp4\"><\/video><\/figure>\n\n\n\n<p>El c\u00f3digo del ejemplo se puede descargar aqu\u00ed: <a rel=\"noreferrer noopener\" href=\"https:\/\/www.rastersoft.com\/ejemplo_sprite1.asm\" target=\"_blank\">ejemplo de sprite<\/a>.<\/p>\n\n\n\n<p>En el pr\u00f3ximo cap\u00edtulo hablar\u00e9 de m\u00e1scaras y transparencia.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Editado: el c\u00f3digo completo del ejemplo de esta entrada est\u00e1 disponible en un enlace al final. Llega la hora de pintar cosas en pantalla. La idea consiste en modificar los valores de la memoria RAM de v\u00eddeo para que, cuando la ULA (que es el chip encargado de generar la imagen) lea esos bytes, \u00e9stos &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=2759\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">Pintando en el Spectrum (5)<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,17,7],"tags":[20],"class_list":["post-2759","post","type-post","status-publish","format-standard","hentry","category-programacion","category-retrocomputacion","category-tutoriales","tag-pintando-en-el-spectrum"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2759","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2759"}],"version-history":[{"count":13,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2759\/revisions"}],"predecessor-version":[{"id":2802,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/2759\/revisions\/2802"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2759"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2759"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2759"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}