tune(battle-xp): raise defaults to XpPerWin=200 / XpPerLoss=50
Doubles the shipped defaults across all modes without touching per-mode overrides. Curve interaction against classexp.csv (L1=50, L2=150): - Win (200 XP): crosses both L1 + L2 → land at L3, Exp=0 - Loss (50 XP): exactly meets L1 → land at L2, Exp=0 Updates every affected test assertion (Practice, Rank, Free, TK2, Colosseum, Story) for the new level/exp math. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -120,9 +120,10 @@ public class ArenaColosseumBattleControllerTests
|
||||
|
||||
// XP wire assertions
|
||||
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(100));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
// XpPerWin=200; classexp.csv L1=50, L2=150 → 200 XP crosses both: L3, Exp=0.
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(200));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(3));
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
@@ -136,8 +137,8 @@ public class ArenaColosseumBattleControllerTests
|
||||
var v = await db.Viewers.Include(x => x.Classes).ThenInclude(c => c.Class)
|
||||
.FirstAsync(x => x.Id == vid);
|
||||
var cls1 = v.Classes.Single(c => c.Class.Id == 1);
|
||||
Assert.That(cls1.Level, Is.EqualTo(2));
|
||||
Assert.That(cls1.Exp, Is.EqualTo(50));
|
||||
Assert.That(cls1.Level, Is.EqualTo(3));
|
||||
Assert.That(cls1.Exp, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -128,10 +128,10 @@ public class FreeBattleControllerTests
|
||||
using var doc = JsonDocument.Parse(raw);
|
||||
var data = doc.RootElement;
|
||||
Assert.That(data.GetProperty("battle_result").GetInt32(), Is.EqualTo(1));
|
||||
// XpPerWin=100, classexp.csv L1=50 → L2 with 50 carry.
|
||||
Assert.That(data.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(100));
|
||||
Assert.That(data.GetProperty("class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(data.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
// XpPerWin=200; classexp.csv L1=50, L2=150 → 200 XP crosses both: L3, Exp=0.
|
||||
Assert.That(data.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(200));
|
||||
Assert.That(data.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(data.GetProperty("class_level").GetInt32(), Is.EqualTo(3));
|
||||
|
||||
// Strict subset — no rank fields. The client doesn't read them on free-battle
|
||||
// finish; emitting them would be wire-format pollution.
|
||||
@@ -153,9 +153,10 @@ public class FreeBattleControllerTests
|
||||
|
||||
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
|
||||
Assert.That(doc.RootElement.GetProperty("battle_result").GetInt32(), Is.EqualTo(2));
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(25));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(25));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(1));
|
||||
// XpPerLoss=50 exactly meets L1 threshold → L2, Exp=0.
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -218,11 +218,11 @@ public class PracticeControllerTests
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
// BattleXpConfig.XpPerWin default = 100. classexp.csv seeds L1=50, L2=150, so 100 XP
|
||||
// crosses L1's threshold: land at Level=2 with 50 carry.
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(100));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
// BattleXpConfig.XpPerWin default = 200. classexp.csv seeds L1=50, L2=150 → 200 XP
|
||||
// crosses L1 (spends 50, level→2, exp=150) then L2 (spends 150, level→3, exp=0).
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(200));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(3));
|
||||
Assert.That(doc.RootElement.GetProperty("reward_list").GetArrayLength(), Is.EqualTo(0));
|
||||
|
||||
// Persistence check — reload viewer and confirm the ViewerClassData row moved.
|
||||
@@ -231,8 +231,8 @@ public class PracticeControllerTests
|
||||
var v = await db.Viewers.Include(x => x.Classes).ThenInclude(c => c.Class)
|
||||
.FirstAsync(x => x.Id == viewerId);
|
||||
var cls1 = v.Classes.Single(c => c.Class.Id == 1);
|
||||
Assert.That(cls1.Level, Is.EqualTo(2));
|
||||
Assert.That(cls1.Exp, Is.EqualTo(50));
|
||||
Assert.That(cls1.Level, Is.EqualTo(3));
|
||||
Assert.That(cls1.Exp, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -252,10 +252,10 @@ public class PracticeControllerTests
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
// BattleXpConfig.XpPerLoss default = 25. classexp.csv seeds L1=50, so 25 XP stays
|
||||
// under threshold: Level=1, Exp=25.
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(25));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(25));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(1));
|
||||
// BattleXpConfig.XpPerLoss default = 50. classexp.csv L1=50 → 50 XP exactly meets
|
||||
// the threshold: Level=2, Exp=0.
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,10 +248,11 @@ public class RankBattleControllerTests
|
||||
Assert.That(data.GetProperty("battle_result").GetInt32(), Is.EqualTo(1));
|
||||
Assert.That(data.GetProperty("rank").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(data.GetProperty("after_battle_point").GetInt32(), Is.EqualTo(0));
|
||||
// BattleXpConfig.XpPerWin=100, classexp.csv L1=50 → land at L2 with 50 carry.
|
||||
Assert.That(data.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(100));
|
||||
Assert.That(data.GetProperty("class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(data.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
// BattleXpConfig.XpPerWin=200; classexp.csv L1=50, L2=150 → 200 XP crosses both
|
||||
// thresholds: land at L3 with 0.
|
||||
Assert.That(data.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(200));
|
||||
Assert.That(data.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(data.GetProperty("class_level").GetInt32(), Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -265,10 +266,10 @@ public class RankBattleControllerTests
|
||||
|
||||
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
|
||||
Assert.That(doc.RootElement.GetProperty("battle_result").GetInt32(), Is.EqualTo(2));
|
||||
// battle_result==2 is loss-shaped: XpPerLoss=25 (< L1 threshold=50).
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(25));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(25));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(1));
|
||||
// battle_result==2 is loss-shaped: XpPerLoss=50 exactly meets L1 threshold → L2, Exp=0.
|
||||
Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(50));
|
||||
Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -286,7 +287,7 @@ public class RankBattleControllerTests
|
||||
var v = await db.Viewers.Include(x => x.Classes).ThenInclude(c => c.Class)
|
||||
.FirstAsync(x => x.Id == viewerId);
|
||||
var cls3 = v.Classes.Single(c => c.Class.Id == 3);
|
||||
Assert.That(cls3.Level, Is.EqualTo(2));
|
||||
Assert.That(cls3.Exp, Is.EqualTo(50));
|
||||
Assert.That(cls3.Level, Is.EqualTo(3));
|
||||
Assert.That(cls3.Exp, Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class ArenaTwoPickServiceFinishTests
|
||||
var result = await svc.RecordBattleResultAsync(vid, isWin: true);
|
||||
|
||||
Assert.That(result.BattleResult, Is.EqualTo(1));
|
||||
Assert.That(result.GetClassExperience, Is.EqualTo(100),
|
||||
Assert.That(result.GetClassExperience, Is.EqualTo(200),
|
||||
"Default BattleXpConfig.XpPerWin");
|
||||
Assert.That(result.AddSpotPoint, Is.EqualTo(10));
|
||||
var run = await db.ViewerArenaTwoPickRuns.FirstAsync(r => r.ViewerId == vid);
|
||||
@@ -154,11 +154,11 @@ public class ArenaTwoPickServiceFinishTests
|
||||
var result = await svc.RecordBattleResultAsync(vid, isWin: false);
|
||||
|
||||
Assert.That(result.BattleResult, Is.EqualTo(0));
|
||||
Assert.That(result.GetClassExperience, Is.EqualTo(25),
|
||||
Assert.That(result.GetClassExperience, Is.EqualTo(50),
|
||||
"Default BattleXpConfig.XpPerLoss");
|
||||
Assert.That(result.ClassExperience, Is.EqualTo(25),
|
||||
"Fresh viewer with Exp=0, +25 loss XP < curve[1]=50 so no level-up.");
|
||||
Assert.That(result.ClassLevel, Is.EqualTo(1));
|
||||
Assert.That(result.ClassExperience, Is.EqualTo(0),
|
||||
"Fresh viewer with Exp=0, +50 loss XP exactly meets curve[1]=50 → L2, Exp=0.");
|
||||
Assert.That(result.ClassLevel, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -632,12 +632,12 @@ public class StoryServiceTests
|
||||
Assert.That(resp.RewardList[0].RewardNum, Is.EqualTo("100"));
|
||||
// Story XP resolves via DI-registered IBattleXpService → real IGameConfigService
|
||||
// (the local NewConfigService mock is passed to StoryService but the XP service
|
||||
// pulls its own config from DI). BattleXpConfig.ShippedDefaults(): XpPerWin=100,
|
||||
// StoryXpPerClear=null → falls back to XpPerWin=100. Curve L1=50, so 100 XP
|
||||
// → L2 with 50 carry.
|
||||
Assert.That(resp.GetClassExperience, Is.EqualTo("100"));
|
||||
Assert.That(resp.ClassExperience, Is.EqualTo(50));
|
||||
Assert.That(resp.ClassLevel, Is.EqualTo("2"));
|
||||
// pulls its own config from DI). BattleXpConfig.ShippedDefaults(): XpPerWin=200,
|
||||
// StoryXpPerClear=null → falls back to XpPerWin=200. Curve L1=50, L2=150 → 200 XP
|
||||
// crosses both thresholds: L3 with 0.
|
||||
Assert.That(resp.GetClassExperience, Is.EqualTo("200"));
|
||||
Assert.That(resp.ClassExperience, Is.EqualTo(0));
|
||||
Assert.That(resp.ClassLevel, Is.EqualTo("3"));
|
||||
_viewer.Verify(v => v.UpsertProgressAsync(viewerId, 100, true, null), Times.Once);
|
||||
|
||||
// Confirm currency + class XP persisted: fetch fresh viewer from a new scope.
|
||||
@@ -648,8 +648,8 @@ public class StoryServiceTests
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
Assert.That(freshViewer.Currency.RedEther, Is.EqualTo(100UL));
|
||||
var cls2 = freshViewer.Classes.Single(c => c.Class.Id == 2);
|
||||
Assert.That(cls2.Level, Is.EqualTo(2));
|
||||
Assert.That(cls2.Exp, Is.EqualTo(50));
|
||||
Assert.That(cls2.Level, Is.EqualTo(3));
|
||||
Assert.That(cls2.Exp, Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user