Play the game now! (damaskus.indigo.spot)

Last year, I participated in the Global Game Jam for the first time, and made Tub Tussle, a 4-player arcade game! I loved the whole experience this much, that when it came around this year, I was already signed up. For Global Game Jam 2026, I teamed up with my good friends Indigo Wolf-Garraway, Josh Wilcox, and Dexter Smith.

The result? Damaskus. And we were incredibly honored to win the highly-coveted People’s Choice Award!

Damaskus

The Workflow: Web-Based Level Editing

One of the biggest bottlenecks in a game jam is content creation. We knew we wanted a puzzle game, but building 20+ levels manually in the Godot editor would have been a nightmare of drag-and-drop, which we quickly discovered.

So, Josh built a custom level editor on the web: damaskus.josh.software.

It allowed us to paint levels quickly in the browser and export them as simple JSON arrays. This decoupled level design from game implementation, meaning we could actually design levels while the core game wasn’t even finished yet.

The output looked something like this - a simple 2D array of integers representing tile IDs:

var campaign_level_layouts = [
    [ # LEVEL 1
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 1, 0, 8, 0, 1, 0, 0, 0, 0, 4, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, 2, 0, 0, 1],
        # ... more rows ...
    ]
]

We utilized a similar array for Masks, which are the core mechanic of the game.

The Importer: Turning Arrays into Acts

In Godot, we wrote a LevelGenerator.gd script to parse these arrays. It iterates through the grid coordinates, checks the ID, and instantiates the corresponding scene.

We defined the mapping in a dictionary to keep it clean:

# scripts/core/LevelGenerator.gd

var tile_definitions = {
    1: {"scene": wall_scene, "container": "Walls", "type": GridManager.TileType.WALL},
    2: {"scene": water_scene, "container": "Water", "type": GridManager.TileType.WATER},
    3: {"scene": crumbled_wall_scene, "container": "CrumbledWalls", "type": GridManager.TileType.CRUMBLED_WALL},
    # ...
    8: {"scene": laser_emitter_scene, "container": "LaserEmitters", "type": GridManager.TileType.LASER_EMITTER},
}

The generation logic is straightforward. It wipes the previous level and builds the new one tile by tile, registering everything into our GridManager:

func _generate_from_arrays(level_layout: Array, mask_layout: Array):
    # ... cleanup old level ...

    for y in range(level_layout.size()):
        for x in range(level_layout[y].size()):
            var cell_value = level_layout[y][x]
            var grid_pos = Vector2i(x, y)
            
            if cell_value == 1: # WALL
                var wall = wall_scene.instantiate()
                wall.position = grid_manager.grid_to_world(grid_pos)
                walls_container.add_child(wall)
                grid_manager.set_tile(grid_pos, GridManager.TileType.WALL)
            
            elif cell_value == 2: # WATER
                # ... instantiate water ...

The Grid System

Since Damaskus is a grid-based puzzle game, we didn’t use Godot’s built-in physics engine for movement. Instead, we wrote a deterministic GridManager.

The GridManager holds the “truth” of the world in a Dictionary mapping Vector2i coordinates to TileType enums.

# scripts/core/GridManager.gd

enum TileType {EMPTY, WALL, CRUMBLED_WALL, WATER, OBSTACLE, ...}

var grid_data: Dictionary = {}

func is_solid(grid_pos: Vector2i) -> bool:
    var type = get_tile_type(grid_pos)
    return type == TileType.WALL or type == TileType.CRUMBLED_WALL or ...

This made implementing mechanics like the Phase Shift (Red/Blue walls) extremely easy. The player interacts with the grid, not the colliders.

Movement & Mask Mechanics

The player’s movement logic in Player.gd queries the GridManager before every step. This is where the Mask System comes in. Masks aren’t just cosmetic; they grant specific “properties” that override collision rules.

For example, the Water Mask grants the FLOAT property, allowing you to walk on water tiles:

# scripts/entities/Player.gd

func can_move_to(target_pos: Vector2i) -> bool:
    var tile_type = grid_manager.get_tile_type(target_pos)

    match tile_type:
        GridManager.TileType.WATER:
            # Only pass if we have FLOAT property
            if has_property("FLOAT"):
                return true
            return false 

        GridManager.TileType.CRUMBLED_WALL:
            # Only pass if we have BREAK_WALL property
            if has_property("BREAK_WALL"):
                return true
            return false
            
        # ... logic for pillars phase walls ...

This architecture allowed us to add new masks and mechanics rapidly. Need a mask that breaks walls? Just add a BREAK_WALL property and check for it in can_move_to.

The Ghost

A key and very fun feature of Damaskus is the co-op ghost mechanic. The Ghost (managed by NPC.gd) mimics your movement. Because we used a strict grid system, keeping the Ghost in sync was simply a matter of connecting a signal.

# scripts/entities/NPC.gd

func _on_player_moved(direction: Vector2i):
	if not is_active: return
	
	# Buffer the move if we are busy, just like the Player does
	if is_moving:
		next_move = direction
	else:
		try_move(direction)

However, the Ghost also has an inventory slot and can wear masks! This leads to complex puzzles where you might need the Ghost to wear the Water Mask to cross a river, while you hold the Dimension Mask to open the path for both of you.

The pillars required a checks for both entities:

GridManager.TileType.RED_WALL:
    var anyone_dim = has_property("DIMENSION_SHIFT") or (target_player and target_player.has_property("DIMENSION_SHIFT"))
    return anyone_dim and grid_manager.is_red_mode

If either character holds the Dimension Mask, both can walk through the matching colored pillars. This created some really fun “aha!” moments in testing.

The Result

The combination of a custom web-based editor and a robust grid system allowed us to crank out over 20 levels in just 48 hours. It was a chaotic sprint, but Indo’s incredible pixel art, along with seeing people enjoy the puzzles - and winning the People’s Choice Award - made it all worth it.

It will be polished a little more and it’s ready to play at damaskus.indigo.spot!