리뷰 발견 수정: 게임버그 6 + 시뮬 충실도 3 + 설명/데이터 정정 (Lua↔JS 동기화) #96

Merged
gahusb merged 11 commits from fix/review-findings into main 2026-06-29 21:46:45 +09:00
Owner

개요

전체 코드 점검(멀티 에이전트 리뷰 + 정적검사)에서 발견한 항목을 하나씩 systematic-debugging/TDD로 수정. 전 커밋 propcheck 0·cbgap GAP 0·재생성 바이트동일·common churn revert. 테스트 84→88, 카드 121장 유지.

게임플레이 버그

  1. BindButtons 1회 가드 — StartRun이 run마다 BindButtons 호출, 앞 7개만 disconnect 가드라 2회차 런부터 ~30개 ConnectEvent 누적 → 보상 카드 2배 추가 등. 1회 바인드 가드(ButtonsBound).
  2. drawDamage/drawPoison per-draw — Speedster/CorrosiveWave가 설명('뽑을 때마다')과 달리 '낼 때마다' 발동. ApplyDrawTrigger 추출 후 DrawCards에서 호출. 부수로 CheckCombatEnd 멱등 가드(전멸 시 보상 중복 차단).
  3. firstCardDamageBonus class→kind — ChargedBlow의 첫-카드 보너스가 c.class=="Attack"(영구 false)로 게이트돼 죽은 코드 → c.kind.
  4. Envenom AoE attackPoison — 광역 공격(DealDamageToAllMonsters)에 attackPoison 누락 → 광역공격이 적을 중독 안 시킴.
  5. DealDamageToAllMonsters isAttack 매개변수화 — 공격과 Outbreak 독 버스트가 한 메서드를 공유해 버스트가 취약 1.5×를 받았고(JS는 평면), #5가 추가한 attackPoison까지 버스트에 적용돼 Envenom+Outbreak 재귀 위험. isAttack으로 분리(공격=취약+attackPoison, 버스트=평면).
  6. useAllEnergy 코스트감소 무시 — Malaise/Skewer가 코스트감소로 소비 에너지가 줄어 X 효과가 약해지는 역설. 항상 full 에너지 소비.

밸런스 시뮬 충실도 (Lua 정답, JS 미러 수정)

  1. enemyStrengthLoss 음수 힘 허용 — PiercingWail이 게임에선 적 공격 -6인데 시뮬은 -0(str 0에서 클램프). calcEnemyAttack 클램프 제거 + EnemyActStep 통일.
  2. firstShivDamageBonus 첫 Shiv만 — 시뮬이 플래그를 else 분기에서 set해 Shiv가 도달 못 함 → 모든 Shiv가 보너스. Attack 분기로 이동.
  3. Prepared 실제 방어 — 시뮬이 addBlock을 안 불러 방어 미부여 + 설명 정정(draw 없음).

설명/데이터 정정

  1. Tactician·Adrenaline 잘린 설명 완성.
  • 설명이 미구현 효과를 주장하던 5장(Malaise·Mirage·KnifeTrap·Strangle·Rage)을 실제 동작에 맞게 정정. Rage는 kind=Power라 damage:4/aoe가 무시되던 死카드 → 데이터 의도대로 Attack으로 기능화.

검증

RED-GREEN TDD = #3·#4·#6·#7(테스트 추가). JS가 이미 정답인 #2·#5·#8은 Lua-only 수정이라 코드리뷰+메이커 검증(JS 미러로 RED 불가). 런타임 동작은 메이커 플레이테스트 권장.

## 개요 전체 코드 점검(멀티 에이전트 리뷰 + 정적검사)에서 발견한 항목을 하나씩 systematic-debugging/TDD로 수정. 전 커밋 propcheck 0·cbgap GAP 0·재생성 바이트동일·common churn revert. 테스트 84→88, 카드 121장 유지. ## 게임플레이 버그 1. **BindButtons 1회 가드** — StartRun이 run마다 BindButtons 호출, 앞 7개만 disconnect 가드라 2회차 런부터 ~30개 ConnectEvent 누적 → 보상 카드 2배 추가 등. 1회 바인드 가드(`ButtonsBound`). 2. **drawDamage/drawPoison per-draw** — Speedster/CorrosiveWave가 설명('뽑을 때마다')과 달리 '낼 때마다' 발동. `ApplyDrawTrigger` 추출 후 DrawCards에서 호출. 부수로 `CheckCombatEnd` 멱등 가드(전멸 시 보상 중복 차단). 3. **firstCardDamageBonus class→kind** — ChargedBlow의 첫-카드 보너스가 `c.class=="Attack"`(영구 false)로 게이트돼 죽은 코드 → `c.kind`. 5. **Envenom AoE attackPoison** — 광역 공격(DealDamageToAllMonsters)에 attackPoison 누락 → 광역공격이 적을 중독 안 시킴. 8. **DealDamageToAllMonsters isAttack 매개변수화** — 공격과 Outbreak 독 버스트가 한 메서드를 공유해 버스트가 취약 1.5×를 받았고(JS는 평면), #5가 추가한 attackPoison까지 버스트에 적용돼 Envenom+Outbreak 재귀 위험. isAttack으로 분리(공격=취약+attackPoison, 버스트=평면). 9. **useAllEnergy 코스트감소 무시** — Malaise/Skewer가 코스트감소로 소비 에너지가 줄어 X 효과가 약해지는 역설. 항상 full 에너지 소비. ## 밸런스 시뮬 충실도 (Lua 정답, JS 미러 수정) 4. **enemyStrengthLoss 음수 힘 허용** — PiercingWail이 게임에선 적 공격 -6인데 시뮬은 -0(str 0에서 클램프). calcEnemyAttack 클램프 제거 + EnemyActStep 통일. 6. **firstShivDamageBonus 첫 Shiv만** — 시뮬이 플래그를 else 분기에서 set해 Shiv가 도달 못 함 → 모든 Shiv가 보너스. Attack 분기로 이동. 7. **Prepared 실제 방어** — 시뮬이 addBlock을 안 불러 방어 미부여 + 설명 정정(draw 없음). ## 설명/데이터 정정 10. Tactician·Adrenaline 잘린 설명 완성. - 설명이 미구현 효과를 주장하던 5장(Malaise·Mirage·KnifeTrap·Strangle·Rage)을 실제 동작에 맞게 정정. **Rage는 kind=Power라 damage:4/aoe가 무시되던 死카드 → 데이터 의도대로 Attack으로 기능화.** ## 검증 RED-GREEN TDD = #3·#4·#6·#7(테스트 추가). JS가 이미 정답인 #2·#5·#8은 Lua-only 수정이라 코드리뷰+메이커 검증(JS 미러로 RED 불가). 런타임 동작은 메이커 플레이테스트 권장.
gahusb added 11 commits 2026-06-29 21:46:30 +09:00
StartRun이 run마다 BindButtons를 호출하는데 앞 7개 핸들러만
disconnect 가드돼 있고 reward/skip/map/shop/monster/relic/potion/job
등 ~30개 ConnectEvent는 미가드라, 2회차+ 런에서 핸들러가 누적된다.
특히 PickReward는 RewardChoices·CombatOver를 클리어하지 않아 중복
핸들러로 두 번 불리면 같은 보상 카드가 RunDeck에 2번 추가된다.

BindLobbyButtons/BindSoulShopButtons와 동일하게 self.ButtonsBound
1회 가드를 추가(런 UI 엔티티는 영속이라 1회 바인드로 충분).
신규 prop ButtonsBound 선언. 산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
Speedster("카드를 뽑을 때마다 피해")·CorrosiveWave("뽑을 때마다 중독")의
효과가 Lua에서는 ResolveCardEffects 끝에서 카드를 '낼 때마다' 발동해
카드 설명·JS 미러(sim-balance draw())와 어긋났다.

per-play 블록을 ApplyDrawTrigger() 메서드로 추출하고 DrawCards에서
뽑은 카드마다 호출해 per-draw로 정렬(JS와 동일). JS 미러는 이미
per-draw라 무변경 — 양쪽 일치.

부수: CheckCombatEnd에 self.CombatOver 멱등 가드 추가. per-draw로
호출이 잦아져(턴시작 5드로 등) 전멸 시 보상/골드/유물이 중복
발동할 수 있던 잠재 버그를 차단(공격+drawDamage 카드에서도 위험했음).

밸런스 영향: Speedster(Power)가 매턴 시작 드로에도 발동해 강해짐 —
값 튜닝은 sim으로 후속 조정 가능. 산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
ChargedBlow(class=warrior, kind=Attack, firstCardDamageBonus=2)의 첫-카드
보너스가 Lua·JS 양쪽에서 `c.class == "Attack"`로 게이트돼 있었다. class는
warrior/bandit 등이라 절대 "Attack"이 아니어서 보너스가 영구 미발동(죽은 코드).
kind가 "Attack"이므로 `c.kind == "Attack"`로 수정(양쪽 미러).

RED-GREEN 회귀 테스트 추가: class=warrior·kind=Attack 카드의 첫 카드
보너스로 7뎀 → 1턴 처치(미수정 시 5뎀 2턴). 테스트 84→85.
산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
PiercingWail(귀를 찢는 비명: 모든 적 힘 -6)에서 Lua는 적 공격을
(value+str-loss, 0클램프)로 줄여 StS처럼 힘이 음수로 작동하는데,
JS 시뮬은 max(0, str-loss)로 힘을 0에서 클램프해 모든 적 str=0일 때
공격이 전혀 안 줄었다(게임 -6, 시뮬 -0). 기존 테스트는 str>=loss
구간만 봐서 못 잡음.

Lua가 정답(게임은 정상) — 시뮬만 수정. calcEnemyAttack의 max(0,...)
제거(음수 힘 허용, 최종 calcAttack이 0클램프) + EnemyActStep을 그
헬퍼로 통일(중복 제거). RED-GREEN 테스트로 loss>str 구간 검증. 86개.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
Envenom(독 바르기: 공격이 막히지 않은 피해를 줄 때마다 중독 1)이
단일타겟(DealDamageToTarget)에는 적용됐지만 광역(DealDamageToAllMonsters)
에는 빠져 있어, Envenom+광역공격이 게임에선 아무 적도 중독 안 됐다
(JS 미러는 양쪽 적용 — Lua가 누락).

DealDamageToAllMonsters의 막히지 않은 피해(dmg>0) 분기에 단일타겟과
동일한 attackPoison 적용을 추가(적별 ApplyPoisonToMonster). JS 미러는
이미 올바라 무변경. 산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
PhantomBlades(환영검: 첫 Shiv +9) 사용 시 Lua는 첫 Shiv 처리 후
ShivFirstDamageBonusUsed를 set(Attack 경로)하는데, JS 시뮬은 이 플래그
set이 else(비-Attack/Skill) 분기에 있어 Shiv(kind=Attack)는 도달 못 함
→ 플래그 영영 false → 모든 Shiv가 +9를 받아 시뮬이 데미지를 과대집계.

Lua가 정답(게임 정상) — 시뮬만 수정: 죽은 else-분기 플래그 set 제거 +
Attack 분기(baseDamage 계산 직후, Lua 순서와 동일)에 추가. RED-GREEN
테스트로 턴당 첫 Shiv만 보너스 검증. 87개.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
Lua는 Prepared(예비)에서 AddCardBlock으로 실제 방어를 부여하는데, JS
시뮬은 blockGained(통계 카운터)만 증가시키고 addBlock을 호출하지 않아
플레이어가 실제 방어를 못 받았다(시뮬이 방어를 과소집계).

JS도 다른 블록 출처처럼 addBlock 경유로 변경(Lua 동기화). 또한
Prepared 데이터는 discard:1 + blockPerDamageDealtThisTurn뿐(draw 없음)
인데 설명이 "1장 뽑고 1장 버립니다"로 부정확해, 실제 동작(1장 버리고
이번 턴 피해만큼 방어)에 맞게 보강. RED-GREEN 테스트 추가. 88개.
산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
DealDamageToAllMonsters는 AoE 공격(취약 1.5x·attackPoison 적용)과
Outbreak 독 버스트(평면 피해) 두 용도로 공유되는데, 취약을 항상
적용해 버스트가 취약 대상에 과다 피해를 줬다(JS 미러는 버스트를
평면 applyDamage로 처리 — Lua만 발산). 또한 직전 커밋에서 추가한
attackPoison도 버스트에 적용돼, Envenom+Outbreak 동시 활성 시
버스트→attackPoison→독 적용→또 버스트의 재귀 위험이 있었다.

isAttack 매개변수를 추가해 취약·attackPoison을 공격일 때만 적용:
AoE 공격(ResolveCardEffects)은 true, 버스트는 미전달(평면). JS의
dealToTarget(취약+attackPoison) vs 버스트(평면) 분리와 일치.
산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
Malaise(불쾌, xWeakPerEnergy)·Skewer(꼬챙이, xDamagePerEnergy) 같은
useAllEnergy 카드는 X 효과가 소비 에너지에 비례하는데, Lua는 코스트
감소(스킬코스트감소·다음스킬무료·전투코스트감소)를 useAllEnergy에도
적용해 소비 에너지가 full보다 줄고 X도 약해졌다(코스트감소가 카드를
약화시키는 역설). JS는 스킬코스트감소만 건너뛰고 combatReduction은
적용해 양쪽이 미묘하게 달랐다.

정답: useAllEnergy는 "전 에너지 소비"이므로 어떤 코스트감소도 무시.
Lua는 3개 감소 조건에 useAllEnergy 제외 추가, JS는 finalCost를
useAllEnergy면 combatReduction 미적용으로. 양쪽 모두 full 에너지 소비로
일치. 산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
Tactician(전략가) "교활. 을 얻습니다." → gainEnergy:1 반영해
"교활. 에너지를 1 얻습니다." Adrenaline(아드레날린) "를 얻습니다..."
→ "에너지를 1 얻습니다. 카드를 2장 뽑습니다. 소멸." 산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
각 카드 설명이 데이터에 없는 효과를 주장하고 있었다. 실제 런타임
동작에 맞게 설명 수정(미구현 메커니즘 구현 대신 설명 정정):
- Malaise(불쾌): "힘 감소+약화+소멸" → 실제는 useAllEnergy+xWeakPerEnergy
  뿐(단일 적 약화, 소멸/힘감소 없음) → "에너지를 모두 사용하고, 사용한
  에너지만큼 적에게 약화를 부여합니다."
- Mirage(신기루): "중독만큼 방어+소멸" → 실제 draw:1 → "카드를 1장 뽑습니다."
- KnifeTrap(칼날 함정): "표창 재사용" → 실제 draw:1 → "카드를 1장 뽑습니다."
- Strangle(목 조르기): "카드마다 체력감소" 부분 미구현 → "피해를 8 줍니다."
- Rage(분노): kind=Power라 damage:4/aoe가 무시되고 powerEffect도 없어
  재생 시 아무 효과 없던 死카드. 데이터의 damage:4/aoe 의도대로
  kind Power→Attack으로 기능화 + "모든 적에게 피해를 4 줍니다."

카드 121장 유지. 산출물 재생성 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
gahusb merged commit de917f812d into main 2026-06-29 21:46:45 +09:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gahusb/maplecontest#96