Files
mc_chat_answer_mod/HANDOVER.md
Claude (owner) b79eff26b7 add HANDOVER.md — mod 작업이 별도 채팅으로 분리되면서 콜드 스타트용 인계 문서
내용:
- 한 줄 요약, 리포/인증, 최신 버전
- 폴더 구조, 빌드 명령
- 핵심 동작 3 개 (handleChat, mod_active_notice, presence pulse) 동작 + 왜 그렇게 짰는지
- mc_datapack 측 통합 인터페이스 (함수/점수 표)
- v1.1.0 ~ v1.3.8 버전 히스토리 요점
- 릴리스 절차 (gradle.properties 버전 → buildAll → tag/push → Gitea API)
- 작업 환경 (클론 위치, JDK, git author override 패턴)

이 mod 채팅은 데이터팩 mc_datapack 채팅과 분리되므로, 호환 깰 가능성 있는
변경 시 데이터팩 쪽 채팅과 조율 필요.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 10:45:12 +09:00

8.9 KiB

mc_chat_answer_mod 인수인계 (Handover)

이 문서는 본 모드 작업이 별도 채팅(Discord 룸)으로 분리되면서 새 채팅이 콜드 스타트로 인계받을 수 있도록 그동안의 컨텍스트를 정리한 것입니다. 모드를 계속 유지보수할 때 이 파일을 먼저 읽으면 됩니다.

한 줄 요약

음악퀴즈 데이터팩(mc_datapack / 데이터팩 namespace mq) 의 짝이 되는 멀티로더(Fabric + NeoForge) 서버사이드 Minecraft 모드. 두 가지 일을 한다:

  1. 정답 채팅 가로채기init main == 5 (정답 입력 단계) 일 때 플레이어 채팅을 정답 제출로 OP 권한으로 처리.
  2. 데이터팩 presence pulsemq_chat_mod objective 의 #server 점수를 1 로 set 해서 데이터팩이 "이 모드 설치돼 있는가" 를 검사할 수 있게 함.
  3. (부가) 모드 활성화 안내 — 로그인 1 초 뒤 mq:players/mod_active_notice 호출.

리포 / 접근

  • URL: https://git.tkrmagid.kr/tkrmagid/mc_chat_answer_mod
  • 기본 브랜치: main
  • 라이센스: MIT
  • 인증: Gitea PAT — 글로벌 메모리 /home/claude/.config/ejclaw/secrets.jsoncredentials["git.tkrmagid.kr"].token. HTTPS URL 에 임베드하거나 Authorization: token <value> 헤더로 사용.
  • git author (커밋 시): -c user.name="Claude (owner)" -c user.email="claude@tkrmagid.kr". 글로벌 git config 안 건드림.

최신 버전

v1.3.8 (2026-05-20). 릴리스: https://git.tkrmagid.kr/tkrmagid/mc_chat_answer_mod/releases/tag/v1.3.8

폴더 구조

  • common/ — 로더 비종속 핵심 로직 (Mojang 매핑 기준).
    • kr/tkrmagid/chatanswer/core/ChatAnswerCore.java — 거의 모든 로직이 여기.
  • fabric-1216/ — Fabric Loader MC 1.21.6 진입점.
  • fabric-2612/ — Fabric Loader MC 26.1.2 진입점.
  • neoforge-1216/ — NeoForge MC 1.21.6 진입점. (26.x 는 NeoForge moddev plugin 이 아직 인식 못 함.)
  • containerJar 태스크가 위 세 로더의 결과물을 단일 jar 로 묶음 (Fabric 은 META-INF/jars/ JiJ, NeoForge 는 outer 본체). 최종 산출: build/libs/chat_answer-<version>.jar.

빌드

JDK 21 필요.

./gradlew buildAll

산출물: build/libs/chat_answer-<version>.jar (서버 mods/ 에 이거 하나만 넣으면 됨 — Fabric 이든 NeoForge 든 자기 진입점만 인식).

핵심 동작 상세

A. handleChat (정답 채팅 처리)

ChatAnswerCore.handleChat(ServerPlayer, String):

  • 정답 단계(init main == 5) 가 아니면 그대로 통과 (true 반환).
  • 정답 단계면 sanitize 후 OP 권한으로 다음 실행:
    execute as <UUID> run function mq:answer/submit {text:'<채팅>'}
    
  • v1.3.7 까지: 정답 단계에서 false 반환 → Fabric ALLOW_CHAT_MESSAGE / NeoForge ServerChatEvent.setCanceled(true) 가 broadcast 차단.
  • v1.3.8 부터: 항상 true 반환 → broadcast 차단 안 함. 정답 채팅도 다른 플레이어에게 평소대로 보임. 정답 보호는 룸 운영자 신뢰 기반.

sanitize 는 큰따옴표/백슬래시/제어문자 제거 (매크로 NBT 호환).

B. mod_active_notice (입장 안내)

onPlayerJoin(ServerPlayer) 가 UUID → 20 ticks 맵에 적재. 매 server tick 마다 카운트다운, 0 되면 player 본인을 source 로 한 CommandSourceStack 으로 function mq:players/mod_active_notice 호출.

왜 지연하는가: JOIN 이벤트는 플레이어가 PlayerList 에 막 들어간 직후라 클라이언트가 system chat 패킷 받을 준비가 안 됐을 수 있음. 즉시 tellraw 를 보내면 사라지는 race 가 v1.3.3 이하에서 재현됐음. 20 ticks (1 초) 지연으로 해결.

C. presence pulse

markModPresence(MinecraftServer):

scoreboard.getOrCreatePlayerScore(
    ScoreHolder.forNameOnly("#server"),
    scoreboard.getObjective("mq_chat_mod")
).set(1);
  • objective 가 없으면 (= 데이터팩 미설치) 조용히 skip.
  • 점수가 이미 1 이면 MC 가 packet 전송 생략 → 매 tick 호출해도 트래픽 안 늘어남.
  • 호출 지점 4 개 (어느 하나만 firing 돼도 가드 통과):
    • ServerLifecycleEvents.SERVER_STARTED / ServerStartedEvent — 부팅 직후
    • onPlayerJoin — 로그인 시
    • ServerTickEvents.END_SERVER_TICK / ServerTickEvent.Post — steady-state
    • ServerLifecycleEvents.END_DATA_PACK_RELOAD (success=true 시) / OnDatapackSyncEvent — /reload 직후

왜 중복: banner/mohist 같은 fabric-bukkit 하이브리드 호스트에서 END_SERVER_TICK 이 안 들어오는 케이스, /reload 가 objective 를 remove/add 해서 #server 점수를 0 으로 reset 하는 케이스 등이 보고됨. 하나만 의존하면 false negative 가 남음.

데이터팩 (mc_datapack / mq) 측 통합

모드 → 데이터팩 모드가 호출하는 함수 / 쓰는 점수
mq:players/mod_active_notice 로그인 1 초 뒤 호출
mq:answer/submit {text:'...'} 정답 단계 채팅 시 호출
scoreboard mq_chat_mod #server ← 1 presence pulse
데이터팩 → 모드 데이터팩이 읽는 점수
scoreboard main init 5 면 정답 단계
scoreboard mq_chat_mod #server 1 이면 모드 설치됨 (가드 통과)

데이터팩의 load.mcfunctionscoreboard objectives add mq_chat_mod dummy 로 objective 를 생성하고 #server 를 0 으로 materialize. 모드는 그 objective 가 있어야 pulse 가 동작. 데이터팩의 commands/start.mcfunction 에서 execute if score #server mq_chat_mod matches 1 로 검증, 미설치 시 사유와 함께 차단.

데이터팩 최신: v1.0.26. README 에 mc_chat_answer_mod 최소 권장 버전 v1.3.7+ 명시 (v1.3.8 은 상위 호환이라 README 갱신 불필요).

버전 히스토리 (요점만)

  • v1.1.0 — PlayerJoin 훅 추가, 데이터팩에 모드 설치 신호.
  • v1.1.1 — 싱글플레이어 (client side) 에서도 로드.
  • v1.2.0 — MC 26.1.2 타깃.
  • v1.2.1 — icon 추가.
  • v1.3.0 — 단일 jar 가 Fabric 1.21.6 + Fabric 26.1.2 + NeoForge 1.21.6 커버.
  • v1.3.1 — 중첩 fabric jar 를 outer fabric.mod.json 에 선언.
  • v1.3.2 — 진단 로깅.
  • v1.3.3 — storage flag 대신 직접 function 호출 (race 해소).
  • v1.3.4mod_active_notice 20 tick 지연 (chat-not-delivered race 해소). 이 버전 미만이면 입장 안내 채팅이 가끔 사라짐.
  • v1.3.5#server mq_chat_mod tick pulse 추가 (가드 도입).
  • v1.3.6 — fabric-bukkit 하이브리드에서 END_SERVER_TICK 안 들어오는 false negative → SERVER_STARTED + JOIN 에도 pulse.
  • v1.3.7 — /reload 가 objective remove/add 로 점수 reset 하는데 죽은 호스트 + 이미 접속 중 조합에서 SERVER_STARTED/JOIN/Tick 모두 안 발화 → END_DATA_PACK_RELOAD / OnDatapackSyncEvent 추가.
  • v1.3.8 — 정답 단계 broadcast 차단 해제 (사용자 요청).

알려진 이슈

v1.3.8 시점 기준 없음. 리뷰어 검증 통과 항목:

  • handleChat 가 항상 true 반환 (jar 바이트코드 기준).
  • 정답 단계에서는 mq:answer/submit 호출 후 broadcast 유지.
  • 다른 채팅 cancel 경로 repo 전체에 없음.
  • ./gradlew buildAll 성공.

릴리스 절차

  1. 코드 수정 + 테스트.
  2. gradle.propertiesmod_version 올림.
  3. ./gradlew buildAll 으로 jar 빌드 → build/libs/chat_answer-<ver>.jar.
  4. 커밋 (위의 git author override 사용) + annotated tag v<version> + push.
  5. Gitea 릴리스 생성:
    POST https://git.tkrmagid.kr/api/v1/repos/tkrmagid/mc_chat_answer_mod/releases
    Authorization: token <PAT>
    Content-Type: application/json
    {"tag_name":"v<ver>","name":"...","body":"..."}
    
    응답의 id 받아서 자산 업로드:
    POST .../releases/<id>/assets?name=chat_answer-<ver>.jar
    -F "attachment=@build/libs/chat_answer-<ver>.jar"
    
  6. 사용자에게 mods 폴더 jar 교체 안내.

호환성 깰 가능성이 있는 변경 (예: mq:answer/submit 호출 인자 변경, mq_chat_mod objective 이름 변경 등) 은 반드시 mc_datapack 쪽 채팅과 조율해야 함.

작업 환경 메모

  • 로컬 클론 경로 (참고): /tmp/mc_chat_answer_mod. owner Claude workspace (/home/claude/EJClaw/data/workspaces/mc_datapack/owner/) 밖이므로 owner 브랜치 protocol 적용 대상 아님. mod repo 는 main 직접 push.
  • 빌드 도구: Gradle wrapper (./gradlew), JDK 21.
  • Loom 1.16.2, NeoForge moddev plugin 사용.

짝 데이터팩 (mc_datapack)

  • URL: https://git.tkrmagid.kr/tkrmagid/mc_datapack
  • 별도 채팅(현 mc_datapack 채팅) 에서 유지보수.
  • 본 모드 작업이 데이터팩 호환을 깨면 그쪽 채팅에 알려야 함.
  • 데이터팩 namespace mq, 메인 디렉토리 music_quiz/data/mq/function/.

이 문서는 mod repo 의 HANDOVER.md 로 커밋돼 있습니다. 새 채팅에서 git clone 후 이 파일부터 읽으면 됩니다.