Skip to content

feat: la semilla del dia, el mismo campo para todos los que jueguen hoy - #48

Closed
leocagli wants to merge 5 commits into
mainfrom
semilla-del-dia-v3
Closed

feat: la semilla del dia, el mismo campo para todos los que jueguen hoy#48
leocagli wants to merge 5 commits into
mainfrom
semilla-del-dia-v3

Conversation

@leocagli

Copy link
Copy Markdown
Collaborator

Reemplaza al #37, que entro en conflicto cuando se mergeo el #19: ese PR saco el bloque del fieldGate, y ahi nacia uno de los tres new Field(...) que esta rama sembraba. Ahora quedan dos en lib/game.js y se siembran los dos.

Verificado contra el main de hoy (263e53a): 73 tests / 532 aserciones, lint limpio, git diff --check limpio. La base de main es 65 y 517.

Primero de tres. Este trae la semilla del dia; el jefe del mundo y los duelos
necesitan contratos y van aparte.

Que cambia para el que juega

Hoy el campo se sembraba con el valor por defecto en los tres lugares donde nace
un Field, asi que cada salida a la pradera era exactamente la misma pradera.
Eso es la issue #3.

Ahora la semilla sale del numero de ledger de Stellar partido en bloques de un
dia. Todos los que jueguen hoy caminan el mismo campo, con los mismos bichos
en los mismos lugares. Manana es otro.

Lo que se compara entre jugadores deja de ser oro suelto y pasa a ser cuanto
aguantaste vos en el campo de hoy.

Por que la cadena y no un servidor

La semilla tiene que cumplir tres cosas: ser igual para todos, cambiar sola, y
que no la haya elegido nadie. Las dos primeras las da cualquier servidor. La
tercera exige que vos puedas comprobarlo por tu cuenta, y por eso sirve un
contador publico que ni el dueno del juego puede mover.

Lo que NO pide

Ni billetera, ni fondos, ni transacciones. Es un GET. Alguien que nunca oyo
hablar de cripto lo juega sin enterarse.

Sin internet cae a una semilla local derivada del jugador, que igual arregla la
issue #3 para el que juega solo. La consulta no bloquea ningun cuadro y nadie la
espera.

Verificado

Dos clientes independientes, sin coordinarse:

primera consulta : {"seed":3310838056,"day":250,"sequence":4320980}
otro jugador     : {"seed":3310838056,"day":250,"sequence":4320980}
coinciden        : true

Y esa semilla llega al mundo:

campo de hoy    = 88721f6c  28 bichos   0@28,13 1@28,20 2@13,30
otro jugador    = 88721f6c  iguales:  true
otro dia        = 8f57e2ee  distinto: true
el viejo (1)    = 2bb6c4bf  distinto: true

La parte tecnica que costo

@stellar/stellar-sdk no carga en Bare: pide TextDecoder, y despues
Event, porque trae su propio cliente HTTP pensado para navegador o Node.

Lo que si funciona es @stellar/stellar-base empaquetado con
--conditions=browser. Esa bandera es la clave: hace que @noble/curves elija
su camino de WebCrypto en vez del que hace require('node:crypto'), que es el
modulo que Bare no tiene. Faltan tres globals mas y los pone lib/stellar.js.

Todo esto queda explicado en vendor/README.md, incluido por que el bundle esta
commiteado: npm run make arma binarios para seis plataformas, y quien clona
tiene que poder jugar sin pasos extra. Se regenera con npm run vendor:stellar.

Sobre los tests

Ocho nuevos, y ninguno toca la red. Un test que pida la semilla de verdad se
pone rojo cuando el RPC publico tiene un mal dia, y eso no prueba el juego sino
el clima. El RPC se reemplaza por una funcion que devuelve el ledger que
queramos, y se prueba lo que decide runa: como se mezcla la semilla, que dos
momentos del mismo dia den el mismo campo, que dias vecinos den campos bien
distintos y no casi calcados, y que si la cadena no contesta no se rompa nada.

tests   = 70/70 pass   (eran 62)
asserts = 488/488 pass
lint    = limpio

Un detalle que elegi a conciencia

Se usa el numero de ledger y no su hash. El hash del ledger que abrio el dia
seria impredecible, que suena mejor, pero para leerlo hay que pedirle al RPC un
ledger de hace 24 horas y eso cae justo en el borde de lo que el RPC conserva.
Un jugador lo conseguiria y otro no, y entonces no caminarian el mismo campo,
que era todo el punto.

Entre impredecible y que todos coincidan, coincidir gana: el mapa del dia se
comparte igual apenas el primero lo publique.

Refs #3

Reconstruido sobre el main de hoy. La version anterior entro en conflicto cuando
se mergeo el PR #19, que saco el bloque del `fieldGate`: ahi nacia uno de los tres
`new Field(...)` que esta rama sembraba. Ahora quedan dos y se siembran los dos.

El campo se sembraba con el valor por defecto, asi que cada salida a la pradera era
exactamente la misma pradera. Es la issue #3, y seguia viva en main: los
`new Field(...)` no llevaban semilla.

Ahora la semilla sale del numero de ledger de Stellar partido en bloques de un dia.
Todos los que jueguen hoy caminan el mismo campo, con los mismos bichos en los
mismos lugares. Manana es otro. Verificado: dos clientes independientes sacan
seed 3310838056 para el dia 250, y esa semilla produce 28 bichos identicos.

Por que la cadena y no un servidor: la semilla tiene que ser igual para todos,
cambiar sola, y sobre todo que no la haya elegido nadie. Las dos primeras las da
cualquier servidor. La tercera exige que el jugador pueda comprobarlo por su
cuenta, y por eso sirve un contador publico que ni el dueno del juego puede mover.

No pide billetera, ni fondos, ni transacciones. Es un GET. Sin internet cae a una
semilla local derivada del jugador, que igual arregla la issue #3 para el que juega
solo. La consulta no bloquea ningun cuadro y nadie la espera.

@stellar/stellar-sdk no carga en Bare: pide TextDecoder y despues Event. Se usa
@stellar/stellar-base empaquetado con --conditions=browser, que es lo que hace que
@noble/curves deje de pedir node:crypto. Los tres globals que faltan los pone
lib/stellar.js. Todo explicado en vendor/README.md.

Ocho tests nuevos y ninguno toca la red: un test que pida la semilla de verdad se
pone rojo cuando el RPC publico tiene un mal dia, y eso no prueba el juego sino el
clima.

  main hoy   65 tests, 517 asserts
  con esto   73 tests, 532 asserts

lint limpio y git diff --check limpio.

Refs #3
Comment thread lib/game.js Outdated
Comment thread lib/stellar.js
Comment on lines +245 to +259
async dailySeed() {
try {
const l = await this.rpc('getLatestLedger')
const day = Math.floor(l.sequence / LEDGERS_PER_DAY)

// Dispersion. El indice del dia crece de a uno, y campos de dias vecinos
// saldrian casi calcados si se lo pasaramos crudo al generador.
let s = day >>> 0
s = Math.imul(s ^ (s >>> 16), 2246822507) >>> 0
s = Math.imul(s ^ (s >>> 13), 3266489909) >>> 0
s = (s ^ (s >>> 16)) >>> 0

this.day = day
this.seed = s
return { seed: s, day, sequence: l.sequence }

@gitar-bot gitar-bot Bot Aug 25, 2026

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: dailySeed dereferences l.sequence without null guard

In dailySeed, this.rpc('getLatestLedger') can resolve to null (rpc returns r.body.result, which may be null/undefined), and the code immediately reads l.sequence. If the RPC returns a non-error body without a result, l is null and Math.floor(l.sequence / ...) throws — though it is caught and returns null, so behavior degrades gracefully. Consider guarding if (!l) return null for clarity and to avoid relying on the catch for a normal-ish response shape.

Guard against a missing ledger result.:

const l = await this.rpc('getLatestLedger')
if (!l || typeof l.sequence !== 'number') return null
const day = Math.floor(l.sequence / LEDGERS_PER_DAY)

Was this helpful? React with 👍 / 👎

Comment thread lib/world-boss-event.js
const dx = Math.sign(target.x - this.x) || -1
const sx = this.x + dx * (BODY_HALF_WIDTH + 1)
for (const offset of [-2, -1, 0, 1, 2]) {
for (let step = 0; step <= Math.max(8, attack.reach * 2); step += 2) {

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: barrido telegraph uses attack.reach*2 without Number coercion

Unlike the other telegraph branches which use Number(attack.reach) || 4, the barrido branch multiplies attack.reach * 2 directly. If a future sweep-type attack were defined without a reach (or a non-numeric one), this evaluates to NaN, Math.max(8, NaN) is NaN, and the loop step <= NaN never runs — silently dropping the warning cells for a damaging sweep. Current data defines reach:6 so it works today, but coercing keeps it robust: use Math.max(8, (Number(attack.reach) || 4) * 2).

Was this helpful? React with 👍 / 👎

root and others added 2 commits August 26, 2026 00:02
El Coliseo ya existe como mapa propio y publica duelSpawns, arenaBounds y
refereeSpawn. Esto no dibuja nada: se ocupa de quien va de que lado, donde
vuelve cada uno al terminar, y que nadie se escape a las gradas mientras la
pelea vive.

Tres decisiones que ordenan el resto.

Los lados se calculan, no se acuerdan. Dos jugadores sin servidor tienen que
llegar al mismo reparto por su cuenta, y cualquier negociacion es un mensaje que
se puede perder o contradecir. Comparar las dos identidades y que la menor sea
oeste no necesita ningun mensaje: los dos hacen la misma cuenta y les da lo
mismo. Hay un test que lo prueba con 72 pares.

La vuelta se guarda al entrar. Un duelo termina porque alguien gano, porque se
rindio, o porque se corto internet. El ultimo caso es el que manda el diseno: si
el regreso dependiera de un mensaje de cierre, el que se desconecta quedaria
varado en el Coliseo para siempre. Por eso `from` se guarda antes de salir y
alcanza con tenerlo. Los tres motivos vuelven al mismo lugar, y terminar dos
veces es inofensivo.

Ninguna coordenada esta escrita aca. Todas salen de MAPS.coliseum y se devuelven
copiadas, para que mover el arte no obligue a tocar la logica y para que nadie le
corra los puntos al mapa sin querer. Hay un test que lo vigila.

El punto 6 del traspaso sale gratis: la capa de presencia ya expone
update(mapId, x, y) y others(mapId), asi que con los dos jugadores parados en el
mapa `coliseum` el rival se replica sin codigo nuevo.

Un test empezo rojo y el mapa tenia razon: los dos lados se ven asimetricos
contra arenaBounds.center.x, que es 64 redondeado, pero el Coliseo mide 128 y su
centro real cae en 63.5. Contra ese, 40 y 87 estan a 23.5 los dos. El test ahora
mide contra el centro geometrico y quedo documentado por que.

Este commit no toca game.js todavia: es la capa de sesion y sus pruebas. El
cableado del recorrido completo va aparte para que se pueda revisar de a una
cosa por vez.

  antes    65 tests, 515 asserts
  ahora    82 tests, 630 asserts     (+17 tests, +115 asserts)

lint limpio y git diff --check limpio.
Comment thread lib/game.js Outdated
Comment on lines +986 to +988
// PvP cooldowns share the visible game clock, but attacks never happen by
// themselves: every damaging action still comes from an ordered input.
if (this.duel && this.duel.active && this.duelCombat) this.duelCombat.tick()

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: Duel cooldown ticks on wall-clock, risking cross-peer divergence

onTick() advances duelCombat.tick() on the local wall-clock game clock, decrementing both fighters' cooldownLeft. DuelCombat's documented determinism ("same moves, attacks and ticks produce the same winner") only holds if ticks are delivered in the same order/count to both peers. Since attacks are gated by cooldownLeft, two peers with different frame timing between attack inputs can disagree on whether an attack was on-cooldown and thus compute different winners — which would break the Soroban consensus/settlement the design relies on. This is latent because the network transport is still pending and current tests advance ticks explicitly. When wiring the P2P layer, treat tick advancement as part of the ordered transported input stream (or derive elapsed ticks from a shared logical clock) rather than driving it from onTick's wall clock.

Was this helpful? React with 👍 / 👎

Comment thread lib/game.js
@@ -393,6 +477,10 @@ class Runa {
* it is dropped whole rather than retried.
*/
dropPresence() {

@gitar-bot gitar-bot Bot Aug 26, 2026

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: Cancelling stale outgoing duel invite derefs null presence

When an outgoing duel invite is pending and the network drops, dropPresence() (lib/game.js:479-488) sets this.presence = null but never clears this.duelInvite or this.duelMessages. If the player then presses N/Esc, the out-invite branch calls this.presence.sendDuel('decline', ...) with no null guard (lib/game.js:1612-1620), throwing a TypeError. onKey is invoked from update() without a try/catch, so this crashes the render loop. The same unguarded this.presence.sendDuel exists in the busy 'challenge' branch of handleDuelMessage (lib/game.js:1230-1236), reachable when a queued challenge is processed after the drop. Fix: clear duelInvite/duelMessages in dropPresence() and/or guard the sendDuel calls with this.presence.

Clear pending invite and queued duel messages when presence is dropped, so no later handler dereferences a null presence.:

dropPresence() {
  if (this.duelNetwork && this.duel && this.duelCombat) {
    this.duelCombat.surrender(this.duelNetwork.rivalId)
    this.finishDuel('desconexion', { broadcast: false })
  }
  this.presence = null
  this.presenceStarted = false
  this.online = false
  this.arrivals = []
  this.duelInvite = null
  this.duelMessages = []
}

Was this helpful? React with 👍 / 👎

Comment thread lib/game.js
Comment on lines +1410 to +1416
this.presence.sendDuel('result', network.rivalId, {
duelId: network.duelId,
winner: result.winner,
loser: result.loser,
reason: result.reason
})
}

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: 'result' duel packet is broadcast but never handled

finishDuel broadcasts a result duel message (lib/game.js:1410-1416) and result is a valid DUEL_KIND, but handleDuelMessage has no branch for it (lib/game.js:1216-1291) — it falls through and returns false. Both peers already end the duel deterministically via the ordered step replay, so the packet is effectively dead traffic today. Either wire it up (for the intended contract/settlement path) or drop the broadcast to avoid confusion about which side is authoritative.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 1 resolved / 7 findings

Implements a shared daily seed derived from the Stellar ledger so all players explore the same field, but new games skip calling startChain and cancelDuelInvite can crash on null presence.

⚠️ Bug: beginNewGame never calls startChain, so new games skip day seed

📄 lib/game.js:929-931

beginNewGame calls startPresence() but not startChain(), unlike loadSlot. A player who starts a fresh game (rather than loading a slot) never requests the daily seed, so this.daySeed stays null and fieldSeed() falls back to the local player-derived seed. That contradicts the feature's core promise that everyone playing today walks the same field — new players won't until they reload a save. Add this.startChain() in beginNewGame alongside startPresence().

Fetch the daily seed for new games too.
this.say(`bienvenido, ${this.name}. usa wasd o flechas; las puertas se abren al pisarlas.`)
this.startPresence()
this.startChain()
this.saveCurrent()
return true
⚠️ Bug: Cancelling stale outgoing duel invite derefs null presence

📄 lib/game.js:479-488 📄 lib/game.js:1230-1236 📄 lib/game.js:1612-1620

When an outgoing duel invite is pending and the network drops, dropPresence() (lib/game.js:479-488) sets this.presence = null but never clears this.duelInvite or this.duelMessages. If the player then presses N/Esc, the out-invite branch calls this.presence.sendDuel('decline', ...) with no null guard (lib/game.js:1612-1620), throwing a TypeError. onKey is invoked from update() without a try/catch, so this crashes the render loop. The same unguarded this.presence.sendDuel exists in the busy 'challenge' branch of handleDuelMessage (lib/game.js:1230-1236), reachable when a queued challenge is processed after the drop. Fix: clear duelInvite/duelMessages in dropPresence() and/or guard the sendDuel calls with this.presence.

Clear pending invite and queued duel messages when presence is dropped, so no later handler dereferences a null presence.
dropPresence() {
  if (this.duelNetwork && this.duel && this.duelCombat) {
    this.duelCombat.surrender(this.duelNetwork.rivalId)
    this.finishDuel('desconexion', { broadcast: false })
  }
  this.presence = null
  this.presenceStarted = false
  this.online = false
  this.arrivals = []
  this.duelInvite = null
  this.duelMessages = []
}
💡 Bug: dailySeed dereferences l.sequence without null guard

📄 lib/stellar.js:245-259

In dailySeed, this.rpc('getLatestLedger') can resolve to null (rpc returns r.body.result, which may be null/undefined), and the code immediately reads l.sequence. If the RPC returns a non-error body without a result, l is null and Math.floor(l.sequence / ...) throws — though it is caught and returns null, so behavior degrades gracefully. Consider guarding if (!l) return null for clarity and to avoid relying on the catch for a normal-ish response shape.

Guard against a missing ledger result.
const l = await this.rpc('getLatestLedger')
if (!l || typeof l.sequence !== 'number') return null
const day = Math.floor(l.sequence / LEDGERS_PER_DAY)
💡 Quality: barrido telegraph uses attack.reach*2 without Number coercion

📄 lib/world-boss-event.js:303

Unlike the other telegraph branches which use Number(attack.reach) || 4, the barrido branch multiplies attack.reach * 2 directly. If a future sweep-type attack were defined without a reach (or a non-numeric one), this evaluates to NaN, Math.max(8, NaN) is NaN, and the loop step <= NaN never runs — silently dropping the warning cells for a damaging sweep. Current data defines reach:6 so it works today, but coercing keeps it robust: use Math.max(8, (Number(attack.reach) || 4) * 2).

💡 Quality: Duel cooldown ticks on wall-clock, risking cross-peer divergence

📄 lib/game.js:986-988 📄 lib/duel.js:238-246 📄 lib/duel.js:309-321

onTick() advances duelCombat.tick() on the local wall-clock game clock, decrementing both fighters' cooldownLeft. DuelCombat's documented determinism ("same moves, attacks and ticks produce the same winner") only holds if ticks are delivered in the same order/count to both peers. Since attacks are gated by cooldownLeft, two peers with different frame timing between attack inputs can disagree on whether an attack was on-cooldown and thus compute different winners — which would break the Soroban consensus/settlement the design relies on. This is latent because the network transport is still pending and current tests advance ticks explicitly. When wiring the P2P layer, treat tick advancement as part of the ordered transported input stream (or derive elapsed ticks from a shared logical clock) rather than driving it from onTick's wall clock.

💡 Quality: 'result' duel packet is broadcast but never handled

📄 lib/game.js:1410-1416 📄 lib/game.js:1216-1230

finishDuel broadcasts a result duel message (lib/game.js:1410-1416) and result is a valid DUEL_KIND, but handleDuelMessage has no branch for it (lib/game.js:1216-1291) — it falls through and returns false. Both peers already end the duel deterministically via the ordered step replay, so the packet is effectively dead traffic today. Either wire it up (for the intended contract/settlement path) or drop the broadcast to avoid confusion about which side is authoritative.

✅ 1 resolved
Bug: startChain() called twice in loadSlot fires duplicate RPC

📄 lib/game.js:869-871
In loadSlot, this.startChain() is invoked twice back-to-back, so loading a save triggers two independent dailySeed() calls and thus two getLatestLedger requests to the public RPC for a single load. The two responses race to set this.daySeed; the second is wasted work and doubles network load. Remove the duplicate line so startChain() is called once.

🤖 Prompt for agents
Code Review: Implements a shared daily seed derived from the Stellar ledger so all players explore the same field, but new games skip calling startChain and cancelDuelInvite can crash on null presence.

1. ⚠️ Bug: beginNewGame never calls startChain, so new games skip day seed
   Files: lib/game.js:929-931

   `beginNewGame` calls `startPresence()` but not `startChain()`, unlike `loadSlot`. A player who starts a fresh game (rather than loading a slot) never requests the daily seed, so `this.daySeed` stays null and `fieldSeed()` falls back to the local player-derived seed. That contradicts the feature's core promise that everyone playing today walks the same field — new players won't until they reload a save. Add `this.startChain()` in `beginNewGame` alongside `startPresence()`.

   Fix (Fetch the daily seed for new games too.):
   this.say(`bienvenido, ${this.name}. usa wasd o flechas; las puertas se abren al pisarlas.`)
   this.startPresence()
   this.startChain()
   this.saveCurrent()
   return true

2. 💡 Bug: dailySeed dereferences l.sequence without null guard
   Files: lib/stellar.js:245-259

   In `dailySeed`, `this.rpc('getLatestLedger')` can resolve to `null` (rpc returns `r.body.result`, which may be null/undefined), and the code immediately reads `l.sequence`. If the RPC returns a non-error body without a `result`, `l` is null and `Math.floor(l.sequence / ...)` throws — though it is caught and returns null, so behavior degrades gracefully. Consider guarding `if (!l) return null` for clarity and to avoid relying on the catch for a normal-ish response shape.

   Fix (Guard against a missing ledger result.):
   const l = await this.rpc('getLatestLedger')
   if (!l || typeof l.sequence !== 'number') return null
   const day = Math.floor(l.sequence / LEDGERS_PER_DAY)

3. 💡 Quality: barrido telegraph uses attack.reach*2 without Number coercion
   Files: lib/world-boss-event.js:303

   Unlike the other telegraph branches which use `Number(attack.reach) || 4`, the barrido branch multiplies `attack.reach * 2` directly. If a future sweep-type attack were defined without a `reach` (or a non-numeric one), this evaluates to `NaN`, `Math.max(8, NaN)` is `NaN`, and the loop `step <= NaN` never runs — silently dropping the warning cells for a damaging sweep. Current data defines reach:6 so it works today, but coercing keeps it robust: use `Math.max(8, (Number(attack.reach) || 4) * 2)`.

4. 💡 Quality: Duel cooldown ticks on wall-clock, risking cross-peer divergence
   Files: lib/game.js:986-988, lib/duel.js:238-246, lib/duel.js:309-321

   `onTick()` advances `duelCombat.tick()` on the local wall-clock game clock, decrementing both fighters' `cooldownLeft`. `DuelCombat`'s documented determinism ("same moves, attacks and ticks produce the same winner") only holds if ticks are delivered in the same order/count to both peers. Since attacks are gated by `cooldownLeft`, two peers with different frame timing between attack inputs can disagree on whether an attack was on-cooldown and thus compute different winners — which would break the Soroban consensus/settlement the design relies on. This is latent because the network transport is still pending and current tests advance ticks explicitly. When wiring the P2P layer, treat tick advancement as part of the ordered transported input stream (or derive elapsed ticks from a shared logical clock) rather than driving it from `onTick`'s wall clock.

5. ⚠️ Bug: Cancelling stale outgoing duel invite derefs null presence
   Files: lib/game.js:479-488, lib/game.js:1230-1236, lib/game.js:1612-1620

   When an outgoing duel invite is pending and the network drops, `dropPresence()` (lib/game.js:479-488) sets `this.presence = null` but never clears `this.duelInvite` or `this.duelMessages`. If the player then presses N/Esc, the out-invite branch calls `this.presence.sendDuel('decline', ...)` with no null guard (lib/game.js:1612-1620), throwing a TypeError. `onKey` is invoked from `update()` without a try/catch, so this crashes the render loop. The same unguarded `this.presence.sendDuel` exists in the busy 'challenge' branch of `handleDuelMessage` (lib/game.js:1230-1236), reachable when a queued challenge is processed after the drop. Fix: clear `duelInvite`/`duelMessages` in `dropPresence()` and/or guard the `sendDuel` calls with `this.presence`.

   Fix (Clear pending invite and queued duel messages when presence is dropped, so no later handler dereferences a null presence.):
   dropPresence() {
     if (this.duelNetwork && this.duel && this.duelCombat) {
       this.duelCombat.surrender(this.duelNetwork.rivalId)
       this.finishDuel('desconexion', { broadcast: false })
     }
     this.presence = null
     this.presenceStarted = false
     this.online = false
     this.arrivals = []
     this.duelInvite = null
     this.duelMessages = []
   }

6. 💡 Quality: 'result' duel packet is broadcast but never handled
   Files: lib/game.js:1410-1416, lib/game.js:1216-1230

   `finishDuel` broadcasts a `result` duel message (lib/game.js:1410-1416) and `result` is a valid DUEL_KIND, but `handleDuelMessage` has no branch for it (lib/game.js:1216-1291) — it falls through and returns false. Both peers already end the duel deterministically via the ordered `step` replay, so the packet is effectively dead traffic today. Either wire it up (for the intended contract/settlement path) or drop the broadcast to avoid confusion about which side is authoritative.

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 6 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 Author

Cierro este porque su contenido ya está integrado en el trabajo local de main, con
el commit 2c29c6a feat: la semilla del dia, el mismo campo para todos los que jueguen hoy.

Este PR ya era el reemplazo del #37, que se rompió cuando entró el #19 y desapareció el
bloque del fieldGate donde nacía uno de los tres new Field(...). Reconstruirlo sobre
el main de ese momento fue lo correcto, y ahora quedó absorbido por la integración local.

La rama semilla-del-dia-v3 queda en el remoto hasta que se pushee el main local,
que hoy está 13 commits adelante de origin/main y es la única versión que tiene esto
integrado junto al equipamiento, los rankings y el interior del castillo.

@leocagli leocagli closed this Aug 27, 2026
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.

1 participant