Skip to content

Commit 48a9819

Browse files
web3blindclaude
andcommitted
Fix chronicle feed, energy cost choice, arena player list, guild discovery, and boss sync
- Chronicle now shows narrative entries for all game actions (hunt, craft, rest, etc.), not just posts — fixes empty feed and uncompletable bless quests - Add 0.1% / 1% energy cost buttons for travel and blessings (like hunt already has) - Add "Known Mages" section on arena screen so players can challenge without manual name entry - Auto-add guild listing on creation so fresh clients discover guilds without manual promotion - Fix boss HP reset: remove duplicate optimistic attackBoss() call, auto-spawn boss if spawn block is outside sync window, add EventBus auto-refresh Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 968be61 commit 48a9819

10 files changed

Lines changed: 255 additions & 36 deletions

File tree

FIX.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,54 @@
6767
- Блоки с контентом — задержка 10мс вместо 50мс при догонке.
6868
- **Коммит:** (текущий)
6969

70+
## Сессия 2026-04-10
71+
72+
### Контекст
73+
Баги из чата с тестировщиками (Кочевник и PAPA MEDBEDb):
74+
- Хроника пустая, невозможно выполнить квест "благословить магов"
75+
- Нет выбора стоимости энергии при путешествии и благословении (как в охоте)
76+
- Гильдии не видны другому аккаунту, приглашения не приходят
77+
- На арене нет списка игроков для вызова на дуэль
78+
- Босс: HP сбрасывается, атаки не видны другим
79+
80+
### Баги и что сделано
81+
82+
#### 1. Хроника пустая — квест "благословить магов" невыполним
83+
- **Причина:** `_actionToPostText()` возвращал `null` для всех типов кроме `chronicle_post`. Если никто не писал в хронику с тегом `#viz_magic`, лента была пустой — нельзя никого благословить.
84+
- **Фикс (chronicle.js):** `_actionToPostText()` теперь генерирует нарративные тексты для всех игровых действий: hunt, char.attune, craft, rest, guild.create, guild.accept, boss.attack, blessing_sent. Нарративные строки уже были в i18n (chronicle_narrative_*).
85+
- **Дополнительно:** `recentActions` лимит увеличен со 100 до 200 (state-engine.js), чтобы больше активности было видно в ленте. Обновлены подсказки в i18n (ru.js, en.js) — убрано упоминание "только через посты в Хронике".
86+
87+
#### 2. Выбор стоимости энергии при путешествии и благословении
88+
- **Было:** Путешествие — фикс. 100bp (1%), Благословение — фикс. 100bp (1%).
89+
- **Фикс (map.js, chronicle.js):** Добавлены две кнопки: 0.1% (10bp) и 1% (100bp). Благословение: `data-energy` атрибут на кнопке, `_onBless()` читает стоимость из атрибута. Путешествие: `data-cost` атрибут, `_travelTo()` принимает параметр cost.
90+
- **CSS (main.css):** `.region-travel-options` — flex-контейнер для двух кнопок путешествия.
91+
92+
#### 3. Список игроков на арене для вызова на дуэль
93+
- **Было:** Только ручной ввод имени + leaderboard (пустой без дуэлей).
94+
- **Фикс (arena.js):** Добавлена секция "Известные маги" (`_renderKnownPlayers()`) — список всех `state.characters` с кнопками "Вызвать". Показывает имя, уровень, класс. Сортировка по уровню. Не дублирует leaderboard.
95+
- **i18n:** `arena_known_players` в ru.js и en.js.
96+
- **CSS (main.css):** `.arena-players-list`, `.arena-player-card` стили.
97+
98+
#### 4. Гильдии не видны — улучшение обнаружения
99+
- **Причина:** Окно синхронизации 28800 блоков (~24ч). Гильдия, созданная ранее, не попадала в state.guilds. Листинг требовал ручного "Продвинуть гильдию".
100+
- **Фикс (state-engine.js):** `_handleGuildCreate()` теперь автоматически добавляет листинг в `worldState.guildListings` при создании гильдии, без необходимости ручного продвижения.
101+
- **Фикс (guild.js):** Добавлена подсказка `guild_sync_hint` на экране гильдий, когда список гильдий и приглашений пуст — объясняет, что идёт синхронизация.
102+
- **i18n:** `guild_sync_hint` в ru.js и en.js.
103+
104+
#### 5. Босс: HP сбрасывается, атаки не видны другим
105+
- **Причина 1:** Если boss spawn-блок был до окна синхронизации, `worldState.worldBoss` оставался `null` → все `boss.attack` действия игнорировались.
106+
- **Причина 2:** UI делал дублирующий вызов `WorldBoss.attackBoss()` (оптимистичное обновление) + state-engine делал тот же вызов при обработке блока → двойной урон. После перезагрузки оптимистичные данные терялись → HP "восстанавливался".
107+
- **Фикс (state-engine.js):** `_handleBossAttack()` теперь автоматически создаёт босса (`WorldBoss.spawnBoss()`), если `worldState.worldBoss` отсутствует, но приходят атаки.
108+
- **Фикс (world-boss.js UI):** Убран дублирующий `WorldBoss.attackBoss()` из UI. Теперь UI показывает только оценку урона. Реальный урон применяется state-engine из блокчейна.
109+
- **Фикс (world-boss.js UI):** Добавлен auto-refresh через EventBus при событиях `boss_attacked`, `boss_defeated`, `world_boss_spawn`.
110+
70111
### Известные нерешённые вопросы
71112

72113
1. **Передача лидерства гильдии** — основатель не может уйти, но нет action type для передачи роли founder другому участнику.
73114
2. **Раздача готовых checkpoint-файлов** — для совсем новых клиентов можно генерировать и хостить готовый checkpoint JSON, чтобы пропустить синхронизацию целиком.
115+
3. **Скорость синхронизации** — при 28800 блоков (~24ч) синхронизация занимает ~7 мин. Можно ускорить параллельными запросами блоков.
116+
4. **Сумка отображается по-разному на десктопе и мобильном** — стили inventory для мобильных нуждаются в доработке.
117+
5. **Пропадание предметов из сумки** — вероятно связано с ресинхронизацией: если предметы были получены до окна синхронизации, они не восстанавливаются из блокчейна (нужен checkpoint).
74118

75119
### Архитектурные заметки
76120
- ES5 strict, IIFE модули, без фреймворков и сборки

app/css/main.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,15 @@ body {
473473
.arena-row-me { background: rgba(212,168,67,0.1) !important; }
474474
.arena-challenge-btn { font-size: 0.8rem; padding: 4px 8px; }
475475
.btn-sm { padding: 6px 12px; font-size: 0.85rem; min-height: 36px; }
476+
.arena-players-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
477+
.arena-player-card {
478+
display: flex; align-items: center; justify-content: space-between; gap: 8px;
479+
padding: 8px 12px; background: var(--color-surface); border-radius: 8px;
480+
border: 1px solid var(--color-border);
481+
}
482+
.arena-player-info { display: flex; align-items: center; gap: 6px; }
483+
.arena-player-level { color: var(--color-text-muted); font-size: 0.85rem; }
484+
.region-travel-options { display: flex; gap: 6px; margin-top: 8px; }
476485

477486
.arena-history-entry {
478487
display: flex; align-items: center; justify-content: space-between; gap: 8px;

app/js/engine/state-engine.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ var StateEngine = (function() {
205205
worldState.headBlock = blockNum;
206206

207207
// Keep recent actions trimmed
208-
while (worldState.recentActions.length > 100) {
208+
while (worldState.recentActions.length > 200) {
209209
worldState.recentActions.shift();
210210
}
211211

@@ -637,7 +637,23 @@ var StateEngine = (function() {
637637
var guild = GuildSystem.createGuild(data.id, sender, data, blockNum);
638638
if (!guild) return [];
639639
worldState.guilds[data.id] = guild;
640-
return [{ type: 'guild_created', guildId: data.id, founder: sender, blockNum: blockNum }];
640+
641+
// Auto-add listing so fresh clients discover this guild
642+
if (!worldState.guildListings) worldState.guildListings = [];
643+
var alreadyListed = false;
644+
for (var gl = 0; gl < worldState.guildListings.length; gl++) {
645+
if (worldState.guildListings[gl].guild_id === data.id) { alreadyListed = true; break; }
646+
}
647+
if (!alreadyListed) {
648+
worldState.guildListings.push({
649+
guild_id: data.id,
650+
created_block: blockNum,
651+
sender: sender,
652+
blockNum: blockNum
653+
});
654+
}
655+
656+
return [{ type: 'guild_created', guildId: data.id, founder: sender, blockNum: blockNum, guildName: data.name || data.id }];
641657
}
642658

643659
function _handleGuildInvite(sender, data, blockNum) {
@@ -1020,7 +1036,14 @@ var StateEngine = (function() {
10201036

10211037
function _handleBossAttack(sender, data, blockNum, blockHash) {
10221038
if (typeof WorldBoss === 'undefined') return [];
1023-
if (!worldState.worldBoss || !worldState.worldBoss.active) return [];
1039+
1040+
// Auto-spawn boss if attack arrives but no boss state exists
1041+
// (happens when boss spawn block is outside the sync window)
1042+
if (!worldState.worldBoss || !worldState.worldBoss.active) {
1043+
var playerCount = Object.keys(worldState.characters).length;
1044+
worldState.worldBoss = WorldBoss.spawnBoss(blockNum, playerCount, WorldBoss.BOSS_ACCOUNT);
1045+
}
1046+
10241047
var character = worldState.characters[sender];
10251048
if (!character) return [];
10261049

app/js/i18n/en.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ var LangEN = {
210210
chronicle_placeholder: 'Write your words into the World...',
211211
chronicle_send: 'Inscribe',
212212
chronicle_empty: 'Chronicle is empty. Be the first to write!',
213-
chronicle_bless_posts_only_hint: 'Blessings are available only through Chronicle posts. There are no suitable posts from other mages in the feed yet.',
214-
chronicle_no_bless_targets_hint: 'There are no other mages\' posts to bless right now. Blessings work only on other players\' Chronicle posts.',
213+
chronicle_bless_posts_only_hint: 'No activity from other mages in the feed yet. You can bless any actions of other mages — hunts, crafts, posts.',
214+
chronicle_no_bless_targets_hint: 'No activity from other mages to bless right now. Try later when other mages perform actions.',
215215
chronicle_narrative_hunt: '{name} set out on a hunt.',
216216
chronicle_narrative_hunt_win: '{name} defeated {creature}!',
217217
chronicle_narrative_hunt_lose: '{name} fell in battle with {creature}.',
@@ -296,6 +296,7 @@ var LangEN = {
296296
arena_filter_level: 'Filter by level',
297297
arena_all_levels: 'All levels',
298298
arena_no_duels: 'No duels yet. Issue a challenge!',
299+
arena_known_players: 'Known Mages',
299300
arena_mage: 'Mage',
300301
arena_wins: 'Wins',
301302
arena_losses: 'Losses',
@@ -393,6 +394,7 @@ var LangEN = {
393394
guild_joined: 'You joined the guild!',
394395
guild_created: 'Guild created!',
395396
guild_not_member: 'You are not in a guild. Create your own or join an existing one.',
397+
guild_sync_hint: 'Syncing with the World. Guilds and invites will appear after sync completes.',
396398
guild_recommended: 'Recommended Guilds',
397399
guild_members: 'Members',
398400
guild_level: 'Level',

app/js/i18n/ru.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ var LangRU = {
210210
chronicle_placeholder: 'Впиши слова в Мир...',
211211
chronicle_send: 'Вписать',
212212
chronicle_empty: 'Хроника пуста. Будь первым, кто впишет слова!',
213-
chronicle_bless_posts_only_hint: 'Благословения доступны только через записи в Хронике. Пока в ленте нет подходящих постов других магов.',
214-
chronicle_no_bless_targets_hint: 'Сейчас в ленте нет чужих записей для благословения. Благословлять можно только посты других магов в Хронике.',
213+
chronicle_bless_posts_only_hint: 'В ленте пока нет активности других магов. Благословлять можно любые действия других магов — охоту, крафт, посты.',
214+
chronicle_no_bless_targets_hint: 'Сейчас в ленте нет активности других магов для благословения. Попробуйте позже, когда другие маги совершат действия.',
215215
chronicle_narrative_hunt: '{name} отправился на охоту.',
216216
chronicle_narrative_hunt_win: '{name} одолел {creature}!',
217217
chronicle_narrative_hunt_lose: '{name} пал в бою с {creature}.',
@@ -296,6 +296,7 @@ var LangRU = {
296296
arena_filter_level: 'Фильтр по уровню',
297297
arena_all_levels: 'Все уровни',
298298
arena_no_duels: 'Дуэлей пока нет. Бросьте вызов!',
299+
arena_known_players: 'Известные маги',
299300
arena_mage: 'Маг',
300301
arena_wins: 'Побед',
301302
arena_losses: 'Поражений',
@@ -393,6 +394,7 @@ var LangRU = {
393394
guild_joined: 'Вы вступили в гильдию!',
394395
guild_created: 'Гильдия создана!',
395396
guild_not_member: 'Вы не состоите в гильдии. Создайте свою или вступите в существующую.',
397+
guild_sync_hint: 'Идёт синхронизация с Миром. Гильдии и приглашения появятся после завершения загрузки.',
396398
guild_recommended: 'Рекомендуемые Гильдии',
397399
guild_members: 'Участники',
398400
guild_level: 'Уровень',

app/js/ui/screens/arena.js

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,7 @@ var ArenaScreen = (function() {
269269

270270
if (leaderboard.length === 0) {
271271
html += '<p class="empty-state">' + t('arena_no_duels') + '</p>';
272-
container.innerHTML = html;
273-
return;
274-
}
272+
} else {
275273

276274
html += '<table class="arena-table" role="table" aria-label="' + t('arena_leaderboard') + '">' +
277275
'<thead><tr>' +
@@ -311,10 +309,15 @@ var ArenaScreen = (function() {
311309
html += '</td></tr>';
312310
}
313311

314-
html += '</tbody></table>';
312+
html += '</tbody></table>';
313+
} // end leaderboard.length > 0
314+
315+
// Known players list — all characters that can be challenged
316+
html += _renderKnownPlayers(state, user, leaderboard);
317+
315318
container.innerHTML = html;
316319

317-
// Bind challenge buttons
320+
// Bind challenge buttons (both leaderboard and known players)
318321
var challengeBtns = container.querySelectorAll('.arena-challenge-btn');
319322
for (var j = 0; j < challengeBtns.length; j++) {
320323
challengeBtns[j].addEventListener('click', function() {
@@ -335,6 +338,50 @@ var ArenaScreen = (function() {
335338
}
336339
}
337340

341+
function _renderKnownPlayers(state, user, leaderboard) {
342+
var t = Helpers.t;
343+
// Collect all known characters not already in leaderboard
344+
var lbAccounts = {};
345+
for (var li = 0; li < leaderboard.length; li++) {
346+
lbAccounts[leaderboard[li].account] = true;
347+
}
348+
349+
var players = [];
350+
if (state.characters) {
351+
for (var account in state.characters) {
352+
if (!state.characters.hasOwnProperty(account)) continue;
353+
if (account === user || lbAccounts[account]) continue;
354+
var ch = state.characters[account];
355+
if (!ch || !ch.name) continue;
356+
players.push({ account: account, name: ch.name, level: ch.level || 1, className: ch.className });
357+
}
358+
}
359+
360+
if (players.length === 0) return '';
361+
362+
// Sort by level descending
363+
players.sort(function(a, b) { return b.level - a.level; });
364+
365+
var html = '<h3>' + (t('arena_known_players') || 'Известные маги') + '</h3>';
366+
html += '<div class="arena-players-list" role="list">';
367+
for (var i = 0; i < Math.min(players.length, 50); i++) {
368+
var p = players[i];
369+
html += '<div class="arena-player-card" role="listitem">' +
370+
'<span class="arena-player-info">' +
371+
'<span aria-hidden="true">' + Helpers.classIcon(p.className) + '</span> ' +
372+
Helpers.escapeHtml(p.name) +
373+
' <span class="arena-player-level">Lv ' + p.level + '</span>' +
374+
'</span>' +
375+
'<button class="btn btn-secondary btn-sm arena-challenge-btn" ' +
376+
'data-account="' + p.account + '" ' +
377+
'aria-label="' + (t('arena_challenge_player', { name: p.name }) || p.name) + '">' +
378+
(t('duel_challenge') || 'Вызвать') + '</button>' +
379+
'</div>';
380+
}
381+
html += '</div>';
382+
return html;
383+
}
384+
338385
function _renderHistory(container) {
339386
var t = Helpers.t;
340387
var user = VizAccount.getCurrentUser();

0 commit comments

Comments
 (0)