Writing pages
Pages are markdown files in the open bullet-hero/docs repository. A change arrives as a pull request: a fork, a branch, the change in every language, a check against the style guide
Repositories
Every repository of the project lives in the bullet-hero organization. The open ones:
| Repository | What it holds |
|---|---|
| bullet-hero/releases | releases hold every public build of the game, issues hold bugs and requests about the game from players |
| bullet-hero/sdk | the SDK code, issues hold bugs and requests about the SDK |
| bullet-hero/docs | these pages and their translations |
| bullet-hero/backend | the server, empty for now |
The code of the game (bullet-hero/game) and of the site (bullet-hero/frontend) is closed
This page is about bullet-hero/docs
How to send a change
- Fork bullet-hero/docs and create a branch
- Edit or add pages in the format described below
- Change the page in every language, or say in the pull request which languages still need the change. More - Translating
- Check the text against the Style guide
- Open a pull request with a short description of what changed and why
Facts come from the game, the SDK or their documentation. Numbers, field names, file names, shortcuts and addresses are never guessed. Not sure something is true? Say so in the pull request, not on the page
An accepted change does not appear on bullethero.space at once, but with the next site update
You cannot build the site yourself, since its code is closed. The closest preview is Obsidian: a link that Obsidian cannot find will not be found on the site either
How the repository works
- Markdown only. There is no build of its own and no scripts
- It is an Obsidian vault. Open the repository's root folder as a vault, and links, embeds and previews work the same way they do on the site. Any other markdown editor works too
- It is a submodule of the site. The site includes the repository as its
content/folder, compiles the pages when it is built and prerenders every one. A change reaches the site when the site's pointer to this repository is moved forward
What goes where
| Path | What it holds | Address on the site |
|---|---|---|
<lang>/docs/1_game/ | for players: installing, playing, settings, the mechanics | /<lang>/docs/game |
<lang>/docs/2_editor/ | for level authors: the editor guide and reference | /<lang>/docs/editor |
<lang>/docs/3_sdk/ | for developers: the open SDK and the level format | /<lang>/docs/sdk |
<lang>/docs/4_server/ | for server hosts: official and community servers | /<lang>/docs/server |
<lang>/docs/5_changelog/ | the changelog, one page per game version | /<lang>/docs/changelog |
<lang>/notes/ | articles, community, editing rules, public documents and policies, by date | /<lang>/notes/<name> |
<lang>/tags/ | tag pages | /<lang>/tags/<tag> |
<lang>/download.md | the download page | /<lang>/download |
assets/ | images for every language | embedded in pages |
<lang> is a language folder: en, ru or zh. A file outside docs/, notes/, tags/ and download.md does not become a page
Docs describe how things work now. A history of changes, an opinion or an essay is a note in notes/
File names and order
- The order in the sidebar is set by an
N_prefix on every file and folder insidedocs/:1_,2_and onward, up to10_and beyond - The prefix never reaches the address.
1_game/3_controls.mdopens at/<lang>/docs/game/controls index.mdhas no prefix. It is the landing page of its folder. It takes the folder's position and gives the sidebar group its name- To change the order, rename the files. Enable "Automatically update internal links" in Obsidian and it fixes every link itself
- A name without its prefix is unique within a language. If two files collapse to the same address, the site build reports it
- A prefixed name is identical in every language.
en/docs/2_editor/4_craft/3_difficulty-curve.mdandru/docs/2_editor/4_craft/3_difficulty-curve.mdare the same page notes/has no prefixes and no subfolders. Never renamenotes/cookie-policy: the cookie banner on the site links to it
Frontmatter, title and description
Every page starts like this:
---
title: Difficulty and the curve
date: 2026-09-24
tags: [level_author]
---
# Difficulty and the curve
One line that says what the page is, up to 200 characters, no markup
title, the H1 and the description are written in the page's language. The example above is from the English version- Frontmatter is
title,dateandtags. Nothing else is read.datein theYYYY-MM-DDformat is the date of the last update, not of creation. When you edit a file, setdateto the day of the edit # H1is required and equalstitle. The site shows only the body of the page, so the H1 is the visible title- The first paragraph after the H1 is the description in listings, search and link previews. It is cut at 200 characters. Write it as one line, with no links or formatting. It briefly answers the page's question instead of describing what is on the page, Style guide › Page opening
- Sections are
##, subsections###. They produce anchors and the table of contents. Deeper levels are not needed
Audience tags
Every docs page has 1 to 3 tags from this list. Tags are written in snake_case without #. The site shows them on the page
| Tag | On the site | Who |
|---|---|---|
player | Player | a regular player |
advanced_player | Advanced player | a player interested in the mechanics |
level_author | Level author | makes levels in the editor |
developer | Developer | builds something on the SDK or extends the game |
server_host | Server host | runs a server for themselves and friends |
server_advanced | Advanced server host | a large server host, ready to extend or write a server |
contributor | Contributor | edits the texts and translations in this repository |
Notes in notes/ use topic tags instead, currently legal (Legal documents)
The reader never sees the tag code. The label comes from the tag page <lang>/tags/<code>.md:
- its
titleis the label - its first paragraph describes the audience
- the site adds the list of tagged pages itself
A new tag needs such a page in every language
To mention a tag in the text, link its page: [[level_author]]. The link shows the label in the reader's language
Links
| What | How |
|---|---|
| Another page | [[3_difficulty-curve]], by the full file name with its prefix |
| A section of a page | [[3_difficulty-curve#Anchor]] |
| A folder landing page | [[2_editor/4_craft/index]], with the path, since every such page is called index |
| An external site | [text](https://…), with the full address and scheme |
Links lead to an address without a language, so one source works in every language. [[cookie-policy]] sends an English reader to the English page, a Russian reader to the Russian one and a Chinese reader to the Chinese one
Link only to pages that exist. The site build reports every link it could not resolve
Images
Images live in assets/ at the root of the repository and are embedded with ![[file.png]]
One folder serves every language. Text drawn on an image is not translated with the page
Callouts
| Syntax | Colour | Use |
|---|---|---|
> [!note], > [!info], > [!tip] | blue | context, a shortcut, a recommendation |
> [!warning], > [!caution] | yellow | costs time or quality, destroys work |
> [!danger], > [!bug] | red | rendered, but the style guide does not use them |
Which callout to pick, its title and the limit of two per page - Style guide
Code and other markup
- Code highlighting exists only for
ts,tsx,js,json,csharp,bash,yaml,cssandmd. Other languages are shown as plain text - GFM tables, task lists, footnotes and
==highlights==work - No MDX and no JSX.
{and<are shown as they are