| name | profile-sync |
| description | blablalink 로그인 세션으로 내 계정의 실제 육성 상태를 받아 육성 프로필(profiles/<이름>.json)로 갱신한다. "내 육성 데이터 가져와" "내 스펙 갱신해" 요청에 쓴다. 게임 마스터 데이터 갱신은 char-scrape, 신규 캐릭터 등록은 char-add를 쓴다. |
profile-sync
내 계정의 사적인 육성 상태를 받아 profiles/<이름>.json으로 만든다.
받아온 프로필로 계산하는 건 이 skill이 아니라 러너의 --profile 옵션이다(아래 §쓰는 쪽).
char-scrape와 성격이 정반대다. 저쪽은 모두가 공유하는 게임 마스터 데이터를 공개 CDN에서
받아 커밋 대상으로 갱신하고, 이쪽은 한 계정의 개인 데이터를 로그인 세션으로 받아 로컬에만
둔다. 문서·절차·gate를 섞지 않는다.
절대 규칙
scraper/.session_cookie는 계정 접근권이다. 내용을 출력하지 않고, 커밋하지 않고,
어떤 파일로도 복사하지 않는다.
profiles/는 통째로 gitignore다. 프로필·원시 응답을 커밋 대상에 올리지 않는다.
- 프로필 수치를 보고할 때 openid를 그대로 노출할 이유가 없으면 생략한다.
절차
python scraper/profile_fetch.py (프로필 이름을 지정하려면 --name <이름>).
- 세션 만료(
game not login)면 아래 §쿠키 확보를 유저에게 안내하고 멈춘다.
에이전트가 대신 로그인할 수 없다.
- 실행 로그의 경고를 하나도 빠뜨리지 말고 유저에게 그대로 옮긴다. 특히:
동기화 소대 밖 N종 — 미보유 판정이 아니다. 소대에 안 넣어 인게임 레벨이 1인
것뿐이고, 레벨은 정책이 정하므로 계산에는 영향이 없다. 다만 그쪽은 스킬·장비 투자가
없는 경우가 많다 — 유저가 "얘 왜 딜이 바닥이야"라고 하면 스킬 레벨부터 확인한다.
모르는 재활용 연구실 tid — 신규 역할군·기업이다. CONSOLE_TIDS에 사람이 추가해야
하며, 그전까지 그 연구실은 콘솔 계산에서 빠진다.
애장품 단계 3 미만 — 그 단계의 스킬 판본으로 계산된다(docs/PARSING.md §애장품).
판본이 아직 파싱 안 됐으면 시뮬이 어느 슬롯이 없는지 알리며 끊는다.
미매핑 오버로드 옵션 타입 — 신규 옵션이다. profile_fetch.py의 FUNC_TO_EQUIP에
사람이 추가해야 하며, 그전까지 그 옵션은 조용히 빠진 채 계산된다.
옵션 수치가 equipment_skills 표에 없다 — 손으로 관리하는
data/base_stat_tables/equipment_skills.json이 게임과 어긋났다는 뜻이다. 게임 쪽이
정본이므로 표를 고치고 _verify에 근거를 남긴다.
- 요약(보유 종수 · 소장품 미장착 · 장비 미장착 · 콘솔 상태)을 보고하고 멈춘다.
변환 규칙만 바뀌었을 때는 재수집이 필요 없다 — --from-raw가 profiles/<이름>.raw.json으로
프로필만 다시 만든다(로그인 세션 불필요, CDN 매핑만 받는다). 결과를 먼저 보고 싶으면
--out <딴이름>으로 써 본다.
쿠키 확보 (최초 1회, 유저가 직접)
유저 데이터 API는 전부 로그인 세션을 강제한다. 익명·공개 조회 API는 없다(실측).
인증은 순수 쿠키라, 한 번 받아두면 이후 수집은 브라우저 없이 HTTP로 돈다.
- blablalink.com 로그인 + 게임 계정 연동
- DevTools > Network에서
api.blablalink.com 요청의 Cookie: 헤더 전체 복사
(핵심은 game_token game_openid game_gameid)
scraper/.session_cookie에 한 줄로 저장
만료되면 game not login(code 300001)이 뜬다 → 2~3만 다시 한다.
프로필에 들어가는 것 / 안 들어가는 것
| 항목 | 프로필 | 근거 |
|---|
| 돌파·코강·호감도 · 스킬 1/2/3 레벨 | ○ | 레벨과 달리 인게임에서 고정되지 않는다 |
| 레벨 | ✗ | 정책이 정한다 (§레벨 정책). 인게임 레벨은 동기화 소대 편성 상태일 뿐이다 |
| 장비 4부위 (기업 강화 / 일반 T1~T9 / 미장착) | ○ | 미장착은 tier: "없음" — 기업 강화0으로 적으면 부위당 수천 atk이 유령으로 붙는다 |
| 오버로드 합산 | ○ | 12슬롯 직접 순회 (§엔드포인트) |
오버로드 부위 경계 (_overload) | ○ | 계산에는 안 들어가는 _ 키다(합산 쪽이 정본). 어느 줄이 어느 부위인지는 더 굴릴지 판단할 때 필요하다 — 굴릴 부위는 배경에서 빼야 하므로(웹앱 레포 overload/README.md §굴릴 부위) 웹앱 오버로드 화면이 이걸 읽는다 |
| 소장품 / 애장품 | ○ | 슬롯 하나를 공유한다. 애장품은 스탯이 SR15와 같아 SR15로 적고, 단계는 favorite_stage 0~3으로 따로 담는다 (스킬 판본을 정한다) |
| 큐브 | ✗ | API는 장착 중인 것만 준다 — 보유 큐브 목록을 주는 라우트는 프론트엔드 전체에 없다(청크 439개 전수 조사). 큐브는 자유롭게 갈아끼우므로 육성이 아니라 케이스가 정하는 축이며, 관찰분은 _account.cubes에 보유 하한으로만 남아 "그 레벨로 갖고 있는가" 경고에만 쓰인다 |
| 콘솔 | ○ | Game/GetUserProfileOutpostInfo의 recycle_room_researches. tid 1001=공통 · 11xx=역할군 · 12xx=기업 |
| 컨트롤·버스트 패턴 | ✗ | 육성이 아니라 운용이다. data/char_defaults.json이나 호출부에 둔다 — 프로필에 들어오면 로드가 끊는다 |
쓰는 쪽 — 프로필로 계산하기
수집과 계산은 따로 요청받는다. 유저가 "내 스펙으로 계산해"라고 하면 재수집 없이 기존
프로필로 돌린다. 프로필이 오래됐으면 _meta.fetched_at을 근거로 갱신을 제안만 한다.
python -m runner.sim "<스쿼드>" --profile me
python -m runner.sim "<스쿼드>" --profile me --profile-level sync
보고서는 스펙 JSON에 "profile": "me"(필요하면 "profile_level": "sync")를 넣는다.
레벨 정책
인게임 캐릭터 레벨은 동기화 소대에 넣었는지에 달린 편성 상태지 육성 상태가 아니고,
솔로레이드는 레벨이 400으로 고정된다. 그래서 프로필은 레벨을 담지 않고 러너가 정한다.
| 정책 | 레벨 | |
|---|
fixed (기본) | 기본 스펙 레벨 400 | 솔로레이드 기준. 고정 스펙과 레벨이 같아 육성 차이만 남는 비교가 된다 |
sync | _account.synchro_level | 동기화 소대 레벨. 소대 밖 캐릭터도 같은 값으로 |
- 프로필에 아예 없는 이름(미보유·수집 후 나온 신캐)은 미육성으로 계산된다 — 돌파 0 ·
스킬 1/1/1 · 장비 미장착 · 소장품 없음, 레벨과 콘솔만 다른 캐릭터와 같다. 고정 스펙으로
떨어뜨리면 만렙 가상 캐릭터가 섞여 "내 계정 기준"이 거짓말이 되므로 그렇게 하지 않는다.
대체 목록은 결과에 실린다. 동기화 소대 밖 캐릭터는 프로필에 있으므로 여기 해당하지
않는다 — 다른 캐릭터와 똑같이 계산된다.
- 프로필로 낸 결과는 고정 스펙 결과와 총딜을 직접 비교하지 않는다. 러너가 그 경고를
강제로 싣는다 — 유저에게 답할 때 그 줄을 그대로 옮긴다.
- 회귀 하네스(
runner/snapshot.py)는 프로필을 받지 않는다. golden baseline은 고정 스펙 전용이다.
레이어 구조와 이탈 보고 규칙은 docs/HARNESS.md §2.5층이 정본이다.
엔드포인트
API (/api/game/proxy/) | 파라미터 | 준다 |
|---|
Game/GetUserCharacters | intl_open_id, nikke_area_id | 니케 로스터: name_code·유효 레벨·grade·core·combat |
Game/GetUserCharacterDetails | + name_codes[] | 스킬레벨·장비4부위(티어·강화·옵션3)·큐브·소장품/애장품·호감도 + state_effects(옵션 해석) |
Game/GetUserProfileOutpostInfo | intl_open_id, nikke_area_id | 전초기지: recycle_room_researches(= 콘솔)·synchro_level·outpost_battle_level |
라우트 전체 목록은 프론트엔드 번들 assets/v4-*.js에 문자열로 들어 있다. 새 항목이 필요하면
거기서 찾는다(/Get... 형태). 조사 시점(2026-08-15) 기준 큐브 보유 목록 라우트는 없다.
실측으로 확인한 사실:
lv <= 1은 미보유 판정이 아니다. 동기화 소대 밖이라 인게임 레벨이 안 오른 것뿐이다.
실측(2026-08-15): 로스터 192종 중 lv 1이 21종인데 그 수가 synchro_nonempty_slot_count(171)의
여집합과 정확히 일치했고, 유저 확인 결과 전원 보유 중이었다(소대에 안 넣었을 뿐).
그래서 로스터 전원을 담고 _unsynced 플래그만 세운다. 레벨은 정책이 정하므로 이 플래그는
계산에 영향이 없다.
이전 구현이 lv > 1을 보유 판정으로 써서 보유 캐릭터 20종이 통째로 빠져 있었다.
같은 실수를 반복하지 않도록 여기 남긴다 — 동기화 소대는 보유와 무관하다.
name_code → resource_id → 우리 캐릭명: CDN character_id_map.json + nikke_scraped.json의 id.
- 오버로드 합산:
state_effects는 옵션 id로 중복 제거된다(같은 옵션이 2부위면 1번만
등장) → 합산에 못 쓴다. 12개 슬롯을 직접 순회하고, state_effects는 옵션id → (타입, 값)
사전으로만 쓴다.
favorite_item_tid는 소장품(R·SR)과 애장품(SSR)이 공유하는 슬롯이다. 등급은 CDN
favorite_rare_map.json이 준다. SSR 애장품의 플랫 스탯·소장품 스킬 레벨은 SR15와 완전히
같고(favorite_{id}.json의 atk·hp·def 배열이 단계와 무관하게 SR15 값, level1=4),
favorite_item_lv 0/1/2가 단계 1/2/3이다.
- 호감도 0(미투자)은 호감도 표가 1부터라 1로 클램프.
CDN 난독화 경로 규칙은 ../char-scrape/SCRAPER.md §난독화 경로 규칙이 정본이다.