Skip to content

Commit 1f754ea

Browse files
Leo Caglieroclaude
andcommitted
fix(campo): el campo se dibujaba como una sola linea y sin heroe
field.render() devuelve una fila por string y compose() espera un string solo. Al pasarle el array, box() lo convertia con String(), las 22 filas quedaban unidas por comas en un unico renglon, y el panel mostraba una franja de pasto con el heroe en ninguna parte. No tiraba ninguna excepcion y los 33 tests seguian en verde, asi que el campo estuvo roto en silencio desde que existe. Aparte el ancho: mapPane divide por CELL_W adentro y despues pinta cada tile con ese ancho. El campo pinta una columna por celda, porque Y_SCALE ya paga la relacion 1:2 de una celda de terminal en las distancias. Dividir tambien aca metia el mundo a media escala en media caja. Va con un test que mira el frame ya pintado, que es lo unico que podia agarrar esto: verifica que el pasto cubra varias filas, que el heroe aparezca, y que el dibujo llegue hasta el borde derecho del panel. Da rojo contra el codigo viejo, comprobado. La captura campo.png del README se rehizo. Antes salia repetida con la del encuentro porque el script de capturas la tomaba en un paso fijo que la pelea siempre se adelantaba, y caia en un fallback que fotografiaba lo que hubiera en pantalla. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e033bd4 commit 1f754ea

3 files changed

Lines changed: 66 additions & 1 deletion

File tree

docs/screens/campo.png

157 Bytes
Loading

lib/game.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,22 @@ class Runa {
762762
height: this.height,
763763
title: 'runa',
764764
mainCaption: snap.zone || 'el campo',
765-
main: (w, h) => this.field.render(Math.floor(w / render.CELL_W), h),
765+
// Two things this line has to get right, both of which it used to get
766+
// wrong and neither of which crashed.
767+
//
768+
// render() hands back one string per row and compose() wants a single
769+
// string. Passing the array straight through meant box() stringified
770+
// it, the rows were joined by commas into one line, and the pane showed
771+
// a single stripe of terrain with the hero nowhere on it. Nothing
772+
// threw. The field was simply invisible, which is exactly the kind of
773+
// bug that survives all the way into a demo.
774+
//
775+
// And the width is the whole pane, not the pane divided by CELL_W.
776+
// mapPane does that division itself and then paints each tile that many
777+
// columns wide; the field paints one column per cell, because Y_SCALE
778+
// already pays for the 1:2 aspect of a terminal cell in the distance
779+
// maths. Dividing here too drew the world at half scale into half a box.
780+
main: (w, h) => this.field.render(w, h).join('\n'),
766781
stats: base.stats,
767782
log: base.log,
768783
footer: 'flechas mover | < volver a la ciudad | r recargar script | q salir'

test/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,53 @@ test('city art remains rectangular and walkable', (t) => {
4949
t.is(TILES[';'].solid, false)
5050
t.is(TILES.O.solid, true)
5151
})
52+
53+
test('the field pane paints the whole field, not one stringified line', (t) => {
54+
const game = new Runa()
55+
game.update({ type: 'resize', width: 88, height: 26 })
56+
game.onKey({ type: 'key', is: (...keys) => keys.includes('x') })
57+
58+
// Find the gate the map itself declares. Hardcoding its coordinates would let
59+
// somebody move the gate and quietly turn this test into a no-op.
60+
let gate = null
61+
const rows = MAPS.city.rows
62+
for (let y = 0; y < rows.length && gate === null; y++) {
63+
for (let x = 0; x < rows[y].length; x++) {
64+
const tile = TILES[rows[y][x]]
65+
if (tile && tile.enter && tile.enter.kind === 'travel') {
66+
gate = { x, y }
67+
break
68+
}
69+
}
70+
}
71+
t.ok(gate !== null, 'the city has a gate out to the field')
72+
73+
game.walker.placeAt('city', gate.x, gate.y)
74+
game.onKey({ type: 'key', is: (...keys) => keys.includes('e') })
75+
t.ok(game.field !== null, 'stepping through the gate opens the field')
76+
77+
const lines = style.stripAnsi(game.view()).split('\n')
78+
const pane = lines.map((line) => line.slice(1, 58))
79+
const painted = pane.filter((line) => /[.,~#@<]/.test(line))
80+
81+
// The bug this guards against, in full, because it cost a demo:
82+
//
83+
// field.render() hands back one string per row and compose() wants a single
84+
// string. Passing the array through meant box() stringified it, all 22 rows
85+
// were joined by commas into one line, and the pane showed a single stripe of
86+
// terrain with the hero nowhere on it. Nothing threw and every other test
87+
// stayed green, so only an assertion about the painted frame can see it.
88+
t.ok(painted.length > 10, 'terrain covers the pane rather than a single row')
89+
t.ok(
90+
lines.some((line) => line.includes('@')),
91+
'the hero is somewhere on screen'
92+
)
93+
94+
// And the field is drawn at full width. Dividing the pane by CELL_W as well
95+
// as letting the field paint one column per cell squeezed the world into the
96+
// left half of the box, which reads as a rendering glitch rather than a bug.
97+
t.ok(
98+
painted.some((line) => /[.,~#]/.test(line.slice(40))),
99+
'the field reaches the right hand side of the pane'
100+
)
101+
})

0 commit comments

Comments
 (0)