Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ versioned release tag yet, so entries are organized as pre-alpha snapshots.
- Character-model taxonomy covering core stats, derived stats, social
attributes, body presentation, identity fields, and multi-axis relationships
for NPC-like Frames and player-inhabitable bodies.
- Character stat and relationship system GDD covering the six-stat MVP backend
- Character stat and relationship system GDD covering the eight-stat MVP backend
contract, deferred stat candidates, secondary stat direction, presentation
attributes, relationship axes, and reincarnation carryover boundaries.
- Human-believable NPC agent design doc covering trait axes, relationship
Expand All @@ -19,7 +19,7 @@ versioned release tag yet, so entries are organized as pre-alpha snapshots.
- Unity `NetworkPlayer` now carries prototype level, combat stats, BodyTime,
lifecycle, SECOND balance, reincarnation count, visual key, and agent-control
state as networked fields.
- Prototype HUD now shows level, HP, energy, attack, defense, agility,
- Prototype HUD now shows level, HP, energy, attack, defense, dexterity,
BodyTime, lifecycle, SECOND balance, and reincarnation count.
- Unity `CharacterMemorySync` now applies Nakama profile body state onto the
authoritative local player after profile load.
Expand Down Expand Up @@ -182,10 +182,11 @@ versioned release tag yet, so entries are organized as pre-alpha snapshots.
visible in Play Mode.
- Nakama agent decisions now default to the `dos-ai` model on `api.dos.ai`;
Claude aliases are not the default path for the prototype NPC brain.
- Nakama character stats now use the six canonical MVP core stats:
`strength`, `agility`, `endurance`, `perception`, `focus`, and `presence`.
Legacy `force`, `vitality`, and `resilience` fields remain as compatibility
aliases for the current Unity prototype.
- Nakama character stats now use the eight canonical MVP core stats:
`strength`, `dexterity`, `endurance`, `perception`, `focus`, `presence`,
`intelligence`, and `luck`. Legacy `force`, `agility`, `vitality`, and
`resilience` fields remain as compatibility aliases for the current Unity
prototype.
- Removed the in-repo Second Spawn Go LLM adapter. Durable profile, soul,
stats, memory, BodyTime, activity state, and model-backed intent validation
now stay on the Nakama side of the backend boundary.
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Recommended views:
AI offline control, agent workflow, and backend boundaries.
- [x] Character-model taxonomy documented for core stats, secondary stats,
social attributes, body presentation, identity, and multi-axis relationships.
- [x] Character stat and relationship system GDD added for the six-stat MVP
- [x] Character stat and relationship system GDD added for the eight-stat MVP
backend contract, secondary stat direction, presentation attributes, and
reincarnation carryover boundaries.
- [x] Backend tests for Nakama runtime behavior and model-backed fallback.
Expand All @@ -90,7 +90,7 @@ Recommended views:
- [x] `ZoneTest_Hub` enters Play Mode with a Fusion-spawned local player.
- [x] The spawned player has networked level, combat stats, BodyTime, lifecycle,
SECOND balance, reincarnation count, visual key, and agent-control flag.
- [x] Unity prototype HUD shows level, HP, energy, attack, defense, agility,
- [x] Unity prototype HUD shows level, HP, energy, attack, defense, dexterity,
BodyTime, lifecycle, SECOND balance, and reincarnation count.
- [x] Unity prototype HUD shows synced agent runtime counters and recent
activity rows from the Nakama body profile.
Expand Down
3 changes: 3 additions & 0 deletions Unity/Assets/_SecondSpawn/Scripts/AI/AgentContextDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,11 @@ public sealed class CharacterStatsDto
public int endurance = 10;
public int perception = 8;
public int presence = 5;
public int intelligence = 8;
public int luck = 5;
public int vitality = 10;
public int force = 8;
public int dexterity = 8;
public int agility = 8;
public int focus = 8;
public int resilience = 8;
Expand Down
5 changes: 4 additions & 1 deletion Unity/Assets/_SecondSpawn/Scripts/AI/CharacterMemorySync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,18 @@ private void ApplyStats(NetworkPlayer player, BodyProfileDto body)
var time = body.time ?? new BodyTimeDto();
var account = _context?.player ?? new PlayerProfileDto();
var strength = ResolveStat(stats.strength, stats.force, 8);
var dexterity = ResolveStat(stats.dexterity, stats.agility, 8);
var endurance = ResolveStat(stats.endurance, stats.vitality, 10);
var resilience = ResolveStat(stats.resilience, endurance, 8);
player.ApplyProfileStats(
stats.level,
endurance,
strength,
stats.agility,
dexterity,
stats.focus,
resilience,
stats.intelligence,
stats.luck,
stats.max_health,
stats.max_energy,
stats.attack_power,
Expand Down
11 changes: 8 additions & 3 deletions Unity/Assets/_SecondSpawn/Scripts/AI/PrototypeAgentBrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,13 +1034,18 @@ private void ApplyContextToPrototypeBody()
var stats = body.stats;
if (stats != null)
{
_moveSpeed = Mathf.Max(0.1f, _baseMoveSpeed * CalculateAgilitySpeedMultiplier(stats.agility));
_moveSpeed = Mathf.Max(0.1f, _baseMoveSpeed * CalculateDexteritySpeedMultiplier(ResolveStat(stats.dexterity, stats.agility, 8)));
}
}

private static float CalculateAgilitySpeedMultiplier(int agility)
private static int ResolveStat(int primary, int legacy, int fallback)
{
return Mathf.Clamp(agility / 8f, 0.75f, 1.4f);
return primary > 0 ? primary : legacy > 0 ? legacy : fallback;
}

private static float CalculateDexteritySpeedMultiplier(int dexterity)
{
return Mathf.Clamp(dexterity / 8f, 0.75f, 1.4f);
}

private void LogPhase(BrainPhase phase, string detail)
Expand Down
15 changes: 13 additions & 2 deletions Unity/Assets/_SecondSpawn/Scripts/Networking/NetworkPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ public sealed class NetworkPlayer : NetworkBehaviour
[Networked] public int Level { get; set; }
[Networked] public int Vitality { get; set; }
[Networked] public int Force { get; set; }
[Networked] public int Dexterity { get; set; }
[Networked] public int Agility { get; set; }
[Networked] public int Focus { get; set; }
[Networked] public int Resilience { get; set; }
[Networked] public int Intelligence { get; set; }
[Networked] public int Luck { get; set; }
[Networked] public int MaxHealth { get; set; }
[Networked] public int MaxEnergy { get; set; }
[Networked] public int AttackPower { get; set; }
Expand Down Expand Up @@ -216,9 +219,11 @@ public void ApplyProfileStats(
int level,
int vitality,
int force,
int agility,
int dexterity,
int focus,
int resilience,
int intelligence,
int luck,
int maxHealth,
int maxEnergy,
int attackPower,
Expand Down Expand Up @@ -246,9 +251,12 @@ public void ApplyProfileStats(
Level = Mathf.Max(1, level);
Vitality = Mathf.Clamp(vitality, 1, 999);
Force = Mathf.Clamp(force, 1, 999);
Agility = Mathf.Clamp(agility, 1, 999);
Dexterity = Mathf.Clamp(dexterity, 1, 999);
Agility = Dexterity;
Focus = Mathf.Clamp(focus, 1, 999);
Resilience = Mathf.Clamp(resilience, 1, 999);
Intelligence = Mathf.Clamp(intelligence, 1, 999);
Luck = Mathf.Clamp(luck, 1, 999);
MaxHealth = Mathf.Max(1, maxHealth);
MaxEnergy = Mathf.Max(1, maxEnergy);
AttackPower = Mathf.Max(0, attackPower);
Expand Down Expand Up @@ -279,9 +287,12 @@ private void ApplyDefaultStats()
Level = 1;
Vitality = 10;
Force = 8;
Dexterity = 8;
Agility = 8;
Focus = 8;
Resilience = 8;
Intelligence = 8;
Luck = 5;
MaxHealth = 100;
MaxEnergy = 50;
AttackPower = 10;
Expand Down
3 changes: 2 additions & 1 deletion Unity/Assets/_SecondSpawn/Scripts/UI/HUDController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ private void DrawStats(NetworkPlayer player)

GUILayout.Label($"Level {player.Level}", _labelStyle);
GUILayout.Label($"HP {player.Hp:0}/{player.MaxHealth} | Energy {player.Stamina:0}/{player.MaxEnergy}", _labelStyle);
GUILayout.Label($"ATK {player.AttackPower} | DEF {player.DefensePower} | AGI {player.Agility}", _labelStyle);
GUILayout.Label($"ATK {player.AttackPower} | DEF {player.DefensePower} | DEX {player.Dexterity}", _labelStyle);
GUILayout.Label($"INT {player.Intelligence} | LUCK {player.Luck} | FOC {player.Focus}", _labelStyle);
GUILayout.Label($"TIME {FormatSeconds(player.BodyTimeRemainingSeconds)} / {FormatSeconds(player.BodyTimeMaxSeconds)}", _labelStyle);
GUILayout.Label($"Lifecycle {(player.IsBodyDead ? "dead" : "alive")} | Drain {player.BodyTimeDangerDrainRate}s/tick", _labelStyle);
GUILayout.Label($"SECOND {FormatSeconds(player.SecondBalanceSeconds)} | Reincarnations {player.ReincarnationCount}", _labelStyle);
Expand Down
21 changes: 17 additions & 4 deletions backend/nakama/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2575,11 +2575,14 @@ function defaultCharacterStats(): any {
return {
level: 1,
strength: 8,
dexterity: 8,
agility: 8,
endurance: 10,
perception: 8,
focus: 8,
presence: 5,
intelligence: 8,
luck: 5,
vitality: 10,
force: 8,
resilience: 8,
Expand All @@ -2593,19 +2596,24 @@ function defaultCharacterStats(): any {
function normalizeStatsWithDefaults(stats: any, defaults: any): any {
var base = normalizeStats(defaults || {});
var strength = clampNumber(numberOrDefault(firstDefined(stats.strength, stats.force), base.strength), 1, 9999);
var agility = clampNumber(numberOrDefault(stats.agility, base.agility), 1, 9999);
var dexterity = clampNumber(numberOrDefault(firstDefined(stats.dexterity, stats.agility), base.dexterity), 1, 9999);
var endurance = clampNumber(numberOrDefault(firstDefined(stats.endurance, firstDefined(stats.vitality, stats.resilience)), base.endurance), 1, 9999);
var perception = clampNumber(numberOrDefault(stats.perception, base.perception), 1, 9999);
var focus = clampNumber(numberOrDefault(stats.focus, base.focus), 1, 9999);
var presence = clampNumber(numberOrDefault(stats.presence, base.presence), 1, 9999);
var intelligence = clampNumber(numberOrDefault(stats.intelligence, base.intelligence), 1, 9999);
var luck = clampNumber(numberOrDefault(stats.luck, base.luck), 1, 9999);
return {
level: clampNumber(numberOrDefault(stats.level, base.level), 1, 100),
strength: strength,
agility: agility,
dexterity: dexterity,
agility: dexterity,
endurance: endurance,
perception: perception,
focus: focus,
presence: presence,
intelligence: intelligence,
luck: luck,
vitality: clampNumber(numberOrDefault(stats.vitality, endurance), 1, 9999),
force: clampNumber(numberOrDefault(stats.force, strength), 1, 9999),
resilience: clampNumber(numberOrDefault(stats.resilience, endurance), 1, 9999),
Expand All @@ -2619,19 +2627,24 @@ function normalizeStatsWithDefaults(stats: any, defaults: any): any {
function normalizeStats(stats: any): any {
var defaults = defaultCharacterStats();
var strength = clampNumber(numberOrDefault(firstDefined(stats.strength, stats.force), defaults.strength), 1, 9999);
var agility = clampNumber(numberOrDefault(stats.agility, defaults.agility), 1, 9999);
var dexterity = clampNumber(numberOrDefault(firstDefined(stats.dexterity, stats.agility), defaults.dexterity), 1, 9999);
var endurance = clampNumber(numberOrDefault(firstDefined(stats.endurance, firstDefined(stats.vitality, stats.resilience)), defaults.endurance), 1, 9999);
var perception = clampNumber(numberOrDefault(stats.perception, defaults.perception), 1, 9999);
var focus = clampNumber(numberOrDefault(stats.focus, defaults.focus), 1, 9999);
var presence = clampNumber(numberOrDefault(stats.presence, defaults.presence), 1, 9999);
var intelligence = clampNumber(numberOrDefault(stats.intelligence, defaults.intelligence), 1, 9999);
var luck = clampNumber(numberOrDefault(stats.luck, defaults.luck), 1, 9999);
return {
level: clampNumber(numberOrDefault(stats.level, defaults.level), 1, 100),
strength: strength,
agility: agility,
dexterity: dexterity,
agility: dexterity,
endurance: endurance,
perception: perception,
focus: focus,
presence: presence,
intelligence: intelligence,
luck: luck,
vitality: clampNumber(numberOrDefault(stats.vitality, endurance), 1, 9999),
force: clampNumber(numberOrDefault(stats.force, strength), 1, 9999),
resilience: clampNumber(numberOrDefault(stats.resilience, endurance), 1, 9999),
Expand Down
11 changes: 10 additions & 1 deletion backend/nakama/tests/supabase_custom_auth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -417,9 +417,12 @@ assert.equal(profile.body.stats.level, 1);
assert.equal(profile.body.stats.strength, 10);
assert.equal(profile.body.stats.endurance, 9);
assert.equal(profile.body.stats.vitality, 9);
assert.equal(profile.body.stats.dexterity, 9);
assert.equal(profile.body.stats.agility, 9);
assert.equal(profile.body.stats.perception, 8);
assert.equal(profile.body.stats.presence, 5);
assert.equal(profile.body.stats.intelligence, 8);
assert.equal(profile.body.stats.luck, 5);
assert.equal(profile.body.stats.max_health, 100);
assert.equal(profile.body.stats.attack_power, 12);
assert.equal(profile.body.time.remaining_seconds, 86400);
Expand Down Expand Up @@ -581,7 +584,7 @@ const npcProfile = JSON.parse(harness.registeredRpcs.get("secondspawn_actor_prof
actor_id: "npc-guide",
actor_type: "npc",
display_name: "Mira Guide",
stats: { level: 0, strength: 12, endurance: 14, agility: 10, perception: 11, focus: 9, presence: 8, max_health: 0, max_energy: 0, attack_power: 0 },
stats: { level: 0, strength: 12, endurance: 14, dexterity: 10, perception: 11, focus: 9, presence: 8, intelligence: 13, luck: 4, max_health: 0, max_energy: 0, attack_power: 0 },
characteristics: { curiosity: 8, sociability: 9 },
time: { remaining_seconds: 0, max_seconds: 0, danger_drain_rate: 0 },
soul: { core_drive: "help new bodies survive the hub" }
Expand All @@ -596,6 +599,8 @@ assert.equal(npcProfile.body.soul.core_drive, "help new bodies survive the hub")
assert.equal(npcProfile.body.stats.level, 1);
assert.equal(npcProfile.body.stats.strength, 12);
assert.equal(npcProfile.body.stats.endurance, 14);
assert.equal(npcProfile.body.stats.dexterity, 10);
assert.equal(npcProfile.body.stats.agility, 10);
assert.equal(npcProfile.body.stats.max_health, 1);
assert.equal(npcProfile.body.stats.max_energy, 0);
assert.equal(npcProfile.body.stats.attack_power, 0);
Expand All @@ -604,6 +609,8 @@ assert.equal(npcProfile.body.stats.vitality, 14);
assert.equal(npcProfile.body.stats.resilience, 14);
assert.equal(npcProfile.body.stats.perception, 11);
assert.equal(npcProfile.body.stats.presence, 8);
assert.equal(npcProfile.body.stats.intelligence, 13);
assert.equal(npcProfile.body.stats.luck, 4);
assert.equal(npcProfile.body.characteristics.sociability, 9);
assert.equal(npcProfile.body.time.remaining_seconds, 0);
assert.equal(npcProfile.body.time.max_seconds, 1);
Expand Down Expand Up @@ -637,6 +644,8 @@ assert.equal(permanentNpcProfile.body.stats.strength, 6);
assert.equal(permanentNpcProfile.body.stats.endurance, 10);
assert.equal(permanentNpcProfile.body.stats.perception, 8);
assert.equal(permanentNpcProfile.body.stats.presence, 5);
assert.equal(permanentNpcProfile.body.stats.intelligence, 8);
assert.equal(permanentNpcProfile.body.stats.luck, 5);
assert.equal(permanentNpcProfile.body.characteristics.discipline, 9);
assert.equal(permanentNpcProfile.body.soul.name, "Clinic-0819 Craft");
assert.equal(permanentNpcProfile.memory[0].id, "memory-repair-bench");
Expand Down
2 changes: 1 addition & 1 deletion docs/design/02-vertical-slice-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Already implemented:
- The spawned player has networked level, combat stats, prototype `BodyTime` /
TIME, lifecycle,
SECOND balance, reincarnation count, visual key, and agent-control state.
- Prototype HUD displays level, HP, energy, attack, defense, agility,
- Prototype HUD displays level, HP, energy, attack, defense, dexterity,
prototype `BodyTime` / TIME, lifecycle, SECOND balance, and reincarnation count.
- Nakama profile bootstrap persists player profile, current body, stats, traits,
soul, memory, agent policy, runtime, activity, prototype `BodyTime` / TIME,
Expand Down
5 changes: 3 additions & 2 deletions docs/design/04-cultivation-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ or Cultivation Master progression in the current vertical slice.
The slice uses:

- `CharacterStats.level` as the baseline progression value.
- Body-bound stats such as strength, agility, endurance, perception, focus,
presence, max health, max energy, attack power, and defense power.
- Body-bound stats such as strength, dexterity, endurance, perception, focus,
presence, intelligence, luck, max health, max energy, attack power, and
defense power.
- TIME / SECOND and reincarnation as the signature systemic loop.

Advanced body or soul progression remains a future design space, but it needs a
Expand Down
Loading
Loading