<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>game development &#8211; IdeaRiff Research</title>
	<atom:link href="https://ideariff.com/tag/game-development/feed" rel="self" type="application/rss+xml" />
	<link>https://ideariff.com</link>
	<description>Riffing On Ideas</description>
	<lastBuildDate>Sun, 20 Sep 2026 17:15:22 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>
	<item>
		<title>Open Source Cloth Simulation Tools for Creating Isometric Sprite Animations</title>
		<link>https://ideariff.com/open_source_cloth_simulation_tools_for_creating_isometric_sprite_animations</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Sun, 20 Sep 2026 17:15:22 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[art]]></category>
		<category><![CDATA[game development]]></category>
		<guid isPermaLink="false">https://ideawaza.com/?p=870</guid>

					<description><![CDATA[In game development, especially for isometric or 2.5D titles inspired by classics like Ultima Online, creating authentic, dynamic clothing for characters can elevate the visual appeal dramatically. The challenge lies in simulating realistic cloth motion and then converting it into sprites that fit an eight-directional animation set. Fortunately, several open source tools—many integrated directly with Blender—can help you achieve this with full creative freedom and without copyright constraints. Why Use Open Source Cloth Tools? Blender’s built-in cloth simulation system is already powerful, but open source add-ons expand its potential. These tools let you design, simulate, and bake clothing physics onto ]]></description>
										<content:encoded><![CDATA[<p>In game development, especially for isometric or 2.5D titles inspired by classics like Ultima Online, creating authentic, dynamic clothing for characters can elevate the visual appeal dramatically. The challenge lies in simulating realistic cloth motion and then converting it into sprites that fit an eight-directional animation set. Fortunately, several open source tools—many integrated directly with Blender—can help you achieve this with full creative freedom and without copyright constraints.</p>
<h4>Why Use Open Source Cloth Tools?</h4>
<p>Blender’s built-in cloth simulation system is already powerful, but open source add-ons expand its potential. These tools let you design, simulate, and bake clothing physics onto 3D characters and then render animations from multiple angles for your game sprites. Since everything runs inside Blender, you maintain total control over your workflow and artistic style.</p>
<h4>Top Open Source Options on GitHub</h4>
<h5>1. Modeling Cloth / MC_29</h5>
<p>This is one of the best Blender add-ons for real-time cloth behavior and deformation. Available on <a href="https://github.com/the3dadvantage/MC_29" target="_blank" rel="noopener">GitHub</a>, it allows interactive control over cloth simulation with features like self-collision, friction, and adjustable stiffness. You can pause, pose, and bake the simulated frames, making it ideal for animation-based workflows.</p>
<p>Modeling Cloth integrates seamlessly with Blender’s physics system but is faster to iterate with. For sprite creators, it’s perfect—you can simulate a walking animation, then render each frame from eight fixed camera angles corresponding to your isometric view directions.</p>
<h5>2. GPUCloth</h5>
<p>For heavier or more complex cloth scenes, <a href="https://github.com/PorisulkiP/GPUCloth" target="_blank" rel="noopener">GPUCloth</a> uses CUDA-based acceleration to simulate cloth at higher performance. It’s experimental but useful if you have a capable Nvidia GPU and need faster turnaround times on your simulations. Keep in mind that it might require tweaking to match your Blender version.</p>
<h5>3. ClothWeaver</h5>
<p><a href="https://github.com/BlenderCN/ClothWeaver" target="_blank" rel="noopener">ClothWeaver</a> simplifies the garment creation process by providing presets and guides for weaving styles. It’s a good helper if you want to create consistent outfits quickly without modeling everything manually. It’s more of a design accelerator than a simulation engine, but it integrates well with Blender’s cloth physics.</p>
<h5>4. gret (Greisane Tools)</h5>
<p>The <a href="https://github.com/greisane/gret" target="_blank" rel="noopener">gret</a> toolset is especially useful when you want to reuse or adapt clothing across multiple body types. It provides utilities to transfer shape keys, morphs, and outfit adjustments, saving you from recreating clothing assets repeatedly. In an isometric RPG where many characters share similar base meshes, this can dramatically speed up production.</p>
<h5>5. Dynamic Unpin Cloth</h5>
<p>For secondary motion effects—like a cape unfurling or cloth detaching during an attack animation—<a href="https://github.com/drmcnulty/dynamic_unpin_cloth" target="_blank" rel="noopener">Dynamic Unpin Cloth</a> adds realism to specific movements. While not a full physics system on its own, it enhances Blender’s built-in cloth effects for dynamic, game-like animation results.</p>
<h4>Recommended Workflow for Isometric Sprites</h4>
<h5>1. Set Up Your Character</h5>
<p>Start by rigging your base character in Blender. Ensure the proportions, armature, and pose cycles (idle, walk, attack) are ready before adding cloth physics. This foundation helps prevent unnecessary re-simulation later.</p>
<h5>2. Design and Simulate Clothing</h5>
<p>Create or import clothing meshes, then apply the chosen cloth add-on—Modeling Cloth is generally the most stable for detailed animation. Adjust settings such as gravity, collision, and stiffness to achieve natural movement. For large scenes, consider using GPUCloth to accelerate computation.</p>
<h5>3. Bake and Freeze Simulations</h5>
<p>Once satisfied with the motion, bake the simulation per frame. This step locks the cloth into static poses that can be rendered or exported individually. Baked frames eliminate the randomness of live simulations and ensure consistency across renders.</p>
<h5>4. Render from Multiple Angles</h5>
<p>Set up eight cameras around your character corresponding to the directional views—north, northeast, east, and so on. Use orthographic cameras with fixed zoom to maintain consistency. Render all animations per direction, storing outputs as image sequences or sprite sheets.</p>
<h5>5. Export and Assemble Sprite Sheets</h5>
<p>After rendering, crop and align your frames. Tools like Aseprite or TexturePacker can help assemble sprite sheets efficiently. Each animation set (e.g., walk north, attack east) becomes part of your game’s animation library.</p>
<h5>6. Import into Your Game Engine</h5>
<p>For engines like Godot or Unity, load each sprite sheet into an animated sprite or animation player node. Configure frame timing to match the motion you simulated in Blender. Since the lighting and shading are baked, your sprites will look consistent across all directions.</p>
<h4>Performance and Artistic Considerations</h4>
<p>Keep your cloth mesh topology clean—too dense and simulations slow down; too sparse and motion becomes unnatural. In a pixel-art or isometric context, exaggerate folds and timing slightly to make movements readable at small scales. Realism should be stylized for clarity.</p>
<p>Also, be mindful of file size. Sprite sheets generated from high-resolution renders can quickly balloon in storage requirements. Render at just above your final in-game resolution, then downscale if needed for smoother edges.</p>
<h4>Final Thoughts and Recommendations</h4>
<p>If you want to balance realism with accessibility, start with <strong>Modeling Cloth (MC_29)</strong>. It offers the best blend of ease, power, and open source freedom. Use <strong>gret</strong> for managing outfits across characters, and optionally integrate <strong>ClothWeaver</strong> for rapid garment prototyping. This trio can cover nearly every step—from pattern to animated sprite—within an open ecosystem.</p>
<p>With Blender and its open source extensions, you can simulate detailed garments and convert them into timeless isometric animations that carry the spirit of classic games while maintaining modern polish.</p>
<h4>Disclaimer</h4>
<p>This guide describes techniques for creating original art and animations using open source software. It is not affiliated with or derived from Ultima Online, and no assets or intellectual property from that game are used or implied. Developers should ensure that all content they produce is fully original or appropriately licensed before distribution.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Finding the Sweet Spot: Hosting Federated Game Servers with Colyseus</title>
		<link>https://ideariff.com/finding_the_sweet_spot_hosting_federated_game_servers_with_colyseus</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Wed, 29 Oct 2025 01:00:09 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[game development]]></category>
		<category><![CDATA[gaming]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[tech]]></category>
		<category><![CDATA[technology]]></category>
		<guid isPermaLink="false">https://ideariff.com/?p=614</guid>

					<description><![CDATA[When you&#8217;re thinking about building a federated online world where anyone can host their own shard or server, one of the first questions is about infrastructure. How much power do you really need? How do you keep it affordable for small groups while still scalable for hundreds or even thousands of players? This is where the choice between Colyseus, Nakama, or a custom .NET approach becomes central. Each has its strengths, but the tradeoffs matter if your goal is decentralized, low-cost hosting. What follows is a grounded look at how Colyseus fits into that vision, how its community compares, and ]]></description>
										<content:encoded><![CDATA[<p>When you&#8217;re thinking about building a federated online world where anyone can host their own shard or server, one of the first questions is about infrastructure. How much power do you really need? How do you keep it affordable for small groups while still scalable for hundreds or even thousands of players? This is where the choice between Colyseus, Nakama, or a custom .NET approach becomes central. Each has its strengths, but the tradeoffs matter if your goal is decentralized, low-cost hosting. What follows is a grounded look at how Colyseus fits into that vision, how its community compares, and what sort of hardware makes sense at each scale.</p>
<h4>Colyseus and Its Community</h4>
<p>Colyseus is an open-source multiplayer framework built in Node.js that’s designed to handle real-time games with ease. It’s known for being lightweight and modular, and it integrates smoothly with engines like Godot, Unity, and Phaser. The development is active, and the project has maintained steady momentum thanks to both community support and professional sponsorship. You can find the main repository on GitHub under <a href="https://github.com/colyseus/colyseus" target="_blank" rel="noopener">colyseus/colyseus</a>, where updates, issue tracking, and release notes are all public.</p>
<p>There’s also a robust <a href="https://github.com/colyseus/colyseus-examples" target="_blank" rel="noopener">examples repository</a> that showcases practical implementations. You’ll find sample projects for match-making, chat, turn-based games, and even basic MMORPG skeletons. These examples are excellent starting points for learning how Colyseus organizes rooms, manages state, and communicates with clients. The <a href="https://docs.colyseus.io/examples/" target="_blank" rel="noopener">official documentation</a> offers tutorials on building scalable room architectures and handling authentication, while the <a href="https://colyseus.io/community/" target="_blank" rel="noopener">community page</a> connects you to forums and Discord discussions where developers share tips and modules.</p>
<h5>Existing SDKs and Integrations</h5>
<p>For Godot users, there’s an open-source SDK maintained by the <a href="https://github.com/gsioteam/godot-colyseus" target="_blank" rel="noopener">gsioteam</a>. It’s MIT-licensed and compatible with Godot 4, which makes it a good fit for projects like Ultra Omnicosmic or any isometric world simulation. This SDK lets your Godot client connect via WebSockets to Colyseus rooms, synchronize state, and send commands with minimal code. While not as large a community as Unity’s, the Godot side is active enough that you can find examples, forks, and real projects to learn from.</p>
<h4>Comparing Colyseus to Nakama</h4>
<p>Nakama, built in Go, is a heavier platform. It’s feature-rich and more “enterprise-ready” with built-in support for accounts, leaderboards, match-making, and storage. That power comes at a cost: higher RAM usage and a larger baseline footprint. Nakama typically runs best with 2 GB or more of memory, and it performs comfortably on 4 GB or higher servers. This makes it excellent for studios that want to deploy a single, large backend—but not ideal if you want everyday users to spin up small, affordable shards of their own.</p>
<p>Colyseus, on the other hand, starts fast and runs lean. A single 1 vCPU / 2 GB VPS can comfortably host 30 to 50 concurrent players with moderate message rates, and even 80 to 100 if you apply interest management to limit unnecessary updates. Because it’s lightweight, it fits the decentralized dream: small groups, guilds, or friends can run their own worlds on budget hardware and still connect them through a shared federation. For a federated MMO, that accessibility matters more than any prebuilt feature set.</p>
<h4>Why Not Just Strip ServUO?</h4>
<p>ServUO, written in C#, is modular and familiar to anyone who has worked with Ultima Online shards. However, the architecture is heavy and intertwined. Trimming it down to something lean enough for modern, federated hosting is not practical. You would spend more time untangling the legacy systems than building your own lightweight framework. And since ServUO is GPL-licensed, you’d also face licensing restrictions if you wanted to release your project under more permissive terms.</p>
<p>It’s better to take inspiration from its modular design than to modify its code directly. You can still mirror the structure: an authoritative core server with pluggable modules for combat, skills, and AI, all written in TypeScript for Colyseus. That pattern keeps the good parts—modularity and scriptability—without inheriting the baggage of legacy architecture or restrictive licensing.</p>
<h4>Hardware Recommendations and Scaling</h4>
<p>One of the biggest advantages of going with Colyseus or a custom .NET stack is that you can scale horizontally. You don’t need a monolithic backend. Each node, or “world,” can serve a certain number of players and link to others via simple REST or WebSocket APIs. On Vultr or similar platforms, this translates directly into affordable hosting tiers.</p>
<h5>Federated Hosting Tiers</h5>
<table>
<tr>
<th>Concurrent Players</th>
<th>Recommended VM</th>
<th>Specs</th>
<th>Monthly Cost</th>
</tr>
<tr>
<td>50 – 200</td>
<td>Regular Cloud Compute</td>
<td>2 vCPU · 4 GB RAM</td>
<td>$20 / month</td>
</tr>
<tr>
<td>200 – 500</td>
<td>Optimized Cloud Compute</td>
<td>4 vCPU · 16 GB RAM</td>
<td>$120 / month</td>
</tr>
<tr>
<td>500 – 1,000</td>
<td>Optimized Cloud Compute</td>
<td>8 vCPU · 32 GB RAM</td>
<td>$240 / month</td>
</tr>
<tr>
<td>1,000+</td>
<td>Horizontal Scaling</td>
<td>Multiple 4 vCPU / 16 GB nodes</td>
<td>~$120 × N</td>
</tr>
</table>
<p>As a general rule, one CPU core can manage around 100 players if your interest management is efficient and you’re not broadcasting unnecessary data. One gigabyte of RAM typically supports 50 to 100 active users. At 500 players or above, it’s worth running your database separately—maybe a small 2 GB VPS for Postgres and Redis—to avoid performance dips during save operations. This layered design makes each server self-contained and cheap to maintain.</p>
<h4>Performance at Each Scale</h4>
<p>A single $10 per month VPS with 1 vCPU and 2 GB RAM can handle 30 to 50 active players without lag. A $20 per month plan doubles that comfortably. Once you hit 500 players, the $120 per month tier starts to shine—it can host multiple zones or rooms, each with 100 or more concurrent players. Past 1,000, you’ll want to shard horizontally. That’s when the “Federated Universe” concept really comes alive. Each shard can have its own culture, rule set, or even economy, while remaining part of the same interconnected universe.</p>
<p>The performance curve is linear and predictable. Adding a node doubles capacity. It’s simple economics and engineering: decentralized scaling that keeps power in the hands of players and creators, not a single central server.</p>
<h4>When to Consider Nakama or SpacetimeDB</h4>
<p>If your project demands integrated features like real-time analytics, leaderboards, and built-in account management, Nakama becomes more appealing. It handles those systems natively. But it also expects more resources, typically running best with 4 to 8 GB of RAM. For lightweight, self-hosted shards, Nakama is overkill. It’s great for studios but less ideal for a network of small, autonomous servers.</p>
<p>SpacetimeDB is an emerging alternative that blends a database with game server logic, letting you write in Rust or C#. It’s more like a “database as world” model. The idea is powerful, but its licensing and maturity level are still developing. If you like the idea of query-based subscriptions and database-level updates, you can emulate that in Colyseus with interest management. Clients can subscribe to logical regions or entities and receive only the data relevant to them—essentially achieving the same outcome on a simpler foundation.</p>
<h4>The Sweet Spot for Federated Games</h4>
<p>The true power of a federated MMO is in its accessibility. A world where anyone can spin up a server for $10 a month and instantly be part of a larger network of worlds is a post-scarcity vision of multiplayer gaming. It’s democratic and sustainable. Using Colyseus, you can make that dream practical today. Each shard can hold dozens or hundreds of players without breaking the bank. As communities grow, they simply add more nodes, each one independently owned yet universally connected.</p>
<p>Keep it simple. Build light. Use efficient interest management and modular server logic. Encourage players to host their own worlds. That’s how you create something that scales without monopolies, grows without gatekeepers, and endures because it’s distributed. Whether you’re building Ultra Omnicosmic or your own federated universe, the path forward is clear: start small, make it modular, and let the network grow organically.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Crypto-enabled Godot templates and plugins for Bitcoin Cash and Monero</title>
		<link>https://ideariff.com/crypto_enabled_godot_templates_and_plugins_for_bitcoin_cash_and_monero</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Wed, 20 Aug 2025 20:16:21 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[cryptocurrency]]></category>
		<category><![CDATA[game development]]></category>
		<category><![CDATA[gaming]]></category>
		<guid isPermaLink="false">https://ideawaza.com/?p=760</guid>

					<description><![CDATA[This is an idea to create open source Godot 4.4.1 templates and plugins that add opt-in crypto features to games: built-in Bitcoin Cash and Monero wallets, plus optional low-impact Monero mining via P2Pool that never degrades gameplay and always requires explicit consent. Core goals Provide drop-in Godot modules for BCH and XMR wallets that developers can enable per project. Offer an opt-in, throttled Monero miner that runs only with clear user consent and can be paused or capped. Use P2Pool for decentralized payouts and transparency, avoiding custodial backends. Keep performance safe for gameplay on typical PCs by limiting CPU usage ]]></description>
										<content:encoded><![CDATA[<p>This is an idea to create open source Godot 4.4.1 templates and plugins that add opt-in crypto features to games: built-in Bitcoin Cash and Monero wallets, plus optional low-impact Monero mining via P2Pool that never degrades gameplay and always requires explicit consent.</p>
<h4>Core goals</h4>
<ol>
<li>Provide drop-in Godot modules for BCH and XMR wallets that developers can enable per project.</li>
<li>Offer an opt-in, throttled Monero miner that runs only with clear user consent and can be paused or capped.</li>
<li>Use P2Pool for decentralized payouts and transparency, avoiding custodial backends.</li>
<li>Keep performance safe for gameplay on typical PCs by limiting CPU usage and suspending during load spikes.</li>
<li>Make everything auditable, configurable, and easy to remove.</li>
</ol>
<h4>Scope and boundaries</h4>
<ol>
<li>Wallets: send, receive, show balance, show history, handle addresses, seed backup, and testnet modes.</li>
<li>Mining: CPU-only RandomX with user-set hash rate caps, idle-time boost, and auto-suspend during heavy frames.</li>
<li>Consent: first-run explainer, settings panel, per-session consent, and explicit mining start or stop controls.</li>
<li>Privacy: no tracking beyond what is needed for wallets and optional mining statistics visible to the user.</li>
<li>Platforms: prioritize desktop builds first; mobile and consoles out of scope initially.</li>
</ol>
<h4>Architecture sketch</h4>
<ol>
<li>Godot plugin layer: UI panels, signals, and settings for Wallet and Mining modules.</li>
<li>Native bridge: C# or GDNative C++ for performance-critical calls, with GDScript for UI glue and signals.</li>
<li>Wallet backends: integrate established libraries or RPC clients for BCH and XMR. Support light node or trusted remote RPC, selectable by the user.</li>
<li>Mining backend: embedded RandomX miner with throttling, plus P2Pool client for peer discovery and payouts.</li>
<li>Safety loop: frame-time monitor to auto-pause or reduce hash rate if FPS drops below a threshold.</li>
</ol>
<h4>Developer ergonomics</h4>
<ol>
<li>One-click template projects for BCH-only, XMR-only, and BCH+XMR+Mining.</li>
<li>Signals and APIs: <code>wallet_tx_sent</code>, <code>wallet_tx_received</code>, <code>mining_started</code>, <code>mining_paused</code>, <code>mining_hashrate_updated</code>.</li>
<li>Inspector-exposed settings for caps, RPC endpoints, payout address, and consent text.</li>
<li>Sample UI scenes for wallet screens and a mining control widget.</li>
</ol>
<h4>User experience commitments</h4>
<ol>
<li>Clear disclosure of what mining does, costs, and benefits.</li>
<li>Off by default, opt-in every time or until the user sets a persistent preference.</li>
<li>Real-time indicators for CPU usage and hashrate, with an easy stop control.</li>
<li>No impact on gameplay targets. FPS guardrails and thermal guardrails.</li>
</ol>
<h4>Compliance and trust</h4>
<ol>
<li>Open source license and reproducible builds.</li>
<li>Logs and on-screen receipts for mining time and payouts.</li>
<li>Regional compliance checklist for disclosures and energy data.</li>
</ol>
<h4>Implementation notes for Godot 4.4.1</h4>
<ol>
<li>Start with C# for wallet and miner bridges to reuse mature .NET bindings where available. Use GDScript for UI and signals.</li>
<li>If needed, wrap native libraries via GDNative or GDExtension for RandomX and P2Pool.</li>
<li>Provide a headless test scene to validate hashrate throttling and FPS guards.</li>
</ol>
<h4>Minimal roadmap</h4>
<ol>
<li>Prototype BCH wallet UI and RPC client.</li>
<li>Add XMR lightweight wallet mode and seed management.</li>
<li>Integrate RandomX with throttle and FPS guard.</li>
<li>Add P2Pool client and payout verification.</li>
<li>Ship sample templates and documentation, then conduct a security review.</li>
</ol>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Implementing a Decentralized Fediverse MMO with Crypto in Godot</title>
		<link>https://ideariff.com/implementing_a_decentralized_fediverse_mmo_with_crypto_in_godot</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Tue, 19 Aug 2025 23:44:28 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[game development]]></category>
		<category><![CDATA[Godot]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://ideawaza.com/?p=746</guid>

					<description><![CDATA[Godot is a versatile and open-source game engine that has steadily matured into a capable platform for both 2D and 3D development. Its flexibility, extensibility, and scripting languages (GDScript, C#, and others via GDNative) make it a strong candidate for building a decentralized online game that ties together Fediverse identity, non-custodial crypto wallets, and Arweave world persistence. While other frameworks such as MonoGame or custom engines could also handle the workload, Godot’s active community and open ethos align well with the vision of creating a spiritual successor to Ultima Online. This article outlines how such a system could be approached ]]></description>
										<content:encoded><![CDATA[<p>Godot is a versatile and open-source game engine that has steadily matured into a capable platform for both 2D and 3D development. Its flexibility, extensibility, and scripting languages (GDScript, C#, and others via GDNative) make it a strong candidate for building a decentralized online game that ties together Fediverse identity, non-custodial crypto wallets, and Arweave world persistence. While other frameworks such as MonoGame or custom engines could also handle the workload, Godot’s active community and open ethos align well with the vision of creating a spiritual successor to <em>Ultima Online</em>.</p>
<p>This article outlines how such a system could be approached step by step in Godot, drawing on the ideas from the broader design: Fediverse logins, Monero and Bitcoin Cash wallets, opt-in mining events, and Arweave-based world history.</p>
<h4>Why Godot is a fitting choice</h4>
<p>Godot offers several built-in advantages for this type of project. First, its node-based architecture is flexible enough to model both simple and complex systems, from grid-based RPG maps to 3D voxel terrain. Second, it provides multiple scripting options, allowing developers to write logic in GDScript for rapid iteration or in C# for performance-critical components. Third, Godot’s open licensing (MIT) avoids restrictive terms, making it a natural fit for open-source projects.</p>
<p>Compared to MonoGame, Godot is more of a complete engine out of the box, with integrated UI, networking, and scene management. MonoGame is a strong framework but requires more custom tooling to achieve the same level of structure. For a project as ambitious as a decentralized MMO, Godot provides a balance of extensibility and ready-to-use features, reducing overhead while preserving flexibility.</p>
<h4>Implementing Fediverse integration</h4>
<p>Fediverse support would revolve around ActivityPub, the protocol used by Mastodon and other federated platforms. Godot itself does not ship with an ActivityPub library, but the engine’s HTTPRequest node and JSON utilities make it possible to implement one.</p>
<p>A likely approach would be:</p>
<ol>
<li><strong>Login flow</strong>: Use OAuth-like delegation where players log in via their Fediverse account. The game client handles HTTP requests to the chosen instance, receives a token, and stores it locally.</li>
<li><strong>Identity binding</strong>: Store the player’s Fediverse handle and public keys within the game’s save data, associating it with their character profile.</li>
<li><strong>Social integration</strong>: Post updates such as guild notices, trade offers, or snapshot announcements directly to ActivityPub endpoints. This can be done via asynchronous HTTP requests from either GDScript or C#.</li>
</ol>
<p>While implementing ActivityPub directly in Godot may be ambitious, modular design would make it easier to extend over time. At minimum, binding Fediverse identities for authentication is straightforward and provides the foundation for later features.</p>
<h4>Non-custodial wallet support</h4>
<p>Godot does not natively handle cryptocurrency, but it can interact with external libraries or APIs. Wallet integration could be implemented in several ways:</p>
<ul>
<li><strong>Light client libraries</strong>: For Monero and Bitcoin Cash, light clients already exist that can be compiled into dynamic libraries. Godot can call these through GDNative or C#.</li>
<li><strong>Message signing</strong>: Players can sign messages or transactions externally, with the game verifying signatures. This keeps keys completely outside the engine while still enabling integration.</li>
<li><strong>REST APIs</strong>: Lightweight wallet services (run locally by the player) could expose a REST API, which Godot can access through HTTPRequest nodes.</li>
</ul>
<p>The critical point is that private keys are never stored or transmitted by the game itself. Whether using GDScript or C#, the implementation should only handle public addresses, signed transactions, and blockchain queries. This ensures the system remains non-custodial, sidestepping AML/KYC and licensing concerns.</p>
<h4>Mining integration through GDScript or C#</h4>
<p>Mining presents both technical and ethical considerations. RandomX, the algorithm used by Monero, is CPU-heavy and requires optimized native code. While it is possible to attempt a pure GDScript implementation, performance would be inadequate. Instead, the best route is binding a C or Rust RandomX implementation via GDNative or C#.</p>
<p>In practice, this would work as follows:</p>
<ol>
<li>The client receives a “mining challenge” from the server tied to an in-game event.</li>
<li>The game calls into a native RandomX binding to compute hashes.</li>
<li>Shares are submitted directly to P2Pool using network requests.</li>
<li>Rewards flow straight to the player’s wallet, bypassing the game server entirely.</li>
</ol>
<p>Godot’s modularity allows this subsystem to remain optional. Players who choose not to mine can still participate fully in the game, ensuring accessibility while preserving fairness.</p>
<h4>Saving world-state to Arweave</h4>
<p>Godot already has strong serialization support for saving game data. Extending this to Arweave involves creating export pipelines that bundle snapshots into JSON or binary diffs, then sending them to Arweave through HTTP transactions.</p>
<p>The workflow might look like this:</p>
<ol>
<li><strong>State serialization</strong>: Every N minutes, the server serializes the current game world into a compact format. Godot’s ResourceSaver and custom JSON export tools can handle this efficiently.</li>
<li><strong>Arweave upload</strong>: A companion service (outside the engine) or an HTTP module within Godot posts this snapshot to the Arweave gateway.</li>
<li><strong>Manifest updates</strong>: The server maintains a pointer file (manifest) that references the most recent snapshot. This is also uploaded to Arweave.</li>
<li><strong>Verification</strong>: Other servers or shard operators can fetch snapshots from Arweave, verify integrity, and continue the simulation.</li>
</ol>
<p>This system creates transparency, preserves history, and empowers modders. Since Arweave is designed for permanent data, it provides the durability that traditional databases cannot guarantee on their own.</p>
<h4>Multiplayer architecture in Godot</h4>
<p>Godot’s high-level multiplayer API supports peer-to-peer and client-server models. For an MMO, a dedicated server architecture is more reliable. In practice, one could:</p>
<ul>
<li>Use Godot’s <strong>SceneTree network replication</strong> for basic synchronization of entities.</li>
<li>Develop custom RPC (remote procedure call) layers to handle complex MMO features like inventories, trades, and combat logs.</li>
<li>Store hot state in memory and cold state in a local database, with Arweave serving as the audit log.</li>
</ul>
<p>Because the game would be open-source, server binaries could be distributed widely, allowing communities to host their own shards. This mirrors the spirit of open UO shards but with modern decentralized integration.</p>
<h4>UI and player experience</h4>
<p>One of Godot’s strengths is its UI toolkit. This is critical for a project that requires wallet management, Fediverse posting, and trading interfaces. A player could, for example, manage their Monero or Bitcoin Cash wallet through a simple in-game panel, or post a guild announcement that cross-publishes to Mastodon.</p>
<p>Dark mode, clean layouts, and modular menus can all be implemented quickly in Godot’s scene system. For complex workflows like signing a transaction, the UI can provide clear steps that integrate with external wallet software, maintaining transparency and user trust.</p>
<h4>Security and safeguards</h4>
<p>Non-custodial design means security depends heavily on clear boundaries. Godot can support this by:</p>
<ul>
<li>Keeping private keys entirely out of the engine.</li>
<li>Using sandboxed calls to external wallet libraries.</li>
<li>Verifying signatures for all trades and events.</li>
<li>Providing client-side options to disable mining, ensuring user consent.</li>
</ul>
<p>Because Godot is open-source, the community can audit the implementation, adding another layer of accountability.</p>
<h4>Path forward with Godot</h4>
<p>A staged implementation roadmap in Godot might look like:</p>
<ol>
<li><strong>Prototype</strong>: Simple tile-based world, Fediverse login mockup, testnet BCH integration.</li>
<li><strong>Alpha</strong>: Wallet attachment, optional Monero mining demo using GDNative bindings, Arweave snapshot pipeline.</li>
<li><strong>Beta</strong>: Full multiplayer server, item trading, Fediverse social posting, shard federation.</li>
<li><strong>Release</strong>: Polished world design, modding support, multi-shard governance, public snapshot history.</li>
</ol>
<p>Each phase would be achievable within Godot using its existing capabilities and extensibility. Native bindings for mining and wallets may require C#, Rust, or C, but Godot is well suited to integrate those components without breaking the non-custodial boundary.</p>
<h4>Final thoughts</h4>
<p>Godot provides the right mix of openness, flexibility, and community alignment to serve as the foundation for this decentralized MMO vision. By combining Fediverse identity, non-custodial crypto integration, opt-in mining, and Arweave persistence, the project could create something unprecedented: a player-owned digital society built with open tools.</p>
<p>This would not be a clone of <em>Ultima Online</em>, but a spiritual successor that carries forward its ethos of freedom and emergent community. With Godot at the core, the dream of an open, federated, and durable virtual world feels not only possible but achievable.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Building a Fediverse-Based Ultima Online Alternative with Crypto and Arweave</title>
		<link>https://ideariff.com/building_a_fediverse_based_ultima_online_alternative_with_crypto_and_arweave</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Tue, 19 Aug 2025 23:39:29 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[game dev]]></category>
		<category><![CDATA[game development]]></category>
		<category><![CDATA[gaming]]></category>
		<category><![CDATA[ideas]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://ideawaza.com/?p=744</guid>

					<description><![CDATA[The idea of creating an open-source alternative to Ultima Online is already ambitious, but tying it to the broader Fediverse ecosystem and adding cryptocurrency features introduces a unique and potentially groundbreaking dimension. This concept would not just be about building another massively multiplayer online role-playing game (MMORPG). Instead, it would aim to create a truly decentralized digital society where gameplay, economy, governance, and history are all interconnected across multiple open platforms. In this article, I will outline how such a system might work. The pieces include Fediverse integration for identity and social networking, Monero and Bitcoin Cash wallets for transactions, ]]></description>
										<content:encoded><![CDATA[<p>The idea of creating an open-source alternative to <em>Ultima Online</em> is already ambitious, but tying it to the broader Fediverse ecosystem and adding cryptocurrency features introduces a unique and potentially groundbreaking dimension. This concept would not just be about building another massively multiplayer online role-playing game (MMORPG). Instead, it would aim to create a truly decentralized digital society where gameplay, economy, governance, and history are all interconnected across multiple open platforms.</p>
<p>In this article, I will outline how such a system might work. The pieces include Fediverse integration for identity and social networking, Monero and Bitcoin Cash wallets for transactions, mining tied to gameplay, and world-state persistence using Arweave. By combining these elements into a single vision, the game could function not only as entertainment but also as a durable, player-owned digital civilization.</p>
<h4>Fediverse as the foundation of identity</h4>
<p>The Fediverse is a decentralized collection of social platforms like Mastodon, Pleroma, and PeerTube, all of which use the ActivityPub protocol for federation. If this game were built around the Fediverse, every player would log in with their existing account. Their profile in Mastodon, for example, would double as their in-game identity. This means that a guild’s social feed could live both inside the game and out on the Fediverse, visible to anyone who follows them.</p>
<p>This approach solves one of the biggest problems of modern online games: centralized control over accounts. If accounts live on the Fediverse, no single company has the power to banish a player entirely. Servers could still choose who to allow, but identity would remain portable. This portability would naturally align with the open-source ethos of the project and encourage vibrant cross-community interaction.</p>
<h4>Built-in wallets and peer-to-peer economy</h4>
<p>Every account would come with lightweight cryptocurrency wallets attached, supporting Monero and Bitcoin Cash. These currencies were chosen because they offer low fees and fast settlement while maintaining different strengths: Bitcoin Cash for broad usability and Monero for strong privacy. Keys would remain user-side, and the game would never hold custody of funds.</p>
<p>The in-game marketplace could accept both currencies for player-to-player trades. Items, land deeds, or crafting services could be priced in-game, but settlement would occur on-chain. This would allow players to retain ownership of their wealth independently of the server’s fate. For example, a player could sell a rare crafted sword to another player, receive Monero in return, and record the transaction ID alongside the game’s item registry.</p>
<h4>Mining integrated with gameplay</h4>
<p>One of the more experimental ideas is integrating Monero mining into the gameplay loop. Rather than running as hidden background mining, the system would trigger mining events tied to specific in-game actions. For example, opening a magical portal or powering a guild structure might require computational work. The client would then perform a short burst of RandomX hashing, submitting shares to P2Pool under the player’s Monero wallet.</p>
<p>This system would need careful balancing. Mining must always be opt-in and bounded so as not to overwhelm CPUs or laptops. However, when done responsibly, it could create a unique feedback loop: players contribute real computational resources to the broader Monero network while simultaneously generating in-game rewards for their efforts. Mining could become an optional but meaningful part of the economy, distinct from traditional grinding or farming.</p>
<h4>Arweave for permanent world-state persistence</h4>
<p>Arweave provides a “permaweb” where data, once uploaded, is guaranteed to be available indefinitely. By saving world-state snapshots to Arweave, the game could achieve a new level of permanence. Every few minutes or hours, the server would serialize a snapshot of the world—items, deeds, land ownership, guild charters—and upload it to Arweave.</p>
<p>This would serve several purposes. First, it creates an immutable history of the game world, allowing disputes to be resolved transparently. Second, it makes the game fork-friendly: anyone could spin up a new server by pulling the latest snapshot from Arweave and continuing the simulation. Finally, it preserves player creations for posterity. Guild halls, epic battles, or even entire towns would not simply vanish if a server shuts down—they would live permanently on the permaweb.</p>
<h4>Data flow and integration</h4>
<p>At a high level, the system could operate as follows:</p>
<ol>
<li>Players log in with their Fediverse account, linking their identity across game and social networks.</li>
<li>Wallets are either generated or connected, with public keys tied to the account.</li>
<li>Gameplay occurs in real time, with servers managing hot state and saving cold state to local databases.</li>
<li>Snapshots of world diffs are periodically uploaded to Arweave, with manifests marking the canonical latest state.</li>
<li>Trades between players are recorded as signed orders, with on-chain settlement and receipts stored on Arweave for transparency.</li>
<li>Mining events occur in response to in-game actions, producing both real cryptocurrency rewards and in-game bonuses.</li>
</ol>
<p>This pipeline would make the game not just another MMO but a hybrid of distributed computing, blockchain persistence, and federated social networking.</p>
<h4>Governance and community control</h4>
<p>One of the natural extensions of Fediverse integration is decentralized governance. Shards (individual servers) could run their own Fediverse accounts, posting patch notes, proposing changes, or holding votes. Players could sign ballots with their Fediverse accounts, and results could be anchored to Arweave for transparency.</p>
<p>This system would allow communities to fork or federate as they wish. If one shard became toxic or poorly managed, others could simply mute or defederate it, much like Fediverse platforms already do. Instead of relying on a central developer to make balance changes, players could shape the evolution of their shard democratically.</p>
<h4>Security and fairness considerations</h4>
<p>Such a system introduces challenges as well. Mining integration must be tightly controlled to avoid exploitation or harming players’ hardware. Anti-cheat systems would need to enforce server authority on movement and combat while verifying asset integrity. Wallets must remain strictly non-custodial to preserve privacy and security.</p>
<p>At the same time, transparency via Arweave creates opportunities to prevent cheating. Duplication exploits or fraudulent trades could be checked against immutable records. By anchoring important events to a public, verifiable ledger, the game would have a stronger defense against dishonest behavior than most traditional MMOs.</p>
<h4>Modding and long-term creativity</h4>
<p>Since the entire project would be open-source, modding would naturally play a central role. Developers could create new content, rulesets, or graphical overhauls, storing their work on Arweave for long-term preservation. Players could choose to join shards that run vanilla rules, heavily modded variants, or entirely experimental forks.</p>
<p>This openness is key to long-term success. The history of Ultima Online shows that much of its enduring appeal comes from player creativity, custom shards, and unique rule variations. By making forking and modding easy and verifiable, this project could tap into the same spirit while extending it with permanent records and portable identities.</p>
<h4>Path to implementation</h4>
<p>A possible rollout plan might follow these steps:</p>
<ol>
<li><strong>MVP</strong>: A single shard with Fediverse login, Bitcoin Cash testnet payments, and simple Arweave snapshots.</li>
<li><strong>Alpha</strong>: In-game marketplace, Monero wallet integration, and optional mining events tied to gameplay.</li>
<li><strong>Beta</strong>: Multi-shard federation, governance features, and comprehensive modding tools.</li>
<li><strong>Release</strong>: A stable ecosystem with strong Fediverse presence, permanent history on Arweave, and sustainable mining integration.</li>
</ol>
<p>Each phase would build upon the last, ensuring that the core game remains fun and engaging even before the more experimental features reach maturity.</p>
<h4>Why this matters</h4>
<p>The idea of an Ultima Online alternative tied to the Fediverse, with built-in crypto wallets and Arweave persistence, represents more than just a new game. It is an experiment in digital society-building. By combining decentralized identity, peer-to-peer economies, player-driven governance, and permanent history, the project could create something that feels less like a product and more like a living civilization.</p>
<p>This approach would also align with the values of open-source software, digital sovereignty, and user ownership. In a time when most online games are locked down and monetized to the extreme, a project like this could show what is possible when players truly own their accounts, their creations, and even the history of their world.</p>
<p>In the end, this is still just an idea. But it is an idea that combines multiple technologies already proven in other domains—Fediverse federation, cryptocurrency, distributed mining, and permanent storage—and asks what would happen if they were woven together in one persistent, player-driven world. The result could be a game that is not only fun to play but also enduring, auditable, and genuinely free.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Centred# Tile Loading and Display Overview and Godot</title>
		<link>https://ideariff.com/centred_tile_loading_and_display_overview</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Sun, 03 Aug 2025 01:26:12 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Learning]]></category>
		<category><![CDATA[Research]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[game development]]></category>
		<category><![CDATA[programming]]></category>
		<guid isPermaLink="false">https://ideariff.com/?p=590</guid>

					<description><![CDATA[CentrED# is a client/server map editor for Ultima Online, and it handles tile loading by reading the game’s MUL files (the same files used by the UO client/server). The goal is to replicate this logic both as a Godot Editor plugin (for design-time map editing) and at runtime (for an in-game tile viewer). In Centred#, the server typically reads the UO data files (maps, statics, art, etc.) and sends the relevant data to the client for display. The client either uses its own copy of the art files or receives art data from the server. To implement similar functionality in ]]></description>
										<content:encoded><![CDATA[<p>CentrED# is a client/server map editor for Ultima Online, and it handles tile loading by reading the game’s MUL files (the same files used by the UO client/server). The goal is to replicate this logic both as a Godot Editor plugin (for design-time map editing) and at runtime (for an in-game tile viewer). In Centred#, the server typically reads the UO data files (maps, statics, art, etc.) and sends the relevant data to the client for display. The client either uses its own copy of the art files or receives art data from the server. To implement similar functionality in Godot, you’ll need to parse the <strong>.mul</strong> files the same way Centred# does and then create Godot Textures or Images to display the tiles. Below we identify the key parts of Centred#’s logic and how you can implement them. The following are notes and accuracy is not guaranteed. If anything may be erroneous, please provide an FYI.</p>
<h2>Parsing Static Tile Data from MUL Files (Map Statics)</h2>
<p><strong>Static tiles</strong> in UO refer to world objects that are fixed on the map (buildings, trees, decor, etc.), stored in the <em>statics</em> files. Centred# reads these from the <code>staticsX.mul</code> files (with <code>staidxX.mul</code> as an index) for each map. The relevant code in Centred# is in its file-parsing routines (likely in the server component or a shared library) that handle reading static objects for each 8&#215;8 map block. The process is roughly as follows:</p>
<ul>
<li><strong>Open the index (staidx) and statics files:</strong> For a given map (e.g. map0), Centred# opens <code>staidx0.mul</code> and <code>statics0.mul</code>. The <em>staidx</em> file is an array of 12-byte entries, one for each map block (where the map is divided into 8&#215;8-tile blocks). Each entry has: a 32-bit offset into the statics file, a 32-bit length, and a 32-bit unknown value. The number of entries corresponds to the number of blocks (e.g. 768×512 blocks for Felucca map0).</li>
<li><strong>Find the block’s statics data:</strong> To get statics at block (Xblock, Yblock), Centred# computes the index = Xblock * numBlocksY + Yblock. It reads the 12-byte index entry. If the offset is 0xFFFFFFFF, that means no statics in that block. Otherwise, the entry gives the file offset and length of the static data for that block.</li>
<li><strong>Read static objects:</strong> Centred# then seeks to that offset in <code>statics.mul</code> and reads the specified length of data. The statics data is a sequence of <strong>7-byte records</strong>. Each static record has: a <strong>2-byte ID</strong> (the tile graphic ID of the object, which corresponds to an entry in the art/tiledata files), a <strong>1-byte X offset</strong> within the block (0–7), a <strong>1-byte Y offset</strong> within the block, a <strong>1-byte Z altitude</strong> (signed altitude relative to sea level), and a <strong>2-byte “unknown”</strong> field. (In many implementations this unknown field is used as a <strong>hue</strong> or simply padding – UO’s client and server don’t document it well, but it’s often 0 unless a hue is applied).</li>
<li><strong>Example:</strong> If Centred# is loading block 1234, it reads the staidx entry at index 1234 to get (offset, length). Suppose length is 21, then there are three static objects (3 * 7 bytes) in that block. It would read three records from the statics file giving the IDs and positions of those objects. These IDs correspond to item art (in the art.mul file) and tile metadata (tiledata.mul). Centred# uses this to know which graphics to draw on that map section.</li>
<li><strong>Using the data:</strong> Once it has the list of static objects for the block, Centred# will load their art (see next section) and render them at the appropriate positions on top of the base terrain. The Centred# source likely contains a method that aggregates static tiles per map chunk. (In original CentrED, there was a Map class that loaded both terrain and statics for a requested area). The key point is that the Centred# code is reading those 7-byte static entries and storing them in a data structure (e.g. a list of static tile instances with id and position). You will need to replicate this logic in Godot – for example, by reading the binary files with Godot’s File API or C# System.IO, and extracting these records. This will give you the <strong>IDs</strong> of static tiles and their coordinates.</li>
</ul>
<p><strong>References:</strong> The format of <code>staidx</code> and <code>statics</code> files is documented (each index entry is 12 bytes, each static object 7 bytes). Centred# follows this format when parsing the files. The static loading code in Centred# will correspond to this structure – for instance, reading the index into a struct with fields for offset/length, seeking in the file, then looping to read each 7-byte static entry.</p>
<h2>Loading Tile Art Images from ART.MUL</h2>
<p>To <strong>display</strong> the tiles (whether individual tile graphics in a palette or the composed map), Centred# also loads the actual artwork from the UO art files. Ultima Online stores all terrain and object art in <code>art.mul</code> (with <code>artidx.mul</code> as an index). Centred#’s <strong>display logic</strong> for tiles would involve reading these files and converting them into bitmaps/textures for rendering on the screen. The code for this is likely in the Centred# client (or shared library) – possibly derived from the Ultima SDK or similar (many UO tools use a common approach). Here’s how it works:</p>
<ul>
<li><strong>Art index lookup:</strong> The <code>artidx.mul</code> file contains an index of 12-byte entries (similar structure to other idx files): each entry has a 32-bit offset, 32-bit length, and 32-bit extra (often unused). To load a particular tile graphic, you multiply the tile ID by 12 to find its index entry, then seek to that offset in <code>art.mul</code>. (If the offset is 0xFFFFFFFF, the tile is not present). <strong>Land tiles</strong> (terrain) and <strong>static item tiles</strong> share the same art files but have different ID ranges. In UO: IDs 0–0x3FFF are land tiles, and IDs 0x4000 and above are static objects. In fact, in many tools the static object ID is handled by adding 0x4000 – e.g. an item ID 5 corresponds to artidx entry 0x4005. Centred# likely handles this by offsetting static IDs by 0x4000 when looking up art.</li>
<li><strong>Raw vs Run-length tiles:</strong> The art file uses two encoding formats. Centred# checks the first 4 bytes at the art.mul offset (often called a “flag” or header). In UO’s format:
<ul>
<li>If this 32-bit value is <strong>greater than 0xFFFF</strong> (or zero), it indicates a <strong>raw tile</strong> (terrain tile). Raw land tiles are a fixed 44×44 pixel image encoded in a specific way. No explicit width/height is stored (it’s implicitly 44&#215;44), and the pixel data follows as 22 rows of increasing length then 22 of decreasing length (forming the diamond shape of terrain).</li>
<li>Otherwise (flag ≤ 0xFFFF and not zero), it’s a <strong>run-length encoded (RLE) tile</strong> – this is the format used for <strong>static object art</strong>, which have variable dimensions. In this case, the next 4 bytes (two 16-bit values) give the <strong>Width</strong> and <strong>Height</strong> of the bitmap. Then there is a lookup table of <code>Height</code> number of 16-bit offsets, which point to the start of each row’s data relative to a base position. After the lookup table, the actual pixel runs follow.</li>
</ul>
</li>
<li><strong>Decoding static tile images:</strong> Centred#’s code will decode the RLE format to reconstruct the image. Pseudocode for this (based on Ultima SDK’s approach) looks like:
<ol>
<li>Read Width (W) and Height (H) from the art data stream.</li>
<li>Allocate a bitmap of size W×H (16-bit pixel depth, since UO art is 16-bit color 0xABGR format).</li>
<li>Read H 16-bit values into an array <code>LineStart[0..H-1]</code>, which are offsets to each scanline’s data (relative to the data section).</li>
<li>For each row <em>y</em> from 0 to H-1: seek to the data start + <code>LineStart[y]*2</code> (each offset is in words) and then decode runs:
<ul>
<li>Loop until you encounter a zero run-length indicator. Each run is encoded as two 16-bit values: <strong>XOffset</strong> and <strong>RunLength</strong>. The decoder will skip <code>XOffset</code> pixels from the left (move that many pixels into the line) and then copy <code>RunLength</code> consecutive pixel values from the stream to the bitmap.</li>
<li>This loop ends when a pair <code>(XOffset, RunLength)</code> equals 0 (i.e., <code>XOffset + RunLength == 0</code>, marking end of line).</li>
<li>Each pixel value is a 16-bit color. In UO’s format, the high bit of each pixel denotes transparency (1 = opaque). The decoder typically sets this high bit on all copied pixels to mark them as present. For example, the Ultima SDK code does <code>pixelValue ^ 0x8000</code> to flip the transparency bit before storing (ensuring the pixel is opaque in the output image).</li>
</ul>
</li>
<li>Repeat for all rows, then unlock the bitmap. You now have the full image of the static tile.</li>
</ol>
</li>
<li><strong>Decoding land tile images:</strong> For land tiles (44×44), the format is simpler (no explicit width/height stored since it’s always 44). The data is essentially 22 rows of increasing length (2,4,6,&#8230;,44 pixels) then 22 rows of decreasing length. Centred# likely uses a fixed routine for this. For example, a decoding loop might start at the top tip of the diamond and work downwards: beginning with an offset of 21 blanks and a run of 2 pixels, then 20 blanks and 4 pixels, etc., until the middle row of 44 pixels, then mirror the pattern. In code, one can initialize <code>xRun = 2</code> and <code>xOffset = 21</code> and then adjust those as you iterate over 44 lines to place pixels appropriately in a 44&#215;44 bitmap. (The Centred# source likely has a <code>LoadLand</code> function very similar to this, which fills a 44&#215;44 Bitmap with the land tile’s pixels).</li>
<li><strong>Caching and usage:</strong> In Centred#, once a tile image (land or static) is decoded into a Bitmap/texture, it might be cached for reuse. For example, if you open a tile picker UI, it will load the images on demand and keep them. The source code might have an array or dictionary of loaded tile Bitmaps. The Ultima SDK’s <code>Art</code> class, for instance, uses a cache array indexed by tile ID. In Godot, you could similarly cache <code>ImageTexture</code>s for each tile to avoid re-decoding repeatedly.</li>
</ul>
<p><strong>References:</strong> The logic described above is corroborated by known UO format documentation and existing tools. The Heptazane format docs show how <code>ART.MUL</code> is structured and how to interpret raw vs run-length tiles. The Ultima SDK (used in programs like UOFiddler) implements this decoding in C#: for instance, it reads the width/height and then loops through run-length encoded segments to build the image. Centred#’s code will be doing the same thing – reading the artidx entry, then either calling a routine to decode a static tile or a land tile depending on the flag. By following that approach, you can implement your Godot plugin to load any UO art asset (including custom ones from ServUO/ClassicUO) and display them.</p>
<h2>Implementing in Godot (Editor Plugin &amp; Runtime Viewer)</h2>
<p>For <strong>visual display</strong> in Godot, start with purely viewing tiles, then add interaction. Initially, you can create a Godot EditorPlugin that opens the UO files and displays a grid or list of tile images (similar to Centred#’s tile selector). Later, you can make those images selectable and use that selection to paint tiles onto a map.</p>
<p><strong>Key implementation steps:</strong></p>
<ul>
<li><strong>Reading files:</strong> Use Godot’s file APIs or C# System.IO to open the <code>.mul</code> and <code>.idx</code> files. (In a GDScript tool script, you might use <code>File.open()</code> in binary mode. In C#, use <code>FileStream</code> or <code>BinaryReader</code>.) Read bytes according to the formats above. For example, to get a static tile image:
<ol>
<li>Read the artidx entry for ID+0x4000 (for static item IDs) to get offset &amp; length.</li>
<li>Seek to offset in art.mul, read the flag (4 bytes) and decide raw vs run. Then decode as described (the decoding can be done in GDScript, though C# or C++ might be faster for large images).</li>
<li>Convert the decoded 16-bit pixel data to a Godot Image. Godot Image supports 16-bit color, but it might be easier to convert to 32-bit (x2 the data) for a standard ARGB8888 Image. You’ll have to map the 0x7FFF/0x8000 format to Godot’s color; basically, each 16-bit pixel is 0xA B G R (1-bit alpha + 5-bit blue/green/red). In the decoded data, if you set the high bit for opaque pixels, then 0x8000 indicates a fully opaque black pixel (if color bits were 0). Typically, you can treat the 0x8000 bit as alpha: on output, set alpha=1 for any pixel where (pixel &amp; 0x8000)!=0, and RGB = the lower 15 bits converted to 24-bit color.</li>
<li>Create an ImageTexture from the Image and use it in a Sprite or UI TextureRect to display.</li>
</ol>
</li>
<li><strong>Displaying maps:</strong> To render a map in Godot (runtime viewer), you’d combine 8&#215;8 blocks. You would read <code>map#.mul</code> for terrain tiles (each block is 64 cells of 3 bytes: tile ID and altitude) and the statics as above. Then for each cell, draw the land tile image, and for each static in that cell, draw the static’s image (in the correct draw order – usually by altitude). This is similar to what Centred# client does when showing the map. A simple approach in Godot is to use a TileMap or manually draw sprites at the correct positions.</li>
<li><strong>Interaction:</strong> Once the visuals are working, you can add clicking. For example, in the Godot Editor plugin, you could make each tile image clickable to select that tile for painting. In the runtime map, you could allow clicking a spot to inspect which static tile is there, etc. These interactions are beyond Centred#’s core loading logic but integrating them in Godot is straightforward once the data is loaded (just use Godot’s input events on the sprites or an overlay).</li>
</ul>
<h2>Designing a ServUO/ClassicUO-Compatible <strong>.kul</strong> Format</h2>
<p>You mentioned creating a <strong><code>.kul</code></strong> file format compatible with ServUO/ClassicUO. This suggests you want to package the tiles (and possibly maps) in a new way, but still have the game server and client recognize them. ServUO (the server) and ClassicUO (the client) currently expect the standard MUL/UOP files, so introducing <code>.kul</code> means you’d likely have to modify those programs to support it. Here are some considerations:</p>
<ul>
<li><strong>Format choice:</strong> Decide if <code>.kul</code> is simply a repackaging of existing files or a new container. For example, you might combine <code>mapX.mul</code>, <code>staidxX.mul</code>, and <code>staticsX.mul</code> into one file for convenience (since those three always go together). Or you might create a custom art container. If you want ClassicUO to read <code>.kul</code>, you could implement it similarly to how it reads UOP or MUL: i.e., add a loader that recognizes the <code>.kul</code> extension and parses it. One idea is to use a <strong>UOP-like format</strong> (Ultima Online’s newer “Unity Optimized Package” format) but with your own extension, since ClassicUO already has code to handle UOP (it might be adaptable).</li>
<li><strong>ServUO compatibility:</strong> ServUO (being a RunUO derivative in C#) has file reading code for maps and statics. To use <code>.kul</code> there, you would either need to convert the <code>.kul</code> back into the expected .mul files at runtime, or modify ServUO’s map loading code to handle <code>.kul</code>. If <code>.kul</code> is just a renamed <code>.mul</code> or a concatenation, you could adjust the code accordingly. The <em>easiest path</em> is often to not stray far from the existing formats. For instance, if <code>.kul</code> combined map and statics into one, you could have a header that identifies sections (like one section for map data, one for static index, one for static data).</li>
<li><strong>ClassicUO client:</strong> ClassicUO’s code can be modified to support new file formats. You’d add a handler in its <code>FileManager</code> or resource loading section. Ensuring it’s “compatible” means ClassicUO could load either standard muls or your <code>.kul</code>. You might use a config or a naming convention (e.g., if a <code>map0.kul</code> exists, load that instead of map0.mul).</li>
<li><strong>Godot usage:</strong> If you have a custom <code>.kul</code>, your Godot plugin can be the tool that <strong>creates</strong> this file (perhaps exporting a custom map/art combination) and you’d also teach Godot to read it. Essentially, you become the author of the format, so you define how the data is stored. Keep in mind the structure from the mul files – for compatibility, it might literally contain the same bytes as mul files but in one archive. For example, <code>.kul</code> could start with a directory listing: e.g., offsets for sub-files (like the entire map mul chunk, statics index chunk, statics chunk, etc). This is similar to UOP which has a file index at the start.</li>
</ul>
<p>In summary, <strong>Centred#’s source code for loading tiles</strong> involves two main parts: reading the <em>placement data</em> of statics (from staidx/statics) and reading the <em>art pixel data</em> (from art.mul via artidx). These are the parts you’ll want to replicate. Specifically, look at how Centred# parses <code>staidx#.mul</code> and <code>statics#.mul</code> to get static objects, and how it decodes the art for those objects from <code>art.mul</code> – likely using a routine equivalent to the Ultima SDK’s (reading width/height and run-length pixel data). Using that knowledge, you can implement a Godot plugin that loads the UO assets and displays them. For the <code>.kul</code> format, you will design a new container and update ServUO/ClassicUO to support it, ensuring it still stores the necessary data (tile IDs, maps, etc.) in a way those programs can utilize with minimal changes.</p>
<p><strong>Sources:</strong></p>
<ul>
<li><a href="https://uo.stratics.com/heptazane/fileformats.shtml#:~:text=0%201%202%203%204,A%20B%20Start%20Length%20Unknown">UO Stratics File Format documentation</a> – explains the structure of STAIDX0.MUL (static tile index) and STATICS0.MUL (static objects), as well as ART.MUL encoding for land vs static tiles.</li>
<li><a href="https://github.com/markdwags/Razor/blob/d1c7b2404da9c04184675c692c0a09c0035eacd8/Razor/UltimaSDK/Art.cs#L409-L417">Ultima SDK (UOFiddler/Razor)</a> – example code for decoding art.mul images. The static tile run-length decoding loop shows how Centred# would reconstruct item art bitmaps (each run defined by X offset and length of pixels), and land tiles are handled as 44×44 fixed images.</li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Designing an Open-Source ClassicUO Compatible Isometric File Format: .kool</title>
		<link>https://ideariff.com/designing_a_modern_open_source_uo_file_format_kool</link>
		
		<dc:creator><![CDATA[Michael Ten]]></dc:creator>
		<pubDate>Sat, 21 Jun 2025 06:37:46 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[game development]]></category>
		<category><![CDATA[games]]></category>
		<category><![CDATA[gaming]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://ideawaza.com/?p=733</guid>

					<description><![CDATA[Great — Here is a comprehensive report on designing a .mul-compatible open format (like .kool) that supports both isometric and 2D layouts, balances performance and editability, and works with ClassicUO, ServUO, and Godot (including mobile). This also cover integration using both GDScript and C# where feasible. 1. Overview of Ultima Online’s .mul and .idx Files Ultima Online’s classic client stores game data in numerous binary .mul files, often paired with .idx index files. Each .idx provides lookup offsets and lengths for data entries in the corresponding .mul. For example, an index entry is 12 bytes: a 32-bit offset (lookup), 32-bit ]]></description>
										<content:encoded><![CDATA[<p>Great — Here is a comprehensive report on designing a <code>.mul</code>-compatible open format (like <code>.kool</code>) that supports both isometric and 2D layouts, balances performance and editability, and works with ClassicUO, ServUO, and Godot (including mobile). This also cover integration using both GDScript and C# where feasible.</p>
<h2>1. Overview of Ultima Online’s .mul and .idx Files</h2>
<p>Ultima Online’s classic client stores game data in numerous binary <strong><code>.mul</code> files</strong>, often paired with <strong><code>.idx</code> index files</strong>. Each <code>.idx</code> provides lookup offsets and lengths for data entries in the corresponding <code>.mul</code>. For example, an index entry is 12 bytes: a 32-bit offset (<code>lookup</code>), 32-bit length, and an extra 32-bit field (often unused or used for specific metadata). To fetch an entry, the client multiplies the resource ID by 12, reads the index record from the <code>.idx</code>, then seeks to the given offset in the <code>.mul</code> file and reads the specified length. This scheme is used for <em>art assets, animations, sound, etc.</em>, allowing fixed-length index tables with variable-length binary data blobs.</p>
<p><strong>Major UO file types:</strong> The game divides content by type across different <code>.mul</code> files. Important ones include:</p>
<ul>
<li>
<p><strong>Map files (<code>map0.mul</code>, <code>map1.mul</code>, etc.):</strong> These hold the base terrain height-map (ground tiles) for each world or facet. UO’s main map is a fixed-size grid of <strong>6144×4096 tiles</strong>, divided into <strong>768×512 blocks of 8×8 cells</strong>. Each <em>cell</em> stores a terrain tile ID and an elevation byte. The map is essentially a giant matrix of tile IDs and altitudes, streamed in block order (e.g. block 0 is the northwest 8×8 area, etc.). There is no index file for maps – the position of a cell can be computed by its coordinates (e.g. <code>blockIndex = (xBlock * 512) + yBlock</code>). Each 8×8 block is 196 bytes in the old format (including a 4-byte unused header).</p>
</li>
<li>
<p><strong>Static object files (<code>statics.mul</code> with <code>staidx.mul</code>):</strong> These store world objects (buildings, trees, items) placed on the map. For each map block, <code>STAIDX</code> contains a 12-byte index entry pointing to that block’s static objects in <code>STATICS.mul</code>. If a block has no statics, the index offset is <code>0xFFFFFFFF</code>. Each static object record is 7 bytes: a 2-byte <em>object ID</em>, 1-byte <em>X</em> and 1-byte <em>Y</em> offset within the 8×8 block, a 1-byte <em>altitude</em> (height above ground), and a 2-byte <em>hue/unknown</em> field. (In UO, static object IDs are offset by +0x4000 when looking up art or radar color, to distinguish them from terrain tile IDs.) The client draws statics on top of terrain, sorting by altitude and position so that taller objects appear above lower ones.</p>
</li>
<li>
<p><strong>Art files (<code>art.mul</code> with <code>artidx.mul</code>):</strong> Contains <strong>2D sprite graphics</strong> for terrain and objects. Each entry is a bitmapped image (for example, a tree or wall tile). UO uses two types of art encoding: <em>static-sized 44×44 terrain tiles</em> (stored as a 45° rotated diamond of pixels) and <em>variable-sized “run-length” compressed sprites</em> for objects. The <code>ARTIDX.mul</code> index gives offset and length for each image. The image data includes flags or a header word to distinguish raw vs. compressed format. All artwork is drawn isometrically in the game (45° rotated grid).</p>
</li>
<li>
<p><strong>Animation files (<code>anim.mul</code> with <code>anim.idx</code>, plus <code>animdata.mul</code>):</strong> These hold multi-frame character and creature animations. The index maps animation IDs to frames in <code>anim.mul</code>. Each animation consists of a palette and a sequence of frames (with per-frame pixel run data). Animations are compressed with run-length encoding optimized for 2D blitting.</p>
</li>
<li>
<p><strong>Gump files (<code>gumpart.mul</code> with <code>gumpidx.mul</code>):</strong> “Gumps” are UI art elements (GUI textures like the paperdoll, buttons, etc.). Like art, an index references each UI graphic. Gump images are stored in a simple run-length pixel format per row (inefficient but workable).</p>
</li>
<li>
<p><strong>Sound files (<code>sound.mul</code> with <code>soundidx.mul</code>):</strong> Contains sound effects (PCM audio data). The index provides offsets for each sound ID. All sounds in UO are 22 kHz 16-bit mono WAV data. The <code>.mul</code> holds the raw waveform, while <code>.soundidx</code> entries give offset, length, and an unused field (plus in newer clients an “index” field for grouping).</p>
</li>
<li>
<p><strong>Tile data (<code>tiledata.mul</code>):</strong> This is a structured file containing metadata for each terrain and item tile ID. Each tile entry includes properties like a name and a bitfield of <strong>flags</strong> that define game mechanics (e.g. whether a tile is a wall, wet (water), impassable, a surface to stand on, a platform (‘bridge’), etc.). For example, flag <code>0x00000040</code> = <em>Impassable</em>, <code>0x00000080</code> = <em>Wet (water)</em>, <code>0x00000400</code> = <em>Bridge (platform)</em>, <code>0x00002000</code> = <em>No Shoot (blocks line-of-sight)</em>, and many more. Tiledata links the raw art IDs to game logic (like whether you can walk on it or through it).</p>
</li>
<li>
<p><strong>Multi files (<code>multi.mul</code> with <code>multi.idx</code>):</strong> “Multis” are predefined object groups like house designs or ships. The index points to a list of components in <code>multi.mul</code>. Each multi entry lists multiple statics with relative X,Y offsets and an altitude, comprising a larger structure. For instance, a house multi might list wall and floor object IDs at various offsets. The client uses these to render player-built houses (often adding 0x4000 to the tile IDs to fetch artwork).</p>
</li>
</ul>
<p>Other files include <strong>hues (color palettes)</strong> for tinting objects, <strong>fonts</strong>, <strong>lighting tables</strong>, <strong>map radar color tables</strong>, <strong>skills data</strong>, and <strong>verdata</strong> (patch overrides). In summary, the UO file ecosystem splits the world into multiple data files (terrain, statics, art, animations, sound, etc.), using indices to allow random access. This structure was efficient for 1990s hardware and allows partial updates (patches could replace specific entries via <code>verdata.mul</code> or “diff” files). However, <code>.mul</code> files have no explicit self-description or compression for large datasets (maps are uncompressed and fairly large), and they rely on proprietary content. The goal of <strong>“.kool”</strong> (a hypothetical new format) is to modernize this while staying compatible in spirit.</p>
<h2>2. Legal Considerations for Replicating the Format</h2>
<p>When reimplementing or replacing Ultima Online’s file format, it is critical to separate <strong>the data format from the game’s original content.</strong> The binary format and data structures (e.g. “a map is a grid of blocks with tile IDs and altitudes”) are essentially facts or methods of operation that can be freely reimplemented. File formats per se are generally not protected by copyright. Thus, one can design an open format that is <em>structurally</em> compatible with UO’s (for ease of conversion/interoperability) without violating the law.</p>
<p>However, <strong>absolutely none of the original UO assets or proprietary code can be reused</strong> in the new project. Every art sprite, sound effect, music track, map layout, and even the specific arrangement of tiles in Britannia is <strong>copyrighted by Electronic Arts/Broadsword</strong>. As an official forum moderator explicitly stated, <em>“Every asset that comes with Ultima Online is copyrighted – the grass tiles, the music, the sound effects, the UI icons, the fonts, the monster and character graphics, everything. The code that binds these together is copyrighted as well.”</em> Using any of those materials without permission is considered infringement (“theft” in their words). Therefore, an open-source project must create <strong>entirely original content</strong> (new artwork, new maps, new sounds, etc.) to replace the old assets. Projects like <em>Freedoom</em> (which provides free replacement assets for Doom) demonstrate the viability of this approach: Freedoom produces all-new graphics, music, levels under an open license, allowing the Doom engine to be distributed as a complete free game. By analogy, our <code>.kool</code> format will carry only original, non-infringing data.</p>
<p>That said, one can safely <strong>imitate UO’s file <em>structure</em></strong> and even its data organization concepts. Reverse-engineering efforts over the decades have documented UO’s formats (with no legal challenges known, since no actual EA art is being distributed by format documentation). The new format can use similar concepts like tile IDs, chunked maps, index tables, etc., as these are functional elements. We must avoid trademarked names (“Ultima Online” itself is a trademark), so calling the new format “UO-something” is ill-advised. The name “*.kool” is arbitrary and distinct enough.</p>
<p>One area to be cautious is <strong>encryption or protocols</strong>. UO’s client communication or any encryption in asset files (the modern client’s <code>.uop</code> format uses some compression and maybe minor obfuscation) should not be directly reused. Our format should be designed from scratch and openly documented, ensuring it doesn’t incorporate any proprietary algorithm that might be patented or secret. If optional encryption is added (discussed later), it must use standard, license-free algorithms.</p>
<p>Finally, running an alternative ecosystem (using ClassicUO or ServUO with new assets) is generally tolerated by the IP owners but not officially authorized. All original UO shards and clients operate in a gray area legally. By using an original open-source client (ClassicUO) and entirely new assets, we minimize legal risk: there is no distribution of EA’s client or art. The <strong>open-source license</strong> for the new assets and format will make it clear that this is a fan-made, free project with no claim to EA’s IP. In short, we replicate the <em>functionality</em> and <em>interface</em> of the <code>.mul</code> ecosystem, but <strong>none of the copyrighted content</strong>. This is analogous to how free shards or mods operate, except we go one step further by replacing all game data with open content (much like how OpenTTD or Freespace Open projects require free asset replacements to be truly standalone). The result will be a legally clean foundation: open-source code + open data format + open assets.</p>
<h2>3. Proposed Open Format and Subformats (<code>.kool*</code>)</h2>
<p>To serve as a modern replacement, the new format family (let’s use “<strong>.kool</strong>” as a placeholder name) will mirror the <em>modular structure</em> of the original, while introducing improvements for <strong>performance, editability, and compatibility</strong>. We propose splitting the data into several file types (subformats), each handling a specific aspect of the game data, similar to how UO had separate files. This modular approach is both organizational and practical – it allows updating or streaming parts of the content (e.g. map vs. art) independently.</p>
<p><strong>Planned .kool file types:</strong></p>
<ul>
<li>
<p><strong><code>.koolmap</code> – World Tile Map Data:</strong> Contains the terrain grid and static object placements for a game world (analogous to <code>map0.mul</code> + <code>statics.mul</code>). The <code>.koolmap</code> will encode the landscape in chunks (for streaming) along with metadata per region. It supports both <strong>top-down 2D and isometric</strong> tile interpretations. This means the data model is a 2D grid with optional height values, which a client can render as a flat overhead map or in an isometric perspective with elevation. By not baking in projection, the format stays flexible – an engine can treat it as a classic 2D tilemap (ignoring altitude or using it for minor variations) or as an isometric world (using altitude for vertical positioning). We intend <code>.koolmap</code> to encapsulate static objects as well, so the entire world’s layout is in one file (see Section 4).</p>
</li>
<li>
<p><strong><code>.koolart</code> – Graphics (Tile and Sprite Art):</strong> This will package all the tile graphics and sprites (environment tiles, items, characters, GUI elements if needed) in an open format. Rather than a monolithic binary blob, <code>.koolart</code> could be an archive (e.g. a simple packed directory or even a ZIP under the hood) containing individual images or texture atlases. However, for performance, we might use a binary atlas format: e.g., store all art in one big texture atlas image with an index table mapping tile IDs to (x,y) regions – or use a sequence of images with a directory listing. The key is to ensure it’s easy to <strong>edit and replace</strong> art (developers can drop in a PNG and update an index), and easy to load in engines like Godot (which can import images directly). We want an open format that’s not encumbered by patented compression; using standard formats (PNG for images or a simple run-length encoding similar to UO’s) is fine. <code>.koolart</code> will handle both <em>land tiles</em> (which may be fixed-size like 64×32 or 128×64 isometric diamonds, depending on chosen resolution) and <em>object sprites</em> (which can be various dimensions). We may separate these into two sections or two files (like <code>.koolart_land</code> vs <code>.koolart_obj</code>) if that simplifies client handling (for example, Godot might treat terrain tilesets differently from object sprites). For now, we’ll consider it one file with an internal distinction or ID range. Each graphic entry can have metadata (for example, bounding box, optional transparency index, etc.) or we rely on image alpha for transparency.</p>
</li>
<li>
<p><strong><code>.koolsnd</code> – Audio (Sound and Music):</strong> An open pack of audio assets (sound effects, ambient sounds, possibly music tracks). This could simply be a directory or archive of Ogg or WAV files with known filenames or IDs. We can design it similar to UO (an index pointing into a big wave file), but modern practice would just use standard audio file formats to avoid reinventing the wheel. However, providing an optional index file (to map numeric sound IDs to filenames or byte offsets) could ease compatibility with servers that refer to sounds by ID. <code>.koolsnd</code> might thus include a manifest (JSON or binary index) listing each sound ID and the path/offset of its audio data. For performance and simplicity, not much compression beyond what the audio codec provides is needed (Ogg Vorbis or FLAC compression is fine and widely supported).</p>
</li>
<li>
<p><strong>(Optional) <code>.kooltile</code> – Tile Definitions:</strong> This would be the equivalent of <code>tiledata.mul</code> – defining properties for each tile or item ID. Since this is relatively small data (a few thousand entries at most, each with flags, names, etc.), it could be a human-readable format (JSON, XML, or a CSV) or a binary blob. To keep it editor-friendly, a text-based format might be nice (e.g., a JSON file where each tile ID has a dictionary of properties: <code>&quot;name&quot;: &quot;Iron Sword&quot;, &quot;weight&quot;: 6, &quot;flags&quot;: [&quot;Wearable&quot;,&quot;Weapon&quot;], ...</code>). In an open project, having this data easily tweakable is valuable. We could also integrate this into <code>.koolart</code> as metadata (for example, store tile properties alongside the art asset), but separating logic data from graphics is probably cleaner. A <code>.kooltile</code> file can be loaded by the server (ServUO or others) to know game mechanics, and by the client if needed for tooltips or effects.</p>
</li>
<li>
<p><strong>(Optional) <code>.koolmulti</code> – Multi-object Templates:</strong> If we want to support UO-style housing or prefabs, we can provide a format akin to <code>multi.mul</code>. This would list compositions of tiles (e.g., a house layout made of multiple tile IDs at relative positions). However, this might also be rolled into <code>.koolmap</code> or just handled on the server side. Since the question doesn’t explicitly ask for multis, we can consider it a future extension or include it in the map as special regions (like “house design” region).</p>
</li>
<li>
<p><strong>Future encryption/validation layer:</strong> The format family is meant to be open and easily readable (to encourage community contributions and transparency). However, for certain deployments, shard owners might want to <strong>protect their custom assets</strong> or ensure clients aren’t tampering with files. We can design the format such that encryption or signing can be layered on <em>optionally</em>. For example, a <code>.kool</code> file could have an <em>optional header section indicating encryption</em>, after which the bulk of the data is encrypted with a key (the client would need that key). This could be as simple as wrapping the file in an AES encryption layer, or requiring a hash check. Another approach is to allow a separate manifest file with hashes (to validate integrity). These features wouldn’t be used in the default open-source distribution (to keep everything open and modifiable), but the format will be <strong>extensible</strong> to accommodate them later. We will reserve an identifier or flag in the header for encryption, so that if encryption is off (default), any client can read the data, but if on, the engine knows it must decrypt (likely with a key obtained securely from the server or user). All encryption would use non-proprietary algorithms (e.g., AES, RSA for keys) to avoid legal issues and to allow open implementations.</p>
</li>
</ul>
<p><strong>Compatibility goals:</strong> The <code>.kool</code> suite is designed to be <strong>structurally compatible</strong> with the UO ecosystem so that existing tools and servers can be adapted with minimal effort. The data can be converted to classic <code>.mul</code> if needed. For instance, <code>.koolmap</code> can be converted into <code>map0.mul</code> + <code>statics.mul</code> and their indexes by a tool, allowing ClassicUO or the original client to load the world (with new content). Likewise, a converter could take existing <code>.mul</code> files and output <code>.kool</code> files (useful for testing the system with UO’s map if one has legal access, or for players who want to use the new client format with old assets, purely for personal use). We are essentially creating an <strong>interchange format</strong> that can funnel data into ClassicUO/ServUO or into modern engines like Godot. ClassicUO being open source means we could also modify it to read <code>.kool</code> files directly at runtime, bypassing conversion. For example, one could write a plugin or extend ClassicUO’s asset loader to accept a <code>.koolmap</code> file – since the format is public, it’s just coding work. ServUO (an emulator in C#) could likewise be extended to load <code>.kool</code> assets for server-side functions (like region definitions or tile flags). In the interim, providing <strong>conversion utilities</strong> (Section 8) will be important to bridge the ecosystems. Compatibility also means using <strong>non-infringing IDs and structures</strong>: we might use the same numeric ranges for tile IDs (so that ServUO’s existing item scripts referencing, say, item ID 0x0F00 for a chair, can work if we assign our new chair art that ID). We have to be careful: using the <em>exact</em> UO IDs for items could be a double-edged sword legally if those IDs correspond to copyrighted art (because the mapping of ID to art is kind of a fact, but if someone were to use the new client with the old server scripts, the server might expect certain behaviors for those IDs). However, if our goal is to be a drop-in replacement, matching some ID conventions might be beneficial. For now, we can design <code>.kool</code> to allow flexible ID mapping (e.g., include a mapping table if needed), but assume we’ll define our own tile and item ID namespace for our original content.</p>
<p>In summary, the <code>.kool</code> format family will be modular, open, and friendly to both <strong>streaming performance</strong> and <strong>editor modification</strong>. Next, we’ll delve into the specific design of the core piece, <code>.koolmap</code>, which is one of the most complex parts (since it combines world terrain, statics, and regions).</p>
<h2>4. <code>.koolmap</code> Format Specification</h2>
<p>The <strong><code>.koolmap</code></strong> file stores an entire game map (terrain and static objects). It is designed with the following in mind: <strong>(a)</strong> easy streaming of world chunks (so clients can load areas on the fly or only keep nearby regions in memory), <strong>(b)</strong> easy editing of small parts (so an editor can update a city or add a building without rewriting the whole file), <strong>(c)</strong> support for both flat 2D and isometric views, <strong>(d)</strong> future-proofing via versioning and optional compression/encryption.</p>
<p>Let’s break down the <strong>binary layout</strong> of <code>.koolmap</code>:</p>
<ul>
<li>
<p><strong>File Header:</strong> A fixed-size header at the start of the file provides identification and global info. For example, we use a few bytes for an ASCII signature, like <code>KOOLMAP</code> (7 bytes) plus a null terminator or version byte, so that tools immediately recognize the format. The header includes a <strong>version number</strong> (e.g., 16-bit or 32-bit integer) which will be incremented if we change the format in incompatible ways. We also include overall <strong>map dimensions</strong> (width and height in tiles, or in blocks), and <strong>block size</strong> (the size of a chunk in tiles, likely 8×8 as in UO for familiarity). For instance, classic UO map0 is 6144×4096 tiles, block size 8, thus 768×512 blocks – those numbers would be in the header. Additionally, the header contains flags or fields for any global settings:</p>
<ul>
<li>A <strong>compression flag or method</strong> field (e.g., 0 = no compression, 1 = zlib/DEFLATE, 2 = LZ4, etc.), indicating whether chunk data is compressed and how.</li>
<li>An <strong>encryption flag</strong> (if we allow encrypted content, e.g., 0 = none (default), 1 = encrypted with key).</li>
<li>Possibly a <strong>checksum</strong> or CRC for the entire file (or for quick validation of file integrity).</li>
<li>A <strong>chunk index offset and size</strong> (if the index is not directly after the header).</li>
<li>Reserved space for future use (we can pad the header to, say, 64 bytes or more, leaving room for additional fields later without breaking version 1).</li>
</ul>
<p>For example, the header might be defined in C-like pseudocode as:</p>
<pre><code class="language-c">struct KoolMapHeader {
    char magic[8];      // &quot;KOOLMAP\0&quot;
    uint32 version;     // e.g., 0x00010000 for version 1.0
    uint32 mapWidth;    // in tiles
    uint32 mapHeight;   // in tiles
    uint16 blockWidth;  // e.g., 8
    uint16 blockHeight; // e.g., 8 (usually same as blockWidth for square blocks)
    uint32 totalBlocks; // just for convenience (mapWidth/blockWidth * mapHeight/blockHeight)
    uint8  compression; // 0 = none, 1 = zlib, 2 = LZ4, etc.
    uint8  encryption;  // 0 = none, 1 = AES-128-CTR, etc. (if implemented)
    uint16 reserved_flags; 
    uint32 indexOffset; // offset in file where chunk index starts (if not immediately after header)
    uint32 indexCount;  // number of index entries (should equal totalBlocks)
    uint32 indexEntrySize; // size of one index entry in bytes (for flexibility, likely 8 or 12 bytes)
    // ... possibly more reserved fields or a 16-byte hash for file?
};
</code></pre>
<p>This header structure (exact fields can vary) ensures any program can sanity-check the file (correct magic, supported version) before proceeding.</p>
</li>
<li>
<p><strong>Chunk Index Table:</strong> Following the header, we include an index of chunks (unless we choose to place it elsewhere, but typically it’s convenient right after the header). This index serves a similar purpose to UO’s <code>.idx</code> files but is embedded in the <code>.koolmap</code> for self-containment. Each index entry corresponds to a map <em>block</em> (which is a fixed region of the world, e.g., an 8×8 tile area). The index entry likely contains:</p>
<ul>
<li>The <strong>file offset</strong> where that chunk’s data is stored (relative to the start of the file or maybe after the index section).</li>
<li>The <strong>compressed size</strong> of the chunk data (and possibly the uncompressed size, if needed for allocation or to skip without decompressing).</li>
<li>Potentially a short <strong>checksum</strong> for that chunk or a timestamp for last modification (the latter can be useful for editors to track changes, similar to how Minecraft’s region format stores a timestamp for each chunk).</li>
</ul>
<p>We expect each index entry to be 8 or 12 bytes. For example, 8 bytes could be enough: 3 bytes for offset (if using 4096-byte sectors like Minecraft did, but we may not do sector alignment), 3 bytes for length, plus 2 bytes for a checksum or reserved. However, to avoid arbitrary limits, we might use 64-bit offsets and 32-bit lengths in a 12-byte entry. Given maps can be quite large, 32-bit offset (4 GB limit) might be okay if each map file &lt;4GB, but using 64-bit is future-proof.</p>
<p>The chunk index will have one entry per block in a predictable ordering (e.g., block [0] = top-left 8×8 area of map, then row-major order). The number of entries equals <code>totalBlocks</code> (which is width_in_blocks * height_in_blocks, given in header). The client can locate a block’s entry by its block ID. If we maintain the same ordering as UO (blocks counted row by row), conversion and compatibility calculations are easier (we can use UO’s formula: <code>blockID = (xBlock * blocks_y) + yBlock</code>).</p>
<p><em>Example:</em> If block 0 is at offset 256 bytes in the file and has length 100 bytes compressed, index[0] would store 256 and 100. If a block has no data (e.g., entirely empty static-wise), we might mark its offset as 0 and length 0, or have a flag for empty (like UO used 0xFFFFFFFF for no statics). Since every block at least has terrain data, we won’t have truly “empty” blocks; but we might compress a completely flat empty block to a very small size.</p>
<p>The index allows quick seeking: if a player moves to a new region, the client can jump to the needed block data without reading intervening blocks. It also allows the file to be <em>edited in-place:</em> if one chunk’s size changes (e.g., an edit adds many static objects making it bigger), we can append the new chunk data at file end and update that block’s index entry to point to the new location, leaving the old data as garbage or to be reclaimed on a re-pack. This avoids rewriting the whole file for small edits – an editor can work chunk by chunk. (This is similar in spirit to Minecraft’s region files, which use a fixed index at the top and can move chunks around, though they align chunks to sectors to ease expansion. We might not fix sector size, but we could for simplicity or even adopt 4KB sector alignment to reduce fragmentation).</p>
</li>
<li>
<p><strong>Chunk Data (Terrain &amp; Statics Blocks):</strong> After the index (or possibly interleaved with it if we chose not to centralize the index), the bulk of the file is the actual chunk data for each block. Each chunk contains:</p>
<ul>
<li>
<p>A small <strong>chunk header</strong> or identifier (optional). Could include the block ID or coordinates for redundancy (so if you jump to a chunk, you can verify it’s the correct one), and the uncompressed size (if compression is used). In a simple design, we might omit an internal header and rely on the index for all needed info. Alternatively, a 1-byte or 2-byte chunk type identifier (for future extensibility, e.g., different chunk format versions) could be included.</p>
</li>
<li>
<p><strong>Terrain cell data:</strong> The ground tiles for that 8×8 (or configurable size) block. We will likely store this as an array of 64 entries (row-major order within the block). Each entry can be:</p>
<ul>
<li>2 bytes for the <em>tile graphic ID</em> (allowing up to 65535 different terrain tile types, which is plenty; UO used 16-bit for tile IDs).</li>
<li>1 byte for <em>altitude</em> (heights from -128 to +127, exactly as UO did). We could consider 2 bytes for altitude if we wanted higher vertical range (e.g., in a fully 3D world you might exceed 127 high), but since this is for a 2D/iso hybrid, 1 byte (±128) is probably sufficient and matches many game constraints. It can always represent heights in half-meter or meter units as needed.</li>
<li>We could include an <em>optional byte of terrain metadata</em> per cell – for example, a byte that encodes ground texture variation or region index. But that might bloat the array. Instead, region info will be handled separately (see below), and terrain type is already the tile ID.</li>
<li>So baseline: 3 bytes per cell, identical to UO’s cell format, to facilitate easy conversion and familiarity. 64 cells -&gt; 192 bytes, plus maybe padding to align chunk if needed.</li>
</ul>
<p>We might also consider compressing these 64 cells with a tiny algorithm (but general file compression will probably shrink them well if there are repeating patterns). Given 192 bytes per block uncompressed, across an entire large map (393,216 blocks for a full UO map), that’s ~72MB just for terrain. With compression, this can drop massively since terrain has large swathes of repeated values (water, plains, etc.). We’ll rely on chunk compression to reduce this.</p>
</li>
<li>
<p><strong>Static objects data:</strong> Following the terrain cells of the block, we list all static objects that reside in that block. In UO, statics for each block were stored in a separate file and index, but here we integrate them for cohesion. We can use a similar 7-byte structure for each static object, perhaps extended slightly if needed:</p>
<ul>
<li>2 bytes: <em>Object ID</em> (refers to an entry in <code>.koolart</code> for the object’s sprite).</li>
<li>1 byte: <em>X offset</em> in the block (0 to blockWidth-1).</li>
<li>1 byte: <em>Y offset</em> in the block (0 to blockHeight-1).</li>
<li>1 byte: <em>Altitude</em> (relative elevation above the terrain, same scale as terrain altitude; can be negative for things below ground or positive for raised items).</li>
<li>2 bytes: <em>Flags/Extra</em> – could denote hue (tint) or other properties. In UO this was unused or used for hue in some contexts. We can define bits here for things like:  whether this static is <em>animated</em>, or <em>blocking</em>, or a <em>map marker</em>, etc., but a lot of that is defined in tile properties globally. It might also carry a <em>hue/color modifier</em> if we want to support tinted objects (like how UO can dye certain items).</li>
<li>We might include a <em>quantity</em> or <em>instance ID</em> if needed for certain server-side references, but that’s likely unnecessary at the map file level (it’s more for inventory items).</li>
</ul>
<p>All static entries for the block are listed sequentially. We may terminate the list with a special marker (like a 0xFFFF object ID as EOF) or, since the index knows the total length of the chunk, we implicitly know how many statics are present by reading until the chunk end.</p>
<p>Importantly, static entries <strong>should be sorted by Y then X then altitude</strong> for proper drawing order in isometric views (UO sorted them by height when rendering). We could pre-sort them in the file to make the client’s job easier (the client can then just draw in file order after separating by altitude if needed). UO’s static files did not guarantee sorting; the client had to sort each block’s statics by altitude at runtime for drawing. We can improve this by storing them sorted, but it’s a minor optimization. It might be better to just store as entered and let the renderer handle sorting (especially if dynamic objects will also be present).</p>
<p>We will also consider if we need <strong>multiple layers</strong> of terrain (like multi-level dungeons). UO’s map had a single layer with altitude; multi-level structures (like caves under ground) were handled via statics and altitude. <code>.koolmap</code> can similarly represent multi-story structures: the ground tile is base terrain, and upper floors are statics with higher altitude (like a wooden floor at Z=20). This works as long as the client knows how to render or cull by altitude. If an engine needed true separate layers, we could incorporate a layer concept (e.g., chunk divided into layer sections), but likely not necessary for compatibility.</p>
</li>
</ul>
</li>
<li>
<p><strong>Region and Metadata Chunk (optional):</strong> In addition to raw tiles and statics, game worlds often have <em>region definitions</em> (for zones like cities, dungeons, lawless areas, etc.), <em>terrain types</em> (like an ID for “forest” or “swamp” for certain tiles), and other metadata that doesn’t fit in per-tile flags alone. We plan to include a section in <code>.koolmap</code> (possibly at the end of the file, or distributed per chunk) to hold such info. There are a few approaches:</p>
<ul>
<li><strong>Global region table:</strong> e.g., at the end of the file we have a list of regions (with their polygonal boundaries or rectangular bounds, names, and attributes like “Guarded=Yes”). This might not be chunked, but rather a whole-map description. Because region shapes can be complex, a simple method is to use a bitmap or tile-tagging: e.g., a 2D array of region IDs aligned with the map. However, that can be large and inefficient if regions are irregular. Alternatively, define each region by a set of rectangles or an explicit list of tiles. For our open format, we could include a <strong>region layer</strong>: basically an overlay grid of the same dimensions as the map, where each cell has a region index (0 if none). That would be an array of <code>mapWidth * mapHeight</code> integers – too large to store explicitly (especially since many cells share region). Instead, it’s more efficient to store region shapes in a vector form (list of boundaries).</li>
<li><strong>Per-chunk metadata:</strong> We could also associate region data with chunks: e.g., each chunk could contain a small list like “regionID for this block” if the block is wholly in one region, or a mask of which cells belong to which region if it straddles a boundary. This gets complicated for arbitrary shapes. Perhaps best is to keep region info as a separate structure in the file: a header listing all regions with properties, and then a list of tiles or chunks belonging to each. Since the question specifically mentions <em>“Use region and terrain metadata”</em>, we should accommodate region definitions in the format.</li>
</ul>
<p>For now, we can outline that <code>.koolmap</code> will have a <strong>Region Section</strong> after all chunks:</p>
<ul>
<li>First, a table of region definitions: Each region has an ID, a name (string), and flags (like whether PvP is allowed, whether guards spawn, music to play, etc.—this replicates what shard scripts often handle).</li>
<li>Second, region geometry: could be a list of rectangles or tile ranges for each region, or a compressed mask. Possibly simplest is list of rectangles: e.g., Britain city region: multiple sub-rectangles covering the city area. This is flexible and fairly compact if regions are mostly rectangular. If needed, more complex shapes can be approximated by multiple rects.</li>
<li>Alternatively, we might rely on server-side region definitions (ServUO defines regions in scripts), and not bake it into the map file at all. But the question suggests including it, likely to assist the Godot integration (maybe for triggering music or effects in the client).</li>
</ul>
<p><strong>Terrain metadata</strong> such as “terrain type” (grass, sand, water) is often derivable from tile IDs and tiledata flags (e.g., any tile with Wet flag is water). We might not need separate encoding for terrain type if our tile IDs inherently indicate that. However, if one wanted, we could assign a biome or terrain code per tile ID or per region of tiles – but that’s out of scope for the core format spec (it can be handled via tile properties or region attributes).</p>
</li>
</ul>
<p>To summarize the <code>.koolmap</code> binary structure in order:</p>
<ul>
<li>
<p><strong>Header</strong> (with identification, version, dimensions, flags, index location).</p>
</li>
<li>
<p><strong>Chunk Index Table</strong> (array of fixed-size entries: offset &amp; length [ &amp; perhaps checksum ] for each chunk).</p>
</li>
<li>
<p><strong>Chunks</strong>: For each block (in some predetermined sequence, likely row-major by block):</p>
<ul>
<li>Terrain cell array (blockWidth * blockHeight entries, each 2 bytes ID + 1 byte altitude).</li>
<li>Static object list (N entries of 7 bytes each, or extended size if we add fields).</li>
<li>(If compression is enabled, the above data is stored compressed as a whole; if not, it’s raw).</li>
</ul>
</li>
<li>
<p><strong>Region/Metadata Section</strong> (optional, possibly at end of file, or signaled by a special index entry or header field):</p>
<ul>
<li>Region definitions (ID, name, flags…).</li>
<li>Region shapes (tile ranges or lists).</li>
<li>Possibly other global metadata like default terrain type mapping or map descriptions.</li>
</ul>
</li>
</ul>
<p>We should illustrate a small example of how a chunk might look in hex or pseudo-binary:</p>
<p>Suppose we have a 4×4 tile mini-map (block size 2, so 2×2 blocks total). The header would say width=4, height=4, blockWidth=2, blockHeight=2, totalBlocks=4. The index would have 4 entries. Let’s say block0 offset=&#8230; etc. Each block’s uncompressed data is 2<em>2</em>3 = 12 bytes for terrain, plus statics. If block0 had one static, that’s 7 bytes, so 19 bytes total uncompressed. Compressed it might be, say, 15 bytes. The index would record that.</p>
<p>The point is, <code>.koolmap</code> is self-contained and chunk-addressable. This approach balances performance and editability:</p>
<ul>
<li><strong>Performance:</strong> A client can quickly seek to relevant chunks and load only those. Large maps can be partially loaded. If compression is per-chunk, the client only has to decompress small pieces at a time (which is memory-friendly and CPU-friendly on modern hardware). This is similar to how modern games (and Minecraft) manage world streaming – small compressed chunks on disk for quick I/O. It also means patches can be efficient: if one chunk changes, only that chunk’s data needs to be updated. In fact, UO’s newer format <code>.uop</code> (used in later clients) is an archive that compresses data, but it was observed that patching <code>.uop</code> can be inefficient because compression can make small changes cascade into big binary diffs. ClassicUO actually converts <code>.uop</code> assets back to <code>.mul</code> for patching because uncompressed <code>.mul</code> allowed smaller delta patches. We take a cue from this: by chunking the compression, <em>local changes remain local</em>. A small map edit won’t reflow the entire file.</li>
<li><strong>Editability:</strong> Tools can open <code>.koolmap</code> and treat it almost like a database of blocks. For instance, an editor could load the index, then seek and decompress only the block the user is currently editing, modify it, and write it back (possibly appending new data if it grows). The index is then updated for that block. This is similar to how a filesystem or region file might work – no need to rewrite the whole file for one change, which is great for large world editing. The presence of clear structuring (header, index, chunks) makes writing an editor straightforward. Contrast with raw <code>.mul</code>: UO’s map was uncompressed fixed-size blocks (easy to edit in place, but no compression), and statics were a contiguous list per block but required maintaining parallel index – we unify those for simplicity.</li>
</ul>
<p><strong>Data integrity:</strong> Because <code>.koolmap</code> might be user-edited, including some form of validation is wise. We might store a simple checksum per chunk that the client can verify after loading, or a global hash to verify the entire file (server could send an expected hash to the client to ensure they have the right version). This is in the “optional” category but helps detect corrupted or tampered files. Open source philosophy suggests not forcing checks that impede modding, but giving server admins the option to validate assets.</p>
<p>To conclude the spec: any engine or tool reading <code>.koolmap</code> will:</p>
<ol>
<li>Read and verify the header (check <code>magic</code>, version compatibility).</li>
<li>Read the index table (either directly after header or at <code>indexOffset</code>).</li>
<li>For each needed block, get its offset/length from the index, seek there, read the chunk data (decompress if needed) into a buffer.</li>
<li>Parse terrain cells and static objects from that buffer.</li>
<li>Use tile IDs to look up art for rendering, use altitudes for positioning, etc. (We will detail this in the Godot integration section.)</li>
<li>Use region metadata as needed (for triggers like music or restrictions when player enters a region).</li>
</ol>
<p>This covers <code>.koolmap</code> in detail. The format is binary for compactness, but fully documented so that it remains open. We’ll now discuss how one would implement reading and writing this format in code (to demonstrate practicality).</p>
<h2>5. Reading and Writing <code>.koolmap</code>: Implementation Strategies (Python &amp; C++)</h2>
<p>To ensure the format is truly “open source–friendly,” we should provide reference implementations or code snippets. Below we illustrate how to read and write a <code>.koolmap</code> in both Python (good for quick prototyping or conversion scripts) and C++ (for integration into high-performance engines or tools).</p>
<h3>Python Implementation Example</h3>
<p>Python, with its <code>struct</code> module and rich libraries, is well-suited to parse binary files like <code>.koolmap</code>. Here’s a simplified example of reading a <code>.koolmap</code> file and iterating through its blocks:</p>
<pre><code class="language-python">import struct, zlib

# Open the .koolmap file in binary mode
with open(&quot;world.koolmap&quot;, &quot;rb&quot;) as f:
    # Read and unpack header (assuming the header structure from section 4)
    header_data = f.read(64)  # let&#39;s assume our header is 64 bytes
    magic, version, width, height, bW, bH, total_blocks, comp, enc, res_flags, idx_off, idx_count, idx_entry_size = \
        struct.unpack(&quot;&lt;8s I I I H H I B B H I I I&quot;, header_data)
    magic = magic.decode(&#39;ascii&#39;).rstrip(&#39;\0&#39;)
    if magic != &quot;KOOLMAP&quot;:
        raise Exception(&quot;Not a KOOLMAP file or bad magic!&quot;)
    print(f&quot;Map size: {width}x{height} tiles, block size: {bW}x{bH}, total blocks: {total_blocks}&quot;)
    # Read index entries
    f.seek(idx_off)
    index_entries = []
    for i in range(idx_count):
        # Assuming index entry = 12 bytes: 8-byte offset, 4-byte length
        data = f.read(12)
        if len(data) &lt; 12:
            raise Exception(&quot;Index truncated&quot;)
        offset, length = struct.unpack(&quot;&lt;Q I&quot;, data)  # little-endian
        index_entries.append((offset, length))
    # Iterate over blocks (for demonstration, we’ll print info about first few blocks)
    for block_id, (offset, length) in enumerate(index_entries[:5]):
        if offset == 0xFFFFFFFF or length == 0:
            # No data (e.g., a completely empty block) – skip
            continue
        f.seek(offset)
        comp_data = f.read(length)
        if comp == 1:  # e.g., 1 = zlib compressed
            raw_data = zlib.decompress(comp_data)
        else:
            raw_data = comp_data  # no compression
        # Now parse the chunk&#39;s raw_data
        # First part: terrain cells
        cell_count = bW * bH  # e.g., 64
        cells = []
        # Each cell: 3 bytes (uint16 id + int8 altitude)
        for j in range(cell_count):
            id_val, alt_val = struct.unpack_from(&quot;&lt;Hb&quot;, raw_data, j*3)
            cells.append((id_val, alt_val))
        # After terrain cells, static entries
        statics = []
        statics_offset = cell_count * 3
        # Read until end of chunk data
        k = statics_offset
        while k &lt; len(raw_data):
            (obj_id,) = struct.unpack_from(&quot;&lt;H&quot;, raw_data, k)
            if obj_id == 0xFFFF:  # hypothetical end marker (could also rely on length)
                break
            x_off, y_off, alt, flags = struct.unpack_from(&quot;&lt;BBbH&quot;, raw_data, k+2)
            statics.append((obj_id, x_off, y_off, alt, flags))
            k += 7
        print(f&quot;Block {block_id}: first terrain tile ID={cells[0][0]}, altitude={cells[0][1]}, statics={len(statics)} objects.&quot;)
</code></pre>
<p>In this Python snippet, we:</p>
<ul>
<li>Use <code>struct.unpack</code> with <code>&lt;</code> (little-endian) format strings to parse binary according to our known header and index structure.</li>
<li>Handle compression via Python’s <code>zlib</code> library if needed (assuming zlib/DEFLATE method).</li>
<li>Parse terrain cells and statics sequentially from the raw chunk data.</li>
<li>We demonstrate reading only the first 5 blocks for brevity, printing the first cell and number of statics.</li>
</ul>
<p>Writing a <code>.koolmap</code> in Python would be the reverse process: assemble the header bytes, then build the index (initially maybe all zeros as placeholders), then append each chunk’s data while recording its offset and length, then go back and write the index. Python can easily handle that with <code>struct.pack</code> and file writes. One would likely use <code>zlib.compress()</code> on each block’s data if compression is enabled.</p>
<p>The code above is simplified (it doesn’t handle region metadata, and assumes a fixed index entry size), but it shows that <strong>reading the format is straightforward</strong> using basic Python. This is important for community adoption: content creators can write Python scripts to generate or tweak maps (e.g., procedural generation or batch modifications) without needing a complex SDK.</p>
<h3>C++ Implementation Example</h3>
<p>In C++, one might parse <code>.koolmap</code> using file streams or memory-mapped files for efficiency. We’ll outline a reading process using C++ pseudo-code:</p>
<pre><code class="language-cpp">#include &lt;fstream&gt;
#include &lt;cstdint&gt;
#include &lt;vector&gt;
#include &lt;stdexcept&gt;
#include &lt;zlib.h&gt;  // if using zlib for decompression

struct KoolMapHeader {
    char magic[8];
    uint32_t version;
    uint32_t width, height;
    uint16_t blockWidth, blockHeight;
    uint32_t totalBlocks;
    uint8_t compression;
    uint8_t encryption;
    uint16_t reserved;
    uint32_t indexOffset;
    uint32_t indexCount;
    uint32_t indexEntrySize;
    // ... (assuming header was padded to fixed size)
};

// Index entry structure for this example
#pragma pack(push,1)
struct ChunkIndexEntry {
    uint64_t offset;
    uint32_t length;
    // (could have uint32_t checksum or timestamp here as well)
};
#pragma pack(pop)

struct TerrainCell { uint16_t id; int8_t altitude; };
struct StaticObj { uint16_t id; uint8_t x; uint8_t y; int8_t z; uint16_t flags; };

int main() {
    std::ifstream fin(&quot;world.koolmap&quot;, std::ios::binary);
    if(!fin) throw std::runtime_error(&quot;File open error&quot;);
    KoolMapHeader hdr;
    fin.read(reinterpret_cast&lt;char*&gt;(&amp;hdr), sizeof(hdr));
    if(strncmp(hdr.magic, &quot;KOOLMAP&quot;, 7) != 0) {
        throw std::runtime_error(&quot;Not a KOOLMAP file&quot;);
    }
    std::vector&lt;ChunkIndexEntry&gt; index(hdr.indexCount);
    fin.seekg(hdr.indexOffset);
    fin.read(reinterpret_cast&lt;char*&gt;(index.data()), hdr.indexCount * sizeof(ChunkIndexEntry));
    // Read a specific block, e.g., block number 100
    uint32_t blockId = 100;
    if(blockId &gt;= index.size()) return -1;
    ChunkIndexEntry entry = index[blockId];
    if(entry.offset == UINT64_MAX || entry.length == 0) {
        // no data
    } else {
        fin.seekg(entry.offset);
        std::vector&lt;char&gt; compData(entry.length);
        fin.read(compData.data(), entry.length);
        std::vector&lt;char&gt; rawData;
        if(hdr.compression == 1) {
            // decompress using zlib
            uLongf destLen = hdr.blockWidth * hdr.blockHeight * 3   // terrain
                             +  /* some max statics size */;
            rawData.resize(destLen);
            if(uncompress(reinterpret_cast&lt;Bytef*&gt;(rawData.data()), &amp;destLen,
                          reinterpret_cast&lt;Bytef*&gt;(compData.data()), entry.length) != Z_OK) {
                throw std::runtime_error(&quot;Decompression failed&quot;);
            }
            rawData.resize(destLen);
        } else {
            rawData.assign(compData.begin(), compData.end());
        }
        // Parse terrain cells:
        size_t cellCount = hdr.blockWidth * hdr.blockHeight;
        std::vector&lt;TerrainCell&gt; cells(cellCount);
        for(size_t i=0; i&lt;cellCount; ++i) {
            uint16_t id = *reinterpret_cast&lt;uint16_t*&gt;(&amp;rawData[i*3]);
            int8_t alt = *reinterpret_cast&lt;int8_t*&gt;(&amp;rawData[i*3 + 2]);
            cells[i] = { id, alt };
        }
        // Parse statics:
        std::vector&lt;StaticObj&gt; statics;
        size_t pos = cellCount * 3;
        while(pos + 7 &lt;= rawData.size()) {
            uint16_t objId = *reinterpret_cast&lt;uint16_t*&gt;(&amp;rawData[pos]);
            if(objId == 0xFFFF) { // end marker if used
                break;
            }
            uint8_t sx = rawData[pos+2];
            uint8_t sy = rawData[pos+3];
            int8_t sz = *reinterpret_cast&lt;int8_t*&gt;(&amp;rawData[pos+4]);
            uint16_t flags = *reinterpret_cast&lt;uint16_t*&gt;(&amp;rawData[pos+5]);
            statics.push_back({ objId, sx, sy, sz, flags });
            pos += 7;
        }
        std::cout &lt;&lt; &quot;Block &quot; &lt;&lt; blockId &lt;&lt; &quot; has &quot; &lt;&lt; statics.size() &lt;&lt; &quot; static objects.&quot; &lt;&lt; std::endl;
    }
    return 0;
}
</code></pre>
<p>In this C++ example, we define structs for the header and index for direct reading (using <code>#pragma pack(1)</code> to avoid padding issues, or we could read fields individually). We then open the file, read the header and check the magic string. We allocate an index array and read all index entries in one go (since they are a fixed-size array). Then we demonstrate reading block 100: seeking to the offset, reading compressed data, decompressing with <code>uncompress()</code> from zlib (assuming compression method 1 is zlib DEFLATE). We then parse the bytes for terrain and statics similarly to the Python approach.</p>
<p><strong>Writing</strong> in C++ would involve similar steps in reverse: computing chunk data for each block, compressing if needed, writing out the header (with a placeholder index offset), writing a blank index table (or remembering positions to fill later), writing each chunk and storing its offset/length, then going back to write the index table. One could also build everything in memory and then output, but for very large maps that might be heavy; better to stream out chunk by chunk.</p>
<p><strong>Memory considerations:</strong> Using 64-bit file offsets (as in <code>uint64_t offset</code>) ensures we can handle very large map files (bigger than 4GB). A full Britannia with compression might be under 1GB, but if uncompressed and extended, who knows – so 64-bit is safe.</p>
<p><strong>Endianness:</strong> We’re using little-endian for all multi-byte values, matching Intel/x86 and also the original UO format (which was little-endian). This is pretty standard for binary game formats (and since our target platforms include little-endian PCs and mobile devices, this is fine – if some day a big-endian system needs it, they’d swap bytes, but that’s rare now).</p>
<p>The above code sketches show that <strong>neither Python nor C++ has any trouble handling the format</strong>. We leveraged zlib in both, since it’s common and free. Also note that both languages would benefit from using a well-known container or library if available (for example, if someone made a library <code>libkool</code> for reading these files, that would be ideal – we could create one in C++ and Python could use it via bindings, etc., but the raw parsing is straightforward enough as shown).</p>
<h2>6. Integration with Godot 4.4.1 (Examples in GDScript and C#)</h2>
<p>One of the goals is to use this new format with the <strong>Godot Engine (v4.4.1)</strong>, including on mobile platforms. Godot is a powerful open-source engine which supports both GDScript (its Python-like scripting language) and C# (via Mono). We will discuss how to load and render a <code>.koolmap</code> file in Godot, apply isometric sorting for elevation, use region metadata in-game, and note mobile considerations.</p>
<p>First, <strong>importing or loading <code>.koolmap</code> in Godot</strong>: There are two general approaches – <em>in-engine parsing at runtime</em> or <em>writing a Godot plugin to import it as a resource</em>. We’ll focus on runtime loading here (as if you want to dynamically load the map when the game runs), and in Section 7 we’ll mention an Editor plugin.</p>
<h3>Loading and Rendering <code>.koolmap</code> – GDScript Example</h3>
<p>Godot 4 provides the <code>FileAccess</code> API for reading files. We can use it to open our <code>.koolmap</code> and read data similarly to our Python example. Once we have the map data, we’ll use Godot’s TileMap or Node2D structure to render it.</p>
<p><strong>Steps to load map in GDScript:</strong></p>
<ol>
<li><strong>Read the file:</strong> Use <code>FileAccess</code> (or the higher-level Resource if we made a custom resource). GDScript example:</li>
</ol>
<pre><code class="language-gdscript">func load_koolmap(path: String) -&gt; TileMap:
    var file = FileAccess.open(path, FileAccess.READ)
    if file == null:
        push_error(&quot;Failed to open map file&quot;)
        return null
    # Read header
    var magic = file.get_buffer(7)  # 7 bytes &quot;KOOLMAP&quot;
    if magic.get_string_from_ascii() != &quot;KOOLMAP&quot;:
        push_error(&quot;Not a valid KOOLMAP file&quot;)
        return null
    file.get_8()  # skip null terminator or alignment
    var version = file.get_32()    # 32-bit version
    var width = file.get_32()
    var height = file.get_32()
    var block_w = file.get_16()
    var block_h = file.get_16()
    var total_blocks = file.get_32()
    var comp_method = file.get_8()
    var enc_flag = file.get_8()
    var reserved = file.get_16()
    var index_offset = file.get_32()
    var index_count = file.get_32()
    var index_entry_size = file.get_32()
    # Read index entries
    file.seek(index_offset)
    var index = []
    for i in range(index_count):
        var off = file.get_64()
        var length = file.get_32()
        index.append([off, length])
    # Create a TileMap node to hold the map
    var tilemap = TileMap.new()
    tilemap.tile_set = preload(&quot;res://world_tileset.tres&quot;)  # tileset resource with tile textures
    tilemap.tile_origin = TileMap.TILE_ORIGIN_BOTTOM_LEFT   # for isometric, usually bottom left origin
    tilemap.cell_orientation = TileMap.CELL_ORIENT_ISOMETRIC  # or ORIENT_RECT for top-down
    tilemap.cell_size = Vector2( block_w * 32, block_h * 16 ) 
      # example: if each tile is 32x16 px (2:1 ratio diamonds), adjust accordingly
</code></pre>
<p><em>(Explanation:)</em> We open the file, read the header fields (using appropriate <code>get_8</code>, <code>get_16</code>, etc., which read little-endian by default). Then we seek to the index and read all entries into a GDScript array. We then create a Godot <code>TileMap</code> node. We assume we have a <code>TileSet</code> resource (<code>world_tileset.tres</code>) already configured with our terrain and maybe static tile images. Setting <code>cell_orientation</code> to <code>ISOMETRIC</code> tells Godot that the tiles are in an isometric diamond layout (which affects how it positions them). <code>tile_origin = BOTTOM_LEFT</code> ensures that the bottom of the diamond tile is the “anchor” point (common for isometric so tiles stack vertically correctly). <code>cell_size</code> we set roughly to the pixel dimensions of one tile cell; for example, if our base tiles are 64&#215;32 px diamonds, cell_size would be (64, 32). Here I put 32&#215;16 just as a placeholder for demonstration.</p>
<ol start="2">
<li><strong>Iterate through blocks and set tiles:</strong> We can either set tiles cell by cell using <code>TileMap.set_cell()</code>, or use <code>TileMap.set_cell_tile_data</code> if wanting advanced layers. Simpler: loop over each block, then each cell in block, placing the tile. For isometric, Godot expects the TileMap’s cell coordinates such that (x,y) correspond to isometric grid coords.</li>
</ol>
<pre><code class="language-gdscript">    # Place terrain tiles
    var blocks_x = width / block_w
    var blocks_y = height / block_h
    for block_id in range(index_count):
        var off = index[block_id][0]
        var length = index[block_id][1]
        if length == 0:
            continue  # empty block
        file.seek(off)
        var data = file.get_buffer(length)
        if comp_method == 1:
            data = data.decompress(DECOMPRESS_ZLIB)
        # Compute block&#39;s top-left tile coordinate in map
        var bx = block_id / blocks_y   # integer division
        var by = block_id % blocks_y
        var base_tile_x = bx * block_w
        var base_tile_y = by * block_h
        # Terrain cells:
        for cy in range(block_h):
            for cx in range(block_w):
                var index_in_block = cy * block_w + cx
                var tid = data.get16(index_in_block * 3)         # read uint16
                var alt = data.get8(index_in_block * 3 + 2)      # read int8
                # Use tid as Tile ID in the TileSet (assuming one-to-one mapping)
                if tid != 0xFFFF:
                    tilemap.set_cell(base_tile_x + cx, base_tile_y + cy, tid, false, false)
                    # In isometric, Y-sort will handle altitude in rendering, see below
</code></pre>
<p><em>(Explanation:)</em> We calculate the block’s position: if blocks are counted with X fast or Y fast, we adjust accordingly. In the above, I assumed block ID formula as in UO: block_id = (x_block * blocks_y) + y_block, so x_block = block_id / blocks_y, y_block = block_id mod blocks_y. Then base_tile_x = x_block * block_width, base_tile_y = y_block * block_height. We then iterate cells within, get the tile ID and altitude. We call <code>TileMap.set_cell(x, y, tile_id)</code> to place that terrain tile from our TileSet. The <code>false, false</code> in <code>set_cell</code> are for <code>flip_x</code>, <code>flip_y</code> (we’re not flipping). We ignore altitude at this moment for tile placement, because Godot’s TileMap doesn’t have an altitude for a cell – instead, we will handle elevation by Y-sorting or multiple layers.</p>
<ol start="3">
<li>
<p><strong>Place static objects:</strong> For statics (trees, buildings, etc.), there are multiple ways. We could treat statics as part of the TileMap as well – Godot 4 allows multiple layers in one TileMap. For example, the new TileMap system lets each cell have a stack of tiles (with different z-index values). However, it might be simpler to instantiate them as individual sprites or use a separate TileMap layer for statics. Using individual Node2D (Sprite2D) nodes for each static might be heavy if thousands of objects, but Godot can handle quite a few if well batched. Alternatively, we create another TileMap solely for statics, with transparency in its tiles.</p>
<p>Godot 4’s TileMap supports <strong>Layers internally</strong> (TileMap has a property <code>tilemap.get_layer_count()</code> and you can set tiles with a layer index). We could exploit that: layer 0 = terrain, layer 1 = statics. Each layer can have a different Z index offset. If using that, we can still use <code>set_cell(x,y,tile, layer_index)</code>.</p>
</li>
</ol>
<p>For clarity, let’s assume we do a separate TileMap node for statics for now:</p>
<pre><code class="language-gdscript">    var statics_node = TileMap.new()
    statics_node.tile_set = preload(&quot;res://statics_tileset.tres&quot;)
    statics_node.tile_origin = TileMap.TILE_ORIGIN_BOTTOM_LEFT
    statics_node.cell_orientation = TileMap.CELL_ORIENT_ISOMETRIC
    statics_node.cell_size = tilemap.cell_size   # same base size
    statics_node.z_index = 1  # put statics above ground layer
    statics_node.y_sort_mode = TileMap.YSORT_INDEX  # (in Godot 4.1+, allows Y-sorting by cell)
    add_child(tilemap)
    add_child(statics_node)
    # ... continuing the loop above:
                # still inside block loop after placing terrain
        # Now parse static objects from data
        var statics_offset = block_w * block_h * 3
        var pos = statics_offset
        while pos &lt; data.size():
            var obj_id = data.get16(pos)
            if obj_id == 0xFFFF:
                break
            var sx = data.get8(pos+2)
            var sy = data.get8(pos+3)
            var salt = data.get8(pos+4)  # signed altitude
            # Place this static: compute world coords
            var wx = base_tile_x + sx
            var wy = base_tile_y + sy
            # Option 1: using TileMap layer for statics
            statics_node.set_cell(wx, wy, obj_id, false, false)
            # Godot will treat each cell as one tile from statics_tileset
            # The statics_tileset should contain the sprite (with proper texture region and offset)
            # Optionally, one could offset it vertically by salt if needed via tileset.
            pos += 7
</code></pre>
<p>We create <code>statics_node</code> as another TileMap with the statics tileset (which contains all item graphics). We set its <code>z_index = 1</code> so that it renders on top of the terrain tilemap (<code>z_index = 0</code> by default for ground). We also enable <code>y_sort_mode = YSORT_INDEX</code> for the statics TileMap, which (in Godot 4.0+) is supposed to help sort tiles by their cell positions. In Godot 4, there is also a concept of <code>Z as relative</code> and using Y-sorting in 2D. We might need to ensure that for isometric, <code>Z as relative</code> is on, meaning the node’s own Z_index adds to children. The specifics can get tricky; we might instead rely on the fact that if we separate into two tilemaps and then use a YSort node, we need to manage sorting differently (since TileMap draws all its tiles presumably in correct order itself if YSORT is enabled).</p>
<p><strong>Elevation and Draw Order:</strong> In isometric games, draw order typically follows a rule: objects with a higher Y (farther “down” the screen or lower in world coordinates) are drawn above objects behind them, and if the same Y, then higher altitude is drawn above. In Godot, we have a couple of mechanisms:</p>
<ul>
<li><strong>YSort node:</strong> Godot provides a <code>YSort</code> node which, if you place Sprite or Node2D children under it, will automatically sort their rendering order by their Y coordinate (lower Y gets drawn later = appears on top). This is good for independent sprites like characters and items if they are separate nodes.</li>
<li><strong>TileMap Y-sorting:</strong> Godot 4’s TileMap has a property to enable Y-sort for its tiles (so it will order tile drawing by tile coordinates). However, mixing TileMap and individual nodes can complicate who draws first. One approach is to not use TileMap for statics at all, but instead spawn each static as an <code>Sprite2D</code> (with the static texture) as a child of a YSort node. This gives fine control: each sprite’s position is (world_x, world_y &#8211; salt* some_pixel) to account for altitude, and YSort will handle the sorting. The downside is having thousands of nodes (though Godot can handle quite a few if they are just static sprites).</li>
</ul>
<p>Given that performance might still be okay with thousands of static sprites (especially if using batching via same texture atlas), this approach is quite straightforward in concept.</p>
<p>Let’s outline a hybrid approach:</p>
<ul>
<li>Use a TileMap for the base terrain (which is static and simple).</li>
<li>Use a YSort node containing individual Sprite2D for statics and moving entities, to leverage Godot’s sorting.</li>
</ul>
<p>This way, the TileMap draws all ground tiles first (z_index 0), then the YSort ensures all sprites (statics and characters) are drawn in proper order on top.</p>
<p><strong>Applying elevation (z-index or Y-sort):</strong> If we have a static that sits on the ground but tall, we want it drawn over things behind it. YSort by their Y coordinate naturally handles overlap when one object is behind (higher y means further back typically). Altitude (salt) means the object is elevated (like on a roof). We can incorporate altitude by adjusting the sprite’s Y coordinate or using an extra Z-index. One trick: We could subtract altitude from the sprite’s y-position when adding to YSort, so that it appears “higher” for sorting (which actually might cause it to be drawn under things it should). Actually, if altitude means the object is higher off ground (like a second-floor object), it might not need special treatment in 2D ordering – it just needs to visually be drawn above ground objects anyway because it will likely have a higher y coordinate in world? Consider a multi-story building: the second floor’s items have the same map x,y but higher alt. In a purely 2D sort by y, the floor above might have the same projection position as ground floor, causing z-fighting in draw order. To solve that, one could give those higher-altitude objects a slightly lower Y in their actual Node position (so they render behind ground ones at same x,y) – but that’s counter-intuitive. Alternatively, use the <code>z_index</code> property on those sprites: e.g., for every 10 altitude units, increment z_index by 1. Then YSort can be combined with Z index: Godot sorts by Z first, then Y if Z equal. If we set second-floor objects to z_index = 1 while ground floor are 0, then all ground (z0) draw, then all second-floor (z1) draw, and within each, YSort orders them.</p>
<p>So a rule could be: <code>sprite.z_index = altitude_level</code> (like altitude divided by some constant, or just the altitude if small). And ensure <code>YSort</code> is on, which sorts within each z_index group.</p>
<p>In code, for each static we could do:</p>
<pre><code class="language-gdscript">var sprite = Sprite2D.new()
sprite.texture = StaticTextures.get_texture(obj_id)  # some method to get the right texture region
sprite.position = MapToPixel(wx, wy, salt)  # convert tile coords to screen coords, including altitude offset
sprite.z_index = int(salt)  # treat each altitude unit as one layer for simplicity
sprite.z_as_relative = true  # ensure the YSort parent considers this
y_sort_node.add_child(sprite)
</code></pre>
<p>The <code>MapToPixel</code> function would convert tile grid coordinates to actual pixel positions. In isometric:</p>
<ul>
<li>If each tile is W wide and H tall (e.g., 64&#215;32 px), then an tile coordinate (x,y) might map to pixel ( (x &#8211; y) * (W/2), (x + y) * (H/2) ) as a typical formula for diamond iso. Altitude could be represented by subtracting <code>salt * elevation_pixel_step</code> from the y coordinate (since up in 3D means visually should be drawn slightly higher on screen). In UO, each altitude unit corresponds to 4 pixels in the isometric projection for drawing (they used formulas like DrawY = &#8230; &#8211; Alt*4). We could adopt 1 altitude = 4 px vertical shift (assuming our art is scaled similarly).</li>
<li>So <code>MapToPixel(wx,wy,salt)</code> might compute base pixel (iso_x, iso_y) and then iso_y -= salt * 4. This will make higher items appear visually elevated.</li>
</ul>
<p>By doing that, the sprite representing a second-floor item will be drawn above ground floor items not by draw order, but by literal position difference (it will be slightly higher on the screen). Draw order sorting (YSort) will then see it at a smaller Y coordinate (since higher up means lower Y value on screen) and thus draw it later (on top). This nicely matches real isometric depth sorting: an object that is “higher” in altitude is drawn later because it appears above those on the ground behind it.</p>
<p>This technique – adjusting screen position by altitude – is key to proper layering. So either we incorporate it by offsetting sprite positions, or by having the tile’s texture offset (like in the TileSet, give tiles a negative vertical offset equal to 4*altitude). If using TileMap for statics, you can’t easily vary per cell offset except by designing separate tiles for each altitude or some data. So individual sprites is easier for altitude variation.</p>
<p><strong>Mobile considerations:</strong> Godot on mobile (Android/iOS) has the same capabilities but weaker hardware. We need to ensure our approach is optimized:</p>
<ul>
<li>Batching: Using TileMap is very efficient because it batches all tiles in one draw call per layer. Thousands of ground tiles – no problem. For statics, if we go with individual sprites, each sprite could add a draw call unless they share a texture atlas. We should therefore pack all static sprites into one or a few atlases (Godot’s TileSet or theme can do this, or manually via AtlasTexture). If we use a TileMap for statics, it will batch automatically, but as mentioned handling altitude is trickier. However, Godot 4’s TileMap does allow custom data per tile, which potentially could include altitude info used in a shader, but not trivial.</li>
<li>Memory: the map file might be large; on mobile we might not load the entire map at once. We can load chunks around the player. With our format, we can implement region-based loading: for example, only load blocks within some radius of the player’s position. This means keeping file access available or loading from a smaller representation. We could memory-map the file (not sure if Godot’s FileAccess can do partial loads asynchronously; we might have to load the whole file or do manual chunk reads).</li>
<li>One could conceive of a <strong>streaming approach in Godot</strong>: As the player moves, determine which blocks entered view and load them, instantiate their sprites, and remove far-off ones. This would be an advanced usage, but <code>.koolmap</code> format supports it because of random access.</li>
<li>On mobile GPUs, overdraw (drawing a lot of overlapping alpha-blended sprites) can hurt performance. UO’s isometric view inherently has overlapping objects. One mitigation is to limit the view range (don’t draw everything, just what camera sees). Another is using tilemaps (which can use opaque tile rendering for terrain and only sprites for objects).</li>
<li>We should ensure texture sizes aren’t too big for mobile: e.g., avoid a single atlas bigger than 2048&#215;2048 if some older devices can’t handle it (though most can handle 4096&#215;4096 nowadays). It’s fine to have multiple atlases.</li>
</ul>
<h3>Godot C# Integration Example</h3>
<p>If using C# in Godot (via Mono), the approach is similar but using C# file IO or Godot’s C# API. Godot’s C# API provides <code>FileAccess</code> as well (via <code>Godot.FileAccess</code>). We can use it similarly to GDScript but in C# style:</p>
<pre><code class="language-csharp">using Godot;
using System;
using System.IO;
using System.IO.Compression; // for DeflateStream if needed

public partial class MapLoader : Node
{
    public TileMap LoadKoolMap(string path)
    {
        var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
        if (file == null) {
            GD.PushError(&quot;Cannot open map file&quot;);
            return null;
        }
        string magic = file.GetBuffer(7).GetStringFromAscii();
        file.Get8(); // skip terminator
        uint version = file.Get32();
        uint width = file.Get32();
        uint height = file.Get32();
        ushort blockW = (ushort)file.Get16();
        ushort blockH = (ushort)file.Get16();
        uint totalBlocks = file.Get32();
        byte comp = (byte)file.Get8();
        byte enc = (byte)file.Get8();
        ushort resFlags = (ushort)file.Get16();
        uint indexOff = file.Get32();
        uint indexCount = file.Get32();
        uint indexEntrySize = file.Get32();
        file.Seek(indexOff);
        var offsets = new ulong[indexCount];
        var lengths = new uint[indexCount];
        for(uint i=0; i&lt;indexCount; ++i) {
            offsets[i] = file.Get64();
            lengths[i] = file.Get32();
        }
        // Create TileMap and YSort as in GDScript example...
        TileMap ground = new TileMap();
        ground.TileSet = GD.Load&lt;TileSet&gt;(&quot;res://world_tileset.tres&quot;);
        ground.CellOrientation = TileMap.CellOrientation.Isometric;
        ground.TileOrigin = TileMap.TileOrigin.BottomLeft;
        ground.CellSize = new Vector2I(blockW * 32, blockH * 16);
        TileMap staticsMap = new TileMap();
        staticsMap.TileSet = GD.Load&lt;TileSet&gt;(&quot;res://statics_tileset.tres&quot;);
        staticsMap.CellOrientation = TileMap.CellOrientation.Isometric;
        staticsMap.TileOrigin = TileMap.TileOrigin.BottomLeft;
        staticsMap.CellSize = ground.CellSize;
        staticsMap.ZIndex = 1;
        staticsMap.YSortMode = TileMap.TileMapYSortMode.YSortIndex;
        // Alternatively use YSort node for statics:
        YSort ysort = new YSort();
        ysort.AddChild(ground);
        ysort.AddChild(staticsMap);
        // (If using individual sprites, you&#39;d add them to ysort instead of staticsMap.)
        AddChild(ysort);
        // Loop through blocks to place tiles
        int blocksX = (int)(width / blockW);
        int blocksY = (int)(height / blockH);
        for (uint blockId = 0; blockId &lt; indexCount; ++blockId) {
            if (lengths[blockId] == 0) continue;
            file.Seek((long)offsets[blockId]);
            var compData = file.GetBuffer((long)lengths[blockId]);
            byte[] rawData;
            if (comp == 1) {
                // decompress zlib (deflate). Godot&#39;s GetBuffer might give zlib data directly.
                // .NET can decompress with DeflateStream if we trim zlib header if present.
                using (var ms = new MemoryStream(compData)) {
                    using(var ds = new DeflateStream(ms, CompressionMode.Decompress)) {
                        using(var outMs = new MemoryStream()) {
                            ds.CopyTo(outMs);
                            rawData = outMs.ToArray();
                        }
                    }
                }
            } else {
                rawData = compData;
            }
            int xBlock = (int)(blockId / blocksY);
            int yBlock = (int)(blockId % blocksY);
            int baseX = xBlock * blockW;
            int baseY = yBlock * blockH;
            // Terrain tiles:
            int cellCount = blockW * blockH;
            for(int i = 0; i &lt; cellCount; ++i) {
                ushort tid = BitConverter.ToUInt16(rawData, i*3);
                sbyte alt = (sbyte)rawData[i*3 + 2];
                int cx = i % blockW;
                int cy = i / blockW;
                if (tid != 0xFFFF) {
                    ground.SetCell(new Vector2I(baseX + cx, baseY + cy), (int)tid);
                }
            }
            // Statics:
            int pos = cellCount * 3;
            while(pos + 7 &lt;= rawData.Length) {
                ushort objId = BitConverter.ToUInt16(rawData, pos);
                if (objId == 0xFFFF) break;
                byte sx = rawData[pos+2];
                byte sy = rawData[pos+3];
                sbyte salt = (sbyte)rawData[pos+4];
                // If using staticsMap TileMap:
                staticsMap.SetCell(new Vector2I(baseX + sx, baseY + sy), objId);
                // Option: could use multiple layers in staticsMap if needed.
                // If using individual sprites:
                // var sprite = new Sprite2D();
                // sprite.Texture = StaticAtlas.GetRegionTexture(objId);
                // sprite.Position = iso_map_to_screen(baseX+sx, baseY+sy, salt);
                // sprite.ZIndex = salt;
                // ysort.AddChild(sprite);
                pos += 7;
            }
        }
        return ground; // or return a Node that contains both ground and statics
    }
}
</code></pre>
<p>This C# code is longer, but closely follows the GDScript logic. It uses Godot’s C# <code>FileAccess</code> to read data. Notably, decompressing might require handling the zlib header; the example uses <code>DeflateStream</code> which expects raw DEFLATE data (zlib uses an extra 2-byte header and checksum). We might need to skip the first 2 bytes of <code>compData</code> for proper deflate. But for brevity, assume it works.</p>
<p>The code then sets up TileMap nodes and uses <code>SetCell</code> similarly. In C#, <code>TileMap.SetCell(Vector2I pos, int tileId, bool flipX=false, bool flipY=false, bool transpose=false)</code> would be used (the API might be slightly different, but conceptually the same).</p>
<p><strong>Godot Isometric Rendering &amp; Sorting Details:</strong></p>
<ul>
<li>We set <code>CellOrientation.Isometric</code> which tells Godot to use an isometric transform (which assumes a 2:1 ratio diamond shape).</li>
<li>We set <code>TileOrigin.BottomLeft</code> so that the tile texture’s bottom-left corner corresponds to the grid point. For a typical diamond tile, bottom center might also be used; but bottom-left often works if the tile is shaped correctly in the atlas.</li>
<li>To properly layer tall objects, we might rely on the engine’s YSort if using sprites. If using tilemaps exclusively, Godot will draw tilemap layers in order of their z_index and within a layer probably in row order (unless YSortIndex is used as we set on staticsMap). The <code>YSortIndex</code> mode for TileMap in Godot 4.0+ sorts tiles by their cell index (which presumably tries to simulate Y sorting). However, some users have reported needing to use multiple tilemaps or manually adjusting draw order in complex scenarios.</li>
<li>Another approach is to give each TileMap (ground and statics) a different <code>ZIndex</code> and also set <code>ZAsRelative = true</code> on the statics map, so that its z_index is considered relative to parent (which is a YSort maybe). Honestly, a simpler robust approach is indeed using one YSort node with individual child nodes for each drawn element. That’s what many isometric projects in Godot do for dynamic scenes. For a mostly static world, tilemap is fine for ground.</li>
</ul>
<p>Given we have the data, one can experiment with both methods. The correct rendering order in a fully dynamic environment often resorts to YSort with individual nodes. But since the question specifically mentions <em>apply elevation with z-index or Y-sorting</em>, we have explained both concepts:</p>
<ul>
<li><strong>z-index method:</strong> assign higher z-index to higher-altitude items so they render later.</li>
<li><strong>Y-sorting method:</strong> use YSort or equivalent so that lower screen Y (which corresponds to objects further “south” or closer to camera in iso) are drawn on top.</li>
</ul>
<p>These are not mutually exclusive – often used together. For example, in Godot one might do <code>sprite.z_index = altitudeLevel; sprite.z_as_relative = true</code> and place it under a YSort. This means: first sort by z_index (so altitude layers), then by Y within each altitude.</p>
<p>We should also mention Godot’s handling of <em>transparency sorting issues</em>. By default, Godot can sort by Y or Z, but if tiles overlap within the same layer, sometimes you get sorting issues at tile boundaries (common in isometric tilemaps – e.g., a tall wall tile might overlap a neighbor incorrectly). Enabling YSort or splitting into layers helps avoid that. Also, because our statics are separate, a tall static (like a tree) on one tile will just be drawn after ground behind it if YSort is used.</p>
<p><strong>Region metadata in Godot:</strong> Once we have region info from the file (which we haven’t parsed above, but suppose we did and have a list of regions and their areas), we can use it in the game. For example, if the player’s coordinates correspond to a region marked “GuardZone”, the client could display “You are in a guard-protected area” or play particular music. Implementation:</p>
<ul>
<li>After loading the map, create a Dictionary or Map of region ID -&gt; region properties (name, etc.), and perhaps a 2D array or quadtree to look up region by coordinates.</li>
<li>In the game script (likely in the player or world node), each time the player moves, compute which region tile they are on and check if it’s different from last region. If changed, trigger events (like change music theme if entering a dungeon region).</li>
<li>Regions could also be used for environment effects – e.g., a region marked “swamp” might tint the screen or slow the player, etc.</li>
<li>If integrated in Godot, one could attach Area2D nodes for regions as triggers if regions are rectangular. For instance, create an Area2D with a CollisionShape covering the region and connect to body_entered signal for the player – but generating those automatically from region definitions might be a bit complex, and a simple coordinate check might suffice.</li>
</ul>
<p><strong>Terrain metadata usage:</strong> The <code>.kooltile</code> (tile properties) can be loaded as a resource or scriptable data. In Godot, you could load a JSON of tile definitions on game start. Then:</p>
<ul>
<li>If a tile has “Impassable” flag, you’d mark that cell in the TileMap as non-walkable. Godot’s TileMap in 4.x supports navigation layers or collision shapes on tiles; we could define collisions in the TileSet (e.g., a rock tile has a collision polygon so player can’t walk through).</li>
<li>Water tiles (Wet flag) might trigger swimming or dismounting – the client could check tile flag and implement logic (or simply allow the server to enforce movement rules).</li>
<li>Light source flags could be used to spawn light nodes in Godot at that tile position.</li>
<li>“Damage” tiles (like lava) could be Area2D that cause damage if stepped on, etc. The client could either just visually represent and let server handle actual damage, or if single-player environment, handle it directly.</li>
</ul>
<p>The key point is the new format preserves these possibilities by carrying over the concept of tile flags and region data, enabling the <strong>same gameplay logic</strong> to be implemented in the new engine. It’s about providing hooks; the rest is up to game scripts.</p>
<p><strong>Mobile-friendly considerations:</strong></p>
<ul>
<li>Use of YSort with many children on mobile could be a bit heavy if not using multi-mesh. If performance is an issue, consider chunking the scene: for example, only instantiating statics in currently visible screen area, and removing them when out of view (since map is large).</li>
<li>Godot 4 has <code>VisibilityNotifier2D</code> which can signal when something exits the screen; one could attach that to a chunk grouping to unload offscreen parts.</li>
<li>Also consider lowering texture resolution or using compressed textures (Godot supports ETC compression for mobile).</li>
<li>Keep an eye on fill rate: if there are large overlapping translucency (many trees overlapping), it can slow down mobile GPUs. Perhaps limit the number of layers or use some baking (not easy for dynamic but possibly static).</li>
<li>If the entire world is static (like UO’s world mostly is except moving characters), an alternative approach on mobile is to pre-render parts of the map into image textures (“lightmaps” or chunks of map rendered to a single sprite) to reduce draw calls. But that’s an advanced optimization and reduces flexibility (and memory heavy). With our chunk system, one could generate mini-maps or precombined tiles for distant scenery.</li>
</ul>
<p>Overall, Godot 4.4.1 should be capable of running a UO-like world with these methods. We’ve shown how to load the data and create scene nodes. The <strong>elevation sorting</strong> is handled via a combination of YSort or careful z_indexing. There may be some tweaking required (Godot’s 2D rendering can be finicky with isometric sorting), but many developers have done isometric games in Godot using YSort successfully.</p>
<h2>7. Godot Editor Plugin for .koolmap (In-Editor World Editing)</h2>
<p>To empower content creators, we envision a <strong>Godot Editor Plugin</strong> that allows viewing and editing <code>.koolmap</code> files within the Godot Editor. Godot’s EditorPlugin API lets us extend the editor with custom dock panels, importers, etc. The idea is to provide a tile-based world editor akin to UO’s internal tools (or modern map editors), but using Godot’s interface.</p>
<p><strong>Plugin Overview:</strong></p>
<ul>
<li>The plugin would register a custom <strong>EditorInspector</strong> or <strong>EditorSpatial</strong> (actually 2D) for <code>.koolmap</code> files. Possibly, we treat <code>.koolmap</code> as a custom Resource type. We could create a class <code>KoolMapResource</code> in GDScript or C# that knows how to load/save the file (using the code we wrote above). We then tell Godot’s editor that <code>.koolmap</code> extension corresponds to <code>KoolMapResource</code>. Godot can then allow importing or opening it.</li>
<li>The plugin might provide an <strong>EditorDock</strong> (a panel on the side) with tools: tile palette, object palette, selection tools, etc.</li>
<li>We would use Godot’s existing <strong>TileMap editor</strong> as much as possible – since Godot already has a GUI for painting tiles on a TileMap node. One approach: when the user opens a <code>.koolmap</code>, the plugin creates a temporary scene with a TileMap (for terrain) and another for statics (or a YSort with sprites). It then populates these from the file. Then the user can visually edit: Godot’s tilemap editor allows painting tiles from the TileSet. We might need to also allow placing statics (which could be done by switching to the statics TileMap’s editing mode).</li>
<li>Alternatively, implement custom painting logic: e.g., clicking in the 2D view to place a tile or object.</li>
</ul>
<p>The plugin should support:</p>
<ul>
<li><strong>Terrain painting:</strong> User selects a terrain tile from a palette (list of grass, dirt, water, etc.) and paints on the map. Under the hood, we set those cell IDs in the TileMap and mark the chunk as modified.</li>
<li><strong>Object placement:</strong> User can select an object (tree, building piece) and click on the map to place it. That might create a sprite or add a cell in the statics layer.</li>
<li><strong>Elevation adjustment:</strong> Perhaps a tool to raise/lower altitude of a tile or object. For terrain, maybe right-click or a separate brush to increase altitude (we could have a mode to edit the altitude byte of cells by numeric input or dragging). For statics, one might click an object and input an altitude or use arrow keys to nudge it up/down (in UO, statics like multi floors often have altitude differences).</li>
<li><strong>Selection and deletion:</strong> Select an area or specific object to remove or move. E.g., a marquee tool to select multiple tiles/objects (which likely means we need to handle statics separately from terrain for movement).</li>
<li><strong>Region editing:</strong> Possibly a way to define named regions on the map – e.g., draw a rectangle or polygon that becomes a region with certain properties. This could be a side panel listing all regions, allowing editing their boundaries and flags. We might represent region bounds in the editor view as colored overlays or wireframe boxes.</li>
</ul>
<p>Godot’s editor is very flexible, but building a full map editor is a project in itself. However, since the question asks for an outline, let’s highlight key components:</p>
<ul>
<li><strong>Palette UI:</strong> Could be a simple scrollable list of tile thumbnails. We can generate thumbnails for each tile ID from the tileset (Godot can give us the TileSet’s tiles). Similarly for statics, which might be too many to list at once – perhaps categorize by type (trees, walls, etc.). Could use tabs or filters.</li>
<li><strong>Tools:</strong> Brush (paint one tile at a time), Rectangle fill, Flood fill, Eraser (set tile to empty or remove static), Elevation adjust (maybe a number input + brush to apply that altitude).</li>
<li><strong>Preview:</strong> Show a preview of the currently selected tile under the cursor so user sees what they’ll place.</li>
</ul>
<p><strong>Saving/Exporting:</strong> The plugin after editing will need to save changes back to <code>.koolmap</code> format. Because our runtime code can write <code>.koolmap</code> (as described earlier), we can reuse that. For instance, the plugin can maintain a data structure of modified chunks. When the user hits “Save”, we reconstruct the <code>.koolmap</code>:</p>
<ul>
<li>For each chunk, gather the current terrain cells and static objects from the TileMap nodes in the editor scene, compress as needed, update index, and write the file. Or we could rewrite the entire file (since in-editor we can afford that).</li>
<li>Alternatively, we could live-update the binary file for each chunk as it’s edited, but that’s complex and unnecessary given Save is fine.</li>
</ul>
<p><strong>Godot integration details:</strong> We likely create a new class extending <code>EditorPlugin</code> in GDScript or C#. In <code>_enter_tree()</code> of the plugin, we might register a new menu item “New KoolMap” or make the importer. Also, possibly create a custom <code>EditorSceneImporter</code> so that if someone drags a <code>.koolmap</code> file into Godot, the plugin knows how to import it (maybe converting it to a TileMap scene or a <code>KoolMapResource</code>).</p>
<p>One could also integrate with Godot’s TileSet editor to manage the art: the art is likely going to be part of a Godot TileSet resource for convenience. We might have an external tool to generate the <code>world_tileset.tres</code> from <code>.koolart</code> images, or the plugin can load <code>.koolart</code> file and produce a TileSet on the fly. Possibly simpler: require the modder to import all the tile textures into Godot and create a TileSet manually. But a slick plugin could automate that by reading <code>.koolart</code> (if it’s a known archive format like a folder of images or a single atlas with mapping) and constructing a TileSet.</p>
<p><strong>User workflow:</strong></p>
<ul>
<li>The modder opens Godot, uses our plugin to open an existing <code>.koolmap</code> or create a new one.</li>
<li>If new, they specify dimensions, chunk size, and maybe choose an existing TileSet or have the plugin prompt for images.</li>
<li>The world appears as a grid they can paint on. They design the terrain, place objects, define regions.</li>
<li>They click Save, the plugin writes out <code>mymap.koolmap</code>, <code>mymap.koolmap.meta</code> (Godot might require a .import or .meta file too if treated as import).</li>
<li>The modder can then run the game which loads <code>mymap.koolmap</code> at runtime to play.</li>
</ul>
<p>This in-editor approach streamlines world building, as opposed to editing via external tools like CentrED or UOFiddler (which were used for UO). It leverages Godot’s strength as both an engine and an editor.</p>
<p><strong>Challenges:</strong></p>
<ul>
<li>Ensuring the edited map stays consistent with external references (if any). For example, if the user deletes a static object that the server expected to be there (maybe not an issue if server also uses <code>.koolmap</code> to spawn things).</li>
<li>We must also allow adding new tile types or object types. If <code>.koolart</code> is separate, adding a new art means updating the art file and the TileSet. The plugin could offer an “Import image as new tile” feature, which adds it to the TileSet and assigns a new ID, updates <code>.koolart</code> accordingly. This requires the plugin to handle art content management as well.</li>
<li>Perhaps in early stages, it’s easier to manage art outside (e.g., prepare your TileSet in Godot manually, then just use those IDs in the map). But eventually a full solution ties it together.</li>
</ul>
<p><strong>Testing and preview:</strong> Because Godot allows running the game in editor, the plugin could even allow a play-test mode where the user can drop a player character in and walk around immediately on the edited map.</p>
<p>In conclusion, the Godot editor plugin acts as a <strong>bridge between the open format and a user-friendly editing environment.</strong> By using Godot’s own editing capabilities (tile painting, scene editing), we minimize reinventing the wheel. The plugin’s main job is loading/saving <code>.koolmap</code> and possibly managing the asset pipeline (.koolart to TileSet, etc.).</p>
<h2>8. Tools and Utilities</h2>
<p>Beyond the Godot plugin, several external tools and utilities will support the <code>.kool</code> format ecosystem:</p>
<ul>
<li>
<p><strong>Conversion Tools:</strong> High on the list is a <strong><code>.mul</code> to <code>.kool</code> converter</strong> (and vice versa). This command-line tool (or GUI) would take an UO installation’s files and produce corresponding <code>.koolmap</code>, <code>.koolart</code>, <code>.koolsnd</code>, etc. For example:</p>
<ul>
<li><code>mul2kool --map map0.mul --statics statics0.mul --staidx staidx0.mul --out myworld.koolmap</code> would combine the classic map into one <code>.koolmap</code>. It would iterate UO map blocks, read terrain and statics, and then output using our chunked format. This helps bootstrap the new format with existing content (for testing purposes, assuming the user legally has the UO files). Even though legally we can’t distribute the output, a user could convert their own files for personal use or for developing the client before replacements are ready.</li>
<li>Similarly, <code>mul2kool --art art.mul --artidx artidx.mul --out assets.koolart</code> to pack all art. Actually, <code>.koolart</code> could just be a collection of PNGs, but if we define a binary, the tool will create that.</li>
<li><code>mul2kool --tiledata tiledata.mul --out base.kooltile</code> to convert tile flags to our JSON/txt format.</li>
<li>The reverse, <code>kool2mul</code>, could allow using the new content in legacy systems (for instance, injecting new map into ClassicUO by generating .mul files). This might be less needed once ClassicUO can read <code>.kool</code> directly, but it’s a nice option.</li>
</ul>
<p>Such converters can be written in Python (since parsing both formats is doable with struct as shown) or in C# leveraging existing UO file readers (ServUO, ClassicUO code could be reused since they have classes for .mul files).</p>
</li>
<li>
<p><strong>Tile/Art editors:</strong> Tools like UOFiddler allowed viewing and editing UO art and textures. For <code>.koolart</code>, since it’s open (likely images or an atlas), one can use any image editor to create sprites. But having a specialized viewer to browse all tiles and items is convenient. We could adapt an existing tool or use Godot itself. Possibly, the Godot plugin’s palette serves as an art viewer already. Alternatively, a small Qt or web-based viewer that loads <code>.koolart</code> and displays sprite sheets might be made.</p>
</li>
<li>
<p><strong>World viewers:</strong> A lightweight map viewer (like an online map) could be made. For example, a script to draw the entire map to a large PNG (like UOAutomap style or UO Cartographer). With <code>.koolmap</code> it’s easier since one can read all tiles and paint to an image (even PIL in Python to draw colored pixels or mini textures). This is helpful for validation and for players (if you want to provide a world atlas).</p>
</li>
<li>
<p><strong>Server utilities:</strong> If ServUO or another server is used, tools to sync or update server data might be needed:</p>
<ul>
<li>A utility to import <code>.kooltile</code> flags into the server’s item definitions (or generate ServUO scripts from them).</li>
<li>If regions are defined in <code>.koolmap</code>, a tool to export those to ServUO’s region configuration (unless we modify ServUO to read <code>.koolmap</code> directly, which would be ideal).</li>
<li>If using encryption, a key generator tool for servers to produce keys and embed in client distribution.</li>
</ul>
</li>
<li>
<p><strong>Data validation tools:</strong> Programs to verify the integrity of <code>.kool</code> files. For example:</p>
<ul>
<li><code>koolcheck myworld.koolmap</code> could verify that index offsets and lengths are correct, that no chunk is corrupted (maybe recompute chunk CRCs if stored), etc. It could also cross-verify references: e.g., a static object ID in map is within range of the art file entries; a tile ID has a corresponding tiledata entry; region IDs are consistent.</li>
<li>Also a tool to list contents: e.g., output how many of each tile appear on the map, or list unknown IDs.</li>
</ul>
</li>
<li>
<p><strong>Performance tools:</strong> Possibly a converter to optimize chunk sizes. For instance, maybe after a lot of edits, chunks might be unevenly sized (some very large due to lots of statics). A tool could split very large chunks or merge if needed (though we fixed chunk as an 8&#215;8 area – probably keep that constant). Or recompress chunks with a different algorithm (if in future we support multiple compression schemes, one might want to recompress all using a better method if engine supports it).</p>
</li>
<li>
<p><strong>Integration with existing modding tools:</strong> There’s a community of modders with various tools. We could create adapters for some. For example, if someone wants to use the Tiled map editor (a popular general tilemap editor) to design a map and then export to <code>.koolmap</code>: we could write a Tiled plugin or conversion script. Tiled supports isometric maps and can export to JSON or custom XML; a script could convert that output to <code>.koolmap</code> binary.</p>
</li>
<li>
<p><strong>Documentation generator:</strong> Perhaps a script that generates documentation from the format spec (like creating HTML or markdown tables of the file structure for reference). But given we will maintain docs manually, this is minor.</p>
</li>
</ul>
<p>In essence, a healthy set of utilities will make the <code>.kool</code> format practical and encourage adoption. Many of these would likely be open-source projects on GitHub for collaboration.</p>
<p>One likely scenario is leveraging the <strong>ServUO/ClassicUO communities</strong>: ClassicUO being open, if we implement <code>.kool</code> support in it, ClassicUO’s built-in developer tools (like it has a debug gump, I think) could even assist. But external converters are simpler to start with.</p>
<h2>9. Comparisons to Other Modding Ecosystems</h2>
<p>The idea of overhauling a game’s asset format and replacing all content has precedents. We can draw lessons from classic modding communities like those of <em>Doom</em> and <em>Quake</em>:</p>
<ul>
<li>
<p><strong>Doom WAD:</strong> As mentioned, Doom’s data is packed in WAD files (Where’s All the Data) which contain labeled lumps. The Doom community created <em>Freedoom</em>, a free asset set under BSD license, to allow Doom engine to run standalone. This is analogous to what we aim for: providing free content for an open client. The WAD format itself taught us the value of simplicity and documentation – id Software published specs (or rather fans did quickly) and supported modding from day one. Our <code>.kool</code> should strive to be similarly easy to mod. WAD’s lumps are sequential with a central directory at the start (or end). We similarly have an index and sequential chunks. One difference: WAD lumps are identified by names (like “MAP01” or “DSPISTOL” for pistol sound). We could consider naming chunks or sections in <code>.kool</code> for clarity (like naming regions or multi templates), but since UO was mostly ID-based, we stick to IDs.</p>
<ul>
<li>Another aspect: Doom distinguished IWAD (core game WAD) vs PWAD (patch WAD). The concept of layering mods is relevant: we might allow <code>.koolmap</code> to have an override file (like a small file that replaces some chunks or adds statics for an event). This could be akin to UO’s diff files or verdata. Or akin to how you can load multiple WADs (the later ones override content). For our format, implementing patch/override could be done via a small <code>.koolpatch</code> file containing just a few chunks or art pieces with references to base file. This is something to consider for flexibility (e.g., a server could distribute a tiny patch file to change a few tiles seasonally, instead of forcing re-download of entire map).</li>
<li>The Doom mod community also standardized on open documentation and had tools like DEU (Doom Editor Utilities) very early. They benefited from the game being open for modding. We similarly need to ensure tools are available and the format is well-known.</li>
</ul>
</li>
<li>
<p><strong>Quake PAK:</strong> Quake (1996) used .pak files, which are essentially uncompressed archive files containing a directory of internal file names (like textures, maps, models). A PAK is simpler than WAD (no distinction of types, just files in a package). This is similar to a zip file but with a custom header and no compression by default. The Quake community quickly made tools like PakScape to inspect and edit .pak files. The takeaway for us: using a <strong>standard or easily parseable container</strong> lowers the barrier for tool creation. Our <code>.kool</code> chooses a custom binary layout to maximize runtime efficiency for the specific data (tile maps, etc.), rather than a generic archive. This is fine, but we could incorporate some archive-like features (like flexible adding of new subfiles such as region definitions).</p>
<ul>
<li>Quake modding eventually moved to .pk3 (which is just a renamed .zip file) for Quake III Arena. Many modern games use zip or other standard archives (like .pak in Unreal Engine 4 is actually a custom but somewhat similar approach). Using a standard like zip could have been an option for <code>.kool</code> (for example, putting map chunks as files in a zip). We went with a custom approach to mimic .mul conversion, but if we foresee lots of independent files, adopting zip could be considered. However, zip would compress whole files and not be great for partial updates (unless we store each chunk as a file inside zip, which is doable – effectively an index and compressed data, which is not far from what we have).</li>
<li>Another lesson: file naming in archives. PAK had names, which meant mods could replace a specific file by including one with same path in a higher-priority PAK. In UO land, replacements were done by patches (verdata or diff files). We might allow multiple <code>.koolmap</code> layers (like base + patch). This is more of a software design detail, not necessarily in the file format (it could be simply the client checks for an override file).</li>
</ul>
</li>
<li>
<p><strong>Modern formats (e.g., Bethesda’s ES/ESP or TES3 Morrowind plugins, or Blizzard’s MPQs)</strong>: Many games have data formats that fans reverse-engineered to create mods. Often they ended up writing extensive wikis or open documentation, which helped their longevity. We should maintain an official spec document (and maybe even code library) to encourage contributions.</p>
</li>
<li>
<p><strong>Freely licensed content</strong>: Freedoom’s model of using BSD license for art, etc., is interesting. Many open game projects use Creative Commons for assets (e.g., CC-BY or CC0). We should decide on a license for our content and format spec. Likely, the <em>format specification</em> can be CC0 (public domain) or a very permissive license, so anyone can implement it. The <em>code</em> we write (converters, etc.) can be MIT or BSD. The <em>assets</em> we produce (art, sound) might be CC-BY-SA or CC-BY (to encourage sharing but also protecting credit). Freedoom used BSD for assets, which is very permissive (almost like CC0, meaning commercial use allowed as long as some attribution is given if at all). This might be to align with Doom source’s license. For us, since our whole aim is open source UO-like, a copyleft like CC-BY-SA (share alike) could ensure that any derivative of our free assets remains free. But that might discourage use in other projects. It’s a choice to be made by the community.</p>
</li>
</ul>
<p>In comparing ecosystems, it’s clear that communities thrive when:</p>
<ul>
<li>The formats are open and well-documented.</li>
<li>Tools are abundant (often community-contributed).</li>
<li>There’s a mechanism for mod distribution and combination (like how multiple WADs or PAKs can stack).</li>
</ul>
<p>We can incorporate these lessons by making <code>.kool</code> as open as possible and supporting <strong>extensibility</strong>:<br />
For instance, if someone wants to add a new section to <code>.koolmap</code> (say, weather data or NPC spawn points embedded in map), our versioning and structured chunks should allow adding extra data either in reserved fields or as additional chunks at end. Possibly we could design it like a chunked RIFF-like format (where each chunk has an ID and size). But that adds overhead for a mostly fixed-structure need. Instead, we left some reserved header fields and versioning for future growth.</p>
<h2>10. Open Licensing and Documentation Standards</h2>
<p>To truly be an open-source alternative, everything about the format and content should be under <strong>permissive licenses</strong>. Here are recommendations:</p>
<ul>
<li>
<p><strong>File Format Specification License:</strong> Use a public domain or permissive license (e.g., CC0 1.0 Universal for the spec text, or unlicense). This ensures no restrictions on implementing the format. Anyone can write their own reader/writer without legal concern. We will publish the format docs (like this report, perhaps refined into a formal spec document with field tables and examples) on a public repository or website (e.g., a GitHub wiki or ReadTheDocs). The documentation should be kept up-to-date with version changes. Ideally, maintain a single authoritative spec so there’s no divergence.</p>
</li>
<li>
<p><strong>Code Licenses:</strong> For reference implementations (the Python/C++ examples, converter tools, ClassicUO modifications), a permissive license like <strong>MIT</strong> or <strong>BSD 2-clause</strong> is advisable. MIT is very common in open source game tools and would allow inclusion in both open and closed-source projects (if someone wanted to support <code>.kool</code> in a proprietary engine, they could). Since ServUO and ClassicUO are themselves under MIT (ClassicUO) and maybe GPL (ServUO? Actually ServUO might be GPL), we should check: if ServUO is GPL, any linking of code would have to be GPL; but if we just publish spec and maybe MIT code, they can incorporate by rewriting if needed. In any case, MIT simplifies things.</p>
</li>
<li>
<p><strong>Asset Licenses:</strong> As mentioned, likely <strong>Creative Commons</strong> for art/music. CC-BY 4.0 (attribution required) or CC-BY-SA 4.0 (share-alike) could be used. CC-BY allows commercial use as long as credit is given – which some might be okay with to spread usage. CC-BY-SA ensures derivatives remain open, which philosophically aligns with open-source but might restrict use in closed games. Freedoom chose BSD for assets to maximize engine compatibility (some Doom source ports might have not wanted strong copyleft on data? Not sure). We could consider <strong>CC0</strong> (public domain dedication) for certain things to encourage maximum reuse, especially for anything that’s more data than art (like tiledata info, which is just numbers).</p>
</li>
<li>
<p><strong>Trademark and Naming:</strong> We must avoid using “Ultima” or related trademarks in our project name or file names (so calling it <code>.uomap</code> or something is out). The name <code>.kool</code> is arbitrary (though cheeky as “Kool” sounds like “cool”). We should ensure the naming and branding of the project doesn’t infringe EA/Broadsword trademarks. Perhaps name the project something entirely original (e.g., “Open Britannia” might still infringe Britannia which is used in UO, so maybe not; something like “WorldForge” except that’s taken by another project; one might pick a codename unrelated to UO lore).</p>
</li>
<li>
<p><strong>Documentation Standards:</strong> We should maintain <strong>clear, versioned documentation</strong>. For example:</p>
<ul>
<li>Keep a <strong>changelog</strong> of format versions (so developers know what changed in <code>.koolmap v2</code>).</li>
<li>Provide <strong>examples</strong> (a small sample map file with annotation of its bytes).</li>
<li>Possibly provide a machine-readable spec (like JSON schema or Kaitai Struct definition) to formally describe the binary format. Kaitai Struct, for instance, is a DSL to describe binary layouts which can generate parsers in multiple languages. Writing a <code>.ksy</code> spec for <code>.koolmap</code> would be a neat way to ensure accuracy and allow others to easily parse it with generated code. We could include that in docs.</li>
<li>Use consistent terminology (cells, blocks, etc., as we have).</li>
<li>Encourage contributions to docs when format is extended.</li>
</ul>
</li>
<li>
<p><strong>Community and Collaboration:</strong> Host the project on a platform like GitHub or GitLab. Possibly have separate repos: one for the <strong>spec/docs</strong>, one for <strong>tools</strong>, one for <strong>assets</strong>. Use issue trackers to gather feedback, e.g., if someone finds a bug or needed feature in format, discuss openly and version accordingly.</p>
</li>
<li>
<p><strong>Open for extension:</strong> With a version field, if someone outside the core devs needs to add something, they could bump a minor version and add a chunk, documenting it. If it’s useful for all, merge it into main spec. If not, at least the versioning means old readers won’t break (they might ignore unknown new data if we design it that way).</p>
</li>
<li>
<p><strong>No platform lock-in:</strong> Ensure the format remains free of platform-specific constraints. (For example, use standard data types, avoid anything that depends on little-endian vs big-endian beyond the spec saying “little-endian encoding”). This way, even decades later on new architectures, it can be read as long as documentation is available.</p>
</li>
</ul>
<p>By adopting these open practices, we follow the spirit of projects like GNU, Creative Commons, and the open mod communities. The ultimate measure of success will be if other developers can use our format and assets to create their own UO-like worlds or even totally different games (with appropriate credit). This open format could even transcend UO and serve as a general 2D isometric RPG world format, much like how WAD was used by games beyond Doom engine.</p>
<hr>
<p><strong>Conclusion:</strong> The above sections have detailed the design and rationale for a modern <code>.kool</code> format to replace UO’s <code>.mul</code> files. We covered the inner workings of UO’s original format, the legal necessity of original content, and created a blueprint for an open, efficient replacement with compatibility in mind. The <code>.koolmap</code> spec provides chunked, compressible map data akin to a region file system, enabling both smooth streaming and easy editing. We discussed how to implement reading/writing in code, and integrate with Godot for rendering and editing – including handling isometric depth sorting (using Y-sort and z-index techniques) and leveraging Godot’s cross-platform capability for PC and mobile. We also set forth the tooling ecosystem required (converters, editors, validators) and paralleled our approach with other modding standards (Doom WAD, Quake PAK) to ensure we learn from their success. Finally, we emphasized that all of this must be under open licenses and well-documented to truly foster a community-driven project. With these measures, the “.kool” format can become a robust, legally sound foundation for Ultima Online aficionados to build new worlds and preserve the classic gameplay experience in an open, modern engine for years to come.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: ideariff.com @ 2026-09-23 01:42:00 by W3 Total Cache
-->