diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java
new file mode 100644
index 0000000..7ebabcb
--- /dev/null
+++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java
@@ -0,0 +1,63 @@
+package thb.jeanluc.adventure.loader;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import thb.jeanluc.adventure.loader.dto.TutorialDto;
+import thb.jeanluc.adventure.loader.dto.TutorialStepDto;
+import thb.jeanluc.adventure.model.Tutorial;
+import thb.jeanluc.adventure.model.TutorialStep;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loads the optional onboarding tutorial from {@code /tutorial.yaml}.
+ * Kept separate from {@link WorldLoader}: the tutorial is onboarding, not world
+ * content. Returns {@link Tutorial#none()} when the file is absent.
+ */
+public class TutorialLoader {
+
+ /** Default classpath base directory. */
+ public static final String DEFAULT_BASE = "/world";
+
+ private final ObjectMapper yaml = new ObjectMapper(new YAMLFactory());
+ private final String basePath;
+
+ public TutorialLoader() {
+ this(DEFAULT_BASE);
+ }
+
+ public TutorialLoader(String basePath) {
+ this.basePath = basePath;
+ }
+
+ /** Reads the tutorial; absent file → {@link Tutorial#none()}. */
+ public Tutorial load() {
+ String resource = basePath + "/tutorial.yaml";
+ try (InputStream in = getClass().getResourceAsStream(resource)) {
+ if (in == null) {
+ return Tutorial.none();
+ }
+ TutorialDto dto = yaml.readValue(in, TutorialDto.class);
+ if (dto == null) {
+ return Tutorial.none();
+ }
+ List steps = new ArrayList<>();
+ if (dto.steps() != null) {
+ for (TutorialStepDto s : dto.steps()) {
+ steps.add(new TutorialStep(
+ s.instruction(),
+ s.expect(),
+ s.minArgs() == null ? 0 : s.minArgs(),
+ s.confirm(),
+ s.hint()));
+ }
+ }
+ return new Tutorial(dto.intro(), steps, dto.closingTips());
+ } catch (IOException e) {
+ throw new WorldLoadException("Failed to parse " + resource, e);
+ }
+ }
+}
diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java
new file mode 100644
index 0000000..ab113eb
--- /dev/null
+++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java
@@ -0,0 +1,7 @@
+package thb.jeanluc.adventure.loader.dto;
+
+import java.util.List;
+
+/** YAML representation of the tutorial document. */
+public record TutorialDto(String intro, List steps, String closingTips) {
+}
diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java
new file mode 100644
index 0000000..e3f622b
--- /dev/null
+++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java
@@ -0,0 +1,11 @@
+package thb.jeanluc.adventure.loader.dto;
+
+/** YAML representation of a tutorial step. */
+public record TutorialStepDto(
+ String instruction,
+ String expect,
+ Integer minArgs,
+ String confirm,
+ String hint
+) {
+}
diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java
new file mode 100644
index 0000000..338b2e2
--- /dev/null
+++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java
@@ -0,0 +1,20 @@
+package thb.jeanluc.adventure.model;
+
+import java.util.List;
+
+/** Onboarding walkthrough: an intro, ordered steps, and closing tips. */
+public record Tutorial(String intro, List steps, String closingTips) {
+
+ public Tutorial {
+ steps = steps == null ? List.of() : List.copyOf(steps);
+ }
+
+ public boolean isEmpty() {
+ return steps.isEmpty();
+ }
+
+ /** An absent tutorial (no steps). */
+ public static Tutorial none() {
+ return new Tutorial(null, List.of(), null);
+ }
+}
diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java
new file mode 100644
index 0000000..e32b587
--- /dev/null
+++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java
@@ -0,0 +1,16 @@
+package thb.jeanluc.adventure.model;
+
+/**
+ * One interactive tutorial step: an instruction, the command that satisfies it
+ * ({@code expect} is a verb name like "look", or "go_direction"/"go_to_room"),
+ * a minimum argument count, a success confirmation, and a hint for a valid but
+ * wrong command.
+ */
+public record TutorialStep(
+ String instruction,
+ String expect,
+ int minArgs,
+ String confirm,
+ String hint
+) {
+}
diff --git a/Semesterprojekt/src/main/resources/world/tutorial.yaml b/Semesterprojekt/src/main/resources/world/tutorial.yaml
new file mode 100644
index 0000000..1dd7573
--- /dev/null
+++ b/Semesterprojekt/src/main/resources/world/tutorial.yaml
@@ -0,0 +1,40 @@
+intro: |
+ Welcome to the manor. Let me show you the ropes — just type the commands as
+ I describe them. (Type 'skip' at any time to jump straight into the game.)
+steps:
+ - instruction: "First, look around the room: type 'look'."
+ expect: look
+ confirm: "Good — 'look' (or 'l') re-describes wherever you are."
+ hint: "When you're ready, just type: look"
+ - instruction: "Examine something you noticed: 'examine ' (or 'x ')."
+ expect: examine
+ minArgs: 1
+ confirm: "That's how you inspect items, exits, and people up close."
+ hint: "Try examining something, e.g.: examine lamp"
+ - instruction: "Pick something up: 'take - '."
+ expect: take
+ minArgs: 1
+ confirm: "Taken. Things you carry go into your inventory."
+ hint: "Pick something up, e.g.: take lamp"
+ - instruction: "Check what you're carrying: 'inventory' (or 'i')."
+ expect: inventory
+ confirm: "That's your inventory."
+ hint: "Type: inventory"
+ - instruction: "Use an item: 'use
- ' — try lighting the lamp."
+ expect: use
+ minArgs: 1
+ confirm: "Some items toggle, read, or react when you use them."
+ hint: "Use something, e.g.: use lamp"
+ - instruction: "Now move to another room: 'go ' (try 'go north')."
+ expect: go_direction
+ confirm: "You can move north / south / east / west between connected rooms."
+ hint: "Head to a connected room, e.g.: go north"
+ - instruction: "Travel back to a room you've already seen: 'go to '."
+ expect: go_to_room
+ confirm: "Nice — 'go to ' auto-walks to any room you've already visited."
+ hint: "Try: go to "
+closingTips: |
+ That's the core. A few more you'll want:
+ read · drop · talk to · give
- to ·
+ use
- on
- (combine things) · map · quests · save · menu.
+ Type 'help' anytime. Good luck.
diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/TutorialLoaderTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/TutorialLoaderTest.java
new file mode 100644
index 0000000..937306b
--- /dev/null
+++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/TutorialLoaderTest.java
@@ -0,0 +1,27 @@
+package thb.jeanluc.adventure.loader;
+
+import org.junit.jupiter.api.Test;
+import thb.jeanluc.adventure.model.Tutorial;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class TutorialLoaderTest {
+
+ @Test
+ void loadsTheTestFixture() {
+ Tutorial t = new TutorialLoader().load(); // test-classpath /world/tutorial.yaml shadows prod
+ assertThat(t.isEmpty()).isFalse();
+ assertThat(t.intro()).isNotBlank();
+ assertThat(t.closingTips()).isNotBlank();
+ assertThat(t.steps()).hasSize(2);
+ assertThat(t.steps().getFirst().expect()).isEqualTo("look");
+ assertThat(t.steps().getFirst().minArgs()).isZero();
+ }
+
+ @Test
+ void missingFileReturnsEmptyTutorial() {
+ Tutorial t = new TutorialLoader("/no-such-base").load();
+ assertThat(t.isEmpty()).isTrue();
+ assertThat(t.steps()).isEmpty();
+ }
+}
diff --git a/Semesterprojekt/src/test/resources/world/tutorial.yaml b/Semesterprojekt/src/test/resources/world/tutorial.yaml
new file mode 100644
index 0000000..950bf44
--- /dev/null
+++ b/Semesterprojekt/src/test/resources/world/tutorial.yaml
@@ -0,0 +1,14 @@
+intro: |
+ Test intro.
+steps:
+ - instruction: "Look: type 'look'."
+ expect: look
+ confirm: "Looked."
+ hint: "Type look."
+ - instruction: "Take: type 'take
- '."
+ expect: take
+ minArgs: 1
+ confirm: "Took it."
+ hint: "Take something."
+closingTips: |
+ Test tips.