Manual/Gameplay Systems/Alliance Duel (VS)
v1.0.364 notes · build 2026-11-07 12:10 UTC

Alliance Duel (VS)

activity id wiring · weekly themes · score table · radar flows
#duel #events #protocol

Weekly alliance-vs-alliance ("VS"), one theme per server day; the server day starts 02:00 UTC.

Entry wiring

Duel = activity 55000 (type 14, EnumActivity.AllianceCompete) in the init push activity map live. Entry fields: id: 55000, activityid: "70000" (heroactivity row — the id the detail read must send), name: "370026" (locale key), startTime/endTime/readyTime/matchGroupId, uuid (32 hex = today's eventList actId). The only live entry whose activityid differs from id [live 2026-10-06].

  • Detail read: hero.event.info.get {activityId: "70000"} — sending "55000" returns bare {success=true}, no eventList. ActivityEventInfoGetMessage only processes replies lacking the success key (ActivityEventInfoGetMessage.lua:26). Override logic in ActivityInfoData:ParseActivityData (ActivityInfoData.lua:126-128, 160-161); sent as UtfString (ActivityListDataManager.lua:158-163).
  • Reply live: activityId, type == 15 (routing, ActivityListDataManager.lua:1180-1186), ranking, isFix, eventList.
  • Per-entry: st/et (unix ms — live has no begintime/endtime; client reads that pair first per ActivityEventInfo.lua:69-74, then st/et per ActivityListDataManager.lua:1193-1198), rt, t (15), eventId (heroevent rows 110000-110005), actId/actName/actDesc, score (|-joined score table ids), target (|-joined chest thresholds — live: 40000|150000|540000|660000|1000000|2300000|2600000|3600000|7200000), value, reward, reward_science, userScore {score, rewardFlagList, newRewardFlagList, finishFlag, canRwdFlag, level, actId, hardMode, refreshTime, sendReward, uid}, vsAllianceInfo[] {allianceId, alName, abbr, icon, power, serverId, win, winScore, alScore, scoreHistory[{score, day}], mvpPlayer{uid, name, pic, picVer, monthCardEndTime}}, fightStartTime, fightEndTime, weekEndTime, minDayScore, minWeekScore, winReward, winWeekReward, isCrossFight, crossFight, canAttackCity, nextId, nextName (empty live).
  • Pick the entry t == 15 containing now; never fall back — a stale scoring list is another day's.

Week map (TB:heroactivity#70000) static

DayeventIdTheme
Mon110000Radar Training
Tue110001Base Expansion
Wed110002Age of Science
Thu110003Train Heroes
Fri110004Total Mobilization
Sat110005Enemy Buster
Sun—rest day

Confirmed live Tue 2026-10-06: theme 110001, window ends 02:00 UTC.

Score model

score table rows {id, type, value, points}; type = ScoreType (EnumType.lua:15728-15744). Predicted award = units × points × (1 + Σ effects) (GetAllyDuelScoreInfo.lua:39-43).

Types in use: 4 train unit (value = tier), 20 shop, 23 building power, 42 recruit hero, 51 speed-up minute (value = queue: 4 training, 6 research, 7 construction, 9 healing — keyed by queue, not item), 82 radar task, 98 UR trade truck (value 5), 99 UR secret task, 119 use item (itemId|count), 120 recruit survivor.

Live 2026-10-06 scoring list: 90201 (51/7, 50 pts), 90202 (23, 10), 90203 (98/5, 100,000), 90204 (99/5, 75,000), 90000 (20, 30), 100910 (120, 1,500), 110301 (119/801000|1, 1), 110302 (119/801001|1, 2,500).

  • Score push static: push.act.score.obtain (Arms Race always; duel when type == 15, PushActScoreObtainMessage.lua:6-16) → GetDuelScoreManager:PushScoreChangeByType.
  • Chest claim static: accept.personal.reward {actId: UtfString, stage: Int (target value), eventType: 15}; claimable when userScore.score ≥ target and flag not in newRewardFlagList (comma-joined claimed indexes, e.g. 1,2,3 live); index within tech-unlocked chest count (3/6/9/12).

Speed-up items-only forms (UISpeedView.lua:1540-1586)

  • build.ccd.m.new {bUUID: Long, isFixRuins: false, itemIDs: "tmpl;n|tmpl;n"}
  • queue.ccd.m.new {qUUID: Long, itemIDs: "tmpl;n|..."}
  • building.camp.accel {uuid: Long, itemId: "tmpl;n|..."}

Diamond form = useGold key (+ empty itemIDs for queue instant finish); isGold never put on the wire (QueueCcdMNewMessage.lua:3-17); golloesSpeedTime = separate free-time pool.

Speed-up goods (goods static, type2 = menu, para3 = seconds): 200200-200207 universal (type2 1), 200210-200217 construction (7), 200220-200227 research (6) — each 60/300/900/3,600/10,800/28,800/86,400/432,000; 200230-200235 training (3), 200240-200245 healing (4) — first six.

Radar tasks (march-free types {6, 18, 26, 44} + talks 14)

Gates: radar_quick_operation_switch in init dataConfig; playerServerSeasonInfo.seasonId ≥ 2 (lw_function_season row 106); radar level ≥ 6 (detect_config.k100, from get.detect.info reply level).

  • detect.event.batch.put.point.in.world {uuidList: "uuid|uuid"} (state 3→0)
  • types 6/26: start.pick.garbage {uuid} … ≥3 s … finish.sampling {uuid} (44 adds finish.visitor)
  • 18: detect.event.help.start/end {uuid, eventType: 18} (10 stamina unless cost == 1)
  • 14: start.detect.event.talk {uuid} … end.detect.event.talk {uuid} (claims at once)
  • the real client never sends reset.detect.event (diamond re-roll)
  • Task-bank model: refresh adds N tasks, loses eventNum + N − max(show − slots, 0) − maxNum when full (detect_level, UIDetectEventCtrl.lua:371-394)
notes build 2026-11-07 12:10 UTC · lw-notes@9c47e1b · sources: own capture/analysis cross-checked against the public dossier