Skip to content

Commit a9056fd

Browse files
committed
Build game design foundation
1 parent 1420bc8 commit a9056fd

19 files changed

Lines changed: 1898 additions & 1474 deletions

‎assets/toolbox/game-design/js/index.js‎

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
readServerToolConstants,
44
requireServerConstant,
55
} from "../../../../src/api/server-api-client.js";
6+
import { getSessionCurrent } from "../../../../src/api/session-api-client.js";
67

78
const constants = readServerToolConstants("game-design");
89

@@ -30,7 +31,9 @@ const elements = {
3031
capabilityDemoNotes: document.querySelector("[data-game-design-capability-notes]"),
3132
capabilityDemoPanel: document.querySelector("[data-game-design-capability-panel]"),
3233
configurationLink: document.querySelector("[data-game-design-configuration-link]"),
34+
coreLoop: document.querySelector("[data-game-design-core-loop]"),
3335
designSummary: document.querySelector("[data-game-design-summary]"),
36+
designNotes: document.querySelector("[data-game-design-notes]"),
3437
designStatus: document.querySelector("[data-game-design-status]"),
3538
form: document.querySelector("[data-game-design-form]"),
3639
gameType: document.querySelector("[data-game-design-type]"),
@@ -40,20 +43,30 @@ const elements = {
4043
handoffProgress: document.querySelector("[data-game-design-game-progress]"),
4144
handoffPublishing: document.querySelector("[data-game-design-publishing-progress]"),
4245
handoffRecommended: document.querySelector("[data-game-design-recommended-tool]"),
46+
loseCondition: document.querySelector("[data-game-design-lose-condition]"),
47+
outputAudience: document.querySelector("[data-game-design-output-audience]"),
4348
outputCapability: document.querySelector("[data-game-design-output-capability]"),
49+
outputCoreLoop: document.querySelector("[data-game-design-output-core-loop]"),
50+
outputLose: document.querySelector("[data-game-design-output-lose]"),
4451
outputMissing: document.querySelector("[data-game-design-output-missing]"),
4552
outputNextStep: document.querySelector("[data-game-design-output-next-step]"),
53+
outputNotes: document.querySelector("[data-game-design-output-notes]"),
4654
outputPlayerMode: document.querySelector("[data-game-design-output-player-mode]"),
55+
outputStory: document.querySelector("[data-game-design-output-story]"),
4756
outputSummary: document.querySelector("[data-game-design-output-summary]"),
4857
outputValidation: document.querySelector("[data-game-design-output-validation]"),
58+
outputWin: document.querySelector("[data-game-design-output-win]"),
4959
playerMode: document.querySelector("[data-game-design-player-mode]"),
5060
playStyle: document.querySelector("[data-game-design-play-style]"),
61+
story: document.querySelector("[data-game-design-story]"),
62+
targetAudience: document.querySelector("[data-game-design-target-audience]"),
5163
gameContext: document.querySelector("[data-game-design-game-context]"),
5264
gameOverlay: document.querySelector("[data-game-design-game-overlay]"),
5365
statusLog: document.querySelector("[data-game-design-log]"),
5466
tableCounts: document.querySelector("[data-game-design-table-counts]"),
5567
validationList: document.querySelector("[data-game-design-validation-list]"),
56-
validationOverlay: document.querySelector("[data-game-design-validation-overlay]")
68+
validationOverlay: document.querySelector("[data-game-design-validation-overlay]"),
69+
winCondition: document.querySelector("[data-game-design-win-condition]")
5770
};
5871

5972
function setText(element, value) {
@@ -92,11 +105,18 @@ function readForm() {
92105
return {
93106
capabilityDemoAuthoring: repository.getActiveGame()?.purpose === "Capability Demo",
94107
capabilityDemoNotes: elements.capabilityDemoNotes?.value,
108+
coreLoop: elements.coreLoop?.value,
109+
designNotes: elements.designNotes?.value,
95110
designSummary: elements.designSummary?.value,
96111
gameType: elements.gameType?.value,
97112
genre: elements.genre?.value,
113+
loseCondition: elements.loseCondition?.value,
98114
playerMode: elements.playerMode?.value,
99-
playStyle: elements.playStyle?.value
115+
playStyle: elements.playStyle?.value,
116+
story: elements.story?.value,
117+
summary: elements.designSummary?.value,
118+
targetAudience: elements.targetAudience?.value,
119+
winCondition: elements.winCondition?.value
100120
};
101121
}
102122

@@ -116,6 +136,24 @@ function clearForm() {
116136
if (elements.designSummary) {
117137
elements.designSummary.value = "";
118138
}
139+
if (elements.story) {
140+
elements.story.value = "";
141+
}
142+
if (elements.coreLoop) {
143+
elements.coreLoop.value = "";
144+
}
145+
if (elements.winCondition) {
146+
elements.winCondition.value = "";
147+
}
148+
if (elements.loseCondition) {
149+
elements.loseCondition.value = "";
150+
}
151+
if (elements.targetAudience) {
152+
elements.targetAudience.value = "";
153+
}
154+
if (elements.designNotes) {
155+
elements.designNotes.value = "";
156+
}
119157
if (elements.capabilityDemoNotes) {
120158
elements.capabilityDemoNotes.value = "";
121159
}
@@ -140,13 +178,48 @@ function applyDesignToForm(design) {
140178
elements.playerMode.value = design.playerMode || "1 Player";
141179
}
142180
if (elements.designSummary) {
143-
elements.designSummary.value = design.designSummary;
181+
elements.designSummary.value = design.summary || design.designSummary;
182+
}
183+
if (elements.story) {
184+
elements.story.value = design.story;
185+
}
186+
if (elements.coreLoop) {
187+
elements.coreLoop.value = design.coreLoop;
188+
}
189+
if (elements.winCondition) {
190+
elements.winCondition.value = design.winCondition;
191+
}
192+
if (elements.loseCondition) {
193+
elements.loseCondition.value = design.loseCondition;
194+
}
195+
if (elements.targetAudience) {
196+
elements.targetAudience.value = design.targetAudience;
197+
}
198+
if (elements.designNotes) {
199+
elements.designNotes.value = design.designNotes;
144200
}
145201
if (elements.capabilityDemoNotes) {
146202
elements.capabilityDemoNotes.value = design.capabilityDemoNotes;
147203
}
148204
}
149205

206+
function currentSession() {
207+
try {
208+
return getSessionCurrent();
209+
} catch {
210+
return { authenticated: false };
211+
}
212+
}
213+
214+
function redirectGuestSaveAction() {
215+
if (currentSession()?.authenticated === true) {
216+
return false;
217+
}
218+
setText(elements.statusLog, "Sign in before saving Game Design.");
219+
window.location.href = new URL("/account/sign-in.html", window.location.href).href;
220+
return true;
221+
}
222+
150223
function renderValidation(validation) {
151224
if (!elements.validationList || !elements.validationOverlay) {
152225
return;
@@ -225,7 +298,13 @@ function renderOutput(snapshot, validation) {
225298
const activeGame = snapshot.activeGame || snapshot.activeProject;
226299
const missingRequirements = validation.findings.map((finding) => finding.label).join(", ");
227300

228-
setText(elements.outputSummary, activeDesign?.designSummary || "No design summary saved yet.");
301+
setText(elements.outputSummary, activeDesign?.summary || activeDesign?.designSummary || "No design summary saved yet.");
302+
setText(elements.outputStory, activeDesign?.story || "No story saved yet.");
303+
setText(elements.outputCoreLoop, activeDesign?.coreLoop || "No core loop saved yet.");
304+
setText(elements.outputWin, activeDesign?.winCondition || "No win condition saved yet.");
305+
setText(elements.outputLose, activeDesign?.loseCondition || "No lose condition saved yet.");
306+
setText(elements.outputAudience, activeDesign?.targetAudience || "No audience saved yet.");
307+
setText(elements.outputNotes, activeDesign?.designNotes || "No notes saved yet.");
229308
setText(elements.outputPlayerMode, activeDesign?.playerMode || "1 Player");
230309
setText(elements.outputValidation, validation.status);
231310
setText(elements.outputNextStep, snapshot.progressHandoff.recommendedNextTool);
@@ -291,6 +370,9 @@ elements.form?.addEventListener("input", renderFormValidation);
291370

292371
elements.form?.addEventListener("submit", (event) => {
293372
event.preventDefault();
373+
if (redirectGuestSaveAction()) {
374+
return;
375+
}
294376
const result = repository.saveDesign(readForm());
295377
setText(elements.statusLog, result.message);
296378
render();

‎docs_build/database/ddl/game-design.sql‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,25 @@
44
-- Target DEV database: gamefoundry_dev
55
-- Scope: executable grouped table DDL for active Supabase/server API migration.
66
-- Authoritative key values are generated by the server/API layer unless a later DEV-only seed exception explicitly applies.
7-
-- Owned tables: game_design_documents, game_design_validation_items
7+
-- Owned tables: game_design_documents, game_design_validation_items, game_design_sections, game_design_capability_demos
88
CREATE TABLE IF NOT EXISTS game_design_documents (
99
key text PRIMARY KEY,
1010
"gameKey" text REFERENCES game_workspace_games(key),
1111
"title" text,
12+
"gamePurpose" text,
13+
"gameType" text,
14+
"genre" text,
15+
"playStyle" text,
16+
"playerMode" text,
17+
"summary" text,
18+
"story" text,
19+
"coreLoop" text,
20+
"winCondition" text,
21+
"loseCondition" text,
22+
"targetAudience" text,
23+
"designNotes" text,
24+
"capabilityDemoAuthoring" boolean NOT NULL DEFAULT false,
25+
"capabilityDemoNotes" text,
1226
"status" text,
1327
"createdAt" timestamptz NOT NULL DEFAULT now(),
1428
"updatedAt" timestamptz NOT NULL DEFAULT now(),
@@ -23,6 +37,7 @@ CREATE INDEX IF NOT EXISTS idx_game_design_documents_updatedby ON game_design_do
2337
CREATE TABLE IF NOT EXISTS game_design_validation_items (
2438
key text PRIMARY KEY,
2539
"gameKey" text REFERENCES game_workspace_games(key),
40+
"field" text,
2641
"label" text,
2742
"status" text,
2843
"action" text,
@@ -35,3 +50,39 @@ CREATE TABLE IF NOT EXISTS game_design_validation_items (
3550
CREATE INDEX IF NOT EXISTS idx_game_design_validation_items_gamekey ON game_design_validation_items ("gameKey");
3651
CREATE INDEX IF NOT EXISTS idx_game_design_validation_items_createdby ON game_design_validation_items ("createdBy");
3752
CREATE INDEX IF NOT EXISTS idx_game_design_validation_items_updatedby ON game_design_validation_items ("updatedBy");
53+
54+
CREATE TABLE IF NOT EXISTS game_design_sections (
55+
key text PRIMARY KEY,
56+
"gameKey" text REFERENCES game_workspace_games(key),
57+
"documentKey" text REFERENCES game_design_documents(key),
58+
"sectionKey" text,
59+
"heading" text,
60+
"body" text,
61+
"sortOrder" integer NOT NULL DEFAULT 0,
62+
"createdAt" timestamptz NOT NULL DEFAULT now(),
63+
"updatedAt" timestamptz NOT NULL DEFAULT now(),
64+
"createdBy" text NOT NULL REFERENCES users(key),
65+
"updatedBy" text NOT NULL REFERENCES users(key)
66+
);
67+
68+
CREATE INDEX IF NOT EXISTS idx_game_design_sections_gamekey ON game_design_sections ("gameKey");
69+
CREATE INDEX IF NOT EXISTS idx_game_design_sections_documentkey ON game_design_sections ("documentKey");
70+
CREATE INDEX IF NOT EXISTS idx_game_design_sections_createdby ON game_design_sections ("createdBy");
71+
CREATE INDEX IF NOT EXISTS idx_game_design_sections_updatedby ON game_design_sections ("updatedBy");
72+
73+
CREATE TABLE IF NOT EXISTS game_design_capability_demos (
74+
key text PRIMARY KEY,
75+
"gameKey" text REFERENCES game_workspace_games(key),
76+
"gameName" text,
77+
"gamePurpose" text,
78+
"authoringMode" text,
79+
"status" text,
80+
"createdAt" timestamptz NOT NULL DEFAULT now(),
81+
"updatedAt" timestamptz NOT NULL DEFAULT now(),
82+
"createdBy" text NOT NULL REFERENCES users(key),
83+
"updatedBy" text NOT NULL REFERENCES users(key)
84+
);
85+
86+
CREATE INDEX IF NOT EXISTS idx_game_design_capability_demos_gamekey ON game_design_capability_demos ("gameKey");
87+
CREATE INDEX IF NOT EXISTS idx_game_design_capability_demos_createdby ON game_design_capability_demos ("createdBy");
88+
CREATE INDEX IF NOT EXISTS idx_game_design_capability_demos_updatedby ON game_design_capability_demos ("updatedBy");

‎docs_build/database/dml/game-design.sql‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
-- Runtime setup/seed operations for this group must run through server-side APIs.
55
-- Temporary scope: DEV/review artifact only until Admin Site Setup/server seed APIs fully own grouped setup.
66
-- Browser pages must not directly seed authoritative DB records.
7-
-- Owned tables: game_design_documents, game_design_validation_items
7+
-- Owned tables: game_design_documents, game_design_validation_items, game_design_sections, game_design_capability_demos
88

99
-- DML status: Server-seed-owned.
1010
-- Setup is performed through the Admin-owned server-side seed API.

‎docs_build/database/seed/game-design.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"browserAuthoritativeKeyGenerationAllowed": false,
77
"tables": {
88
"game_design_documents": [],
9-
"game_design_validation_items": []
9+
"game_design_validation_items": [],
10+
"game_design_sections": [],
11+
"game_design_capability_demos": []
1012
},
1113
"note": "Seed records for this group are intentionally empty until an Admin-owned server API seeds them."
1214
}

‎docs_build/database/seed/guest/game-design.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"signInRedirect": "account/sign-in.html",
88
"tables": {
99
"game_design_documents": [],
10-
"game_design_validation_items": []
10+
"game_design_validation_items": [],
11+
"game_design_sections": [],
12+
"game_design_capability_demos": []
1113
},
1214
"samplePackages": [
1315
{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# PR_26177_ALFA_060 Branch Validation
2+
3+
| Check | Result | Evidence |
4+
| --- | --- | --- |
5+
| Branch | PASS | `PR_26177_ALFA_060-game-design-foundation` |
6+
| Base gate | PASS | Branch created from clean synced `main` after `main...origin/main` reported `0 0`. |
7+
| Scope | PASS | Changes are limited to Game Design foundation, DB docs, targeted tests, and required reports. |
8+
| Runtime architecture | PASS | Browser saves through the API repository; server owns design rows and keys. |
9+
| No JSON editor | PASS | UI remains form/table based with Creator-facing fields. |
10+
| SQLite/tmp dependency | PASS | Scoped Game Design surface search found no SQLite or `tmp/local-api` runtime references. |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# PR_26177_ALFA_060 Manual Validation Notes
2+
3+
## Reviewed
4+
- Game Design loads the active Demo Game context.
5+
- The form presents Creator-facing fields for summary, story, core loop, win condition, lose condition, target audience, and notes.
6+
- Saving complete fields marks Game Design `Ready` and updates the Game Configuration handoff link.
7+
- Output panel renders the saved fields without JSON.
8+
- Capability demo authoring remains game-owned.
9+
- Guest save redirects to `account/sign-in.html`.
10+
11+
## Not In Scope
12+
- Applying DB migrations to the developer workstation database.
13+
- Game engine internals.
14+
- Non-Alfa tools outside the existing Game Design handoff test surface.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# PR_26177_ALFA_060-game-design-foundation Report
2+
3+
## Summary
4+
- Expanded Game Design into a DB/API-backed design foundation for summary, story, core loop, win condition, lose condition, target audience, and design notes.
5+
- Preserved existing Game Type, Genre, Play Style, Player Mode, capability demo authoring, and Game Configuration handoff behavior.
6+
- Added audit-ready `game_design_sections` and `game_design_capability_demos` schema coverage alongside expanded `game_design_documents`.
7+
- Added guest save redirect to `account/sign-in.html`.
8+
9+
## Changed Areas
10+
- Game Design UI and API client script.
11+
- Game Design server repository fields, validation, section rows, capability rows, and audit fields.
12+
- Mock DB schema metadata and grouped Game Design database docs/seeds.
13+
- Impacted Playwright coverage for Game Design save, guest redirect, capability demos, and toolbox build path.
14+
15+
## Validation
16+
- PASS: `node --check` on changed JS/MJS files.
17+
- PASS: `git diff --check`.
18+
- PASS: `npx playwright test tests/playwright/tools/GameDesignMockRepository.spec.mjs --workers=1 --reporter=line` (`5 passed`).
19+
20+
## Notes
21+
- No JSON editor or browser-owned product data was introduced.
22+
- Product/runtime wording uses `API`.
23+
- Full migration execution remains outside this PR; docs define the DB contract.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# PR_26177_ALFA_060 Requirement Checklist
2+
3+
| Requirement | Result | Notes |
4+
| --- | --- | --- |
5+
| Browser -> API -> Database architecture | PASS | Game Design UI uses the API repository for save/load. |
6+
| Product/runtime wording says API | PASS | Creator-facing page copy uses API wording. |
7+
| One API contract across environments | PASS | No environment-specific Game Design product code path was added. |
8+
| Environment differences are config/.env only | PASS | Game Design behavior does not branch by environment. |
9+
| No SQLite | PASS | No SQLite references added. |
10+
| No tmp runtime dependency | PASS | Runtime does not inspect or depend on tmp paths. |
11+
| No JSON source of truth | PASS | JSON files remain seed documentation only. |
12+
| Browser does not own product data | PASS | No browser storage product-data SSoT was added. |
13+
| API/server owns authoritative keys | PASS | Server repository assigns ULID-shaped keys for design documents, sections, validation rows, and capability rows. |
14+
| Audit fields | PASS | Rows include `createdAt`, `updatedAt`, `createdBy`, and `updatedBy`. |
15+
| Ownership references `users.key` | PASS | DDL references `users(key)` for audit fields. |
16+
| Summary | PASS | Form, output, repository, and DDL include summary. |
17+
| Story | PASS | Form, output, repository, and DDL include story. |
18+
| Core loop | PASS | Form, output, repository, and DDL include core loop. |
19+
| Win condition | PASS | Form, output, repository, and DDL include win condition. |
20+
| Lose condition | PASS | Form, output, repository, and DDL include lose condition. |
21+
| Target audience | PASS | Form, output, repository, and DDL include target audience. |
22+
| Design notes/sections | PASS | Form captures notes and repository emits `game_design_sections`. |
23+
| Guest save redirect | PASS | Playwright verifies redirect to `account/sign-in.html`. |
24+
| Required reports and ZIP | PASS | PR report, branch validation, checklist, lane report, manual notes, `codex_*` reports, and ZIP are produced. |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# PR_26177_ALFA_060 Validation Lane
2+
3+
## Commands
4+
- `node --check src/dev-runtime/persistence/tool-repositories/game-design-mock-repository.js`
5+
- `node --check assets/toolbox/game-design/js/index.js`
6+
- `node --check src/dev-runtime/persistence/mock-db-store.js`
7+
- `node --check src/dev-runtime/server/local-api-router.mjs`
8+
- `node --check tests/playwright/tools/GameDesignMockRepository.spec.mjs`
9+
- `git diff --check`
10+
- `npx playwright test tests/playwright/tools/GameDesignMockRepository.spec.mjs --workers=1 --reporter=line`
11+
12+
## Result
13+
PASS
14+
15+
## Evidence
16+
- Playwright Game Design lane: `5 passed`.
17+
- Syntax checks passed for changed runtime/test JS/MJS files.
18+
- `git diff --check` exited `0`; only line-ending normalization warnings were printed.

0 commit comments

Comments
 (0)