Overview
When I presented Kroz at the AWS Summit 2024 in Milan, it was a fascinating but simple prototype: a text adventure dynamically generated by AWS Bedrock, with procedural rooms and basic game logic. The demo worked, people were intrigued, but I knew it was just the beginning. The game had potential, but it lacked depth, coherence, and the kind of carefully crafted experience that makes players want to keep exploring.

Fast forward several months, and Kroz has evolved into something completely different. It’s now a complete adventure spanning 8 chapters, featuring a sophisticated combat system, complex puzzle mechanics, and a coherent storyline that takes players from an abandoned manor through dark forests and ancient ruins, all the way to Montezuma’s legendary treasure chamber.
This article chronicles the technical evolution of the game: how I transitioned from on-the-fly Bedrock-generated content to a structured JSON database, how I implemented intricate combat and status effect systems, and how every single modification was manually designed and then implemented with the help of Kiro Pro, AWS’s CLI assistant.
๐ฎ Phase 1: The Bedrock Prototype
The Original Architecture
In its first incarnation, Kroz was essentially an intelligent wrapper around AWS Bedrock. The architecture was straightforward but effectiveโa serverless pipeline that transformed player commands into AI-generated narrative responses.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AWS Cloud โ
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ CloudFront โโโโโถโ S3 โ โ API Gateway โ โ
โ โ + Static โ โ Frontend โโโโโโ โ โ
โ โ Assets โ โ โ โ โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโฌโโโโโโโโ โ
โ โ โ
โ โโโโโโโโผโโโโโโโโ โ
โ โ Lambda โ โ
โ โ Game Handler โ โ
โ โโโโโโโโฌโโโโโโโโ โ
โ โ โ
โ โโโโโโโโผโโโโโโโโ โ
โ โ Bedrock โ โ
โ โ Titan/Claude โ โ
โ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Every action the player took triggered a chain of events: the command would be sent to Lambda, packaged into a carefully crafted prompt, dispatched to Bedrock, and returned as a JSON structure describing the next room, its contents, and available exits.
The beauty of this approach was its simplicity. There was no database to maintain, no content to curate, no complex state management. The AI handled everything, generating unique experiences on the fly. But as I would soon discover, this simplicity came with significant trade-offs.
The Master Prompt
The heart of the system was a sophisticated prompt that instructed Bedrock on how to generate coherent content. It wasn’t just about asking for a room descriptionโit was about maintaining context, ensuring consistency, and creating a sense of progression.
const prompt = `Generate a JSON structure for a text adventure game room.
Context: ${previousRooms}
Player action: ${command}
Return JSON with:
{
"title": "Room name",
"description": "Detailed description",
"items": ["item1", "item2"],
"exits": {"north": true, "south": false}
}`;
This approach had clear advantages. The content was theoretically infiniteโevery playthrough could be unique. There was no database to maintain, no content pipeline to manage. The AI adapted to player actions in real-time, creating a sense of dynamic storytelling that felt almost magical when it worked well.
But the limitations became apparent quickly:
- Narrative coherence was difficult to maintain across multiple rooms. The AI might generate a dark forest in one room and a futuristic spaceship in the next, with no logical connection between them.
- Each API call to Bedrock cost money and introduced latencyโplayers had to wait 2-3 seconds for each action to resolve.
- Complex puzzles were impossible to design. The AI simply couldn’t maintain that level of state consistency.
๐ Phase 2: The Turning Point
Why Change the Approach
After the Summit, I spent time reflecting on what makes text adventures truly engaging. The classics of the genre weren’t procedurally generatedโthey were meticulously crafted experiences where every room, every object, every puzzle was deliberately placed to serve the overall narrative.
I realized that Bedrock’s strengthโits ability to generate endless variationsโwas actually working against the core experience I wanted to create. What players needed wasn’t infinite content; they needed meaningful content. They needed puzzles that made sense, narratives that built to satisfying conclusions, and a world that felt coherent and intentional.
The decision crystallized: use Bedrock as a development tool to generate initial content drafts, but then curate every element manually. Build a structured JSON database that could be loaded instantly, with no API calls, no latency, and complete control over every aspect of the experience.
The New Architecture
The new architecture represented a complete philosophical shift. Instead of generating content on-demand, everything would be pre-defined in a comprehensive JSON database. The frontend would load this database once at startup, and all game logic would run entirely client-side.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Frontend (Vue.js) โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ GameView.vue โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Game State Management โ โ โ
โ โ โ - Current Chapter/Room โ โ โ
โ โ โ - Player HP/Status โ โ โ
โ โ โ - Inventory โ โ โ
โ โ โ - Combat State โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Game Logic (logic.js) โ โ
โ โ - Command Processing โ โ
โ โ - Combat System โ โ
โ โ - Status Effects โ โ
โ โ - Movement & Locks โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Utilities (utils.js) โ โ
โ โ - JSON Database Loader โ โ
โ โ - Save/Load (localStorage) โ โ
โ โ - Text Rendering (Typed.js) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Game Database (game.json) โ โ
โ โ - 8 Chapters โ โ
โ โ - ~60 Rooms โ โ
โ โ - 20+ Enemies โ โ
โ โ - 30+ Items โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
No cloud calls during gameplay, no latency, no costs per actionโjust pure, instant interaction. This architecture meant that once the page loaded, the entire game ran locally in the browser. The game could even work offline.
๐ฏ The Evolution of Game Systems
1. Movement System: From Simple to Complex
The original Bedrock version had a rudimentary movement systemโjust four cardinal directions with boolean flags. Real adventure games have vertical movement, diagonal directions, and most importantly, meaningful obstacles that require specific items or actions to overcome.
Before (Bedrock):
// Basic movement: 4 directions
const moves = {
north: true,
south: false,
east: true,
west: false
};
After (Structured):
// Complex movement with room IDs and multiple directions
"moves": {
"north": "2",
"up": "3",
"northeast": "5",
"down": "secret_passage"
},
"locks": {
"north": {
"type": "key",
"required_items": ["brass_key"],
"error": "The door is locked. You need a brass-colored key."
},
"down": {
"type": "mechanism",
"required_items": ["silver_lever"],
"error": "There's a mechanism here but it's missing a lever."
}
}
The new system supports ten directions (including vertical and diagonal movement), multiple lock types (keys, mechanisms, combinations), and a sophisticated item identification system where each key has unique visual characteristicsโshape, color, materialโthat match specific locks.
2. Combat System: From Nothing to Complete
The original prototype had no combat at all. It was purely exploration-based. But as the game evolved, it became clear that combat would add crucial tension and variety to the experience.
I drew inspiration from tabletop role-playing games, where combat is turn-based and strategic. Each enemy needed distinct characteristicsโnot just hit points and damage, but attack patterns, special abilities, and varying difficulty levels.
// Enemy structure
{
"id": "ancient_guardian",
"name": "Ancient Guardian",
"hp": 120,
"damage": 25,
"attacks": ["crushing_blow", "stone_fist"],
"status_effects": {
"stunned": {
"chance": 0.3,
"duration": 2,
"description": "You are stunned!"
}
},
"flee_chance": 0.0 // Boss: cannot flee
}
Combat Flow:
โโโโโโโโโโโโโโโโโโโ
โ Enemy Appears โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ Player Turn โ
โ attack/defend/ โ
โ flee โ
โโโฌโโโโโโฌโโโโโฌโโโโโ
โ โ โ
โโโโโโโโโโโโโโ โ โโโโโโโโโโโโ
โ โ โ
โโโโโโผโโโโโโ โโโโโโโโโผโโโโโโโ โโโโโโผโโโโโโ
โ Attack โ โ Defend โ โ Flee โ
โ Calculateโ โ Reduce Dmg โ โ Check โ
โ Damage โ โ 50% โ โ โ
โโโโโโฌโโโโโโ โโโโโโโโโฌโโโโโโโ โโโฌโโโโโฌโโโโ
โ โ โ โ
โ enemy HP = 0 โ โ โ fail
โโโโโโโโโโโโ โ โ โ
โ โ โ โ โ
โโโโโโผโโโโโโ โ โโโโโโผโโโโโโโโโ โ โ
โ Victory โ โ โ Enemy Turn โโโโโ โ
โ โ โ โ Attack โ โ
โโโโโโโโโโโโ โ โ Player โ โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ โ
โ โโโโโโผโโโโโโโโโ โ
โ โCheck Status โ โ
โ โPoison/Para โ โ
โ โโโโฌโโโโโโโฌโโโโ โ
โ โ โ โ
โ HP=0 โ โ continue โ
โ โ โ โ
โโโโโโผโโโโ โ โโโโโผโโโโโโโ โ
โVictory โ โ โContinue โ โ
โโโโโโโโโโ โ โPlaying โ โ
โ โโโโโโโโโโโโ โ
โโโโโโผโโโโโโ โ
โGame Over โ โ
โโโโโโโโโโโโ โ
โ
โโโโโโผโโโโโโ
โ Escaped โ
โโโโโโโโโโโโ
Every mechanic was first designed on paper, then implemented with Kiro Pro’s assistance. The workflow was iterative: I would describe the desired behavior in natural language, Kiro would generate the code, and we’d refine it together until it worked perfectly.
// Manually designed, implemented with Kiro Pro
export function handleAttack(data, weapon) {
const enemy = data.actualRoom.enemies[0];
if (!enemy) return;
const weaponData = utils.getItemData(data.chapter, weapon);
if (!weaponData || weaponData.type !== 'weapon') {
utils.updateTypedBox(data.errorBox, [
utils.prefix("That's not a valid weapon!")
]);
return;
}
// Damage calculation: base weapon damage + random variance
const baseDamage = weaponData.damage;
const variance = Math.floor(Math.random() * 5) - 2;
const totalDamage = Math.max(1, baseDamage + variance);
enemy.hp -= totalDamage;
utils.updateTypedBox(data.infoBox, [
utils.prefix(`You attack with ${weapon} for ${totalDamage} damage!`)
]);
if (enemy.hp <= 0) {
handleVictory(data, enemy);
} else {
enemyTurn(data, enemy);
}
}
The damage calculation includes a random variance to keep combat unpredictable. A weapon with 15 base damage might deal anywhere from 13 to 17 damage on any given hit. This small randomness adds tensionโyou can’t always predict whether you’ll defeat an enemy before they get another attack in.
3. HP and Status Effects System
Health management adds another layer of strategy to the game. Players start with 100 HP and must carefully manage this resource throughout their adventure. Healing items are scattered throughout the world, but they’re limitedโyou can’t just spam potions to survive every encounter.
Status effects make combat more interesting by introducing conditions that persist beyond individual turns:
- Poison deals damage over time, forcing players to find antidotes or risk slowly dying
- Paralysis randomly prevents movement, creating tense moments where you might be stuck in place while an enemy attacks
- Stun causes you to stumble in random directions, potentially leading you into more danger
export function processStatusEffects(data) {
if (data.player.status !== 'normal' && data.player.status_duration > 0) {
data.player.status_duration--;
switch(data.player.status) {
case 'poisoned':
const damage = 3;
data.player.hp = Math.max(0, data.player.hp - damage);
utils.updateTypedBox(data.infoBox, [
utils.prefix(`You take ${damage} poison damage! HP: ${data.player.hp}/${data.player.max_hp}`)
]);
break;
case 'paralyzed':
if (Math.random() < 0.33) {
utils.updateTypedBox(data.errorBox, [
utils.prefix("You are paralyzed and can't move!")
]);
data.actionAllowed = false;
return;
}
break;
case 'stunned':
if (Math.random() < 0.5) {
const randomDir = ['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)];
data.command = randomDir;
utils.updateTypedBox(data.infoBox, [
utils.prefix("You stumble in a random direction!")
]);
}
break;
}
if (data.player.status_duration === 0) {
data.player.status = 'normal';
utils.updateTypedBox(data.infoBox, [
utils.prefix("Status effect wore off.")
]);
}
}
}
4. Searchable Objects System
One of the most appreciated additions was the searchable objects system. Instead of having all items immediately visible in a room, some are hidden inside containers or behind objects that players must explicitly examine.
A room might mention a desk in the corner or a wardrobe against the wall. These aren’t just flavor textโthey’re interactive elements that can be searched for hidden items. This creates a more immersive experience where players feel like they’re actively investigating their environment.
// Room definition with searchable objects
"searchable": {
"table": {
"description": "An old wooden table with a drawer.",
"items": ["bandage", "old_map_fragment"]
},
"wardrobe": {
"description": "An old wardrobe with clothes.",
"items": ["healing_potion"]
}
}
๐ The Storyline: 8 Coherent Chapters
One of the biggest challenges was creating a coherent narrative across 8 chapters. Each chapter needed its own identity while contributing to the overall arc. The solution was to design each chapter around a distinct environmental theme and gradually increase the difficulty.
The narrative follows a treasure hunter’s quest for Montezuma’s legendary treasure. Each chapter represents a stage in this journey, with the player collecting map fragments that gradually reveal the treasure’s location.
Chapter Structure:
Chapter 1 Chapter 2 Chapter 3 Chapter 4
Abandoned โโโโถ Dark โโโโถ Mountain โโโโถ Ancient
Manor Forest Pass Ruins
โ
โผ
Chapter 8 Chapter 7 Chapter 6 Chapter 5
Treasure โโโโ Ancient โโโโ Cursed โโโโ The
Chamber Crypt Cemetery Pyramid
Each chapter was designed with:
- Environmental Theme: Distinct atmosphere and setting that feels different from previous chapters
- Difficulty Progression: Stronger enemies, more complex puzzles, higher stakes
- Narrative Arc: Story elements that connect to the overall quest while providing chapter-specific intrigue
๐บ๏ธ Visualization: Maps and Documentation
For each chapter, I generated visual maps using Graphviz. This proved essential for several reasons. First, it helped me design the room layouts before implementing themโI could see at a glance whether the spatial relationships made sense. Second, it served as documentation, making it easy to verify that all connections were correct.
The DOT format is particularly well-suited for this because it’s text-based and version-controllable, yet can be rendered into professional-looking diagrams. Each room becomes a node, each connection an edge, and special attributes (locked doors, enemies, items) can be annotated directly in the graph.
Example DOT Graph:
digraph ExampleChapter {
rankdir=TB;
node [shape=box, style="rounded,filled", fillcolor=lightblue];
edge [fontsize=10];
// Rooms
entrance [label="Entrance Hall\n[torch]"];
corridor [label="Long Corridor\n[key]"];
chamber [label="Main Chamber\n[sword]\nEnemy: Guard"];
treasury [label="Treasury\n[END]", fillcolor=lightgreen];
// Connections
entrance -> corridor [label="north\n(no lock)"];
corridor -> chamber [label="east\n(key required)"];
chamber -> treasury [label="south\n(defeat guard)"];
// Legend
legend [shape=note, label="Example Chapter\n4 rooms | 1 enemy | 1 lock"];
}
This graph is then converted to PNG using:
dot -Tpng example.dot -o example.png
The resulting images serve as both design documents and player guides, showing the complete structure of each chapter at a glance.
Simplified ASCII Representation:
โโโโโโโโโโโโโโโโโโโ
โ Dusty Foyer โ
โ [bandage table] โ
โโโโโโฌโโโโโโโฌโโโโโโ
โ โ
north โ โ up
(brass_key) โ โ
โ โ
โโโโโโผโโโโโโโผโโโโโโ
โ Dark Hallway โ Upper Landing
โ [rusty_dagger] โ [brass_key]
โโโโโโฌโโโโโโโโโโโโโ
โ
east โ
โ
โโโโโโผโโโโโโโโโโโโโ
โ Library โ
โ [chocolate_bar] โ
โ Enemy: Rat โ
โโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโ
โ Master Bedroom โ
โ [silver_lever] โ
โโโโโโฌโโโโโโโโโโโโโ
โ
down โ (silver_lever)
โ
โโโโโโผโโโโโโโโโโโโโ
โ Secret Passage โ
โโโโโโฌโโโโโโโโโโโโโ
โ
down โ
โ
โโโโโโผโโโโโโโโโโโโโ
โ Garden โ
โ [END] โ
โโโโโโโโโโโโโโโโโโโ
The DOT format is particularly well-suited for this because it’s text-based and version-controllable, yet can be rendered into professional-looking diagrams.
๐ค The Role of Kiro Pro
Every feature was manually designed, but the implementation was dramatically accelerated by Kiro Pro. The typical workflow looked like this:
- Design on paper: Sketch out the system (e.g., combat mechanics)
- Pseudocode: Write the logic in pseudocode or detailed natural language
- Kiro Pro: Ask Kiro to implement the JavaScript code
- Testing: Verify the functionality works as intended
- Iteration: Refine with Kiro until it’s perfect
This hybrid approach proved incredibly effective. I maintained complete creative control over what the game should do, while Kiro handled the tedious work of translating those ideas into working code.
Example interaction:
> Kiro, implement a function that handles status effects. The player can be
> poisoned (3 dmg/turn for 4-6 turns), paralyzed (33% chance of being unable
> to move for 2-3 turns), or stunned (50% chance of moving in a random
> direction for 2 turns). Duration should decrement each turn and when it
> reaches 0, status returns to normal.
Kiro would generate the code, I’d test it, and we’d iterate until it was perfect. This reduced development time from weeks to days, allowing me to focus on game design rather than syntax and debugging.
๐พ Save System and Persistence
The save/load system uses browser localStorage to maintain game state. This was a deliberate choiceโit keeps the game entirely client-side with no server dependencies, while still allowing players to save their progress and return later.
export function saveGame(state) {
try {
localStorage.setItem('kroz_save', JSON.stringify({
version: '2.0',
timestamp: Date.now(),
chapterNum: state.chapterNum,
roomId: state.roomId,
inventory: state.inventory,
player: state.player,
moveHistory: state.moveHistory
}));
return true;
} catch (e) {
console.error('Save failed:', e);
return false;
}
}
๐ Performance and Optimizations
The transition from Bedrock to local JSON brought dramatic improvements across every metric that matters:
| Metric | Bedrock (Before) | JSON (After) |
|---|---|---|
| Command Latency | 2-3 seconds | <50ms |
| Cost per Playthrough | $0.10-0.50 | $0.00 |
| Narrative Coherence | Variable | 100% |
| Design Control | Limited | Total |
| Offline Play | No | Yes |
The performance improvement alone would have justified the change, but the real win was in design control and narrative coherence.
๐ฎ What We Built
Looking back at what started as a simple Bedrock prototype, the transformation is remarkable. The game now spans 8 complete chapters, each one a self-contained adventure that contributes to the larger narrative. We’re talking about roughly 60 meticulously designed roomsโnot procedurally generated, but hand-crafted with specific purposes and connections.
The combat system alone represents a massive leap forward. There are over 20 unique enemy types, each with their own attack patterns and behaviors. Weapons range from a rusty 8-damage dagger you find in the first chapter to a legendary 35-damage sword hidden in the final chamber. Four boss encounters punctuate the journey, each requiring different strategies and offering no option to fleeโyou either defeat them or reload your save.
The item system grew organically as the game evolved. Beyond the 12 weapons, there are healing items, keys with unique visual characteristics (brass, crystal, scarab-shaped), cure items for status effects, and searchable objects hidden throughout the world. Speaking of which, the searchable objects systemโtables, chests, wardrobes, altarsโadds about 15 different interactive elements that reward thorough exploration.
Movement evolved from simple four-direction navigation to a full ten-direction system including vertical movement (up/down) and diagonals. This opened up entirely new puzzle possibilitiesโsecret passages accessed by climbing, hidden chambers reached by descending through wells, treetop platforms that provide different perspectives on the world below.
The status effect system adds tactical depth with three distinct conditions: poison that slowly drains your health, paralysis that randomly prevents movement, and stun that causes you to stumble in random directions. Each one creates different challenges and requires different solutions.
All of this is packaged in a structured JSON database of 1005 linesโevery room, every enemy, every item, every connection documented and version-controlled. It’s the difference between a tech demo and a complete game, between procedural chaos and intentional design.
๐ฏ Conclusions
The evolution of Kroz demonstrates that generative AI like Bedrock is fantastic for rapid prototyping and exploring ideas, but creating a quality game experience requires intentional design and manual curation of details.
The hybrid workflow I adoptedโhuman design combined with AI-assisted implementation through Kiro Proโproved to be the best of both worlds:
- Human creativity drives the narrative and game design decisions
- AI speed handles the code implementation
- Complete control over the final product
Bedrock remains in the project as a development tool for generating content drafts and exploring possibilities, but the final game is completely structured and local. This ensures:
- Zero latency during gameplay
- Zero costs for players
- Consistent experience that’s reproducible
- Offline play capability
- Complete creative control over every detail
The game is available at kroz.madeddu.xyz and the source code is open source. Every line of code, every room, every enemy was designed to contribute to a memorable gaming experience.
From an AI-generated prototype to a hand-crafted complete adventure: this is Kroz’s journey. And the journey continues.
Kroz - Text Adventure Game Designed by hand, implemented with Kiro Pro