Skip to main content
View

Understanding System Tags

System Tags are per-tile markers that tell the PSDK map engine how a tile behaves: which tiles start wild battles, let you surf, slide on ice, jump a ledge or climb stairs. This guide explains how the engine stores and reads them, the API to query the tile under or in front of a character, and the full catalogue of tags grouped by role.

The problem: making the terrain react

When a player walks into tall grass, a wild battle can start. When they step onto a ledge, the character hops down and cannot climb back up. When they reach a patch of ice, they keep sliding until a wall stops them. Surf only works on water, the bike refuses to climb certain ledges, and counters count steps differently in a swamp.

None of this is hardcoded per map. Every one of those behaviours is the engine reading a single marker on the tile and acting on it. That marker is a System Tag: an integer attached to a tileset tile, looked up by the map engine on every move. Understanding System Tags is understanding how PSDK turns a painted map into terrain that reacts.

System Tags are distinct from RPG Maker XP's historical terrain_tag. The old terrain tag still exists (Game_Character#terrain_tag), but PSDK's own map logic is driven by System Tags, which are richer and tied to gameplay symbols.

Where the tags live

A System Tag is stored per tileset, not per map. The engine keeps a global table $data_system_tags indexed by tileset id, and each map loads the row for its own tileset when it is set up:

# Game_Map#load_systemtags, called from Game_Map#setup
$data_system_tags[@map.tileset_id] ||= Array.new($data_tilesets[@map.tileset_id].priorities.xsize, 0)
@system_tags = $data_system_tags[@map.tileset_id]

So @system_tags[tile_id] gives the tag of a given tile in the tileset. A value of 0 means "no tag". Because a map cell stacks up to three tile layers, the engine reads them from the top layer down and returns the first non-zero tag it finds.

You do not set System Tags in code: they are painted in Tiled, the map editor PSDK maps are built with. Each map is a .tmx file under Data/Tiled/Maps, and the System Tags live on dedicated tile layers: systemtags for the terrain tags, plus systemtags_bridge1 and systemtags_bridge2 for bridges. Painting a tag means placing a tile from the System Tags tileset, whose tiles map to the constants listed further down. Pokémon Studio is the project hub that opens a map in Tiled for you; it does not paint the tags itself.

Each tag constant is generated by a helper:

# GameData::SystemTags
def gen(x, y)
return 384 + x + (y * 8)
end

TGrass = gen 5, 0
TIce = gen 1, 0

Here (x, y) is the tag's position in the System Tags tileset image (8 columns wide). You never need the raw number in your own code: a tag is always referred to by its constant name, like GameData::SystemTags::TGrass.

Reading a tag from a script

All the reading methods live on Game_Character (so every event, NPC and the player share them) and on Game_Map. This is the part you actually call from an event's script command.

The two most common queries are the tile the character stands on and the tile right in front of them:

# The tag under the player
$game_player.system_tag # => Integer id, 0 if none

# The tag of the tile the player is facing (uses the character's direction)
$game_player.front_system_tag # => Integer id, 0 if none

Comparing a raw id is fine, but the engine also exposes a readable symbol for the gameplay-relevant tags, which is easier to branch on:

$game_player.system_tag_db_symbol # => :grass, :cave, :sea, :ice, :headbutt...
$game_player.front_system_tag_db_symbol # => same, for the tile ahead

The symbol comes from GameData::SystemTags.system_tag_db_symbol, which maps the encounter and particle tags to a symbol and returns :regular_ground for everything else.

To compare against a precise tag, use the constant rather than a literal number:

if $game_player.front_system_tag == GameData::SystemTags::HeadButt
# the player is facing a Headbutt tile
end

When you need to inspect an arbitrary cell instead of the character's position, query the map directly:

# Tag at an absolute tile coordinate
$game_map.system_tag(x, y) # => Integer id, 0 if none

# Is this exact tag present on that cell?
$game_map.system_tag_here?(x, y, GameData::SystemTags::TGrass) # => true / false

Game_Map#system_tag accepts a skip_bridge: keyword. With skip_bridge: true it ignores bridge tags (BridgeRL, BridgeUD) and keeps looking at the layers below, which is how the engine checks the ground under a bridge rather than the bridge itself.

MethodReceivesReturns
Game_Character#system_tagnothingtag id of the tile the character stands on
Game_Character#front_system_tagnothingtag id of the tile the character faces
Game_Character#system_tag_db_symbolnothingsymbol for the current tile
Game_Character#front_system_tag_db_symbolnothingsymbol for the faced tile
Game_Map#system_tagx, y, skip_bridge:tag id at that cell, 0 if none
Game_Map#system_tag_here?x, y, tagtrue if that exact tag is on the cell
GameData::SystemTags.system_tag_db_symbola tag idthe matching symbol, or :regular_ground

The families of tags

Every tag is a constant in the GameData::SystemTags module. The engine groups several of them into arrays it reuses across the movement logic (SurfTag, SlideTags, BRIDGE_TILES, ZTag...). The tables below list every tag by role; the effect column reflects what the engine actually does with each one.

Terrain and encounters

These tags drive wild battles, the location type and the ground particles.

TagEffect
EmptyNeutral tile; cancels the effect of water tags such as TSea or TPond.
TGrassGrass; shows grass particles and starts grass wild battles.
TTallGrassTaller grass; same role as TGrass with a distinct animation.
TCaveCave; starts cave wild battles.
TMountMountain; starts mountain wild battles.
TSandSand; starts sand wild battles.
TWetSandWet sand; shows a particle when walking, otherwise behaves like sand.
TPondPond or river; starts pond wild battles (also surfable).
TSeaSea or ocean; starts sea wild battles (also surfable).
TUnderWaterUnderwater terrain; starts underwater wild battles.
TSnowSnow; starts snow wild battles.
PuddlePuddle; shows a water particle when walking.
HeadButtA tile you can use Headbutt on; counts as grass for the location type.

Water and surfing

The engine keeps two arrays here: SurfTag (the water tiles you can only enter while surfing) and SurfLTag (every tile you are allowed to stand on while surfing, which adds bridges, jumps and the acro-bike bridges).

TagEffect
TSea, TPondSurfable water; part of SurfTag.
RapidsL, RapidsD, RapidsU, RapidsRWater current forcing the character left / down / up / right.
WaterFallWaterfall tile; an aid for waterfall events.
WhirlpoolWhirlpool tile.

Sliding

These tags take control of the character and move them automatically. They are collected in SlideTags.

TagEffect
TIceIce; the character keeps sliding forward.
RapidsL, RapidsD, RapidsU, RapidsRForce the character to slide in that direction.
RocketL, RocketD, RocketU, RocketRForce the character to move that way until they hit a wall.
RocketRL, RocketRD, RocketRU, RocketRRSame as the Rocket* tags but the character also rotates.
StopSlideStops a sliding character.

Ledges and jumps

TagEffect
JumpR, JumpL, JumpD, JumpULedge; the character jumps across it to the right / left / down / up.

Stairs and slopes

TagEffect
StairsL, StairsD, StairsU, StairsRStairs; adjust the character's elevation while moving.
SlopesL, SlopesRLeft and right slopes; shift the character's on-screen position as they climb or descend.

Bridges and elevation

Bridges are grouped in BRIDGE_TILES; ZTag is an array of tiles that change the character's z (priority layer).

TagEffect
BridgeUDBridge crossed up and down (the layer below stays reachable).
BridgeRLBridge crossed right and left.
ZTagSeven tiles that set the character's z layer, so they pass above or below other tiles.

Bikes

TagEffect
AcroBikeLedge passable only by the Acro bike bunny hop.
AcroBikeRLBike bridge allowing only right and left movement (plus up/down jumps).
AcroBikeUDBike bridge allowing only up and down movement (plus left/right jumps).
MachBikeRequires high speed; otherwise the character falls down.
CrackedSoilRequires high speed; otherwise the character falls into a hole.
HoleHole tile.

Swamp and pathfinding

TagEffect
SwampBorderShallow swamp; slows the character down.
DeepSwampDeep swamp; the character can get stuck.
RoadMarks a road, used as a preferred route by the pathfinding system.
RClimbRock Climb tile.

The canonical, always-current list is the engine file that declares them. If a future PSDK version adds a tag, it appears there first.

Usage examples

The reading API is plain Ruby, so you can branch on a System Tag anywhere game code runs: an event's script command, a move route, a scheduled task or a field-move handler. Here are the patterns you are most likely to need, each mirroring how the engine itself uses tags.

Gate an event on the tile ahead

The simplest case: only let an interaction happen on the right kind of tile. Drop a guard straight into an event's script command, reading the tile the player faces:

# In an event's script command
if $game_player.front_system_tag_db_symbol == :grass
# the player faces grass: run the interaction
$game_temp.common_event_id = MY_GRASS_EVENT_ID
end

To branch on a tag that has no dedicated symbol, compare the constant instead:

# Only react when the player stands on a bridge crossed right and left
if $game_player.system_tag == GameData::SystemTags::BridgeRL
# ...
end

The same reads work from a script command in the interpreter.

Read a tag from a Move Route

A move route's Script command is evaluated in the moving character's own context, so every Game_Character method is in scope with no receiver. The engine ships a move-route helper built on exactly this: move_random_within_systemtag makes a character wander at random but only across tiles carrying a given tag, so an NPC or a roaming Pokémon stays confined to, say, grass or water:

# Script command inside the event's Move Route
move_random_within_systemtag(GameData::SystemTags::TGrass)

Each time the move route runs it tries one random step and takes it only if the neighbouring tile shares the tag, so the character never leaves the patch.

React to a step with the Scheduler

To run code when a character steps onto a tile rather than poll it, register a map event task. Scheduler::EventTasks fires on :begin_step / :end_step, :begin_jump / :end_jump and :begin_slide / :end_slide; the task receives the Game_Character that moved, and the event id -1 targets the player. The engine itself uses this to pick the landing particle after a jump:

# How PSDK chooses water dust vs normal dust after a jump
Scheduler::EventTasks.on(:end_jump, 'Dust after jumping') do |event|
next if event.particles_disabled

particle = Game_Character::SurfTag.include?(event.system_tag) ? :water_dust : :dust
Yuki::Particles.add_particle(event, particle)
end

Your own task follows the same shape, here reacting only when the player lands on ice:

Scheduler::EventTasks.on(:end_step, 'React to ice', -1) do |event|
next unless event.system_tag == GameData::SystemTags::TIce
# the player just stepped onto an ice tile
end

This is the cleanest way to add step-driven behaviour; see the Scheduler guide for the full task model.

Add a field move that checks the terrain

Out-of-battle moves (Surf, Fly, Sweet Scent...) are registered in the PFM::SKILL_PROCESS hash, keyed by the move's db_symbol. Each entry is a proc receiving the Pokémon, the move and a test flag (the dry run the menu uses to know whether the move is usable). This is exactly where Surf reads a System Tag to decide it may be used: it looks at the tile in front of the player and refuses unless it is water.

# PFM::SKILL_PROCESS[:surf], abridged
surf: proc do |_pkmn, _skill, test = false|
new_x, new_y = $game_player.front_tile
sys_tag = $game_map.system_tag(new_x, new_y)
# refuse unless the faced tile is surfable water
next :block unless $game_player.z <= 1 && !$game_player.surfing? &&
Game_Character::SurfTag.include?(sys_tag)
next false if test

$game_temp.common_event_id = Game_CommonEvent::SURF_ENTER
next true
end

You can register your own field move the same way: add an entry keyed by your move's db_symbol, return :block to refuse with the default message, and act when the terrain is right.

# A custom field move usable only when facing a Rock Climb tile
PFM::SKILL_PROCESS[:my_move] = proc do |_pkmn, _skill, test = false|
next :block unless $game_player.front_system_tag == GameData::SystemTags::RClimb
next false if test

# perform the move's overworld effect here
end

When you instead need to change what an existing engine method does around a tag, patch it with a prepend module calling super rather than editing the engine, as described in monkey-patching.

To go one step further and register a brand-new tag of your own, with its own wild encounters, battle background and particles, see Create a custom System Tag.

Conclusion

  • A System Tag is a per-tile marker, stored per tileset, that tells the map engine how a tile behaves; 0 means no tag.
  • The engine reads a cell's three layers from the top down and returns the first non-zero tag.
  • Read the tile under a character with system_tag, the tile ahead with front_system_tag, and get a readable symbol with system_tag_db_symbol; query any cell with Game_Map#system_tag and system_tag_here?.
  • Always compare against a constant name (GameData::SystemTags::TGrass), never a raw number.
  • The tags fall into clear families: terrain and encounters, water and surfing, sliding, ledges, stairs and slopes, bridges and elevation, bikes, swamp and pathfinding.
  • Tags are painted in Tiled on a dedicated systemtags layer; the engine file that declares the constants is the source of truth for the full list.