I was thinking about what it could be like to implement a Glk (GlkOte? GlkOte only?) extension that could support displaying a map provided by the interpreter, e.g. ADRIFT.
Automapping is a pretty weird topic with a lot of edge cases; I’m not surprised nobody’s tried to take it on before.
I started imagining a “Glk Mapping” extension shaped like this:
#define mapcolor_Default 0xFFFFFFFFu
typedef struct glk_mappoint_struct {
glsi32 x, y;
} glk_mappoint_t;
typedef struct glk_maphyperlink_struct {
glui32 id; /* opaque; nonzero */
const char *label; /* null-terminated UTF-8; NULL or "" = unlabeled */
glui32 npoints; /* >= 3 */
const glk_mappoint_t *points; /* polygon points */
} glk_maphyperlink_t;
glui32 glk_map_present_svg(
const unsigned char *data, /* UTF-8 SVG */
glui32 len,
glui32 bgcolor, /* Glk color code */
glsi32 focusleft,
glsi32 focustop,
glui32 focuswidth,
glui32 focusheight,
const glk_maphyperlink_t *hyperlinks,
glui32 nhyperlinks
);
void glk_map_close(void);
glk_map_present_svg() would create or replace the current map.
- Returns 1 on success, 0 on failure (invalid SVG).
- On return (success or failure), the library retains its own copy of the SVG data; the program may free
dataimmediately. - The SVG root element must define a
viewBoxorwidth/height - The runner controls the layout, placement and visibility of the map UI. The runner should provide some UI for panning and zooming the map, as well as hiding/showing the map, but this isn’t required. (Maybe there’d be testable gestalts for those features…? That way the terp/author could say “if this Glk runner doesn’t support panning and zooming, don’t show the map at all”)
- The map UI is not a Glk “window”; it doesn’t have a Glk window ID, or fit into the Glk window-management system.
- A successful
glk_map_present_svgdoes not require the map to become visible. If the player previously hid the map, it generally stays hidden, but the latent map updates. Also, if the map is only available as a full-screen experience (e.g. on mobile), then the UI may not display the full-screen map when youpresentit. - The focus variables are ignored if the width or height are 0. In that case, the library may keep the current pan/zoom or fit the whole map — implementation-defined.
- The
bgcolorbackground color only shows when the user pans the map outside of its bounds, or when the map window’s dimensions are so wide/tall that the map can’t fill the window.mapcolor_Defaultdisplays the map with a default background color, chosen by the runner. - The map may include clickable hyperlinks, defined as labeled polygons with IDs. (You can pass 0 hyperlinks with a NULL array of hyperlinks.)
- The mouse cursor should change to a pointing hand when hovering over a hyperlink polygon.
- The hyperlinks could be navigable via keyboard or other accessibility devices.
- Each hyperlink must have at least three points in its polygon points array. Hyperlinks with 0, 1, or 2 points will be ignored.
- Hyperlinks with id 0 are also ignored.
- If hyperlink polygons overlap, and the user clicks on an overlapping point, the hyperlink that comes last in the array will be selected.
- Just one map is allowed at a time. (Right??)
glk_map_close() destroys the map data. The library should close/hide map UI. It is legal when no map exists.
Refocus without re-sending the map
void glk_map_set_focus(
glsi32 focusleft,
glsi32 focustop,
glui32 focuswidth,
glui32 focusheight);
void glk_map_clear_focus(void);
Update hyperlinks without re-sending the map
void glk_map_set_hyperlinks(const glk_maphyperlink_t *hyperlinks,
glui32 nhyperlinks);
You can clear all hyperlinks by passing 0 hyperlinks.
Present an image resource from a Blorb
glui32 glk_map_present_image(
glui32 image, /* Blorb pict id, as in glk_image_draw */
glui32 bgcolor,
glsi32 focusleft,
glsi32 focustop,
glui32 focuswidth,
glui32 focusheight,
const glk_maphyperlink_t *hyperlinks,
glui32 nhyperlinks
);
Blorbs don’t support SVG (yet, maybe ever). SVG is an enormously complicated standard, with zillions of optional features, some of which can do I/O.
But for something like Counterfeit Monkey, where the map is a handrolled PNG, this feels like a nice convenience.
Map Overlays
“Map overlays” are layered images that you can stack on top of a map (and on top of each other). They’re useful for drawing a “you are here” symbol on top of an existing map, or for changing the background color of one room in the map (by redrawing the entire room with a different background color).
typedef glui32 overlayid_t;
overlayid_t glk_map_overlay(
glui32 image, /* Blorb pict id, as in glk_image_draw */
glsi32 left,
glsi32 top,
glui32 width, /* 0 width = natural image width */
glui32 height, /* 0 height = natural image height */
glui32 zindex,
glui32 link_id, /* link_id 0 means not hyperlinked */
const char *linklabel
);
extern overlayid_t glk_map_overlay_svg(
const unsigned char *data,
glui32 len,
glsi32 top,
glui32 width, /* 0 width = natural image width */
glui32 height, /* 0 height = natural image height */
glui32 zindex,
glui32 link_id, /* link_id 0 means not hyperlinked */
const char *linklabel
);
- The map itself would have
zindex0; overlays with higherzindexwould render on top of (obscuring) overlays with lowerzindex. - You can optionally associate an overlay with a hyperlink.
- In case of overlap:
- overlay hyperlinks will be selected instead of
presenthyperlinks - overlays with higher
zindexwill be selected first - other than that, the most recently declared (last) hyperlink will be selected
- overlay hyperlinks will be selected instead of
- In case of overlap:
You can also add basic rectangular overlays, like this:
overlayid_t glk_map_fill_rect(glui32 color, glsi32 left, glsi32 top,
glui32 width, glui32 height, glui32 zindex);
And you can move and clear overlays like this:
glui32 glk_map_overlay_move(
overlayid_t overlay,
glsi32 left,
glsi32 top,
glui32 width,
glui32 height,
glui32 zindex);
glui32 glk_map_overlay_clear(overlayid_t overlay);
glui32 glk_map_overlay_clear_all(void);
Note that a successful call to glk_map_present_svg or glk_map_present_image clears all overlays.
Also note: it’s a good idea to defer/batch (“debounce”) overlay calls, rather than rendering overlay updates synchronously on every call to glk_map_overlay* function. Otherwise, you might end up displaying the UI in an intermediate state.
Map events
void glk_request_map_event(void);
void glk_cancel_map_event(void);
#define evtype_Map (10) /* actual value TBD */
#define mapevent_Hyperlink (1) /* val2 = link id */
#define mapevent_UserHide (2) /* val2 = 0 */
#define mapevent_UserShow (3) /* val2 = 0 */
For map events, the win window will always be NULL; val1 would be the event type (Hyperlink, UserHide, or UserShow), and val2 would be the payload or 0.
- The Hyperlink map event would fire when the user clicks on a hyperlink in the map, e.g. to automatically navigate the user to the selected room.
- We’re not using the standard Glk hyperlink event type, because the map doesn’t have a Glk window ID.
- The UserHide event would fire when the user closes the map.
- The interpreter might decide to print a message when this happens, e.g. “To reopen the map, type MAP.”
- The UserShow event would fire when the user opens the map using runner chrome (e.g. using the runner’s menu bar or a toolbar button; something outside the Glk-managed UI)
- This is useful for ADRIFT games, which want to show a “Map” hyperlink in the status bar when the map is closed, and hide the hyperlink when the user opens the map.
Showing the map after the user closes it
void glk_map_show_at_user_request(void);
glui32 glk_map_get_visibility(void);
glk_map_show_at_user_request() shows the map. It’s meant to be used when the user asks to open the map, e.g. after running the MAP command.
glk_map_get_visibility returns true when the map is visible, and false when the map is not visible, either because the user closed it, or because, in the current interpreter, the map is only displayed in a full-screen experience (e.g. mobile).
You can use this to show a hint at/near the start of the game, like, “To show the map, type MAP,” if the map isn’t currently visible.
Out of scope?
- Multiple maps
What would the Glk runner do, actually?
For comparison
- Counterfeit Monkey: Managing its map has been a huge source of bugs over the years.
- As of v11, Counterfeit Monkey starts by disabling its map, allowing users to reveal the map as a graphics window that occupies the side pane with
map on. If you do that on mobile, you’re going to have a bad time, but you’ll probably be able to figure out how tomap offto make it go away. - Alternately, if you just want to peek at the map, you can type
map, which displays in the text buffer as a full-screen image. - Each room has its own pre-rendered map, with the “you are here” symbol burned into the image. When showing the map, the UI just shows the current room’s pre-rendered image. (This feels suboptimal. Separate map overlay layers would work much, much better.)
- As of v11, Counterfeit Monkey starts by disabling its map, allowing users to reveal the map as a graphics window that occupies the side pane with
- Hadean Lands: Custom UI everywhere for Hadean Lands. I don’t think any of the source of that is publicly available…?
- On desktop OSes, it starts by opening multiple OS windows, one for the text buffer, one for the map, and one for the “journal” that includes rituals, formulas, and facts. The map is pre-rastered as a PNG, with labels as overlays that you can hide/show with a “Labels” button in the map menu, and an animated “you are here” symbol, a star. There’s a “Flash Star” button that enables/disables flashing.
- On iOS, the game launches in a tabbed view, with these tabs: Game (the text buffer), Journal, Map, Help, Settings.
- ADRIFT: (This is what originally got me thinking about this problem.) In ADRIFT, authors can declare a map of rooms and exits, each with visibilities. You can make the whole map visible at game start, or reveal the map room-by-room as you explore. From the user’s perspective, it feels like an “automap”, but it’s not trying to automatically parse the transcript; it’s revealing more and more of the author’s hand-coded map model.
- ADRIFT also lets you click on rooms of the map; the game runs Dijkstra’s algorithm to find the shortest path from your location to the destination, and automatically emits “go north.” “go west.” etc. commands to get you there.
- The official ADRIFT-5 runner is only available for Windows. ADRIFT’s runner has a fancy dockable UI. You can drag and drop the map window (or other “named windows”) to the top, bottom, right, and left of the screen; you can pop them out and turn them into floating windows that you can reposition wherever you like.
- FrankenDrift is available on other platforms; it makes a separate OS window available for the map, which the user can position manually. (On macOS, the map window spawns underneath the text buffer, making it difficult to discover, but it is there.) FrankenDrift doesn’t attempt to render a map on Glk.
Terp-rendered SVG maps
Some interpreters support author-provided automaps, like ADRIFT.
In that case, the terp would render the map as an SVG string and present it via glk_map_present_svg().
As the map updates (e.g. moving the “you are here” marker), the terp would call glk_map_present_svg() on each turn, presenting (representing) the entire SVG string each time the map updates. (SVG strings are quite short.)
Color recommendation for terp-rendered SVG maps: Measure the Normal TextColor/BackColor with glk_style_measure, and use those colors in the generated SVG (for now?)
glk_style_measure can report on the foreground text color and background color of the “Normal” style for the transcript window. If the user has opted to override stylehints that the terp has provided, glk_style_measure will report that, giving you a reasonable background color and foreground color for rendering.
If glk_style_measure returns false, use a default color scheme. For example, RemGlk interpreters (including Parchment and Lectrote) don’t implement glk_style_measure at all, returning FALSE in all cases. (glk_style_measure would require an asynchronous remote procedure call, asyncifying the function.)
I note that stylehints have been kinda “deprecated” for years, partially replaced by the garglk_set_zcolors Glk extension, and hopefully soon to be replaced by the CSS Glk extension.
But, as I write this, there is no other API proposed to access the normal text colors, so glk_style_measure is all we have.
My guesses as to what the UI would look like
I’m imagining that Glk runners that support mapping would have different UI for large/small screens (and that the runners would be the best decision makers for how/when to do that).
- Parchment/webUI runners would probably display the map in a side pane with a close button if the window is “wide enough.” If the window is too narrow, the map would be hidden by default. If there’s an on-screen menu (a hamburger menu, or a toolbar), a “Show Map” button could appear there if the map isn’t visible. (And, I think webUI runners should have a menu, at least a hamburger menu, with buttons to save, restore, restart, adjust settings, and get help.)
- The terp would call
glk_get_visibility()early on, and show a hint, “To open the map, type MAP,” if the map isn’t visible.
- The terp would call
- Desktop runner apps (Gargoyle, Spatterlight, Lectrote) could open the map in an OS window, which the user could reposition. ADRIFT’s runner has a fancy dockable UI, which feels like overkill to me, but it works, and maybe that’s “best” for power users. IMO non-power users struggle to understand it. Grandpa’s Ranch (a beginner-friendly ADRIFT game intended for the Text Adventure Literacy Jam) had to include a bunch of in-game documentation trying to explain to newbies how ADRIFT’s layout system works, which suggests to me that ADRIFT’s layout system is the wrong approach, even for a desktop-only IF interpreter.
- When closing the map, the terp would get a UserHide event, and print a message, “To reopen the map, type MAP.”
- Mobile apps have been pretty creative about menus.
- I mentioned Hadean Lands’s tabs; that looks pretty good.
- A hamburger menu would work, too.
- iOS Frotz has a little menu “book” in the prompt; you can tap on it to get a bunch of commands to auto-type. Adding “map” in there would be nice.
- Check out Fabularium’s custom-keyboard menu. I notice the M key doesn’t have anything on it, which would be perfect for showing the map!

What do you think?
Is this anything? If I filed PRs on Parchment, Gargoyle, Lectrote, etc. to support this, would you consider merging those PRs, or just close them without merging?





