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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user