Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when using the Popochiu addon — point-and-click adventure framework with rooms, characters, props/hotspots, inventory, dialog trees, and a command-based GUI
.claude/skills/jame581-popochiu/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 158% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 57% | 0% |
> Related skills: dialogue-system for hand-rolled dialogue data, inventory-system for generic inventory patterns, save-load for core-engine persistence.
> Addon: Popochiu · version v2.1.1 · Godot 4.6 · MIT · source: https://github.com/carenalgas/popochiu · pure GDScript (no C# API — GDScript-only skill).
| Approach | Best for | |---|---| | Hand-rolled (dialogue-system + inventory-system + state-machine) | Custom genre, tight control over every system, no editor tooling needed | | Popochiu | Full point-and-click adventure (AGS/PowerQuest-style): rooms, walk-to-click, verb-based interaction, dialog trees, inventory, save/load — wired together with dock-generated scaffolding |
Choose Popochiu when you're building a classic point-and-click adventure and want the room/character/prop/dialog/inventory pipeline pre-built, with a dock that scaffolds new objects and autoload registries that give autocomplete (R.House, C.Popsy, I.Key, D.PopsyHouseChat). Hand-roll with dialogue-system + inventory-system + state-machine when your genre isn't point-and-click, or you need full control over the interaction model. Popochiu requires Godot 4.6 and is GDScript-only (no C# API).
addons/popochiu/ into your project.PopochiuResources.init_file_structure() scaffolds res://game/ (layout below) plus popochiu_data.cfg (a ConfigFile registry of every room/character/item/dialog) and popochiu_globals.gd (an empty Globals autoload — add your own state here).9 Verb, Sierra, SimpleClick, or Custom).Dock-generated layout:
res://game/
popochiu_globals.gd # Globals autoload — add vars/on_save/on_load
popochiu_data.cfg # registry of rooms/characters/items/dialogs/GUI/setup
autoloads/{r,c,i,d,a}.gd # generated — never hand-edit; rewritten per new object
rooms/<name>/room_<name>.gd (+ .tscn, state script)
characters/<name>/character_<name>.gd (+ .tscn, state script)
inventory_items/<name>/inventory_item_<name>.gd (+ .tscn, state script)
dialogs/<name>/dialog_<name>.gd (+ .tres)
gui/gui.tscn, gui/gui_commands.gd
transition_layer/transition_layer.tscn (+ .gd)Game scripts talk to Popochiu almost entirely through short autoload singletons (not PopochiuUtils or class names, which are for engine-internal code):
| Autoload | Interface | Purpose | Key signature | |---|---|---|---| | E | Popochiu | Core engine: queue/cutscene scripting, save/load, camera, commands | func queue(instructions: Array, show_gui := true) -> void | | R | PopochiuIRoom | Room registry & navigation | func goto_room(script_name := "", use_transition := true, store_state := true, ignore_change := false) -> void | | C | PopochiuICharacter | Character registry (C.player, C.Popsy) | func walk_to_clicked(offset := Vector2.ZERO) -> void | | I | PopochiuIInventory | Inventory registry & active item | func set_active_item(item: PopochiuInventoryItem = null) -> void | | D | PopochiuIDialog | Dialog registry & inline dialogs | func show_inline_dialog(options: Array) -> PopochiuDialogOption | | G | PopochiuIGraphicInterface | GUI control: block/unblock, show/hide, hover/system text | func show_system_text(msg: String) -> void | | A | PopochiuIAudio | Audio cue registry (A.sfx_boing) | func is_playing_cue(cue_name: String) -> bool |
R/C/I/D back onto generated files (res://game/autoloads/*.gd) rewritten every time you create an object in the dock — never hand-edit them; put custom logic in the object's own script instead. Two more singletons round out the surface: T (PopochiuITransitionLayer, screen transitions — §4) and Cursor (mouse cursor rendering).
Create rooms from the dock's "Create room" button — it scaffolds room_<name>.gd/.tscn under res://game/rooms/<name>/ with child nodes $Props, $Characters, $Hotspots, $Regions, $WalkableAreas, $Markers. A room needs at least one enabled Walkable Area or characters won't path on click.
R.goto_room() changes rooms — there is no E.goto_room(); room navigation lives on R:
gdscriptfunc _on_click() -> void: await C.player.walk_to_clicked() R.goto_room("Garden")
E.queue() scripts a cutscene from an ordered instruction list — mix Callables (the queue_* twin of any action) and plain strings:
gdscriptfunc _on_room_transition_finished() -> void: E.queue([ "Popsy: Finally, some fresh air.", C.Popsy.queue_walk_to(Vector2(400, 300)), "...", # 1s pause C.Popsy.queue_say("Now, where did I put that key?"), ])
String shorthand accepted by queue()/cutscene(): "Name: text" (says a line), "Name(emotion): text", "Name[N]: text" (auto-continues after N seconds), "."/".."/"..." (0.25s/0.5s/1s pauses, doubling per dot), any other string shown via G.show_system_text(). E.cutscene() is additionally skippable via the popochiu-skip input action (default Esc).
Room transitions go through T (new in 2.1) — not the deprecated E.play_transition()/E.queue_play_transition() (still present in 2.1.1 but print a runtime warning):
gdscriptawait T.play_transition("fade", 0.5, T.PLAY_MODE.IN_OUT)
R.goto_room() plays the project's default transition automatically unless called with use_transition := false.
| Type | Base | Purpose | |---|---|---| | Prop | PopochiuProp (Area2D) | Visible/interactive scenery — sprite, optional link_to_item, can be a nav obstacle | | Hotspot | PopochiuHotspot (Area2D) | Invisible interaction zone (no sprite of its own — e.g. part of the background art) | | Region | PopochiuRegion (Area2D) | Trigger area that tints/scales a character on enter/exit; does not receive clicks |
Props, Hotspots, and Characters all extend PopochiuClickable and share one virtual lifecycle. Override these underscore-prefixed methods in generated scripts — the engine calls public wrappers (on_click(), etc.) that call these internally, so never override the public wrapper itself:
gdscript# prop_key.gd extends PopochiuProp func _on_click() -> void: await C.player.walk_to_clicked() I.Key.add_as_active() func _on_item_used(item: PopochiuInventoryItem) -> void: if item == I.Oil: await C.player.say("That should stop it squeaking.")
Full callback set: _on_room_set, _on_click, _on_double_click, _on_right_click, _on_middle_click, _on_item_used(item), _on_position_changed, _on_movement_started, _on_movement_ended.
Command-based GUI, at a glance: each shipped GUI template (9 Verb / Sierra / SimpleClick) registers verbs via E.register_command(id, "Display Name", fallback). On click, Popochiu snake_cases the active command and looks for on_<command>() on the clicked object (e.g. on_look_at()) before falling back to _on_click(). Full mechanics — including E.current_command, registering custom commands, and the deprecated E.active_command snippet you may see in older tutorials — are in references/gui.md.
Create characters from the dock; the first one created becomes the Player Character. Say a line with an optional emotion:
gdscriptawait C.player.walk_to_clicked() await C.player.say("What a mess.", "annoyed")
Emotions map to voice_cues and avatars export arrays (emotion name → audio cue / GUI portrait); say() sets emotion for the duration of the line, then resets it to "".
Start a named dialog tree from a click handler:
gdscriptfunc _on_click() -> void: await C.player.face_clicked() D.PopsyHouseChat.start()
Dialog trees are PopochiuDialog resources with branching options, per-option conditions, and handler methods — full authoring pattern (including the Array-only turn_on_options()/turn_off_options() gotcha) is in references/dialogs.md.
Deep dives: references/dialogs.md, references/inventory.md, references/gui.md, references/pipeline.md
R.goto_room() — not a nonexistent E.goto_room()E.queue([...])/E.cutscene([...]), not bare queue_*() calls (those are silent no-ops outside a queue)queue_ action calls (say(), walk_to(), …) are awaited, or they run concurrently instead of sequentiallyT.play_transition(), not the deprecated E.play_transition()res://game/autoloads/{r,c,i,d,a}.gd are never hand-edited — they're dock-regenerated*_state.gd resource (JSON-safe types, or _on_save/_on_load overrides for complex ones)turn_on_options()/turn_off_options()/turn_off_forever_options() are called with an Array, even for one idon_<command>() (verb-specific) or the _on_click() fallback — never override the public on_click() wrapper itselfres://game/gui/ tree| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 13,175 | 11,369 | -14% | 1 | 1 | 0% | 2,283 | 5,220 | +129% | 0 | 0 | — |
case-02 | fail→pass | 17,189 | 12,368 | -28% | 1 | 1 | 0% | 2,842 | 5,206 | +83% | 0 | 0 | — |
case-03 | fail→pass | 14,294 | 14,823 | +4% | 1 | 1 | 0% | 2,254 | 5,809 | +158% | 0 | 0 | — |
case-04 | fail→pass | 20,946 | 14,941 | -29% | 1 | 1 | 0% | 4,386 | 6,008 | +37% | 0 | 0 | — |
case-05 | pass→pass | 21,560 | 16,546 | -23% | 1 | 1 | 0% | 4,624 | 6,249 | +35% | 0 | 0 | — |
case-06 | pass→pass | 12,898 | 10,022 | -22% | 1 | 1 | 0% | 2,531 | 4,885 | +93% | 0 | 0 | — |
case-07 | fail→pass | 15,690 | 12,458 | -21% | 1 | 1 | 0% | 2,461 | 5,262 | +114% | 0 | 0 | — |
case-08 | pass→pass | 10,342 | 4,423 | -57% | 1 | 1 | 0% | 1,707 | 3,778 | +121% | 0 | 0 | — |
case-09 | pass→pass | 12,245 | 3,895 | -68% | 1 | 1 | 0% | 2,121 | 3,726 | +76% | 0 | 0 | — |
case-10 | fail→pass | 15,166 | 3,854 | -75% | 1 | 1 | 0% | 2,334 | 3,672 | +57% | 0 | 0 | — |
case-11 | pass→pass | 11,001 | 3,831 | -65% | 1 | 1 | 0% | 1,695 | 3,614 | +113% | 0 | 0 | — |
case-12 | pass→pass | 5,628 | 3,802 | -32% | 1 | 1 | 0% | 937 | 3,679 | +293% | 0 | 0 | — |
case-13 | fail→fail | 13,965 | 4,270 | -69% | 1 | 1 | 0% | 2,406 | 3,726 | +55% | 0 | 0 | — |
case-24 | pass→pass | 18,891 | 2,314 | -88% | 1 | 1 | 0% | 3,230 | 3,331 | +3% | 0 | 0 | — |
case-14 | fail→pass | 13,667 | 4,475 | -67% | 1 | 1 | 0% | 2,410 | 3,758 | +56% | 0 | 0 | — |
case-15 | fail→pass | 17,217 | 4,568 | -73% | 1 | 1 | 0% | 2,834 | 3,829 | +35% | 0 | 0 | — |
case-16 | pass→pass | 14,051 | 11,744 | -16% | 1 | 1 | 0% | 2,244 | 5,143 | +129% | 0 | 0 | — |
case-17 | pass→pass | 9,908 | 5,060 | -49% | 1 | 1 | 0% | 1,430 | 3,868 | +170% | 0 | 0 | — |
case-18 | pass→pass | 11,933 | 8,203 | -31% | 1 | 1 | 0% | 1,776 | 4,257 | +140% | 0 | 0 | — |
case-19 | fail→pass | 13,459 | 6,855 | -49% | 1 | 1 | 0% | 2,354 | 4,230 | +80% | 0 | 0 | — |
case-20 | fail→pass | 17,095 | 11,961 | -30% | 1 | 1 | 0% | 2,644 | 5,183 | +96% | 0 | 0 | — |
case-21 | pass→pass | 9,957 | 6,128 | -38% | 1 | 1 | 0% | 1,497 | 4,041 | +170% | 0 | 0 | — |
case-22 | pass→pass | 8,602 | 6,506 | -24% | 1 | 1 | 0% | 1,306 | 4,065 | +211% | 0 | 0 | — |
case-23 | pass→pass | 7,807 | 6,291 | -19% | 1 | 1 | 0% | 1,252 | 3,889 | +211% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +38 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.