The Tutorial Is Not the Game.
Make Your First Open-Source Contribution to Rustbound Idle

Canonical: https://journeytocode.io/the-tutorial-is-not-the-game/
Up until now, I knew what came next.
I knew we needed damage before enemies. Enemies needed a player. The player needed a game to own the fight, and the game needed time before it could keep moving while we were away. A save had to become text before it could cross into a browser. The browser had to be told, firmly, not to get the rules.
That worked because this was a tutorial. Somebody had to choose the path.
The path is finished.
So I do not want to keep making every decision myself. Rustbound Idle is now an open-source community project. If you followed this series, you already understand enough of it to take part.
This is the final Rustbound article. It has no new game code. It is about how a finished tutorial becomes a project other people can change—and how your first change can be small enough to finish this week.
Where the game stands
Count what we built, one article at a time:
small pure functions → composition → Player + Enemy → borrowing
→ Game state → gold, XP, and levels → upgrades and named outcomes
→ tick(seconds) → a headless library → saves as text
→ WebAssembly → a Web Component → a public URL
→ an installable offline app → saves that do not get the final word
It is a small game. Click a slime, earn gold, buy an upgrade, close the tab, and return to progress. It is deployed, installable, and tested in Rust and a real browser.
It is also unfinished on purpose. There are no bosses. There is one upgrade. The interface is plain. Every gap is something a reader of this series can fill because every gap lives in a layer you have already seen.
You do not have to be a Rust developer
Open source on Rustbound does not mean everybody disappears into game.rs.
| Part of the project | Ways to help |
|---|---|
| Rust engine | rules, combat, progression, saves |
| WebAssembly border | what JavaScript may ask Rust to do |
| Web Component | the screen, browser clock, and keyboard |
| Design and accessibility | how the game looks, feels, and communicates |
| PWA | installation, caching, and updates |
| Tests | Rust behavior and real-browser checks |
| Documentation | setup, architecture, and clearer explanations |
| Community | triage, review, and welcoming the next person |
If the Rust lessons were your favorite, there is plenty of Rust. If you liked the browser lessons more than the ownership ones, improve the layout or reduced-motion behavior. If you do not want to write code yet, play the game and explain what feels wrong.
None of those is a lesser contribution. A project is bigger than the language in its engine.
Question: Which contribution requires writing Rust?
- Reporting a confusing keyboard interaction
- Clarifying a setup step in CONTRIBUTING.md
- Adding a new combat rule inside the engine
- Testing the installed app while offline
Answer: 3. Adding a new combat rule inside the engine
Explanation: Rust owns game rules. The other three still improve the project, and all three are real contributions.
Give each GitHub tool one job
Repositories offer several places to talk. Mixing them together makes a project feel louder than it is.
| Tool | The question it answers |
|---|---|
| Discussions | “I have an idea. What should this become?” |
| Issues | “We understand a specific piece of work. Who wants it?” |
| Pull requests | “Here is a change we can review.” |
| Versioned docs | “What must stay true as this code changes?” |
An idea can begin half formed in a Discussion. Once its behavior and boundaries are clear, it becomes an Issue. Somebody builds that issue on a branch and opens a pull request. Architecture that must change with the code lives in docs/, reviewed in the same pull request.
A project board will help when enough work is active that Issues no longer answer “what matters now?” A wiki will help when community decisions and meeting notes need a durable home. Creating either on day one would give us another empty place to maintain, so we are waiting until the project earns them.
That is part of open source too: infrastructure should solve a problem the community actually has.
A public repository still has to say yes
Public code does not automatically welcome contributions. A newcomer needs to know where to begin, how to run the project, and what the maintainers will protect.
Rustbound now has that path:
README.md what this project is
CONTRIBUTING.md how to take part
CODE_OF_CONDUCT.md how we treat one another
SECURITY.md what to report privately
SUPPORT.md where questions belong
CHANGELOG.md what changed
docs/ARCHITECTURE.md which layer owns what
.github/
├── ISSUE_TEMPLATE/ useful bug and feature reports
├── PULL_REQUEST_TEMPLATE.md
└── workflows/ci.yml checks every proposed change
The contribution guide says one thing before it says anything about tools:
You do not need to write Rust to contribute. Ideas, testing, documentation, design, accessibility feedback, and welcoming other contributors all count.
Then it protects the three boundaries we spent this series building:
- The Rust engine never learns about the browser.
- The browser never calculates a game rule.
- The service worker makes application files available offline; it owns no game state.
Those are not arbitrary style preferences. They keep one change from quietly putting reward math in JavaScript or saves in the cache.
The repository also has pull-request CI now. Every proposal runs formatting, Clippy, and the Rust test suite before a person reviews it. Automation cannot decide whether an idea is good. It can save reviewers from repeatedly finding the same mechanical mistakes.
The guide also says what to do when an AI tool helps. Say that it helped, and roughly how. Keep the commit authored by the person doing the work. A tool can be credited with a Co-authored-by: trailer, but it should not replace the contributor's name with its own.
That sounds like a small distinction. Our first pull request showed why it matters.
Question: A proposed offline-progress cap changes how much progress a player earns. Where should the design conversation begin?
- In a Discussion, because the rule and its owner are not settled
- In a pull request with whichever implementation was quickest
- In SECURITY.md
- In the service-worker cache
Answer: 1. In a Discussion, because the rule and its owner are not settled
Explanation: A pull request is late in the conversation. First agree on the behavior and the architectural owner; then open a scoped issue.
Someone got here first. Then somebody did it again.
I had a walkthrough ready for this section. It added two assertions to src/rules.rs: one checking that level 3 costs 50 XP, and one checking that the largest possible stage cannot overflow an enemy's health. I filed it as issue #1, added the good first issue label, and planned to publish this article a week later.
The repository was public. The issue was open. Before the article went out, somebody found it.
@ShahadatTest opened pull request #8. They put both assertions inside the existing tests, explained what saturating_* protects us from, disclosed that a Cursor agent helped, and ran the checks. The pull request changed three lines in one file. It was merged the same day.
So I rewrote the walkthrough.
The next exercise tested click_upgrade_bonus, the one rule in rules.rs with no test. It is the small function from Article 7 that turns a count of upgrades into bonus damage. I filed issue #9 and left one decision for the contributor: work out what click_upgrade_bonus(u32::MAX) should return before running the test.
Ten minutes later, @dannyward630 opened pull request #10. It has three assertions, a comment explaining the maximum-value boundary, every local check from CONTRIBUTING.md, and a description that names the Codex assistance. The commit is authored by the contributor.
The first contribution landed, and the next proposal arrived, before Rustbound Idle had officially invited anyone to contribute.
There was one wrinkle in the first pull request. Its commit was authored by cursoragent, so GitHub credited the tool account instead of the person who found the issue and carried the work through review. That does not erase the contribution. It did reveal a gap in our instructions, so CONTRIBUTING.md now says plainly that the person doing the work should author the commit.
The second pull request followed that guidance. The guide changed, and the next contributor used it. That is a rather efficient documentation test.
Then came the third, and it taught a different lesson. Pull request #11 took a help wanted issue from Article 14: make sure the service worker's list of cached files cannot drift away from what the release build actually produces. The first version worked. Every check passed. It was also 971 lines long, most of them a small shell interpreter written to guard a ten-entry list, and each round of automated review had added more code to review.
I asked one question in my own words: what does this test do the first time somebody adds a line to build.sh that it does not recognize, and is that what you want? The answer came back clear—it would silently skip the line, and no, that is not what a guard should do—and the next version was less than half the size. It accepts only the exact command shapes the build script uses today and fails loudly on anything else. That is the version on main.
A test can pass and still be too big. Review is where that gets noticed, and the contributor who takes that feedback well is the one you want back.
I stopped rewriting the exercise after that. A specific issue can close between writing a paragraph and checking the paragraph. The useful part is not the issue number. It is the loop.
Question: An AI assistant helped write a change. Who should be the commit author?
- The tool account, because it produced some of the text
- The person submitting and standing behind the work
- Nobody; AI-assisted changes cannot have an author
- The maintainer who merges the pull request
Answer: 2. The person submitting and standing behind the work
Explanation: Disclose the help, but keep the commit authored by the person responsible for the change. A Co-authored-by trailer can credit the tool separately.
Read the tests before you touch the code
Every contributor to Rustbound does the same first thing, whether they know it or not: they read a test to learn what a rule is supposed to do. The tests in src/rules.rs are the project's specification, one assertion at a time.
Try it in reverse. Here is the test for one rule from Article 6, and the function body is missing. Rebuild the function from its tests, then press Check my code.
Rebuild the rule from its tests
/// How much XP a player needs to move past `level`.
///
/// The tests below are the specification. Level 1 costs 20 XP, and
/// every level after that costs 15 more than the one before it.
pub fn xp_to_next_level(level: u32) -> u32 {
// TODO: level 1 → 20, level 2 → 35, level 3 → 50.
// Use saturating arithmetic so a huge level cannot wrap around.
0
}
Spoiler: the checks for this challenge
These tests run when you press Check my code on the original article. Reading them first gives away the answer.
/// Level 1 should cost 20 XP.
#[test]
fn level_one_costs_twenty() {
assert_eq!(xp_to_next_level(1), 20);
}
/// Each level after the first costs 15 more: level 3 should cost 50.
#[test]
fn levels_get_more_expensive() {
assert_eq!(xp_to_next_level(2), 35);
assert_eq!(xp_to_next_level(3), 50);
}
/// A huge level must not wrap around to a cheap one. Try saturating_sub, saturating_mul and saturating_add.
#[test]
fn the_top_level_does_not_overflow() {
assert_eq!(xp_to_next_level(u32::MAX), u32::MAX);
}
Run and edit this example on the canonical article.
Tip: If your version passes the first two checks and fails the third, you have just met the bug that
saturating_*exists to prevent. Article 1 explains why the engine never uses plain+or*.
That is the whole skill. A contributor who can read a test and say what it protects can review a pull request, write the next test, and explain a change in their own words.
A first contribution, all the way through
Start with the live good first issue list. A useful first issue has a boundary, a definition of done, and at least one decision left for you to make.
Before writing code, open the issue and check whether somebody has commented or linked a pull request. Leave a short comment saying you want to work on it. That gives the maintainer a chance to confirm the scope, and it keeps two people from solving the same small problem at once.
Note: The label may be empty when you arrive. That is not a dead end. It means the small prepared work was taken. Check the other issues, or open a Discussion about an idea and ask how to make its first step small.
Once you have something scoped, fork the repository on GitHub, clone your fork, and make a branch named for the change. This example names a documentation branch. Use the words from your issue for yours:
git clone https://github.com/YOUR-USERNAME/rustbound-idle.git
cd rustbound-idle
git switch -c docs-clarify-setup
cargo test -j 2 --all-targets
The existing tests should pass before you touch anything. If they do not, stop and describe what happened in the issue. That is useful information, not a failed attempt.
Make the change the issue asks for. Keep it focused. A test belongs in the existing tests module of the file it checks. A game rule belongs in rules.rs as a small pure function. Browser work still respects the three boundaries: Rust owns the rules, the browser owns browser facts, and the service worker owns cached application files.
Run the checks the project asks for:
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test -j 2 --all-targets
Then stage the pieces you intend to commit, review them, commit, and push the branch you are on:
git add -p
git diff --staged
git commit -m "Clarify the local setup instructions"
git push -u origin HEAD
Open the pull request GitHub offers. The template asks what changed, why it helps, and how you checked it. Answer those in your own words. That is the part that shows the reviewer you understand the change instead of only copying its syntax.
Important: If an AI tool helped, say so. Keep the commit authored by you. The tool may have produced a useful line, but you are the person choosing to put that line into the project and stand behind it.
A reviewer may request a change. That is not a polite rejection; review is part of building the thing together.
If the branch-and-merge part of that loop is new to you, practice it here first. Type each command yourself; the terminal tells you what Git would print.
Practice the branch-and-merge loop
Starting state: main has a few commits, and a change is already staged.
- Create a branch for the work and switch to it.
- Commit the staged change with a message in quotes.
- Go back to
main. - Merge the branch, then delete it.
What to notice: main does not move until the merge. On GitHub, the pull request, its conversation, and the automated checks all happen between steps 2 and 4.
Try the interactive Git practice terminal on the canonical article.
Find the next door
The issue list will change. That is a sign that the project is moving, not that the links in this article failed.
- Browse
good first issuefor small prepared work. - Browse all open issues by the part of the project you want to learn.
- Start in Discussions when the behavior or architectural owner is not settled.
- Read pull request #8, #10 and #11 to compare how three proposals explain their changes, and how one of them got smaller in review.
Labels help you choose by interest: rust, frontend, pwa, testing, accessibility, and design. good first issue means the task is scoped, not that the contribution matters less.
“Improve accessibility” is a wish. “Test with a keyboard and screen reader, record the setup, then fix one focused group of problems” is work somebody can finish.
label: Find a good first issue
href: https://github.com/journey-to-code-org/rustbound-idle/labels/good%20first%20issue
style: primary
Question: What makes a good first issue good?
- It is unimportant if nobody finishes it
- It quietly requires understanding every layer
- Its scope, context, and definition of done are clear
- It has no review because beginners should not receive feedback
Answer: 3. Its scope, context, and definition of done are clear
Explanation: Small and clear is the goal. A first issue should teach one useful part of the project without hiding six more projects inside it.
The first disagreement is already open
Our first design Discussion asks: where should the offline-progress cap live?
The browser owns the wall clock. Rust owns gameplay rules. “Only eight hours away should count” touches both truths.
Should the engine own the cap so every future interface behaves the same? Should the browser clamp elapsed time because it measured that time? Should the browser provide friendly behavior while Rust enforces the invariant? Or is unlimited offline progress part of the game's identity?
There is no implementation issue yet. That is deliberate. We should agree on the rule and its owner before asking somebody to code it.
The welcome Discussion is also live. Tell us what you want to learn, test, or change. We will begin asynchronously. A recurring live meeting should solve a need for real contributors, not create a calendar obligation before the community exists.
Maintainers still have a job
Community-driven does not mean directionless.
Maintainers still protect code quality, architectural boundaries, security, accessibility, and how people treat one another. They keep issues ready, review pull requests, and say no when a change would harm the project.
But maintaining is different from owning every idea. If somebody proposes a mechanic I never would have designed, that is not a reason to refuse it. The useful question is:
Does this make Rustbound better, and can we build it without breaking the boundaries that are helping us?
I want this project to be able to surprise me.
It already has, three times, before the invitation even went out.
The tutorial stays still
One practical promise: the lesson checkpoints are frozen.
article_1 article_2 ... article_13 article_14 article_15
Matching tags, article-01 through article-15, make those snapshots permanent. The main branch is where the community keeps building.
There is no article_16 code branch because this lesson does not change the game. Its artifact is the community around it.
Somebody reading Article 4 next year should not compare beginner code with Rustbound v3.7, an inventory system, and fifteen contributors. The tutorial stays reproducible while the game grows past it.
From your first function to your first contribution
We began with this:
pub fn click_damage(base: u32, bonus: u32) -> u32 {
base.saturating_add(bonus)
}
That function became part of a player. The player fought enemies. Enemies paid gold and XP. Gold needed somewhere to go, and time became part of the game. A growing program became modules, then a library, then a saved file, then a WebAssembly module, then a screen, then a public URL, then an app that works with no signal.
Through all of it, that first function never changed.
Up until this point, I knew what came next.
I do not anymore.
That is your part.
Open a Discussion. Pick up an issue. Add one Rust test. Fix something that is hard to use with a keyboard. Explain a confusing setup step. Propose something ridiculous, then help us find the useful version hiding inside it.
We finished building my example game.
Now let us see what we can build.
Small function. Bigger game.
Keep going
About the author
- Web developer building full-stack applications with JavaScript, React, Node.js, and modern web APIs.
Comments
Join the discussionFreezing the lesson checkpoints while letting main keep moving is the decision I admire most here — it protects the reader who arrives next year from comparing beginner code with a v3.7 game they never built. I also liked that you named the non-Rust contributions as real ones: docs, keyboard testing, accessibility. Too many open-source invitations say “everyone welcome” and then only reward code. And the story about the first PR landing before the article even went out is the best proof the invitation works — people were already reading the tests as the specification. Good luck with the offline-progress cap discussion; that question about who owns the rule is exactly the right first disagreement to have in public.
Rustbound - Build Your First Rust App, One Function at a Time
Learn Rust by building a real idle game from small functions to a complete WebAssembly app with testing, persistence, Web Components, PWA support, and deployment.





