Scale lesson skill gain by the teacher's subject skill.

Hiring a 78-math applicant already cost more than a 41; the class now actually learns faster when that teacher is in the room.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 08:54:13 +03:00
co-authored by Cursor
parent 6dcbb3303c
commit c2579d6767
5 changed files with 207 additions and 20 deletions
+44 -4
View File
@@ -3,15 +3,49 @@ using HSchool.Content;
namespace HSchool.Ai;
/// <summary>
/// How much a lesson adds to one skill this step. Hungry learns worse; trait offsets scale the
/// rate. The world stores the running total — this is just the number.
/// How much a lesson adds to one skill this step. Hungry learns worse; trait offsets and the
/// teacher's subject skill scale the rate. The world stores the running total — this is just
/// the number.
/// </summary>
public static class LessonLearning
{
public static float NeedFactor(float hunger) => Math.Clamp(0.25f + (0.75f * hunger), 0.25f, 1f);
/// <summary>Same curve as <see cref="NeedFactor"/>: skill 0 → 0.25, 100 → 1.</summary>
public static float TeacherFactor(float skill) => NeedFactor(Math.Clamp(skill / 100f, 0f, 1f));
public static float TraitFactor(int offset) => Math.Max(0.1f, 1f + (offset / 100f));
/// <summary>
/// Mean of the teacher's values for <see cref="SubjectDef.Skills"/>. A missing key uses that
/// skill's <see cref="SkillDef.Range"/> minimum, not zero — a gap is "untrained", not a
/// vanished lesson.
/// </summary>
public static float AverageTeacherSkill(
SubjectDef subject,
IReadOnlyDictionary<string, float> skills,
DefCatalog catalog)
{
ArgumentNullException.ThrowIfNull(subject);
ArgumentNullException.ThrowIfNull(skills);
ArgumentNullException.ThrowIfNull(catalog);
var total = 0f;
var count = 0;
foreach (var share in subject.Skills)
{
if (!catalog.Skills.TryGetValue(share.Skill, out var def) || def.Abstract)
{
continue;
}
total += skills.TryGetValue(share.Skill, out var value) ? value : def.Range.Min;
count++;
}
return count == 0 ? 0f : total / count;
}
public static float Gain(
float current,
SkillDef skill,
@@ -19,10 +53,16 @@ public static class LessonLearning
float lessonSkillPerHour,
float hours,
float hunger,
int traitOffset)
int traitOffset,
float teacherSkill)
{
ArgumentNullException.ThrowIfNull(skill);
var delta = share * lessonSkillPerHour * hours * NeedFactor(hunger) * TraitFactor(traitOffset);
var delta = share
* lessonSkillPerHour
* hours
* NeedFactor(hunger)
* TraitFactor(traitOffset)
* TeacherFactor(teacherSkill);
return Math.Clamp(current + delta, skill.Range.Min, skill.Range.Max);
}
}
+10 -3
View File
@@ -14,7 +14,7 @@ namespace HSchool.Simulation;
internal static class LessonLearningSystem
{
private static readonly QueryDescription Places =
new QueryDescription().WithAll<PersonIdentity, Presence>();
new QueryDescription().WithAll<PersonIdentity, Presence, PersonSkills>();
private static readonly QueryDescription People =
new QueryDescription().WithAll<PersonIdentity, PersonSkills, PersonTraits, PersonNeeds, PersonRoles, Presence, PersonActivity>();
@@ -43,11 +43,13 @@ internal static class LessonLearningSystem
var catalog = school.Catalog;
var world = school.World;
var places = new Dictionary<string, Presence>(StringComparer.Ordinal);
var teacherSkills = new Dictionary<string, Dictionary<string, float>>(StringComparer.Ordinal);
world.Query(
in Places,
(ref PersonIdentity identity, ref Presence presence) =>
(ref PersonIdentity identity, ref Presence presence, ref PersonSkills personSkills) =>
{
places[identity.Id] = presence;
teacherSkills[identity.Id] = personSkills.Values;
});
world.Query(
@@ -82,6 +84,10 @@ internal static class LessonLearningSystem
}
var hunger = needs.Values.GetValueOrDefault("Hunger", 1f);
IReadOnlyDictionary<string, float> taught = teacherSkills.TryGetValue(lesson.TeacherId, out var found)
? found
: new Dictionary<string, float>(StringComparer.Ordinal);
var teacherSkill = LessonLearning.AverageTeacherSkill(subject, taught, catalog);
foreach (var share in subject.Skills)
{
if (!catalog.Skills.TryGetValue(share.Skill, out var skill) || skill.Abstract)
@@ -101,7 +107,8 @@ internal static class LessonLearningSystem
rules.LessonSkillPerHour,
hours,
hunger,
TraitOffset(catalog, traits, share.Skill));
TraitOffset(catalog, traits, share.Skill),
teacherSkill);
}
});
}