Skip to content

Complete Phases 5-8: Adult tone tags, content validation, dashboard U… - #18

Merged
Earnest-Williams merged 1 commit into
mainfrom
vibe/phase5-8-complete-eb4d3b
Jun 7, 2026
Merged

Complete Phases 5-8: Adult tone tags, content validation, dashboard U…#18
Earnest-Williams merged 1 commit into
mainfrom
vibe/phase5-8-complete-eb4d3b

Conversation

@Earnest-Williams

Copy link
Copy Markdown
Owner

…X, documentation

  • Phase 5: Added Brad and Marcus to ROMANCE_ARCS with tone tags, created adult-tone-guide.md and toneTags.test.js
  • Phase 6: Created validate-content.mjs script and 5 content structure tests
  • Phase 7: Added HomeStylePanel, HomeActivityPanel, NpcHomeReaction components to Dashboard
  • Phase 8: Created 6 documentation files (reputation, balance-scenarios, save-migrations, content-validation, npc-schedules)
  • Updated ROADMAP.md completion matrix (23/26 tasks complete)

…X, documentation

- Phase 5: Added Brad and Marcus to ROMANCE_ARCS with tone tags, created adult-tone-guide.md and toneTags.test.js
- Phase 6: Created validate-content.mjs script and 5 content structure tests
- Phase 7: Added HomeStylePanel, HomeActivityPanel, NpcHomeReaction components to Dashboard
- Phase 8: Created 6 documentation files (reputation, balance-scenarios, save-migrations, content-validation, npc-schedules)
- Updated ROADMAP.md completion matrix (23/26 tasks complete)

Co-authored-by: Earnest-Williams <Earnest-Williams@users.noreply.github.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the project roadmap, adds comprehensive documentation for adult tone guides, balance scenarios, content validation, NPC schedules, reputation, and save migrations, and introduces several new dashboard panels for home style and activities. It also adds romance arcs for Brad and Marcus, along with new schema validation tests. Feedback on these changes includes addressing a potential runtime crash in HomeActivityPanel by using the correct helper function for home style profiles, removing unused imports in validate-content.mjs and Dashboard.jsx, correcting pronoun inconsistencies in the new romance arcs, and updating the validation script to prevent false warnings on nested choice impacts and missing valid stats.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@@ -0,0 +1,97 @@
import React from 'react';
import { useGameStore } from '../../state/store';
import { HOME_ACTIVITIES } from '../../data/furniture';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Import the calculateHomeStyleProfile helper function from the static furniture data module to calculate the home style profile consistently.

Suggested change
import { HOME_ACTIVITIES } from '../../data/furniture';
import { HOME_ACTIVITIES, calculateHomeStyleProfile } from '../../data/furniture';

Comment on lines +8 to +19
const gameState = useGameStore(state => state.gameState);

// Calculate home style profile
const profile = {};
for (const furnitureId of placedFurniture) {
const furniture = useGameStore.getState().gameState.furniture[furnitureId];
if (furniture?.tags) {
for (const tag of furniture.tags) {
profile[tag] = (profile[tag] || 0) + 1;
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing useGameStore.getState().gameState.furniture is highly likely to return undefined because furniture is static data defined in src/data/furniture.js and not part of the game state. This will cause a runtime TypeError and crash the dashboard. Additionally, calling getState() inside a render function is an anti-pattern as it bypasses React's reactive updates. Use the existing calculateHomeStyleProfile helper function instead.

Suggested change
const gameState = useGameStore(state => state.gameState);
// Calculate home style profile
const profile = {};
for (const furnitureId of placedFurniture) {
const furniture = useGameStore.getState().gameState.furniture[furnitureId];
if (furniture?.tags) {
for (const tag of furniture.tags) {
profile[tag] = (profile[tag] || 0) + 1;
}
}
}
// Calculate home style profile
const profile = calculateHomeStyleProfile(placedFurniture);

import { NPCS } from '../src/data/npcs.js';
import { DATE_TEMPLATES } from '../src/data/dates.js';
import { ITEMS } from '../src/data/items.js';
import { readFileSync } from 'node:fs';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The readFileSync import from node:fs is unused in this script and can be safely removed.

}

// Check for relationship/chemistry impact
const hasImpact = 'relationshipImpact' in choice || 'chemistryImpact' in choice || 'relationship' in choice || 'chemistry' in choice;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The hasImpact check only looks for direct relationship/chemistry impact properties on the choice object. However, choices that perform a stat check have their impacts nested inside onSuccess and onFail objects. This causes the script to output false warnings for valid stat-check choices. Update the check to also inspect onSuccess and onFail objects.

  const hasImpact = 'relationshipImpact' in choice || 'chemistryImpact' in choice || 'relationship' in choice || 'chemistry' in choice || (choice.onSuccess && ('relationshipImpact' in choice.onSuccess || 'chemistryImpact' in choice.onSuccess)) || (choice.onFail && ('relationshipImpact' in choice.onFail || 'chemistryImpact' in choice.onFail));


// Validate checkStat if present
if (choice.checkStat) {
const validStats = ['intelligence', 'fitness', 'charisma', 'style', 'corporate', 'finance', 'culinary', 'confidence', 'empathy', 'negotiation', 'socialIq', 'programming', 'music', 'hygiene', 'money'];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validStats array is missing several valid stats that are checked or used in the game, such as creativity, gaming, and marketing. This can lead to false validation failures when new content uses these stats. Add them to the list of valid stats.

    const validStats = ['intelligence', 'fitness', 'charisma', 'style', 'corporate', 'finance', 'culinary', 'confidence', 'empathy', 'negotiation', 'socialIq', 'programming', 'music', 'hygiene', 'money', 'creativity', 'gaming', 'marketing'];

import TimeControls from './dashboard/TimeControls';
import HomeStylePanel from './dashboard/HomeStylePanel';
import HomeActivityPanel from './dashboard/HomeActivityPanel';
import NpcHomeReaction from './dashboard/NpcHomeReaction';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The NpcHomeReaction component is imported but never rendered or used anywhere in the dashboard. Remove the unused import.

Comment thread src/data/npcs.js
Comment on lines +91 to +94
{ id: "brad_intro", type: "introduction", minRelationship: 0, title: "First Real Conversation", prompt: "Brad gives you a careful opening to show who you are.", emotionalBeat: "Curiosity and first-impression nerves.", choices: [{ text: "Ask what kind of day they are having before trying to impress them.", relationshipImpact: 6, chemistryImpact: 5, unlocksMemory: "brad_honest_opening" }, { text: "Share a small honest detail about yourself.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Try too hard to sound impressive.", checkStat: "charisma", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 4 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "brad_early", type: "early connection", minRelationship: 18, title: "A Shared Rhythm", prompt: "Brad lets the conversation slow down enough for trust to start forming.", emotionalBeat: "Warmth, pacing, and guarded hope.", choices: [{ text: "Match their pace and listen for what matters.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "brad_shared_rhythm" }, { text: "Suggest a low-pressure follow-up together.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Fill every silence.", relationshipImpact: 1, chemistryImpact: -1 }] },
{ id: "brad_reveal", type: "personal reveal", minRelationship: 35, title: "Something Underneath", prompt: "Brad admits there is more going on than his usual public routine shows.", emotionalBeat: "Vulnerability balanced against self-protection.", choices: [{ text: "Thank them for trusting you with the truth.", relationshipImpact: 9, chemistryImpact: 6, unlocksMemory: "brad_trusted_reveal" }, { text: "Ask what support would actually help.", relationshipImpact: 7, chemistryImpact: 6 }, { text: "Rush into advice.", checkStat: "empathy", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 3 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "brad_conflict", type: "conflict", minRelationship: 52, title: "A Misread Moment", prompt: "Brad pulls back after a choice lands differently than you intended.", emotionalBeat: "Hurt, uncertainty, and a chance to repair without defensiveness.", choices: [{ text: "Own the impact before explaining intent.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "brad_owned_impact" }, { text: "Give them room, then follow up clearly.", relationshipImpact: 5, chemistryImpact: 4 }, { text: "Act like they are overreacting.", relationshipImpact: -6, chemistryImpact: -4 }] },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pronoun inconsistency in Brad's romance arc. Brad is a male NPC, but the choices in brad_intro, brad_early, brad_reveal, and brad_conflict use gender-neutral "they/them/their" pronouns (likely copied from the generic makePhase2RomanceArc template), while the rest of his arc correctly uses "he/him/his". Update the pronouns to "he/him/his" for consistency.

Suggested change
{ id: "brad_intro", type: "introduction", minRelationship: 0, title: "First Real Conversation", prompt: "Brad gives you a careful opening to show who you are.", emotionalBeat: "Curiosity and first-impression nerves.", choices: [{ text: "Ask what kind of day they are having before trying to impress them.", relationshipImpact: 6, chemistryImpact: 5, unlocksMemory: "brad_honest_opening" }, { text: "Share a small honest detail about yourself.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Try too hard to sound impressive.", checkStat: "charisma", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 4 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "brad_early", type: "early connection", minRelationship: 18, title: "A Shared Rhythm", prompt: "Brad lets the conversation slow down enough for trust to start forming.", emotionalBeat: "Warmth, pacing, and guarded hope.", choices: [{ text: "Match their pace and listen for what matters.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "brad_shared_rhythm" }, { text: "Suggest a low-pressure follow-up together.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Fill every silence.", relationshipImpact: 1, chemistryImpact: -1 }] },
{ id: "brad_reveal", type: "personal reveal", minRelationship: 35, title: "Something Underneath", prompt: "Brad admits there is more going on than his usual public routine shows.", emotionalBeat: "Vulnerability balanced against self-protection.", choices: [{ text: "Thank them for trusting you with the truth.", relationshipImpact: 9, chemistryImpact: 6, unlocksMemory: "brad_trusted_reveal" }, { text: "Ask what support would actually help.", relationshipImpact: 7, chemistryImpact: 6 }, { text: "Rush into advice.", checkStat: "empathy", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 3 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "brad_conflict", type: "conflict", minRelationship: 52, title: "A Misread Moment", prompt: "Brad pulls back after a choice lands differently than you intended.", emotionalBeat: "Hurt, uncertainty, and a chance to repair without defensiveness.", choices: [{ text: "Own the impact before explaining intent.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "brad_owned_impact" }, { text: "Give them room, then follow up clearly.", relationshipImpact: 5, chemistryImpact: 4 }, { text: "Act like they are overreacting.", relationshipImpact: -6, chemistryImpact: -4 }] },
{ id: "brad_intro", type: "introduction", minRelationship: 0, title: "First Real Conversation", prompt: "Brad gives you a careful opening to show who you are.", emotionalBeat: "Curiosity and first-impression nerves.", choices: [{ text: "Ask what kind of day he is having before trying to impress him.", relationshipImpact: 6, chemistryImpact: 5, unlocksMemory: "brad_honest_opening" }, { text: "Share a small honest detail about yourself.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Try too hard to sound impressive.", checkStat: "charisma", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 4 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "brad_early", type: "early connection", minRelationship: 18, title: "A Shared Rhythm", prompt: "Brad lets the conversation slow down enough for trust to start forming.", emotionalBeat: "Warmth, pacing, and guarded hope.", choices: [{ text: "Match his pace and listen for what matters.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "brad_shared_rhythm" }, { text: "Suggest a low-pressure follow-up together.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Fill every silence.", relationshipImpact: 1, chemistryImpact: -1 }] },
{ id: "brad_reveal", type: "personal reveal", minRelationship: 35, title: "Something Underneath", prompt: "Brad admits there is more going on than his usual public routine shows.", emotionalBeat: "Vulnerability balanced against self-protection.", choices: [{ text: "Thank him for trusting you with the truth.", relationshipImpact: 9, chemistryImpact: 6, unlocksMemory: "brad_trusted_reveal" }, { text: "Ask what support would actually help.", relationshipImpact: 7, chemistryImpact: 6 }, { text: "Rush into advice.", checkStat: "empathy", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 3 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "brad_conflict", type: "conflict", minRelationship: 52, title: "A Misread Moment", prompt: "Brad pulls back after a choice lands differently than you intended.", emotionalBeat: "Hurt, uncertainty, and a chance to repair without defensiveness.", choices: [{ text: "Own the impact before explaining intent.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "brad_owned_impact" }, { text: "Give him room, then follow up clearly.", relationshipImpact: 5, chemistryImpact: 4 }, { text: "Act like he is overreacting.", relationshipImpact: -6, chemistryImpact: -4 }] },

Comment thread src/data/npcs.js
Comment on lines +99 to +102
{ id: "marcus_intro", type: "introduction", minRelationship: 0, title: "First Real Conversation", prompt: "Marcus gives you a careful opening to show who you are.", emotionalBeat: "Curiosity and first-impression nerves.", choices: [{ text: "Ask what kind of day they are having before trying to impress them.", relationshipImpact: 6, chemistryImpact: 5, unlocksMemory: "marcus_honest_opening" }, { text: "Share a small honest detail about yourself.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Try too hard to sound impressive.", checkStat: "charisma", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 4 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "marcus_early", type: "early connection", minRelationship: 18, title: "A Shared Rhythm", prompt: "Marcus lets the conversation slow down enough for trust to start forming.", emotionalBeat: "Warmth, pacing, and guarded hope.", choices: [{ text: "Match their pace and listen for what matters.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "marcus_shared_rhythm" }, { text: "Suggest a low-pressure follow-up together.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Fill every silence.", relationshipImpact: 1, chemistryImpact: -1 }] },
{ id: "marcus_reveal", type: "personal reveal", minRelationship: 35, title: "Something Underneath", prompt: "Marcus admits there is more going on than his usual public routine shows.", emotionalBeat: "Vulnerability balanced against self-protection.", choices: [{ text: "Thank them for trusting you with the truth.", relationshipImpact: 9, chemistryImpact: 6, unlocksMemory: "marcus_trusted_reveal" }, { text: "Ask what support would actually help.", relationshipImpact: 7, chemistryImpact: 6 }, { text: "Rush into advice.", checkStat: "empathy", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 3 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "marcus_conflict", type: "conflict", minRelationship: 52, title: "A Misread Moment", prompt: "Marcus pulls back after a choice lands differently than you intended.", emotionalBeat: "Hurt, uncertainty, and a chance to repair without defensiveness.", choices: [{ text: "Own the impact before explaining intent.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "marcus_owned_impact" }, { text: "Give them room, then follow up clearly.", relationshipImpact: 5, chemistryImpact: 4 }, { text: "Act like they are overreacting.", relationshipImpact: -6, chemistryImpact: -4 }] },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pronoun inconsistency in Marcus's romance arc. Marcus is a male NPC, but the choices in marcus_intro, marcus_early, marcus_reveal, and marcus_conflict use gender-neutral "they/them/their" pronouns (likely copied from the generic makePhase2RomanceArc template), while the rest of his arc correctly uses "he/him/his". Update the pronouns to "he/him/his" for consistency.

Suggested change
{ id: "marcus_intro", type: "introduction", minRelationship: 0, title: "First Real Conversation", prompt: "Marcus gives you a careful opening to show who you are.", emotionalBeat: "Curiosity and first-impression nerves.", choices: [{ text: "Ask what kind of day they are having before trying to impress them.", relationshipImpact: 6, chemistryImpact: 5, unlocksMemory: "marcus_honest_opening" }, { text: "Share a small honest detail about yourself.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Try too hard to sound impressive.", checkStat: "charisma", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 4 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "marcus_early", type: "early connection", minRelationship: 18, title: "A Shared Rhythm", prompt: "Marcus lets the conversation slow down enough for trust to start forming.", emotionalBeat: "Warmth, pacing, and guarded hope.", choices: [{ text: "Match their pace and listen for what matters.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "marcus_shared_rhythm" }, { text: "Suggest a low-pressure follow-up together.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Fill every silence.", relationshipImpact: 1, chemistryImpact: -1 }] },
{ id: "marcus_reveal", type: "personal reveal", minRelationship: 35, title: "Something Underneath", prompt: "Marcus admits there is more going on than his usual public routine shows.", emotionalBeat: "Vulnerability balanced against self-protection.", choices: [{ text: "Thank them for trusting you with the truth.", relationshipImpact: 9, chemistryImpact: 6, unlocksMemory: "marcus_trusted_reveal" }, { text: "Ask what support would actually help.", relationshipImpact: 7, chemistryImpact: 6 }, { text: "Rush into advice.", checkStat: "empathy", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 3 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "marcus_conflict", type: "conflict", minRelationship: 52, title: "A Misread Moment", prompt: "Marcus pulls back after a choice lands differently than you intended.", emotionalBeat: "Hurt, uncertainty, and a chance to repair without defensiveness.", choices: [{ text: "Own the impact before explaining intent.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "marcus_owned_impact" }, { text: "Give them room, then follow up clearly.", relationshipImpact: 5, chemistryImpact: 4 }, { text: "Act like they are overreacting.", relationshipImpact: -6, chemistryImpact: -4 }] },
{ id: "marcus_intro", type: "introduction", minRelationship: 0, title: "First Real Conversation", prompt: "Marcus gives you a careful opening to show who you are.", emotionalBeat: "Curiosity and first-impression nerves.", choices: [{ text: "Ask what kind of day he is having before trying to impress him.", relationshipImpact: 6, chemistryImpact: 5, unlocksMemory: "marcus_honest_opening" }, { text: "Share a small honest detail about yourself.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Try too hard to sound impressive.", checkStat: "charisma", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 4 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "marcus_early", type: "early connection", minRelationship: 18, title: "A Shared Rhythm", prompt: "Marcus lets the conversation slow down enough for trust to start forming.", emotionalBeat: "Warmth, pacing, and guarded hope.", choices: [{ text: "Match his pace and listen for what matters.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "marcus_shared_rhythm" }, { text: "Suggest a low-pressure follow-up together.", relationshipImpact: 5, chemistryImpact: 6 }, { text: "Fill every silence.", relationshipImpact: 1, chemistryImpact: -1 }] },
{ id: "marcus_reveal", type: "personal reveal", minRelationship: 35, title: "Something Underneath", prompt: "Marcus admits there is more going on than his usual public routine shows.", emotionalBeat: "Vulnerability balanced against self-protection.", choices: [{ text: "Thank him for trusting you with the truth.", relationshipImpact: 9, chemistryImpact: 6, unlocksMemory: "marcus_trusted_reveal" }, { text: "Ask what support would actually help.", relationshipImpact: 7, chemistryImpact: 6 }, { text: "Rush into advice.", checkStat: "empathy", threshold: 35, onSuccess: { relationshipImpact: 4, chemistryImpact: 3 }, onFail: { relationshipImpact: -2, chemistryImpact: -1 } }] },
{ id: "marcus_conflict", type: "conflict", minRelationship: 52, title: "A Misread Moment", prompt: "Marcus pulls back after a choice lands differently than you intended.", emotionalBeat: "Hurt, uncertainty, and a chance to repair without defensiveness.", choices: [{ text: "Own the impact before explaining intent.", relationshipImpact: 7, chemistryImpact: 5, unlocksMemory: "marcus_owned_impact" }, { text: "Give him room, then follow up clearly.", relationshipImpact: 5, chemistryImpact: 4 }, { text: "Act like he is overreacting.", relationshipImpact: -6, chemistryImpact: -4 }] },

@Earnest-Williams
Earnest-Williams merged commit 24dea98 into main Jun 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants