Skip to content

feat(sage): cablea el sabio como NPC + leyenda de glifos (#11) - #50

Open
blippip69 wants to merge 1 commit into
Bitcoindefi:mainfrom
blippip69:feat/sage-npc-wiring
Open

feat(sage): cablea el sabio como NPC + leyenda de glifos (#11)#50
blippip69 wants to merge 1 commit into
Bitcoindefi:mainfrom
blippip69:feat/sage-npc-wiring

Conversation

@blippip69

Copy link
Copy Markdown
Contributor

feat(sage): cablea el sabio como NPC + leyenda de glifos (#11)

Que se conecta

  1. lib/sage.js deja de estar huerfano (1494 lineas testeadas que el
    jugador no podia alcanzar):

    • Nuevo NPC el sabio en la plaza (action { kind: 'sage' }),
      lib/map.js.
    • lib/sage-npc.js (nuevo): SageSession — traduce una frase via
      Sage.ask(), agrega la regla resultante a script.txt y recarga por el
      camino normal (loadScript(true)), asi el script vive donde siempre.
    • La tecla ? sigue funcionando como camino manual; el sabio es ahora la
      entrada principal que la issue pedia.
  2. LEGEND consigue lector: tecla l en la ciudad abre un overlay con los
    glifos del mapa (esc/l cierra). Era exportada y no consumida por nadie.

  3. JSDoc huerfano en lib/game.js removido junto al estado nuevo documentado.

Tests

  • 3 tests nuevos: traduccion agrega la regla al script preservando lo anterior,
    frases sin sentido no escriben nada y la sesion cierra limpia,
  • contador de residentes actualizado 9 -> 10 (el sabio se suma al roster).
# tests = 67/67 pass
# asserts = 524/524 pass
node --check en game/render/map/sage-npc -> OK

Partido del main de hoy como pedia la issue. El resto del inventario de arte
muerto (makeCityRows, market(), sprites legacy) queda para un PR siguiente
para mantener este revisable.

Comment thread lib/game.js
Comment on lines +1304 to +1317
case 'sage':
this.sageSession = new SageSession(
null,
() => {
try { return fs.readFileSync(SCRIPT_PATH, 'utf8') } catch { return '' }
},
(next) => fs.writeFileSync(SCRIPT_PATH, next),
(lines) => {
for (const line of lines || []) this.say(line)
if (this.activeSlot) this.loadScript(true)
this.sageSession = null
}
).start()
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Bug: Sage NPC is unreachable — interactNpc ignores kind 'sage'

The sabio is placed as a CITY_NPC with action: { kind: 'sage' } (map.js), but NPCs are activated through interactNpc, which only handles shop/church/tavern and silently returns for anything else. The case 'sage' block that actually starts the SageSession lives in enter()'s tile switch, which is only reached via walker.action() (tile enter descriptors) — no tile carries kind: 'sage'. So talking to the sabio just prints his line and never opens the conversation, leaving the feature (the PR's main goal) unreachable. Add a sage branch to interactNpc (e.g. call this.startSage()), which already handles the say-line display and reload wiring.

Route NPC 'sage' action to startSage() inside interactNpc.:

if (action.kind === 'tavern') {
  this.restAtTavern()
  return true
}
if (action.kind === 'sage') {
  this.startSage()
  return true
}
return true
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment thread lib/game.js
Comment on lines +1670 to +1672
if (this.legendOpen) {
const { LEGEND } = require('./map.js')
const rows = Object.entries(LEGEND || {}).map(([glyph, meaning]) => `${glyph} ${meaning}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: LEGEND is an array but rendered via Object.entries

In view() the legend overlay does Object.entries(LEGEND || {}).map(([glyph, meaning]) => ${glyph} ${meaning}), but LEGEND in map.js is an array of { glyph, text } objects. Object.entries on an array yields index keys and object values, so every row renders as 0 [object Object], 1 [object Object], … instead of the intended glyph/meaning. Iterate the array and read the fields explicitly.

Map the array of {glyph,text} entries directly.:

const rows = (LEGEND || []).map((e) => `${e.glyph}  ${e.text}`)
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment thread lib/game.js
Comment on lines +1148 to +1162
// Map glyph legend (issue #11): the exported LEGEND finally has a reader.
if (key.matches(msg, 'l') && !this.field) {
this.legendOpen = true
return null
}
if (this.legendOpen) {
if (key.matches(msg, 'escape', 'l', 'enter')) {
this.legendOpen = false
this.say('cerraste la leyenda de glifos')
} else {
this.say('esc o l para cerrar la leyenda')
}
return null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: 'l' key hijacks sage input and opens the legend

In onKey, the legend toggle key.matches(msg, 'l') && !this.field is evaluated before the sageSession input branch. While the sage conversation is open in town, pressing the letter 'l' opens the legend overlay instead of appending to the sage buffer, so any sentence containing 'l' (extremely common in Spanish: 'la', 'el', 'ballesta') cannot be typed. Gate the legend toggle so it does not fire while a sage session is active (or move the sage/legend checks so an active session takes precedence).

Don't treat 'l' as the legend toggle while the sage is listening.:

if (key.matches(msg, 'l') && !this.field && !(this.sageSession && this.sageSession.active)) {
  this.legendOpen = true
  return null
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment thread lib/game.js
Comment on lines +299 to +313
sageKey(msg) {
const ch = typeof msg === 'string' ? msg : (msg && msg.key) || ''
if (!ch) return null
if (ch === 'enter' || ch === 'return') {
const sentence = this.sageBuffer.trim()
this.sageBuffer = ''
if (sentence) this.sageSession.ask(sentence)
else this.sageSession.close('el sabio espera una frase')
return null
}
if (ch === 'backspace' || ch === 'delete') {
this.sageBuffer = this.sageBuffer.slice(0, -1)
return render.sagePrompt(this.sageBuffer)
}
if (ch.length === 1) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: sageKey reads msg.key; printable chars use msg.sequence

sageKey extracts the character with (msg && msg.key) || '', but key events in this codebase carry the typed character in msg.sequence (see the test harness typeText, which sends { type: 'key', sequence, ... }, and the fact that all other key routing uses key.matches). Reading msg.key likely yields undefined for printable input, so the sage buffer never fills and enter/backspace comparisons also fail. There is no integration test covering this path, so it would go unnoticed. Use msg.sequence for the character and key.matches(msg, 'enter'/'backspace') for control keys.

Was this helpful? React with 👍 / 👎

Comment thread lib/game.js
Comment on lines +275 to +289
startSage() {
this.sageSession = new SageSession(
null,
() => {
try { return fs.readFileSync(SCRIPT_PATH, 'utf8') } catch { return '' }
},
(next) => fs.writeFileSync(SCRIPT_PATH, next),
(lines) => {
for (const line of lines || []) this.say(line)
if (this.activeSlot) this.loadScript(true)
this.sageSession = null
}
).start()
for (const line of this.sageSession.say || []) this.say(line)
return this.sageSession

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Quality: NPC 'sage' path doesn't show the sage's opening lines

The case 'sage' block in enter() builds and .start()s the SageSession but, unlike startSage(), never iterates this.sageSession.say to push the intro lines to the log. If the sage is entered through this path the player gets no on-screen prompt that the sage is listening. Prefer calling startSage() from both entry points to avoid the duplicated (and divergent) session-construction logic.

Was this helpful? React with 👍 / 👎

Comment thread lib/sage-npc.js
* by the normal script reload path.
*/

const fs = require('bare-fs')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Quality: Unused fs import in sage-npc.js

sage-npc.js requires bare-fs at the top, but SageSession receives its read/write functions via constructor injection and never touches fs directly. The import is dead and can be removed to keep the module's IO boundary honest.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown
CI failed: Linting failed because Prettier found code style issues in 3 files.

Overview

Prettier code formatting checks failed during the lint job, identifying style issues in 3 files. Total logs analyzed: 1.

Failures

Prettier Formatting Check Failed (confidence: high)

  • Type: tooling
  • Affected jobs: 97956474888
  • Related to change: yes
  • Root cause: Prettier formatting check failed because code style issues were found in 3 files.
  • Suggested fix: Run 'npx prettier . --write' locally to fix formatting issues, then commit and push the changes.

Summary

  • Change-related failures: 1 Prettier formatting check failure during the lint stage
  • Infrastructure/flaky failures: None
  • Recommended action: Run Prettier with --write to format the codebase correctly and push the update.
Code Review 🚫 Blocked 0 resolved / 6 findings

Wires up the sage NPC and glyph legend overlay, but the NPC interaction is currently unreachable, the legend rendering crashes due to type mismatch, and several keyboard handling bugs require attention.

🚨 Bug: Sage NPC is unreachable — interactNpc ignores kind 'sage'

📄 lib/game.js:533-547 📄 lib/game.js:1304-1317 📄 lib/map.js:374-385

The sabio is placed as a CITY_NPC with action: { kind: 'sage' } (map.js), but NPCs are activated through interactNpc, which only handles shop/church/tavern and silently returns for anything else. The case 'sage' block that actually starts the SageSession lives in enter()'s tile switch, which is only reached via walker.action() (tile enter descriptors) — no tile carries kind: 'sage'. So talking to the sabio just prints his line and never opens the conversation, leaving the feature (the PR's main goal) unreachable. Add a sage branch to interactNpc (e.g. call this.startSage()), which already handles the say-line display and reload wiring.

Route NPC 'sage' action to startSage() inside interactNpc.
if (action.kind === 'tavern') {
  this.restAtTavern()
  return true
}
if (action.kind === 'sage') {
  this.startSage()
  return true
}
return true
⚠️ Bug: LEGEND is an array but rendered via Object.entries

📄 lib/game.js:1670-1672 📄 lib/map.js:133-143

In view() the legend overlay does Object.entries(LEGEND || {}).map(([glyph, meaning]) => ${glyph} ${meaning}), but LEGEND in map.js is an array of { glyph, text } objects. Object.entries on an array yields index keys and object values, so every row renders as 0 [object Object], 1 [object Object], … instead of the intended glyph/meaning. Iterate the array and read the fields explicitly.

Map the array of {glyph,text} entries directly.
const rows = (LEGEND || []).map((e) => `${e.glyph}  ${e.text}`)
⚠️ Bug: 'l' key hijacks sage input and opens the legend

📄 lib/game.js:1148-1162

In onKey, the legend toggle key.matches(msg, 'l') && !this.field is evaluated before the sageSession input branch. While the sage conversation is open in town, pressing the letter 'l' opens the legend overlay instead of appending to the sage buffer, so any sentence containing 'l' (extremely common in Spanish: 'la', 'el', 'ballesta') cannot be typed. Gate the legend toggle so it does not fire while a sage session is active (or move the sage/legend checks so an active session takes precedence).

Don't treat 'l' as the legend toggle while the sage is listening.
if (key.matches(msg, 'l') && !this.field && !(this.sageSession && this.sageSession.active)) {
  this.legendOpen = true
  return null
}
⚠️ Bug: sageKey reads msg.key; printable chars use msg.sequence

📄 lib/game.js:299-313 📄 test/index.js:27-31

sageKey extracts the character with (msg && msg.key) || '', but key events in this codebase carry the typed character in msg.sequence (see the test harness typeText, which sends { type: 'key', sequence, ... }, and the fact that all other key routing uses key.matches). Reading msg.key likely yields undefined for printable input, so the sage buffer never fills and enter/backspace comparisons also fail. There is no integration test covering this path, so it would go unnoticed. Use msg.sequence for the character and key.matches(msg, 'enter'/'backspace') for control keys.

💡 Quality: NPC 'sage' path doesn't show the sage's opening lines

📄 lib/game.js:275-289 📄 lib/game.js:1304-1317

The case 'sage' block in enter() builds and .start()s the SageSession but, unlike startSage(), never iterates this.sageSession.say to push the intro lines to the log. If the sage is entered through this path the player gets no on-screen prompt that the sage is listening. Prefer calling startSage() from both entry points to avoid the duplicated (and divergent) session-construction logic.

💡 Quality: Unused fs import in sage-npc.js

📄 lib/sage-npc.js:11

sage-npc.js requires bare-fs at the top, but SageSession receives its read/write functions via constructor injection and never touches fs directly. The import is dead and can be removed to keep the module's IO boundary honest.

🤖 Prompt for agents
Code Review: Wires up the sage NPC and glyph legend overlay, but the NPC interaction is currently unreachable, the legend rendering crashes due to type mismatch, and several keyboard handling bugs require attention.

1. 🚨 Bug: Sage NPC is unreachable — interactNpc ignores kind 'sage'
   Files: lib/game.js:533-547, lib/game.js:1304-1317, lib/map.js:374-385

   The sabio is placed as a CITY_NPC with `action: { kind: 'sage' }` (map.js), but NPCs are activated through `interactNpc`, which only handles `shop`/`church`/`tavern` and silently returns for anything else. The `case 'sage'` block that actually starts the SageSession lives in `enter()`'s tile switch, which is only reached via `walker.action()` (tile `enter` descriptors) — no tile carries `kind: 'sage'`. So talking to the sabio just prints his line and never opens the conversation, leaving the feature (the PR's main goal) unreachable. Add a `sage` branch to `interactNpc` (e.g. call `this.startSage()`), which already handles the say-line display and reload wiring.

   Fix (Route NPC 'sage' action to startSage() inside interactNpc.):
   if (action.kind === 'tavern') {
     this.restAtTavern()
     return true
   }
   if (action.kind === 'sage') {
     this.startSage()
     return true
   }
   return true

2. ⚠️ Bug: LEGEND is an array but rendered via Object.entries
   Files: lib/game.js:1670-1672, lib/map.js:133-143

   In `view()` the legend overlay does `Object.entries(LEGEND || {}).map(([glyph, meaning]) => `${glyph}  ${meaning}`)`, but `LEGEND` in map.js is an array of `{ glyph, text }` objects. `Object.entries` on an array yields index keys and object values, so every row renders as `0  [object Object]`, `1  [object Object]`, … instead of the intended glyph/meaning. Iterate the array and read the fields explicitly.

   Fix (Map the array of {glyph,text} entries directly.):
   const rows = (LEGEND || []).map((e) => `${e.glyph}  ${e.text}`)

3. ⚠️ Bug: 'l' key hijacks sage input and opens the legend
   Files: lib/game.js:1148-1162

   In `onKey`, the legend toggle `key.matches(msg, 'l') && !this.field` is evaluated before the `sageSession` input branch. While the sage conversation is open in town, pressing the letter 'l' opens the legend overlay instead of appending to the sage buffer, so any sentence containing 'l' (extremely common in Spanish: 'la', 'el', 'ballesta') cannot be typed. Gate the legend toggle so it does not fire while a sage session is active (or move the sage/legend checks so an active session takes precedence).

   Fix (Don't treat 'l' as the legend toggle while the sage is listening.):
   if (key.matches(msg, 'l') && !this.field && !(this.sageSession && this.sageSession.active)) {
     this.legendOpen = true
     return null
   }

4. ⚠️ Bug: sageKey reads msg.key; printable chars use msg.sequence
   Files: lib/game.js:299-313, test/index.js:27-31

   `sageKey` extracts the character with `(msg && msg.key) || ''`, but key events in this codebase carry the typed character in `msg.sequence` (see the test harness `typeText`, which sends `{ type: 'key', sequence, ... }`, and the fact that all other key routing uses `key.matches`). Reading `msg.key` likely yields undefined for printable input, so the sage buffer never fills and enter/backspace comparisons also fail. There is no integration test covering this path, so it would go unnoticed. Use `msg.sequence` for the character and `key.matches(msg, 'enter'/'backspace')` for control keys.

5. 💡 Quality: NPC 'sage' path doesn't show the sage's opening lines
   Files: lib/game.js:275-289, lib/game.js:1304-1317

   The `case 'sage'` block in `enter()` builds and `.start()`s the SageSession but, unlike `startSage()`, never iterates `this.sageSession.say` to push the intro lines to the log. If the sage is entered through this path the player gets no on-screen prompt that the sage is listening. Prefer calling `startSage()` from both entry points to avoid the duplicated (and divergent) session-construction logic.

6. 💡 Quality: Unused fs import in sage-npc.js
   Files: lib/sage-npc.js:11

   `sage-npc.js` requires `bare-fs` at the top, but SageSession receives its read/write functions via constructor injection and never touches `fs` directly. The import is dead and can be removed to keep the module's IO boundary honest.

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Important

Your trial ends in 7 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@leocagli

Copy link
Copy Markdown
Collaborator

Te dejo un mensaje que aplica a todos tus PRs abiertos, para no repetirlo seis veces.

Lo primero: tu trabajo entra

Tu PR #45, el arbitro Soroban de commit-reveal para los duelos, ya esta en main.
Y sos el que mas volumen entrego en el repositorio. Esto no es una queja.

Estas a un comando de cinco merges

Habilite la ejecucion del CI en tus PRs, que estaba trabada esperando aprobacion de
mantenedor (GitHub lo pide para colaboradores nuevos; por eso figuraban sin checks).
Ahora corrio, y el resultado es este:

#39 #44 #47 #49 #50    7 checks pasan, 1 falla
#38                    1 pasa, 7 fallan

En los cinco primeros falla unicamente Lint, y siempre por lo mismo:

> prettier . --check && lunte
[warn] test/hero-clip.test.js
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

Los siete builds de plataforma pasan. Es formato del archivo de test que agregaste,
nada del codigo. npm run format, commit y push, y los cinco quedan verdes.

El #38 es distinto, ahi hay un problema real

Ese no falla por formato: el archivo de tests no parsea, asi que la suite entera
no arranca.

SyntaxError: Unexpected end of input

El test nuevo quedo insertado antes del }) que cerraba el test anterior
(the world boss animates powers with real field damage), asi que el archivo termina
con una llave de menos. Cerra el test anterior antes de abrir el tuyo y se arregla.

Dicho sea de paso: el arreglo del #38 en si es el mejor de los dos que llegaron
para esa issue. Tocar solo el sitio de llamada y dejar el default de la firma
tranquilo es lo correcto.

Sobre la wallet

Vi que la pegas en los comentarios. No hace falta, y lo aclaro porque el problema era
nuestro, no tuyo: no estaba escrito en ningun lado como se cobra.

El pago lo maneja enteramente la plataforma de la campana. Una direccion en un
comentario no registra nada, no acelera nada, y no es por donde te va a llegar. Lo
dejamos escrito en el CONTRIBUTING.md que acabamos de agregar, junto con como se
reclama una issue y que hace mergeable un PR.

Una sugerencia, y es solo eso

Tenes 36 PRs abiertos en la organizacion. Ninguno es malo, pero revisarlos lleva
tiempo y ninguno avanza mientras esperan. Si mandas los cinco arreglos de formato
primero, esos entran rapido y el resto queda mas facil de mirar de a poco.

Cuando empujes los cambios avisá por aca y los vuelvo a correr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants