Compare commits
2 Commits
55c5e3c2c7
...
35b12e50da
| Author | SHA1 | Date |
|---|---|---|
|
|
35b12e50da | |
|
|
a95b94d73d |
|
|
@ -1550,3 +1550,11 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
|||
## Step 16.9
|
||||
|
||||
- **The game design document is the designer's** (CR-016 §11): its destinations table and map description are left for them to update.
|
||||
|
||||
## CR-017 recorded (Tutorial XP and Skill Point Prompt)
|
||||
|
||||
- **Numbering:** received as "CR-016 — Tutorial XP and Skill Point Prompt" for "Phase 16"; stored as **CR-017**, built as **Phase 17** (its 16.n = 17.n). Its decisions CR-016.D1–D4 are **0.5.106–0.5.109**. Its references: CR-007 → CR-010, CR-008 → CR-011, CR-010 → CR-013, CR-011 → CR-014; its "CR-001 0.5.22" is 0.5.30. The numbering note at the top of the CR file has the full map.
|
||||
|
||||
## Step 17.1
|
||||
|
||||
- **The "loaner at Earth" condition went with the run count** (it is Step 17.4's rule): with five runs ending at the Moon, the deal could never come otherwise. No placement code was needed: accepting the deal assigns the avatar to the Courier at the Earth bay, and an assigned pilot is wherever their ship is (decision 0.5.27).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
# Change Request CR-017 — Tutorial XP and Skill Point Prompt
|
||||
|
||||
> **Numbering note (added when recorded):** this request arrived titled "CR-016 — Tutorial XP and Skill Point Prompt" for "Phase 16". CR-016 (Venus, Io, Europa and the belt) and Phase 16 already existed, so it is stored as **CR-017** and done as **Phase 17**: every "16.n" below means **17.n**. Its references map to this repo as: "CR-001 faster start" = **CR-001** (its Steps 10A.n are the same); "CR-007 veteran pilot" = **CR-010**; "CR-008 settings" = **CR-011** (one-time tips, "Show tips again"); "CR-010 veteran Clipper" = **CR-013**; "CR-011 local-first" = **CR-014** (the notification permission prompt is decision 0.5.91); "CR-001 through CR-015" means this repo's CR-001 to CR-016. Its "CR-001 0.5.22" (the deal) is this plan's **0.5.30**. Its new decisions CR-016.D1–D4 are recorded as **0.5.106 Five tutorial runs, 0.5.107 Tutorial XP multiplier, 0.5.108 Deal location, 0.5.109 Skill point nudge**.
|
||||
>
|
||||
> **On recording:** the Pilots tab already has an attention badge (decision 0.5.49) counting *pilots* with unspent points; this CR makes it count the points. On the web build there is no notification prompt (decision 0.5.91), so there the pop-up follows the deal directly. The save keeps the completed-run count and the deal's availability is computed from it, so no save data changes.
|
||||
|
||||
**Project:** Deadhead: Space Freight Tycoon
|
||||
**Applies to:** a build with Phase 13 and CR-001 through CR-015 complete
|
||||
**Do this:** as **Phase 16**
|
||||
**Related docs:** `change-request-faster-start.md` (CR-001), `change-request-veteran-pilot.md` (CR-007), `change-request-settings.md` (CR-008), `change-request-veteran-clipper.md` (CR-010), `change-request-local-first.md` (CR-011), `DECISIONS.md`
|
||||
|
||||
This change request is written for the local-first engine (CR-011).
|
||||
|
||||
---
|
||||
|
||||
## 1. Why
|
||||
|
||||
When the player takes over Halvorsen's company, their own pilot has no skill points yet, so the Pilots screen and skill system go unnoticed until much later. Rosa arrives with unspent points too, and nothing tells the player.
|
||||
|
||||
This change:
|
||||
|
||||
- Makes the tutorial **five runs instead of four**.
|
||||
- **Doubles the XP** earned on tutorial runs, so the avatar reaches level 1 (and has a skill point) by the time the deal is offered.
|
||||
- Shows a **pop-up right after the deal** telling the player both pilots have skill points and sending them to the Pilots tab.
|
||||
- Adds a **badge on the Pilots tab** showing unspent skill points, so the nudge stays visible if the player dismisses the pop-up.
|
||||
|
||||
## 2. Rules
|
||||
|
||||
| Rule | Detail |
|
||||
|---|---|
|
||||
| Tutorial runs before the deal | 5 (was 4) |
|
||||
| Tutorial XP | Normal XP for the run × the tutorial XP multiplier (default 2). Applies only to tutorial runs. |
|
||||
| The intent | After 5 tutorial runs, the avatar has reached at least level 1 and has at least one unspent skill point, using the slowest-XP choices on the tutorial board. If 2× isn't enough, raise the multiplier until it is, and log the value in `DECISIONS.md`. |
|
||||
| Where the deal happens | Five runs end at the Moon. The deal becomes available after the fifth run **wherever the loaner is**. Accepting it removes the loaner and places the avatar at Earth (Halvorsen flies them home), assigned to the Courier as before. |
|
||||
| Pop-up | After the deal is accepted, and after the notification permission prompt (CR-011) is answered, show the skill point pop-up if any pilot has unspent points. |
|
||||
| Pilots tab badge | Shows the total unspent skill points across all pilots; hidden when zero. |
|
||||
|
||||
### Pop-up content
|
||||
> **Your pilots are ready to train**
|
||||
> You and Rosa both have skill points to spend. Skills unlock better-paying contracts: heavier loads, valuable cargo, longer routes.
|
||||
> **You:** 1 point · **Rosa:** 2 points
|
||||
>
|
||||
> [Go to Pilots] [Later]
|
||||
|
||||
Pilot names and point counts come from state. "Go to Pilots" opens the Pilots tab. "Later" dismisses it. The pop-up is a one-time tip (CR-008), so "Show tips again" in Settings brings it back.
|
||||
|
||||
## 3. Rules for this change
|
||||
|
||||
- All rules in `implementation-plan.md` §0.1 apply: steps in order, one commit per step, each step's test must pass, `packages/sim` stays pure, all numbers live in `packages/content`.
|
||||
- Save migration: a save still in the tutorial keeps its completed-run count; the deal now unlocks at 5. Saves past the tutorial are unchanged.
|
||||
- Update existing tests whose expectations change (tutorial run count, deal availability, tutorial timing). Update `progress.md` after every step.
|
||||
- Stop for the user's review at the end of Phase 16.
|
||||
|
||||
## 4. Decisions
|
||||
|
||||
Record these in `DECISIONS.md` and `implementation-plan.md` §0.5, using the next free numbers (labeled CR-016.D1–D4 here; note the mapping).
|
||||
|
||||
- **CR-016.D1 Five tutorial runs.** The deal unlocks after 5 tutorial runs (replaces 4 in CR-001 0.5.22).
|
||||
- **CR-016.D2 Tutorial XP multiplier.** Tutorial runs earn normal XP × the tutorial XP multiplier (default 2), tuned so the avatar has at least one skill point at the deal.
|
||||
- **CR-016.D3 Deal location.** The deal is available after the fifth run wherever the loaner is; accepting it places the avatar at Earth (replaces the "loaner at Earth" condition in CR-001 0.5.22).
|
||||
- **CR-016.D4 Skill point nudge.** A one-time pop-up after the deal, plus a persistent unspent-points badge on the Pilots tab.
|
||||
|
||||
## 5. Steps — Phase 16
|
||||
|
||||
### Step 16.1 — Content: tutorial settings
|
||||
- Set tutorial runs before the deal to 5. Add the tutorial XP multiplier to GameConfig (default 2).
|
||||
- **Test:** Config validates; the multiplier is at least 1.
|
||||
|
||||
### Step 16.2 — Sim: tutorial XP and the level-1 guarantee
|
||||
- XP for a tutorial run is normal XP × the multiplier.
|
||||
- **Test:** A tutorial run grants exactly the multiplier times a non-tutorial run's XP for the same route and cargo. Taking the **lowest-XP** contract on every board for 5 tutorial runs (including the scripted "Unmarked crates" run), the avatar reaches at least level 1 with at least one unspent skill point. If this fails, raise the multiplier in content, not the test.
|
||||
|
||||
### Step 16.3 — Sim: tutorial timing
|
||||
- Update the CR-001 Step 10A.3 timing test for 5 runs.
|
||||
- **Test:** The sum of the five slowest possible tutorial trip times is under 7 real minutes, keeping the "own a ship in under 10 minutes" target.
|
||||
|
||||
### Step 16.4 — Engine: deal availability and placement
|
||||
- The deal becomes available after 5 resolved tutorial runs with nothing in flight, regardless of the loaner's location. Accepting it places the avatar at Earth before assigning them to the Courier.
|
||||
- Save migration per §3.
|
||||
- **Test:** After 4 runs the deal is not available; after the fifth (ending at the Moon) it is. Accepting it leaves the avatar at Earth crewing the Courier, Rosa at Earth crewing the Clipper (CR-010), and no loaner. A save migrated mid-tutorial with 4 runs done needs one more run.
|
||||
|
||||
### Step 16.5 — Client: Pilots tab badge
|
||||
- Show the total unspent skill points on the Pilots tab; hide it at zero.
|
||||
- **Test:** Component test: with 3 unspent points across pilots the badge reads 3; after spending all of them it disappears.
|
||||
|
||||
### Step 16.6 — Client: skill point pop-up
|
||||
- After the deal is accepted and the notification permission prompt is answered, show the pop-up in §2 if any pilot has unspent points, listing each such pilot and their count. Register it as a one-time tip (CR-008).
|
||||
- **Test:** Component test: the pop-up appears only after both the deal and the permission prompt, lists the right pilots and counts, "Go to Pilots" navigates to the Pilots tab, "Later" dismisses it, and it doesn't appear again until tips are reset. With no unspent points, it doesn't appear.
|
||||
|
||||
### Step 16.7 — Plan amendments and regression
|
||||
- Apply the amendments in §6.
|
||||
- Run the full suite.
|
||||
- **Test:** CI is green. The Phase 12 balance script still meets all timing targets, including the deal in under 10 minutes. Manual check: on a fresh account, fly 5 tutorial runs, accept the deal, confirm the pop-up shows points for both pilots, and spend them from the Pilots tab.
|
||||
|
||||
## 6. Amendments
|
||||
|
||||
| Where | Change |
|
||||
|---|---|
|
||||
| Plan 0.5 | Add CR-016.D1–D4; amend CR-001 0.5.22 (run count and loaner-at-Earth condition). |
|
||||
| CR-001 §2 | "After four runs (back at Earth)" becomes "after five runs". |
|
||||
| CR-001 10A.1 | Tutorial runs default 5. |
|
||||
| CR-001 10A.3 | Timing test uses five runs (16.3). |
|
||||
| CR-001 10A.7, 10A.8 | Deal availability and avatar placement (16.4). |
|
||||
| CR-008 | New one-time tip registered (16.6). |
|
||||
|
||||
## 7. Out of scope
|
||||
|
||||
Changing normal (non-tutorial) XP rates, giving Rosa extra points, forcing the player to spend points before continuing, and anything else in `implementation-plan.md` §0.3.
|
||||
|
|
@ -161,6 +161,10 @@ These answer the pre-build review. Log each one in `DECISIONS.md` as it is imple
|
|||
103. **Goods additions** (CR-016). CR-016 §4's goods and producer/destination changes join the catalog (decision 0.5.54); every location produces at least one Standard good.
|
||||
104. **Asteroid belt** (CR-016). A visual band of haze and rocks between 314M and 494M km from the Sun, static and non-interactive. No hazards, mining claims or belt events (deferred).
|
||||
105. **Jupiter-system layout** (CR-016). Io and Europa sit at fixed screen offsets from Jupiter (as the Moon from Earth, decision 0.5.39); colliding labels collapse to the parent's with a "+N" marker until tapped or zoomed in.
|
||||
106. **Five tutorial runs** (CR-017). The deal unlocks after 5 tutorial runs (was 4, decision 0.5.30).
|
||||
107. **Tutorial XP multiplier** (CR-017). Tutorial runs earn normal XP × the tutorial XP multiplier (default 2), so the avatar has at least one skill point at the deal.
|
||||
108. **Deal location** (CR-017). The deal is available after the fifth run wherever the loaner is; accepting it places the avatar at Earth (replaces 0.5.30's "loaner at Earth" condition).
|
||||
109. **Skill point nudge** (CR-017). A one-time pop-up after the deal (and after the notification prompt) lists each pilot's unspent points; the Pilots tab badge shows the total unspent points.
|
||||
|
||||
### 0.6 Translating change requests written for the server (CR-014 §7)
|
||||
|
||||
|
|
@ -812,6 +816,12 @@ Steps 16.1–16.9 are specified in `memory-bank/change-requests/CR-016-venus-io-
|
|||
|
||||
---
|
||||
|
||||
## Phase 17 — Tutorial XP and Skill Point Prompt (CR-017)
|
||||
|
||||
Steps 17.1–17.7 are specified in `memory-bank/change-requests/CR-017-tutorial-xp-skill-prompt.md` §5 (written there as 16.1–16.7), with its §6 amendments. Stop for review at the end.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done (Base Game)
|
||||
|
||||
- All step tests pass in CI.
|
||||
|
|
|
|||
|
|
@ -409,3 +409,15 @@ Spec: `memory-bank/change-requests/CR-016-venus-io-europa-belt.md` (received as
|
|||
3. **Map review:** the screenshots in `docs/screenshots/16-6-*`, `16-7-*` and `16-8-*` (desktop and phone; `16-8-new-bodies-jupiter-*` zoomed in on the Jupiter system).
|
||||
|
||||
**Phase 16 complete. Stopped for the user's review.**
|
||||
|
||||
## Phase 17 — Tutorial XP and Skill Point Prompt (CR-017)
|
||||
|
||||
Spec: `memory-bank/change-requests/CR-017-tutorial-xp-skill-prompt.md` (received as "CR-016 … Phase 16"). New decisions recorded as plan §0.5.106–0.5.109.
|
||||
|
||||
- [x] **Step 17.1 — Content: tutorial settings.** GameConfig: `tutorialRunsBeforeDeal` 5 (was 4) and a new `tutorialXpMultiplier` 2 (at least 1; used from 17.2). Pulled forward so the game keeps working: the deal no longer waits for the loaner to be back at Earth (decision 0.5.108) in the engine (`isDealAvailable`) and the balance simulator, since five runs end at the Moon; accepting it already puts the avatar on the Courier at the Earth bay. Balance medians: deal 3.7 min, first purchase 92.6 (45–95), discovery 49.6, loan 138.5, four ships 136 vs 138. Tests: `packages/content/test/config.test.ts` (five runs at double XP; a multiplier under 1 is rejected); the engine's deal tests now expect the deal after five runs ending at the Moon (not after four), after three with the three-run config, and accepting with the loaner at the Moon to put the avatar and the Courier at Earth.
|
||||
- [ ] Step 17.2 — Sim: tutorial XP and the level-1 guarantee
|
||||
- [ ] Step 17.3 — Sim: tutorial timing
|
||||
- [ ] Step 17.4 — Engine: deal availability and placement
|
||||
- [ ] Step 17.5 — Client: Pilots tab badge
|
||||
- [ ] Step 17.6 — Client: skill point pop-up
|
||||
- [ ] Step 17.7 — Plan amendments and regression
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ export const gameConfig: GameConfig = {
|
|||
// One tutorial contract per mass tier (CR-001).
|
||||
tutorialBoardSize: 3,
|
||||
tutorialLoanerShipModelId: "company-courier",
|
||||
tutorialRunsBeforeDeal: 4,
|
||||
// Five runs, at double XP, so the avatar has a skill point at the deal (CR-017).
|
||||
tutorialRunsBeforeDeal: 5,
|
||||
tutorialXpMultiplier: 2,
|
||||
tutorialMassTonnes: { light: 5, medium: 12, full: 20 },
|
||||
// 20 t adds 0.4 game hours (24 real seconds) of loading and unloading.
|
||||
handlingGameHoursPerTonne: 0.02,
|
||||
|
|
|
|||
|
|
@ -216,8 +216,10 @@ export const gameConfigSchema = z
|
|||
tutorialBoardSize: positiveInt,
|
||||
/** Ship model the company lends during the tutorial (decision 0.5.29). */
|
||||
tutorialLoanerShipModelId: idSchema,
|
||||
/** Tutorial runs completed before the deal is offered (decision 0.5.30). */
|
||||
/** Tutorial runs completed before the deal is offered (decisions 0.5.30, 0.5.106). */
|
||||
tutorialRunsBeforeDeal: positiveInt,
|
||||
/** Tutorial runs earn normal XP × this (decision 0.5.107). */
|
||||
tutorialXpMultiplier: z.number().min(1),
|
||||
/** Cargo mass of the three tutorial contracts (decision 0.5.31). */
|
||||
tutorialMassTonnes: z
|
||||
.object({
|
||||
|
|
|
|||
|
|
@ -84,3 +84,17 @@ describe("game config", () => {
|
|||
expect(gameConfig.longHaulDutyBonusGameHours).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tutorial settings (Step 17.1, decisions 0.5.106, 0.5.107)", () => {
|
||||
it("runs five tutorial runs before the deal, at double XP", () => {
|
||||
expect(gameConfig.tutorialRunsBeforeDeal).toBe(5);
|
||||
expect(gameConfig.tutorialXpMultiplier).toBe(2);
|
||||
});
|
||||
|
||||
it("never lets the tutorial XP multiplier fall below 1", () => {
|
||||
expect(gameConfigSchema.safeParse(gameConfig).success).toBe(true);
|
||||
expect(gameConfigSchema.safeParse({ ...gameConfig, tutorialXpMultiplier: 0.5 }).success).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ const validGameConfig = {
|
|||
pilotHireCost: 1000,
|
||||
pilotProfitShare: 0.25,
|
||||
tutorialLoanerShipModelId: "company-courier",
|
||||
tutorialRunsBeforeDeal: 4,
|
||||
tutorialRunsBeforeDeal: 5,
|
||||
tutorialXpMultiplier: 2,
|
||||
tutorialMassTonnes: { light: 5, medium: 12, full: 20 },
|
||||
handlingGameHoursPerTonne: 0.02,
|
||||
upgradeLevels: [{ costPercentOfPrice: 10, shopRealMinutes: 10 }],
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ export function isDealAvailable(save: SaveDocument): boolean {
|
|||
player.tutorialActive &&
|
||||
!player.dealAccepted &&
|
||||
player.tutorialRunsCompleted >= content.config.tutorialRunsBeforeDeal &&
|
||||
player.loanerLocationId === "earth" &&
|
||||
// Wherever the loaner is (decision 0.5.108): accepting puts the avatar at Earth.
|
||||
!runInFlight
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,13 +81,20 @@ describe("accepting the deal", () => {
|
|||
expect(await snapshot(guest)).toEqual(before);
|
||||
});
|
||||
|
||||
it("fails while the loaner is at the Moon", async () => {
|
||||
it("is accepted with the loaner at the Moon, and the avatar ends up at Earth (CR-017)", async () => {
|
||||
const guest = await signUp(testApp().app);
|
||||
await readyForDeal(guest);
|
||||
await edit(testApp().app, (save) => {
|
||||
save.player.loanerLocationId = "moon";
|
||||
});
|
||||
expect((await accept(guest)).statusCode).toBe(409);
|
||||
const response = await accept(guest);
|
||||
expect(response.statusCode).toBe(200);
|
||||
const state = playerStateSchema.parse(response.json());
|
||||
const avatar = state.pilots.find((p) => p.isAvatar)!;
|
||||
const courier = state.ships.find((s) => s.id === avatar.assignedShipId)!;
|
||||
expect(avatar.locationId).toBe("earth");
|
||||
expect(courier).toMatchObject({ modelId: "courier", locationId: "earth" });
|
||||
expect(state.tutorial).toBeNull();
|
||||
});
|
||||
|
||||
it("fails the second time", async () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { useTestApp, signUp, getState, flyTutorialRun } from "../support";
|
||||
|
||||
// The deal after three runs instead of four: the loaner is then at the Moon.
|
||||
// The deal after three runs instead of five: the loaner is then at the Moon.
|
||||
vi.mock("@deadhead/content", async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import("@deadhead/content")>();
|
||||
return {
|
||||
|
|
@ -17,19 +17,18 @@ vi.mock("@deadhead/content", async (importOriginal) => {
|
|||
const testApp = useTestApp();
|
||||
|
||||
describe("deal availability with tutorialRunsBeforeDeal = 3", () => {
|
||||
it("waits until a fourth run returns the loaner to Earth", async () => {
|
||||
it("offers the deal after the third run, though the loaner is at the Moon (decision 0.5.108)", async () => {
|
||||
const guest = await signUp(testApp().app);
|
||||
for (let run = 1; run <= 3; run++) await flyTutorialRun(testApp(), guest);
|
||||
for (let run = 1; run <= 2; run++) await flyTutorialRun(testApp(), guest);
|
||||
expect((await getState(testApp().app, guest)).tutorial).toMatchObject({
|
||||
runsCompleted: 3,
|
||||
loanerLocationId: "moon",
|
||||
runsCompleted: 2,
|
||||
dealAvailable: false,
|
||||
});
|
||||
|
||||
const state = await flyTutorialRun(testApp(), guest);
|
||||
expect(state.tutorial).toMatchObject({
|
||||
runsCompleted: 4,
|
||||
loanerLocationId: "earth",
|
||||
runsCompleted: 3,
|
||||
loanerLocationId: "moon",
|
||||
dealAvailable: true,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,20 +55,20 @@ describe("tutorial flow v2", () => {
|
|||
expect(third.find((c) => c.scriptedId === "unmarked-crates")).toBeDefined();
|
||||
});
|
||||
|
||||
it("makes the deal available after four runs ending at Earth, not after three", async () => {
|
||||
it("makes the deal available after five runs ending at the Moon, not after four (CR-017)", async () => {
|
||||
const guest = await signUp(testApp().app);
|
||||
for (let run = 1; run <= 3; run++) await flyTutorialRun(testApp(), guest);
|
||||
const afterThree = await getState(testApp().app, guest);
|
||||
expect(afterThree.tutorial).toMatchObject({
|
||||
runsCompleted: 3,
|
||||
loanerLocationId: "moon",
|
||||
dealAvailable: false,
|
||||
});
|
||||
|
||||
const afterFour = await flyTutorialRun(testApp(), guest);
|
||||
for (let run = 1; run <= 4; run++) await flyTutorialRun(testApp(), guest);
|
||||
const afterFour = await getState(testApp().app, guest);
|
||||
expect(afterFour.tutorial).toMatchObject({
|
||||
runsCompleted: 4,
|
||||
loanerLocationId: "earth",
|
||||
dealAvailable: false,
|
||||
});
|
||||
|
||||
const afterFive = await flyTutorialRun(testApp(), guest);
|
||||
expect(afterFive.tutorial).toMatchObject({
|
||||
runsCompleted: 5,
|
||||
loanerLocationId: "moon",
|
||||
runInFlight: false,
|
||||
dealAvailable: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -384,7 +384,8 @@ export function simulate(options: SimulationOptions): SimulationResult {
|
|||
// --- Tutorial and the deal (server dispatch.ts, deal.ts). ---
|
||||
function playTutorial(): boolean {
|
||||
if (flights.some((f) => f.record.tutorial)) return false;
|
||||
if (runsCompleted >= config.tutorialRunsBeforeDeal && loanerLocationId === "earth") {
|
||||
// Wherever the loaner is (decision 0.5.108).
|
||||
if (runsCompleted >= config.tutorialRunsBeforeDeal) {
|
||||
acceptDeal();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue