Roll named beaver rows up to their kind (NF1) - #1
Merged
Merged
Conversation
The game renames every character loaded from a save to "<template> <its
name>" (NamedEntityGameObjectSynchronizer, at PostInitialize) before
TickableEntityLifecycleManager records entity.Name, while a character
made during play keeps Unity's "<template>(Clone)". Profile keyed entity
rows by that raw name, so the real 0.1.3 session 2026-09-21_23-13-11 has
316 "BeaverAdult <name>" / "BeaverChild <name>" keys, and its entity
table shows BeaverAdult(Clone) at 14% of entity time when adult beavers
are 85% of it.
Profile.IdFor now keys ProfileKind.Entity by EntityKindOf(name): the text
before the first ' ' or '(', trimmed, or the whole name if that leaves
nothing. It runs only on a dictionary miss and remembers raw name -> kind
id, so each distinct name costs one lookup-and-register and every later
sampled tick is the existing allocation-free lookup. A name that already
is a kind resolves to the same id. Singletons, load steps, components and
watched methods keep their exact names.
tools/perflog.py applies the identical rule to entity rows in
profile_totals (entity_kind), so reports of older recordings rank beavers
right and compare lines an old recording up with a new one instead of
listing every entity kind as only in A or only in B. A KNOWN_ISSUES entry
(fixed in 0.1.4) says the recording's own profile.csv and summary.md are
still split.
Every entity name in the local recordings is "<template>(Clone)" or
"BeaverAdult|BeaverChild <name>", and none of the game's 393 blueprint
template names contains a space or '(', so the rule merges no distinct
kinds. CoreTests.WordsAndTails used an entity key only to test the CSV
cleaning of a name with a space; it now uses a method key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-up. The KNOWN_ISSUES entry for split entity rows was keyed
only by version ("fixed in 0.1.4"), but this change does not bump the
version, so every recording made by a build of it, and the checked-in
fixture the mod's own code wrote, still say 0.1.3 and were told their
entity rows are split when they are already kinds.
known_issues() now also consults KNOWN_ISSUE_APPLIES, a map from a note
to a check on the recording. The entity-split note (ENTITY_SPLIT_NOTE)
is printed only when some entity row's name is not its own kind, so the
real 0.1.3 session 2026-09-21_23-13-11 still gets it and the fixture and
unreleased builds do not. Notes without a check behave as before, and
KNOWN_ISSUES keeps its (fixed in, note) shape.
The note now says profile.csv has rows named after single beavers and
the in-game summary.md ranks beavers too low (its entity table is capped
at 15 rows, so "hundreds of rows" was only true of profile.csv). The
0.1.1 known-issues test asserts that none of the notes fixed by 0.1.1 is
printed, instead of one of them. CLAUDE.md records that a modded
template name containing a space or '(' would be cut short (none does).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # docs/TESTING.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Entity rows in
profile.csvandsummary.mdare now keyed by the entity's kind, not by the name the tick system recorded.When the game loads a character from a save, it renames it to
<template> <its own name>(NamedEntityGameObjectSynchronizer, at PostInitialize). That happens beforeTickableEntityLifecycleManagerrecordsentity.Name. A character made during play keeps Unity's<template>(Clone). As a result, every loaded beaver got its own row. In the real 0.1.3 session2026-09-21_23-13-11there are 316BeaverAdult <name>/BeaverChild <name>keys, and the entity table showsBeaverAdult(Clone)at 14% of entity time when adult beavers are really about 85%.tools/perflog.pyapplies the same rule to older recordings.reportnow ranks beavers correctly, andcomparelines an old recording up with a new one.Items
NF1: beaver rows are split by name, so beavers are badly under-ranked (fixed)
What changed
source/Core/Profile.cs:Profile.IdForkeysProfileKind.EntitybyEntityKindOf(name): the text before the first' 'or'(', trimmed, or the whole name if that leaves nothing.tools/perflog.py:entity_kindapplies the identical rule to entity rows inprofile_totals. That covers report, JSON report and compare.KNOWN_ISSUESentry, fixed in 0.1.4, says the recording's ownprofile.csvandsummary.mdare still split. After review, this note is printed only when the recording's entity rows really are split (KNOWN_ISSUE_APPLIES, see Review).CoreTests.WordsAndTailsused an entity key only to test CSV cleaning of a name with a space. It now uses a method key, which keeps the test's intent.Tests that prove it
tests/ProfileTests.cs, check "Profile: a beaver's own name and Unity's '(Clone)' are counted under the entity's kind, and only for entities". It checks:tools/test_perflog.pyEntityRollupTests:entity_kindcase table (the same cases as C#);test_named_entity_rows_are_rolled_up_to_their_kind: totals and the report show BeaverAdult at 78%, noMalak, and the known-issue line;test_an_old_recording_lines_up_with_a_new_one: compare shows no(only inlines;test_a_recording_whose_entity_rows_are_kinds_gets_no_split_note: an already rolled-up 0.1.3 recording and the checked-in fixture get no split note.Fail before, pass after
FAIL ... one row per kind of entity: expected BeaverAdult,BeaverChild,DistrictCenter.Folktails, got BeaverAdult Malak,BeaverAdult Zengu,BeaverAdult(Clone),BeaverChild Malak,DistrictCenter.Folktails(Clone)(91/92). After: 92/92.test_named_entity_rows_are_rolled_up_to_their_kind:first differing element ('entity', 'BeaverAdult') vs ('entity', 'BeaverAdult Malak');'(only in' unexpectedly found;AttributeError: no entity_kind.KNOWN_ISSUE_APPLIESfilter removed,test_a_recording_whose_entity_rows_are_kinds_gets_no_split_notefails (the note is listed for a recording of kinds). With the check forced to false,test_named_entity_rows_are_rolled_up_to_their_kindfails. Both pass with the change.profile_totalsnot callingentity_kind.2026-09-21_23-13-11now shows BeaverAdult 85%, DistrictCenter.Folktails 5%, BeaverChild 2%, and still prints the split note.(Clone)templates merge.' 'or'('.Tests
dotnet run --project tests -c Release ...python -W error::ResourceWarning -m unittest discover -s tools -p test_perflog.pydotnet build source/PerformanceLog.csproj -c ReleaseFixtures are unchanged: no columns changed, and the sample session's entity names have no
' 'or'('.git diff --checkis clean.Co-op impact
None. PerformanceLog only observes. This changes no Harmony patch, no patch priority, no simulation state and no wire format.
EntityKindOfis a pure string function, run on the game thread inside the existing try/catch inEndEntity, andidsByNameis still cleared at session start. Co-op players do not need to update together (beyond BeaverBuddies' usual mod-list version warning once a release is cut).Proposed CHANGELOG lines
(Not added to CHANGELOG.md. The
KNOWN_ISSUESentry assumes the release carrying this is 0.1.4; if it gets another number, change the string intools/perflog.pyto match.)Needs in-game / two-player testing
Solo is enough; this does not touch simulation. Use a local build of this branch, never the Workshop or the live Mods folder from an automated run.
profile.csv, everyentityrow should be a kind (BeaverAdult,BeaverChild,DistrictCenter.Folktails), with noBeaverAdult <name>rows and no...(Clone)rows.summary.md's "Entity kinds (sampled)" table should show BeaverAdult near the top (about 85% of entity time on a big colony).BeaverAdult/BeaverChildrow. On an Iron Teeth save, bots should appear asBot.IronTeeth.python tools/perflog.py report <the new folder>should print no "Entity rows are split by name" note, even though the build still says 0.1.3.python tools/perflog.py compare <a 0.1.3 recording, e.g. 2026-09-21_23-13-11> <the new recording>: section 4 should list entity kinds with values in both A and B, with no(only in A)/(only in B)lines for BeaverAdult or building kinds.Review
Three adversarial reviewers approved: determinism/co-op, save compatibility/cross-mod, and "is the test real". They found no blocker or major issue.
Fixed in 01cdc14:
KNOWN_ISSUESentry was keyed only by "fixed in 0.1.4", and this branch cannot bump the version. So the checked-in fixture, the CI "Report on the sample session" step and any recording from a build of this branch (all saying 0.1.3) printed "Entity rows are split by name" although their rows are already kinds.known_issues()now consultsKNOWN_ISSUE_APPLIES, a map from a note to a check on the recording, and the split note is printed only when some entity row's name is not its own kind.KNOWN_ISSUESkeeps its(fixed in, note)shape, so other branches adding plain entries are unaffected.summary.mdlists "hundreds of rows", but its entity table is capped at 15. It now saysprofile.csvhas rows named after single beavers and the in-gamesummary.mdranks beavers too low.Declined (out of scope, pre-existing, not touched by this diff):
compare()never printsknown_issues()for either recording. This predates this PR, and compare's figures are already rolled up correctly. It is left as a separate follow-up so this PR does not change compare's section 1 while other PerformanceLog branches edit compare.WriterTests"a file someone holds without sharing is retried" failed once in 7 reviewer runs with a sharing violation onframes.csv, a race in the test's ownFileShare.Noneopen. Neither the test norLogWriteris in this diff. It passed in every run here.Coordination: other PerformanceLog branches (NF2 and others) also add
("0.1.4", ...)KNOWN_ISSUES entries and change the TESTING.md check counts, so expect small conflicts there when they land.🤖 Generated with Claude Code