Implement phase 35 dress appropriateness and school rules.

Adds outfit evaluation, morning home dressing, locker-room ChangeClothes with day-log events, PE/weather AI goals, dress-rules API, and tests.
This commit is contained in:
Leonid Pershin
2026-08-20 05:16:52 +03:00
parent 5b63c72806
commit 6a0d5a9886
32 changed files with 1894 additions and 25 deletions
+34 -4
View File
@@ -6,6 +6,7 @@ public enum GoalKind
{
None,
Duty,
Apparel,
Need,
Leisure,
}
@@ -39,7 +40,8 @@ public readonly record struct ActorState(
string? DutyRoom,
IReadOnlyDictionary<string, float> Needs,
Intent Intent,
bool LunchWindowOpen = false);
bool LunchWindowOpen = false,
ApparelActor Apparel = default);
/// <summary>
/// Picks a goal by weight and plans walk-then-do. No world, no clock — a table of inputs to an
@@ -101,6 +103,10 @@ public static class DecisionPlanner
{
var best = Intent.None;
Consider(ref best, DutyGoal(state, rules));
if (ApparelGoals.Goal(catalog, map, walks, state.Apparel, state, occupied) is { } apparelGoal)
{
Consider(ref best, apparelGoal);
}
foreach (var need in catalog.Needs.Values.OrderBy(def => def.DefName, StringComparer.Ordinal))
{
Consider(ref best, NeedGoal(catalog, map, walks, state, occupied, need, rules));
@@ -138,6 +144,8 @@ public static class DecisionPlanner
{
case GoalKind.Duty:
return DutyGoal(state, rules);
case GoalKind.Apparel:
return ApparelGoals.Goal(catalog, map, walks, state.Apparel, state, occupied) ?? Intent.None;
case GoalKind.Need:
if (state.Intent.Id is null || !catalog.Needs.TryGetValue(state.Intent.Id, out var need))
{
@@ -305,6 +313,27 @@ public static class DecisionPlanner
return new Decision(room, null, goal);
}
if (goal.Kind == GoalKind.Apparel)
{
if (goal.ActionId is null || !catalog.Actions.TryGetValue(goal.ActionId, out var apparelAction))
{
return Decision.Stay(Intent.None);
}
var apparelNode = RoomFor(catalog, map, walks, state, occupied, apparelAction);
if (apparelNode is null)
{
return Decision.Stay(Intent.None);
}
if (state.NodeId is not null && state.NodeId.Equals(apparelNode, StringComparison.Ordinal) && !state.IsWalking)
{
return new Decision(null, apparelAction.DefName, goal);
}
return new Decision(apparelNode, null, goal);
}
if (goal.ActionId is null || !catalog.Actions.TryGetValue(goal.ActionId, out var action))
{
return Decision.Stay(Intent.None);
@@ -485,9 +514,10 @@ public static class DecisionPlanner
private static int Order(GoalKind kind) => kind switch
{
GoalKind.Duty => 0,
GoalKind.Need => 1,
GoalKind.Leisure => 2,
_ => 3,
GoalKind.Apparel => 1,
GoalKind.Need => 2,
GoalKind.Leisure => 3,
_ => 4,
};
/// <summary>Effective numbers: the catalog's BehaviorDef, or the constants when a pack has none.</summary>