<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Pokémon World Dev Blog]]></title><description><![CDATA[Pokémon World Dev Blog]]></description><link>https://pokemonworld.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 06:25:11 GMT</lastBuildDate><atom:link href="https://pokemonworld.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[One browser MMO, seven languages: RTL, CJK fonts behind the Great Firewall, and translation overlays]]></title><description><![CDATA[How we run a fan-made 3D Pokémon browser MMO as seven language editions: translation overlays, RTL bidi traps, CJK fonts behind the Great Firewall…]]></description><link>https://pokemonworld.hashnode.dev/one-browser-mmo-seven-languages-rtl-cjk-fonts-behind-the-great-firewall-and-translation-overlays</link><guid isPermaLink="true">https://pokemonworld.hashnode.dev/one-browser-mmo-seven-languages-rtl-cjk-fonts-behind-the-great-firewall-and-translation-overlays</guid><category><![CDATA[GameDev]]></category><category><![CDATA[i18n]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[ThreeJS]]></category><dc:creator><![CDATA[Pokémon World Team]]></dc:creator><pubDate>Mon, 26 Oct 2026 17:50:33 GMT</pubDate><content:encoded><![CDATA[<p>We are a small team running a free, fan-made 3D Pokémon MMO that plays in the browser on PC and phone (three.js on the client, PHP and MySQL on the server). It exists as seven language editions: English, Hebrew, Japanese, Simplified Chinese, Brazilian Portuguese, Hindi and Arabic, each on its own domain.</p>
<p>Here is what we learned from running one game in seven languages. It isn't a framework pitch. It's a list of the decisions we made, the traps we fell into, and the code that came out of it.</p>
<h2>Seven deployments, one code lineage</h2>
<p>All seven editions run on one server, but each is its own site with its own MySQL database, sessions and secrets. Accounts never cross editions. English is the trunk and gets new code first.</p>
<p>A new edition starts the same way every time: we copy the English site on the server, delete anything private (uploads, logs, backups, tools), write a fresh <code>.env</code> and secret, and create an empty database. It gets the full schema but only the static reference tables: species, moves, items, NPCs (on the newest editions, 33 tables and about 11,700 rows). It gets no players, sessions or payments.</p>
<p>The language layer then goes on top, and each edition has a slightly different history:</p>
<ul>
<li><strong>Hebrew</strong> is a <em>dictionary overlay</em> (more below).</li>
<li><strong>Japanese</strong> has its own layer, which routes canvas and 3D text through a <code>JapanUI</code> helper.</li>
<li><strong>Simplified Chinese</strong> was cut from the Japanese code base. We took an English copy, laid the roughly 300 files where the Japanese edition differs on top, and ran a script that turned every <code>'ja'</code>/<code>ja-JP</code>/<code>lang="ja"</code> into Chinese. Then came the translation. Internal names still say "Japanese". The Chinese catalog literally starts with <code>window.PKW_JA={"locale":"zh",...</code>, and we decided renaming wasn't worth the risk.</li>
<li><strong>Brazilian Portuguese</strong> is a server copy of English plus an overlay of only the files that differ (configuration, the language engine re-pointed to <code>pt</code>, translated assets). Its catalog reuses the Chinese catalog's structure with Portuguese values.</li>
<li><strong>Hindi</strong> and <strong>Arabic</strong> are also English copies. Arabic reused the Hindi translation runtime and added right-to-left support.</li>
</ul>
<p>One more layer connects the separate databases: a small hub on one of the servers. Each edition publishes its online players to it, in batches of up to 80 players at most every five seconds. Trainers from other editions near you then show up on your map, there is a shared chat, and you can invite a player from another edition to a friendly battle. That battle uses a snapshot of both teams at full HP and writes nothing back (no ranking, rewards or items). Identities are <code>site:userId</code>, so IDs never collide. Every server-to-server call is signed:</p>
<pre><code class="language-php">$sig = hash_hmac('sha256', $cfg['site']."\n".$ts."\n".$raw, $cfg['key']);
// hub side: reject if abs(time() - $ts) &gt; 60 or the signature fails hash_equals()
</code></pre>
<h2>Translation as an overlay, not a fork</h2>
<p>The source strings stay English. On the Hebrew server, an output buffer injects the language runtime into every page and sends <code>Content-Language: he</code>. API responses are translated only at the boundary, and only the presentation fields:</p>
<pre><code class="language-php">function pkw_hebrew_response(array $data): array {
    foreach (['message', 'error'] as $key) {
        if (!isset($data[$key]) || !is_string($data[$key])) continue;
        // stable machine tokens such as already_claimed stay untouched
        if ($key === 'error' &amp;&amp; preg_match('/^[a-z0-9_]+$/D', $data[$key])) continue;
        $original = $data[$key];
        $data[$key] = pkw_he_text($original);
        if ($key === 'error' &amp;&amp; $data[$key] !== $original &amp;&amp; !isset($data['error_source']))
            $data['error_source'] = $original; // old clients branch on the English text
    }
    return $data;
}
</code></pre>
<p>In the browser, <code>localize.js</code> uses a <code>MutationObserver</code> to walk text nodes, <code>placeholder</code> and <code>aria-label</code>. It matches exact phrases or <code>{slot}</code> templates, and it never touches usernames or chat (those sit under a preserve attribute). The catalog it reads (about 2 MB) is generated, not hand-edited.</p>
<p>Here's what that taught us:</p>
<ul>
<li><strong>Sentences glued together from fragments can't be translated by a dictionary.</strong> Our first crawl of the Hebrew edition found 578 visible English strings across 77 pages. Getting that to zero took about 9,000 source rewrites (whole sentences instead of fragments) and about 7,000 new dictionary entries.</li>
<li><strong>Canvas and three.js labels are not in the DOM.</strong> A text-node walker never sees them, so 3D code has to call the translation API explicitly.</li>
<li><strong>The hooks were named after Hebrew, and the names stuck.</strong> <code>_hebrew.php</code>, <code>hebrew-site.php</code> and <code>pkwHeText</code> became the generic seams. The Portuguese edition ships <code>game/assets/hebrew/pt-catalog.js</code>, and Hindi keeps the <code>pkwHe*</code> aliases. The names are ugly, but the seams are stable.</li>
</ul>
<p>The newer editions each carry a catalog of 13,000 to 15,000 phrases plus a 1,061-key UI dictionary. The first pass was machine translation and AI agents working from glossaries: official Simplified Chinese Pokémon names from PokéAPI's data, Brazilian terms such as "Professor Carvalho" and "Poké Bola". After that, crawlers report any English still visible on screen. The Arabic long tail is still machine-drafted, and we say so.</p>
<h2>RTL: Hebrew and Arabic</h2>
<p><code>dir="rtl"</code> and a mirrored layout are the easy part. Arabic got its own font (Cairo) and its own RTL stylesheet. Mixed-direction text is the hard part.</p>
<p><strong>Names and numbers inside RTL sentences.</strong> In the DOM we use <code>&lt;bdi&gt;</code>, <code>unicode-bidi: isolate</code> and <code>dir="auto"</code>. A canvas has none of those, so labels drawn with <code>fillText</code> wrap the name in Unicode isolates:</p>
<pre><code class="language-js">// 3D map nameplate: FSI ... PDI keeps a Latin or mixed name from reordering the Hebrew around it
return (isAdmin ? 'מנהל · ' : '') + '⁨' + handle + '⁩' + (level ? ' · רמה ' + String(level) : '');
// a resolution inside a Hebrew sentence: LRI ... PDI
qualityLabel + ' — ⁦' + render.width + ' × ' + render.height + '⁩ פיקסלים בתצוגה'
</code></pre>
<p><strong>"Hebrew" is not the same thing as "RTL".</strong> Code written for the Hebrew edition used its Hebrew flag as the direction switch (<code>dialog.dir = he ? 'rtl' : 'ltr'</code>). When Arabic arrived, the two meanings had to be split:</p>
<pre><code class="language-js">const he = lang.startsWith('he'), rtl = /^(he|ar)/.test(lang);
dialog.dir = rtl ? 'rtl' : 'ltr';
</code></pre>
<p><strong>Usernames.</strong> On the Arabic edition, a username may contain Latin letters, digits, <code>_</code>, Arabic script and combining marks, but nothing from the Unicode "format" category:</p>
<pre><code class="language-php">preg_match('/^[a-zA-Z0-9_\p{Arabic}\p{M}]+$/u', $name) === 1
  &amp;&amp; preg_match('/^[\p{M}]|[\p{Cf}\p{Z}\p{P}\p{S}]/u', str_replace('_', '', $name)) !== 1;
</code></pre>
<p>The second check is needed. U+061C ARABIC LETTER MARK belongs to the Arabic script <em>and</em> is a bidi control character (category Cf), so a whitelist on <code>\p{Arabic}</code> alone lets it in. U+202E RIGHT-TO-LEFT OVERRIDE, the classic trick for reversing the text that follows a name, is rejected as well.</p>
<h2>CJK fonts, and the Great Firewall</h2>
<p>The Japanese edition's font stack starts with Hiragino Kaku Gothic ProN, Yu Gothic and Meiryo. The Chinese edition must not inherit it. Windows ships Yu Gothic and Meiryo, and because of Han unification, the same code points render with Japanese glyph shapes when a Japanese font wins. The Chinese stack is:</p>
<pre><code class="language-css">font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC",
             "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Micro Hei", sans-serif;
</code></pre>
<p>We don't ship a Chinese web font at all, since a full one is several megabytes, so CJK text uses system fonts. What we do self-host for that edition:</p>
<ul>
<li><strong>Google Fonts.</strong> The Latin display fonts' CSS and files live under <code>/assets/fonts/gf/</code>.</li>
<li><strong>Pokémon sprites.</strong> The PokéAPI sprites are mirrored under <code>/assets/pokeapi-sprites/</code>, with every URL rewritten.</li>
</ul>
<p>Both Google's font CDN and <code>raw.githubusercontent.com</code> are unreliable or blocked from mainland China. A page that waits on them simply doesn't finish loading for that audience.</p>
<p>One honest inconsistency: the Chinese <code>&lt;html&gt;</code> tag says <code>lang="zh"</code>, because our helper code compares the string exactly, while <code>hreflang</code> declares <code>zh-Hans</code>.</p>
<h2>Per-edition SEO</h2>
<p>Each landing page has its own title and description written in its language, a canonical URL pointing to itself, seven reciprocal <code>hreflang</code> alternates, and an <code>x-default</code> that points to the hub where you pick a server:</p>
<pre><code class="language-html">&lt;link rel="alternate" hreflang="en" href="https://pokemonworld.net/"&gt;
&lt;link rel="alternate" hreflang="he" href="https://pokemon-battle.net/"&gt;
&lt;link rel="alternate" hreflang="zh-Hans" href="https://pokemon-world.net/"&gt;
&lt;link rel="alternate" hreflang="pt-BR" href="https://pokemonarena.net/"&gt;
&lt;!-- ja, hi, ar ... --&gt;
&lt;link rel="alternate" hreflang="x-default" href="https://tamerra.com/"&gt;
</code></pre>
<p>The mistakes we found were all side effects of copying a site. On the Portuguese and Arabic editions, the secondary pages (<code>/about.php</code>, <code>/contact.php</code>, <code>/privacy.php</code>, <code>/terms.php</code>, <code>/copyright.php</code>, <code>/blog/</code>) still had a hardcoded canonical pointing at the English domain. That tells Google to fold them into the English pages. The Portuguese <code>robots.txt</code> and sitemap also pointed at the English domain, and the Arabic <code>/guides/</code> pages were English copies until we translated them. If you clone a site, grep the copy for the original domain. We also replaced a 3.2 MB PNG share image with a 1200×630 JPEG.</p>
<h2>A small bug: the loader that failed in background tabs</h2>
<p>The landing page shows a loader that waits for the hero image, the emblem and the fonts, with a 15-second timeout. People who opened the game in a background tab came back to a "graphics failed" error. In Chrome, <code>img.decode()</code> stayed pending while the tab was hidden, but our timer kept running. The fix was to arm the timeout only while the page is visible:</p>
<pre><code class="language-js">arm = () =&gt; {
  if (document.hidden) return;                     // wait until the tab is actually shown
  document.removeEventListener('visibilitychange', arm);
  timer = setTimeout(() =&gt; reject(Error('timeout')), 15000);
};
document.addEventListener('visibilitychange', arm);
arm();
</code></pre>
<p>If a failure still happens while the page is hidden, the loader waits for <code>visibilitychange</code> and retries instead of showing the error. The fix went out on all eight domains.</p>
<h2>Two systems that are live today</h2>
<ul>
<li><strong>Real League.</strong> Every Monday the league resets, and the nine strongest active trainers (by ranked points, then rating) are crowned. Number 1 becomes the Champion and numbers 2 to 9 become the gym leaders. Their teams are frozen at crowning and scaled to each gym's level cap. Everyone else can challenge each seat once per day, and the Champion needs eight badges.</li>
<li><strong>Move Forge.</strong> You design one signature move for a Pokémon from a 100-point budget: power 40–120, accuracy, PP, and exactly one effect. A 90-power, 100%-accuracy, 15 PP move with a 10% burn chance costs 76 points. The name has to be unique on the server. The move becomes a real row in the <code>moves</code> table, so every battle engine resolves it like any other move.</li>
</ul>
<p>Both are live on the English, Hebrew, Japanese, Portuguese, Hindi and Arabic editions, but not on the Chinese one. It was cut from the Japanese code just before these systems were added and hasn't received them.</p>
<h2>Links</h2>
<ul>
<li>English: <a href="https://pokemonworld.net/">https://pokemonworld.net/</a></li>
<li>Hebrew: <a href="https://pokemon-battle.net/">https://pokemon-battle.net/</a></li>
<li>Japanese: <a href="https://pokemon-arena.net/">https://pokemon-arena.net/</a></li>
<li>Simplified Chinese: <a href="https://pokemon-world.net/">https://pokemon-world.net/</a></li>
<li>Brazilian Portuguese: <a href="https://pokemonarena.net/">https://pokemonarena.net/</a></li>
<li>Hindi: <a href="https://poke-world.net/">https://poke-world.net/</a></li>
<li>Arabic: <a href="https://pokemonar.net/">https://pokemonar.net/</a></li>
<li>All servers: <a href="https://tamerra.com/">https://tamerra.com/</a></li>
<li>Trailer (English): <a href="https://youtu.be/nIE2PVdz4DY">https://youtu.be/nIE2PVdz4DY</a></li>
</ul>
<p>Unofficial fan project, not affiliated with Nintendo, Game Freak or The Pokémon Company. The game is free with optional purchases. This article was written with the help of an AI assistant.</p>
]]></content:encoded></item><item><title><![CDATA[做了一款浏览器里的 3D 宝可梦同人 MMO：会"记仇"的野生宝可梦、玩家的"回声"和有预算的自创招式]]></title><description><![CDATA[三个系统的设计取舍：会记住你的野生宝可梦个体、真实玩家的回声对战和 100 点预算的招式工坊（目前都没有在中文服务器上开放）。]]></description><link>https://pokemonworld.hashnode.dev/pokemon-world-chinese-three-systems</link><guid isPermaLink="true">https://pokemonworld.hashnode.dev/pokemon-world-chinese-three-systems</guid><category><![CDATA[GameDev]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[ThreeJS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Chinese]]></category><dc:creator><![CDATA[Pokémon World Team]]></dc:creator><pubDate>Mon, 26 Oct 2026 10:07:25 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>说明（2026 年 10 月 26 日更新）：</strong> 下文介绍的系统目前在中文服务器上都还没有开放。其中第二节的野生宝可梦"个体"（会记仇）和第三节的"回声"已在所有服务器上暂时关闭，我们正在重新制作；"真实联赛"和第四节的"招式工坊"目前只在其他语言的服务器上运行。中文版现在可以体验的是：与真实玩家共享的 3D 世界、对战、交换和图鉴。</p>
</blockquote>
<p>我们是一个小团队，在做一款粉丝向的 3D 宝可梦 MMO「宝可梦世界」，完全在浏览器里运行，电脑和手机都能玩（画面用 three.js，后端是 PHP + MySQL）。这个月我们为游戏做了三个系统，目标是让这个世界"有记忆"（目前都没有在中文服务器上开放，见上方说明）。下面说说设计思路和实现上的取舍。</p>
<h2>一、前提：结果永远由服务器决定</h2>
<p>三个系统都遵守同一条规则：客户端不决定结果。浏览器只负责发请求和显示，"谁赢了""谁逃走了""谁拿到了金币"，一律从服务器保存的对战记录里读出来。每个功能都是一个小模块（一个服务端接口 + 一个客户端脚本），可以用 URL 参数单独关闭，方便同时发布到多个语言服务器，也方便单独回滚。</p>
<h2>二、野生宝可梦变成"个体"</h2>
<p><strong>状态</strong>：自 2026 年 10 月 23 日起在所有服务器上暂时关闭，我们正在重新制作。本节记录的是当时实现的设计。</p>
<p>刷怪逻辑本身没改。改的是：你遇到的每一只野生宝可梦都会绑定到一个"个体"上——</p>
<ul>
<li>有编号，比如 #0248-117（图鉴编号 + 该物种的序号）</li>
<li>有出生区域、会随时间成长的等级，以及遇见、逃走、击败过谁的记录</li>
<li>有了故事之后还会获得称号</li>
</ul>
<p>记仇：如果某个个体从你手里逃走，或者打败了你的队伍，它会记住"你"。等级会小幅上升（有上限），下次你经过同一区域时，很可能还是它来找你。逃走和获胜累计两次后，会根据属性获得称号（比如岩石属性的"碎山者"）。</p>
<p>"变强回来了"必须是真的：只在界面上演出是骗人的，所以记仇的个体和你交战时，服务器会直接提高这场战斗会话里的出场等级，战斗按强化后的数值生成。</p>
<p>结算靠"读取"而不是"上报"：战斗窗口关闭后，客户端只问"结果如何"，服务器从已完成的对战记录判断是捕获、逃走、击倒还是战败。终于抓住那只逃了三次的个体时，它的经历（"逃走 3 次后被捕获"）会一起保留下来。</p>
<h2>三、"回声"：用真实玩家取代假 NPC 玩家</h2>
<p><strong>状态</strong>：自 2026 年 10 月 24 日起在所有服务器上暂时关闭，我们正在重新制作。</p>
<p>为了让地图看起来热闹而放的假玩家，我们曾经全部换成了"回声"：每个玩家都会在最后站的位置留下一个回声，带着真实的名字、队伍、外观和段位。按这个设计，任何人都可以挑战，对手就是主人真实的队伍。挑战者赢了拿金币和经验；回声守住了，离线的主人也能拿到金币，下次打开地图会看到"离线期间的战报"。</p>
<p>因为离线玩家也会产生奖励，经济上加了限制：同一个回声每天只能挑战一次；每天有奖励的胜场和防守次数有上限；胜负以服务器的对战记录为准。</p>
<p>"真实联赛"是独立的系统（目前只在其他语言的服务器上运行，中文服务器暂未开放）：每周一，排名前 9 的训练家成为道馆馆主和冠军，名字会显示在 3D 地图里道馆的上方。</p>
<h2>四、招式工坊：100 点预算设计自己的招式</h2>
<p><strong>状态</strong>：目前只在其他语言的服务器上运行，中文服务器暂未开放。</p>
<p>玩家一直想要自创招式，但完全放开的话，所有招式都会变成"威力 150、命中 100、还附带能力提升"。所以我们用 100 点预算：</p>
<ul>
<li>威力（40～120）要花点数，超过 90 之后更贵</li>
<li>命中率和 PP 可以换回点数（命中 70% 或 PP 5 的招式更便宜）</li>
<li>追加效果只能选一个（灼伤、麻痹、中毒概率，按伤害回复，自身能力 +1，对手能力 -1，各有价格）</li>
</ul>
<p>作为参考，相当于喷射火焰的招式（威力 90、命中 100、PP 15、10% 灼伤）要花 76 点。做出来的招式不是特例，而是作为普通的一行写进招式表，所有战斗模式都像官方招式一样按名字解析。只有一个追加效果、不能选先制度，这两个限制都来自战斗引擎本身，我们把它们作为设计的一部分直接展示，而不是绕过去。</p>
<h2>五、顺便说一个坑：隐藏标签页里的 img.decode()</h2>
<p>Chrome 在标签页隐藏时会推迟 img.decode() 的完成。我们的加载画面带 15 秒超时等待图片解码，结果从论坛 Ctrl+点击、在后台标签页打开游戏的人，切回来看到的是错误提示。解决办法：只在 document.visibilityState === 'visible' 时才开始计时，隐藏期间失败的话，等标签页显示时再安静地重试。</p>
<h2>试玩</h2>
<ul>
<li>中文版：<a href="https://pokemon-world.net/">https://pokemon-world.net/</a> （免下载，游客一键开始；目前可以体验共享 3D 世界、对战、交换和图鉴）</li>
<li>其他语言服务器：<a href="https://tamerra.com/">https://tamerra.com/</a></li>
<li>预告片：<a href="https://youtu.be/MvLuNw2IIm4">https://youtu.be/MvLuNw2IIm4</a></li>
</ul>
<p>本游戏为爱好者制作的非官方同人作品，与任天堂、株式会社宝可梦、Game Freak 无关。游戏免费，有可选的付费道具。本文在 AI 助手的协助下撰写。</p>
]]></content:encoded></item><item><title><![CDATA[Pokémon selvagens que guardam rancor: três sistemas que deixam o nosso MMO de navegador vivo]]></title><description><![CDATA[Como criamos Pokémon selvagens que lembram de você e Ecos de jogadores reais (ambos pausados por enquanto) e a forja de golpes de 100 pontos, que está no…]]></description><link>https://pokemonworld.hashnode.dev/pok-mon-selvagens-que-guardam-rancor-tr-s-sistemas-que-deixam-o-nosso-mmo-de-navegador-vivo</link><guid isPermaLink="true">https://pokemonworld.hashnode.dev/pok-mon-selvagens-que-guardam-rancor-tr-s-sistemas-que-deixam-o-nosso-mmo-de-navegador-vivo</guid><category><![CDATA[GameDev]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[ThreeJS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[brasil]]></category><dc:creator><![CDATA[Pokémon World Team]]></dc:creator><pubDate>Mon, 26 Oct 2026 10:06:15 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>Atualização, 26 out. 2026:</strong> o Mundo Vivo (Pokémon selvagens que se lembram de você) e os Ecos estão desligados por enquanto, enquanto refazemos os dois. A Liga Real e a Forja de Golpes descritas abaixo estão no ar.</p>
</blockquote>
<p>Na maioria dos jogos de colecionar monstros, os encontros selvagens são descartáveis: uma espécie aleatória, um nível aleatório, e tudo some quando a batalha acaba. Quando começamos a construir o <strong>Pokémon Arena</strong>, um MMO de Pokémon em 3D feito por fãs que roda inteiro no navegador, queríamos o contrário: um mundo que se lembra.</p>
<p>Este texto mostra três sistemas que criamos este mês na edição brasileira: Pokémon selvagens que são indivíduos, "Ecos" de jogadores reais que dava para enfrentar mesmo quando eles estavam offline, e uma forja onde o treinador cria o seu golpe exclusivo com um orçamento limitado. Os dois primeiros estão desligados por enquanto, enquanto os refazemos. A Forja de Golpes e a Liga Real semanal estão no ar, e dá para testar no navegador, sem download, com uma conta de convidado em um clique: <a href="https://pokemonarena.net/">https://pokemonarena.net/</a></p>
<h2>A regra de base: quem decide o resultado é o servidor</h2>
<p>O navegador só pede e mostra. Quem venceu a batalha, quem fugiu e quem ganhou moedas é sempre lido dos registros de batalha guardados no servidor. Cada recurso é um módulo pequeno que pode ser desligado sozinho, o que facilita publicar a mesma coisa em vários servidores de idiomas ao mesmo tempo.</p>
<h2>1. Mundo Vivo: cada Pokémon selvagem é alguém</h2>
<p><strong>Situação:</strong> desligado desde 23 de outubro de 2026, enquanto o refazemos. Esta seção descreve o sistema como ele foi construído.</p>
<p>Com o Mundo Vivo, todo Pokémon selvagem que você encontrava tinha:</p>
<ul>
<li>um número próprio, como <code>#0248-117</code></li>
<li>uma região de nascimento e um nível que cresce com o tempo</li>
<li>um registro de quem o encontrou, de quem ele fugiu e quem ele derrotou</li>
<li>e um título, quando já tem uma história</li>
</ul>
<p><strong>Rancor.</strong> Se um Pokémon foge de você ou derrota o seu time, ele se lembra de você especificamente. O nível dele sobe um pouco (com limite), e da próxima vez que você passar pela mesma região há uma boa chance de ser ele a aparecer. Depois de duas fugas ou vitórias, ele ganha um título de acordo com o tipo.</p>
<p><strong>"Voltou mais forte" tem que ser verdade.</strong> Não ficamos só no efeito visual: quando um Pokémon com rancor te enfrenta, o servidor aumenta o nível do próprio encontro, e a batalha é montada com os valores mais fortes de verdade. E quando você finalmente captura aquele que fugiu três vezes, a história vem junto: "Capturado depois de 3 fugas".</p>
<h2>2. Ecos: jogadores reais no lugar de bots falsos</h2>
<p><strong>Situação:</strong> desligado desde 24 de outubro de 2026, enquanto o refazemos.</p>
<p>Os jogadores falsos que deixavam o mapa parecendo cheio foram substituídos por <strong>Ecos</strong>: cada jogador deixava um eco no último lugar onde ficou parado, com o nome real, o time real, a aparência e o rank.</p>
<p>Qualquer um podia desafiar um eco, e a batalha era contra o time verdadeiro do dono. Se o desafiante vencia, ganhava moedas e XP; se o eco resistia, o dono ganhava moedas offline e via um "relatório da sua ausência" quando voltava. Para proteger a economia: um desafio por eco por dia, limites diários de vitórias e defesas pagas, e o resultado conferido no registro do servidor.</p>
<p>A <strong>Liga Real</strong> é separada e continua no ar: toda segunda-feira, os nove treinadores mais fortes viram os líderes de ginásio e o Campeão com os próprios times, e os nomes aparecem em cima dos ginásios no mundo 3D.</p>
<h2>3. Forja de Golpes: um orçamento de 100 pontos</h2>
<p>Os jogadores pediam golpes próprios, mas liberdade total significa que todo golpe teria poder 150, precisão 100% e ainda um bônus. Por isso a forja funciona com um <strong>orçamento de 100 pontos</strong>:</p>
<ul>
<li>o poder (de 40 a 120) custa pontos, e fica mais caro acima de 90</li>
<li>precisão e PP devolvem pontos: um golpe menos preciso ou com menos PP sai mais barato</li>
<li><strong>um único</strong> efeito extra: chance de queimar, paralisar ou envenenar, cura pelo dano, +1 para você ou −1 no oponente</li>
</ul>
<p>Para comparar: um golpe no estilo Lança-Chamas (poder 90, precisão 100%, 15 PP, 10% de queimadura) custa 76 dos 100 pontos. O golpe criado entra como uma linha comum na tabela de golpes, então todos os modos de batalha o reconhecem pelo nome, igual a um golpe oficial.</p>
<h2>Experimente</h2>
<ul>
<li>Edição brasileira: <a href="https://pokemonarena.net/">https://pokemonarena.net/</a></li>
<li>Todos os servidores de idiomas: <a href="https://tamerra.com/">https://tamerra.com/</a></li>
<li>Vídeo curto do jogo: <a href="https://youtu.be/W87zYWxU_RU">https://youtu.be/W87zYWxU_RU</a></li>
</ul>
<p>O jogo é grátis e roda no PC e no celular, com compras opcionais. Queremos muito saber o que você achou, principalmente da tradução.</p>
<p><em>Pokémon Arena é um projeto de fãs não oficial, sem ligação com a Nintendo, a Game Freak ou a The Pokémon Company. Este texto foi escrito com a ajuda de um assistente de IA.</em></p>
]]></content:encoded></item><item><title><![CDATA[दुश्मनी याद रखने वाले जंगली पोकेमॉन: ब्राउज़र MMO को ज़िंदा बनाने वाले तीन सिस्टम]]></title><description><![CDATA[Pokémon World India के तीन सिस्टम कैसे बने: याद रखने वाले जंगली पोकेमॉन और असली खिलाड़ियों की गूँज (दोनों फ़िलहाल बंद), और चालू 100 पॉइंट का मूव वर्कशॉप।]]></description><link>https://pokemonworld.hashnode.dev/pokemon-world-india-hindi-three-systems</link><guid isPermaLink="true">https://pokemonworld.hashnode.dev/pokemon-world-india-hindi-three-systems</guid><category><![CDATA[GameDev]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[ThreeJS]]></category><category><![CDATA[india]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Pokémon World Team]]></dc:creator><pubDate>Mon, 26 Oct 2026 09:59:49 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>अपडेट, 26 अक्टूबर 2026:</strong> ज़िंदा दुनिया (जंगली पोकेमॉन जो आपको याद रखते हैं) और गूँज (Echo) फ़िलहाल बंद हैं, क्योंकि हम इन्हें दोबारा बना रहे हैं। नीचे बताई गई रियल लीग और मूव वर्कशॉप चालू हैं।</p>
</blockquote>
<p>ज़्यादातर मॉन्स्टर-कलेक्टिंग गेम्स में जंगली मुकाबले एक बार के होते हैं: कोई भी प्रजाति, कोई भी लेवल, और बैटल खत्म होते ही सब गायब। जब हमने <strong>Pokémon World India</strong> बनाना शुरू किया, जो एक फ़ैन-मेड 3D पोकेमॉन MMO है और पूरी तरह ब्राउज़र में चलता है, तो हम इसका उल्टा चाहते थे: एक ऐसी दुनिया जो याद रखे।</p>
<p>इस पोस्ट में हम इस महीने बनाए गए तीन सिस्टम के बारे में बताते हैं: हर जंगली पोकेमॉन की अपनी पहचान, असली खिलाड़ियों की "गूँज" (Echo) जिनसे उनके ऑफ़लाइन रहते भी लड़ा जा सकता था, और एक वर्कशॉप जहाँ ट्रेनर तय बजट में अपना सिग्नेचर मूव बनाते हैं। पहले दो सिस्टम फ़िलहाल बंद हैं, क्योंकि हम इन्हें दोबारा बना रहे हैं। मूव वर्कशॉप और हर हफ़्ते की रियल लीग चालू हैं, और इन्हें ब्राउज़र में आज़माया जा सकता है, बिना डाउनलोड, एक क्लिक में गेस्ट अकाउंट से: <a href="https://poke-world.net/">https://poke-world.net/</a></p>
<h2>बुनियादी नियम: नतीजा हमेशा सर्वर तय करता है</h2>
<p>ब्राउज़र सिर्फ़ माँगता और दिखाता है। बैटल कौन जीता, कौन भागा, किसे सिक्के मिले, यह सब हमेशा सर्वर पर रखे बैटल रिकॉर्ड से पढ़ा जाता है। हर फ़ीचर एक छोटा मॉड्यूल है जिसे अलग से बंद किया जा सकता है, इसलिए इसे कई भाषाओं के सर्वरों पर एक साथ भेजना आसान है।</p>
<h2>1. ज़िंदा दुनिया: हर जंगली पोकेमॉन एक "व्यक्ति" है</h2>
<p><strong>स्थिति:</strong> 23 अक्टूबर 2026 से बंद है, क्योंकि हम इसे दोबारा बना रहे हैं। यह हिस्सा बताता है कि हमने यह सिस्टम कैसे बनाया था।</p>
<p>इस सिस्टम में हर जंगली पोकेमॉन के पास था:</p>
<ul>
<li>एक नंबर, जैसे <code>#0248-117</code></li>
<li>जन्म का इलाका और समय के साथ बढ़ता लेवल</li>
<li>किससे मिला, किससे भागा और किसे हराया, इसका रिकॉर्ड</li>
<li>और कहानी बनने पर एक उपाधि (टाइटल)</li>
</ul>
<p><strong>दुश्मनी याद रहती है।</strong> अगर कोई पोकेमॉन आपसे भाग जाए या आपकी टीम को हरा दे, तो वह खास तौर पर आपको याद रखता है। उसका लेवल थोड़ा बढ़ता है (एक सीमा तक), और उसी इलाके में अगली बार अक्सर वही आपके सामने आता है। दो बार भागने या जीतने के बाद उसे उसके टाइप के हिसाब से एक उपाधि मिलती है।</p>
<p><strong>"ताक़तवर होकर लौटा" सच होना चाहिए।</strong> हमने इसे सिर्फ़ स्क्रीन पर नहीं दिखाया: दुश्मनी रखने वाला पोकेमॉन जब आपसे भिड़ता है, तो सर्वर उस मुकाबले का लेवल ही बढ़ा देता है, ताकि बैटल सच में ताक़तवर आँकड़ों से बने। और जब आप आख़िरकार उस पोकेमॉन को पकड़ लेते हैं जो तीन बार भागा था, तो उसकी कहानी भी साथ आती है: "3 बार भागने के बाद पकड़ा गया"।</p>
<h2>2. गूँज (Echo): नकली खिलाड़ियों की जगह असली खिलाड़ी</h2>
<p><strong>स्थिति:</strong> 24 अक्टूबर 2026 से बंद है, क्योंकि हम इसे दोबारा बना रहे हैं।</p>
<p>नक्शे को भरा-भरा दिखाने वाले नकली खिलाड़ियों की जगह हमने <strong>गूँज</strong> रखी थीं: हर खिलाड़ी उस जगह अपनी गूँज छोड़ जाता था जहाँ वह आख़िरी बार खड़ा था, असली नाम, असली टीम, रूप और रैंक के साथ।</p>
<p>कोई भी गूँज को चुनौती दे सकता था, और मुकाबला मालिक की असली टीम से होता था। चुनौती देने वाला जीतता तो उसे सिक्के और XP मिलते; गूँज टिकी रहती तो ऑफ़लाइन मालिक को सिक्के मिलते और लौटने पर "आपकी गैरमौजूदगी की रिपोर्ट" दिखती। अर्थव्यवस्था बचाने के लिए: हर गूँज को दिन में एक बार चुनौती, इनाम वाली जीत और बचाव पर रोज़ की सीमा, और नतीजे की जाँच सर्वर रिकॉर्ड से।</p>
<p><strong>रियल लीग</strong> इससे अलग है और चालू है: हर सोमवार सबसे ताक़तवर 9 ट्रेनर अपनी असली टीमों के साथ जिम लीडर और चैंपियन बनते हैं, और उनके नाम 3D दुनिया में जिमों के ऊपर दिखते हैं।</p>
<h2>3. मूव वर्कशॉप: 100 पॉइंट का बजट</h2>
<p>खिलाड़ी अपने मूव बनाना चाहते थे, लेकिन पूरी छूट का मतलब होता हर मूव 150 पावर, 100% सटीकता और ऊपर से बूस्ट। इसलिए वर्कशॉप <strong>100 पॉइंट के बजट</strong> पर चलता है:</p>
<ul>
<li>पावर (40 से 120) पॉइंट खर्च करती है, 90 के बाद महँगी हो जाती है</li>
<li>सटीकता और PP पॉइंट वापस देते हैं: कम सटीक या कम PP वाला मूव सस्ता है</li>
<li>सिर्फ़ <strong>एक</strong> अतिरिक्त असर: जलना, लकवा या ज़हर का मौका, नुकसान से हीलिंग, खुद पर +1, या विरोधी पर -1</li>
</ul>
<p>तुलना के लिए: फ़्लेमथ्रोअर जैसा मूव (पावर 90, सटीकता 100%, 15 PP, 10% जलने का मौका) 100 में से 76 पॉइंट लेता है। बना हुआ मूव मूव-टेबल में एक साधारण पंक्ति की तरह जुड़ता है, इसलिए हर बैटल मोड उसे आधिकारिक मूव की तरह नाम से पहचानता है।</p>
<h2>आज़माइए</h2>
<ul>
<li>भारतीय संस्करण (हिंदी): <a href="https://poke-world.net/">https://poke-world.net/</a></li>
<li>सभी भाषाओं के सर्वर: <a href="https://tamerra.com/">https://tamerra.com/</a></li>
<li>छोटा वीडियो: <a href="https://youtu.be/Mzq2ZkoDYnA">https://youtu.be/Mzq2ZkoDYnA</a></li>
</ul>
<p>गेम मुफ़्त है, कंप्यूटर और फ़ोन दोनों पर चलता है, पुराने फ़ोन के लिए हल्के ग्राफ़िक्स का विकल्प भी है, और कुछ वैकल्पिक खरीदारी है। अपनी राय ज़रूर बताइए!</p>
<p><em>Pokémon World India एक अनौपचारिक फ़ैन प्रोजेक्ट है; Nintendo, Game Freak या The Pokémon Company से इसका कोई संबंध नहीं है। यह लेख एक AI सहायक की मदद से लिखा गया है।</em></p>
]]></content:encoded></item><item><title><![CDATA[Wild Pokémon that hold grudges: three systems that make our browser MMO feel alive]]></title><description><![CDATA[How we built wild Pokémon that remember you, offline player Echoes (both paused for now) and a live 100-point move forge in a three.js browser MMO.]]></description><link>https://pokemonworld.hashnode.dev/wild-pok-mon-that-hold-grudges-three-systems-that-make-our-browser-mmo-feel-alive</link><guid isPermaLink="true">https://pokemonworld.hashnode.dev/wild-pok-mon-that-hold-grudges-three-systems-that-make-our-browser-mmo-feel-alive</guid><category><![CDATA[GameDev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[ThreeJS]]></category><dc:creator><![CDATA[Pokémon World Team]]></dc:creator><pubDate>Mon, 26 Oct 2026 09:34:58 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>Update, 26 Oct 2026:</strong> Living World (wild Pokémon that remember you) and Echoes are switched off for now while we rework them. The Real League and Move Forge described below are live.</p>
</blockquote>
<p>Most monster-collecting games treat wild encounters as disposable: a random species, a random level, gone the moment the battle ends. When we started building <strong>Pokémon World</strong>, a free, fan-made 3D Pokémon MMO that runs entirely in the browser, we wanted the opposite. We wanted a world that remembers.</p>
<p>This post walks through three systems we built this month to get there: wild Pokémon that are individuals, "Echoes" of real players that you could battle while they were offline, and a forge where trainers design their own signature move under a strict budget. The first two are switched off for now while we rework them. The Move Forge and the weekly Real League are live, and you can try them in a browser tab, no download, with a one-click guest account: <a href="https://pokemonworld.net/">https://pokemonworld.net/</a></p>
<h2>The stack, briefly</h2>
<p>The overworld and the battles are rendered with three.js. The backend is plain PHP and MySQL. Each feature lives in its own small module (a server endpoint plus a client script) that can be switched off with a URL flag, which makes it easy to ship to several language servers at once and to roll back a single feature without touching the rest.</p>
<p>The rule that shaped all three systems: <strong>the client never decides outcomes.</strong> The browser can ask for things and display things, but who won a battle, who escaped and who earned coins is always read back from the server's own battle records.</p>
<h2>1. Living World: every wild Pokémon is somebody</h2>
<p><strong>Status:</strong> switched off since 23 Oct 2026 while we rework it. This section describes the system as we built it.</p>
<p>Spawning itself did not change. What changed was that every wild Pokémon you met was bound to an <em>individual</em> with:</p>
<ul>
<li>a number, in the form <code>#0248-117</code> (species, then a per-species sequence),</li>
<li>a birth zone and a level that grows over time,</li>
<li>a record of who met it, who it escaped from and who it beat,</li>
<li>and, once it has a story, a title.</li>
</ul>
<p><strong>Grudges.</strong> If an individual escapes from you, or knocks out your team, it remembers <em>you</em> specifically. Its level goes up a little (with a cap), and the next time you walk through that zone there is a good chance the same individual is the one that shows up. After two escapes or victories it earns a title from a pool that depends on its type: a Rock type might become "the Mountain-Breaker", a Water type "the Ship-Sinker". A small plate floated above marked spawns so you could see "REMEMBERS YOU · escaped ×2" before you engaged.</p>
<p><strong>"Came back stronger" has to be true.</strong> It would have been easy to fake this in the UI. Instead, when a grudge-holder engages you, the server raises the encounter level in the battle session itself, so the arena builds the fight from the stronger stats.</p>
<p><strong>Settlement is read, not reported.</strong> When the battle overlay closes, the client asks "what happened?" and the server answers from the finished battle record: caught, fled, knocked out, or beat you. If you finally catch the individual that escaped you three times, its life story goes with it: "Caught by you after 3 escapes."</p>
<h2>2. Echoes: real players instead of fake map bots</h2>
<p><strong>Status:</strong> switched off since 24 Oct 2026 while we rework it.</p>
<p>Like many MMOs, our map used to have NPC "players" walking around to make it feel busy. We replaced them with <strong>Echoes</strong>: every player left an echo at the spot where they last stood, with their real name, real team, real outfit and rank.</p>
<p>Anyone could challenge an echo. The fight was a duel against the owner's actual team. If the challenger won, they got coins and XP. If the echo held, the <em>owner</em> earned coins while offline and got a "while you were away" report the next time they opened the map.</p>
<p>Because echoes generated rewards for people who were not online, the economy needed guardrails:</p>
<ul>
<li>each challenger could fight a given echo once per day,</li>
<li>paid wins per challenger and paid defenses per owner were capped daily,</li>
<li>the result was verified from the server's battle record, never from the client.</li>
</ul>
<p>The <strong>Real League</strong> is separate and stays live: every Monday, the nine strongest trainers become the gym leaders and the Champion, with their own teams, and their names appear on plates above the gyms in the 3D world. The league is made of the players themselves.</p>
<h2>3. Move Forge: design a move, live with the trade-offs</h2>
<p>Players asked for custom moves. The risk is obvious: give people free rein and every custom move is 150 power, 100% accuracy, with a stat boost on top. So the forge runs on a <strong>100-point budget</strong>.</p>
<ul>
<li>Power costs points (40 to 120, getting more expensive above 90).</li>
<li>Accuracy and PP are the levers that give points back: a 70%-accurate move or a 5 PP move is cheaper.</li>
<li>You get exactly <strong>one</strong> secondary effect: a burn, paralysis or poison chance, healing from damage dealt, a +1 boost to yourself, or a −1 drop on the opponent, each with its own price.</li>
</ul>
<p>As a calibration point, a Flamethrower-class move (90 power, 100% accuracy, 15 PP, 10% burn) costs 76 of the 100 points.</p>
<p>There are a few other rules: the Pokémon needs level 20 and high friendship, the move must match one of its types (or be Normal), and the name has to be unique across the whole server. A forged move is not a special case in the battle code. It becomes an ordinary entry in the moves table, so every battle mode resolves it by name like any official move.</p>
<p>Two constraints came straight from the battle engine and we kept them visible in the design instead of hacking around them. A move gets <strong>one</strong> effect, because the engine applies a single effect chance per move. And there is <strong>no priority option</strong>, because our squad battles ignore priority. Drawbacks are expressed only through accuracy and PP.</p>
<h2>A small lesson from this week</h2>
<p>Chrome defers <code>img.decode()</code> while a tab is hidden. Our loading screen awaited <code>decode()</code> on its artwork with a 15-second timeout, so players who ctrl-clicked the game from a forum or a list into a background tab came back to an error dialog. The fix: only start the timeout while <code>document.visibilityState === 'visible'</code>, and retry quietly when the tab becomes visible.</p>
<h2>Try it</h2>
<ul>
<li>English (global): <a href="https://pokemonworld.net/">https://pokemonworld.net/</a></li>
<li>All language servers (Japanese, Portuguese, Hindi, Arabic, Chinese, Hebrew): <a href="https://tamerra.com/">https://tamerra.com/</a></li>
<li>46-second trailer: <a href="https://youtu.be/nIE2PVdz4DY">https://youtu.be/nIE2PVdz4DY</a></li>
<li>Gameplay videos: <a href="https://www.youtube.com/@PokemonWorldNet">https://www.youtube.com/@PokemonWorldNet</a></li>
</ul>
<p>It is free to play and runs on desktop and mobile browsers. We would love feedback, especially from anyone who has built persistent-world systems like these.</p>
<p><em>Pokémon World is an unofficial fan project and is not affiliated with Nintendo, Game Freak or The Pokémon Company. This article was drafted with the help of an AI assistant.</em></p>
]]></content:encoded></item></channel></rss>