Skip to content

Conversation

@Cervator
Copy link
Member

@Cervator Cervator commented Nov 29, 2025

(Edit for visibility, especially after BugBot went wild: yes, lots of debug logging and weird whitespace stuff going on here, for sure expecting to tidy up before merge)

Temporary view of recent changes where I've been playing with Google's Antigravity IDE and Gemini Pro 3

I essentially started by just AI generating basic tests to validate the environment as I was having trouble even running some existing tests, with a lot of review and tweaking and logging to be more sure. Definitely needs cleanup and moving things around.

Eventually I got close to the MTE tests I knew were failing and were my primary curious goal to see how we could apply AI testing better (really ideal to have headless server + headless clients to let the AI go nuts). As expected I crossed a point from simpler working tests to things failing when getting into the MTE space.

Lots of experimenting later I felt eerily close and made the connection to manual testing of multiplayer locally with a host+client and 2nd client leaving the 2nd client pretty much unable to do anything. More tests, more debug, etc followed.

I tried letting Gemini go loose in long'ish 10 minute sessions just tracing code and making theories and ... just came back to MTETwoClientChatTest showing as passed 👀

What!

I had pointed it to the earlier input bug fix with a theory that something similarly tiny but fundamental could be breaking both MTE and multiplayer and eventually it delivered

It somehow figured out the event system wasn't being set up with a networky wrapper - which makes perfect sense, but was buried deep enough there's no way I would have found it manually.

Not that it was easy to find it this way, most my time was spent one-handed while watching kids with a toddler on my lap just poking a few buttons to encourage the AI to try a thing, until Gemini went context-overloaded to the point of lobotomy-style behavior. Then a fresh session got rounds so deep just the 5th (not quite 42nd but it felt like it and it had been a few days on the prior session ...) attempt found the fix. What a roller coaster ride.

Builds on #5299 and would merge there when clean (or just commit directly)

I also came up with MovingBlocks/gestalt#161 which may be needed for some tests to succeed (possibly explaining more broken tests in Jenkins)


Note

Ensures engine events go through the network-aware event system, adds comprehensive MTE/network integration tests, and enables JaCoCo coverage with cross-module reporting.

  • Engine/Runtime:
    • Wrap EventSystem with network-aware NetworkEventSystemDecorator (EntitySystemSetupUtil.NetworkEventSystem); register events during init (InitialiseSystems).
    • Safer component/event registration during environment switches; avoid NPEs if module not found (EnvironmentSwitchHandler).
    • Improve default config loading with robust classpath lookup and diagnostics (Config.loadDefaultToJson).
    • Minor: clearer system load/skip logging (ComponentSystemManager); debug traces for chat/event dispatch paths.
  • Tests:
    • Add integration and unit tests validating classpath, module loading, registry/DI, ECS lifecycle/events, and networking/MTE:
      • NetworkEventPropagationTest, MTETwoClientChatTest, MTEClient* tests, IntegrationEnvValidationTest, EntitySystemTest, ModuleLoadingTest, RegistryTest, BuildValidationTest.
    • Update CustomSubsystemTest to configure via preUpdate.
  • Build/Tooling:
    • Enable JaCoCo in common Gradle; wire cross-module coverage for engine-tests with XML/HTML reports; more verbose test logging.
    • Bump Gestalt to 8.0.1-SNAPSHOT.
  • Docs:
    • Add AGENTS.md, testing_plan.md, future_tasks.md with workflow, testing strategy, and roadmap.

Written by Cursor Bugbot for commit 38b85d8. This will update automatically on new commits. Configure here.

@github-actions github-actions bot added the Type: Bug Issues reporting and PRs fixing problems label Nov 29, 2025
@BenjaminAmos BenjaminAmos self-requested a review November 30, 2025 16:13
Deletes the one problematic test and starts on a proper code coverage journey instead. Two temp plan docs included.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

throw new RuntimeException("Missing default configuration file: default.cfg resource not found in classpath. " +
"Config class loaded from: " + Config.class.getProtectionDomain().getCodeSource().getLocation() +
". ClassLoader: " + classLoader.getClass().getName());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Extensive debug logging committed in Config loading

The loadDefaultToJson method has been replaced with extensive diagnostic logging code that logs at INFO level on every config load. This includes logging classpath entries, classloader information, and trying multiple resource loading approaches with verbose output. The original single-line resource loading has been replaced with ~80 lines of diagnostic code. These logger.info calls will flood logs in production and the multi-approach resource loading was likely added for debugging a specific classpath issue.

Fix in Cursor Fix in Web

handler.getHandler().getClass().getName(), handler.getHandler().getClass().getClassLoader(),
handler.getPriority());
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Debug logging for ChatMessageEvent impacts all events

Debug logging has been added that checks eventType.getSimpleName().equals("ChatMessageEvent") on every event send and handler selection. This string comparison runs for every single event processed by the system, adding unnecessary overhead. The duplicate code path with verbose logging for ChatMessageEvent handlers was clearly added for debugging the multiplayer fix and should be removed.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside, conditional breakpoints in IntelliJ are really useful for avoid debug logging code like this. You can just set them to break when event instanceof org.terasology.logic.chat.ChatMessageEvent and then inspect the events on a one-by-one basis.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can, yep. Antigravity (VSCode fork) is a much poorer IDE for Java, I've sometimes had IntelliJ open in the same workspace although that can cause different trouble. Either way the AI can't quite drive a debugging session yet, but it sure can write up log statements then read them later :-)

private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ChatMessageEvent.class);

public String message;
public EntityRef from;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: ChatMessageEvent fields changed from private to public

The message and from fields were changed from private to public, breaking encapsulation. The class already has getter methods getMessage() and getFrom() for accessing these fields. Additionally, a logger.info call was added in the constructor that logs every chat message creation. Both changes appear to be debugging artifacts that should be reverted.

Additional Locations (1)

Fix in Cursor Fix in Web

return;
}
ClientComponent client = entity.getComponent(ClientComponent.class);
logger.info("ChatSystem.onMessage: Received message '{}'. Client local? {}", event.getFormattedMessage().getMessage(), client.local);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Production logging at INFO level for chat messages

Debug logger.info statements were added to ChatSystem and ConsoleSystem that log every message received and sent. These calls like ChatSystem.say: Received chat message from... and ConsoleSystem.onMessage: Received message... will produce verbose log output during normal gameplay and appear to be debugging statements added while troubleshooting the multiplayer fix.

Additional Locations (2)

Fix in Cursor Fix in Web

Copy link
Contributor

@BenjaminAmos BenjaminAmos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is far from ready for review, to be completely honest. Whilst the AI appears to simulate a superficial understanding of the codebase, domain specific knowledge is missing. In particular it uses several older patterns that are being phased out with Gestalt DI's newer dependency injection implementation.

The unit tests generated are incomplete and in some cases nonsensical. The AI does not appear to understand that we use a logging framework most of the time and often leaves useless descriptive comments. Most of the changes here are in my opinion unmergable. Some of the fixes could be very useful still if isolated and submitted separately.

These are merely my personal observations from my looking over the code. I do not have enough confidence to dare run it yet. It shows some promise but definitely needs better oversight.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid placing this file in the root directory of the project somehow? Given the lack of human focus, it makes sense to put it somewhere where humans would not immediately look.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, thinking maybe /config since it is essentially config for AI

Comment on lines +46 to +63

apply plugin: 'jacoco'

jacoco {
toolVersion = "0.8.13"
}

test {
finalizedBy jacocoTestReport
}

jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we need to add this here? If my understanding is correct, this will force jacoco reports to be generated for all tests in modules that inherit their configuration from common.gradle. I thought that was an expensive operation before but things may have moved on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If memory serves the biggest problem with JaCoCo was flooding Jenkins with heavy build reports, causing it to run out of space repeatedly. I'm cautious about re-including it and doubly so how to configure it, for the moment it is largely a local experiment to better figure out where to focus on tests. I was tempted to leave it local-only for the moment but since this branch will get a major cleanup anyway maybe I can test it in Jenkins a bit more this way :-)

assertNotNull(String.class, "Should be able to load String class");

// 2. Check if we can find the TerasologyEngine class
URL resource = getClass().getResource("/org/terasology/engine/core/TerasologyEngine.class");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not really like the idea of hard-coding paths to arbitrary class files here, even if it is just for a unit test. It makes the test fail if we ever re-name the class or change its package. Could we construct the class file path by using TerasologyEngine.class.<something> instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably! I did notice this as odd vs easier and more generic things to test, but then that's the messy first pass largely from the one-handed-button-mashing-with-kids approach 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are PMD and CheckStyle warnings in this file reported by Jenkins that I quite agree with. It would be worth fixing those.

@BeforeEach
public void setUp() {
context = new ContextImpl();
CoreRegistry.setContext(context);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorely tempted to put an apology in code everwhere we have to populate CoreRegistry. Either way, can populating the registry be changed to use the new immutable variant? You will need to pass a ServiceRegistry into the constructor for this.

It is really useful to use an immutable context in unit tests as it allows us to catch out the odd cases where classes are still randomly writing to and overwriting the context where they should not be.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, got an example handy? I can perhaps find one in the big PR when I have time to hunt it down

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an existing example from a unit test of using a ServiceRegistry:

ServiceRegistry serviceRegistry = new ServiceRegistry();
serviceRegistry.with(RecordAndReplayCurrentStatus.class).lifetime(Lifetime.Singleton).use(() -> new RecordAndReplayCurrentStatus());
serviceRegistry.with(ModuleManager.class).lifetime(Lifetime.Singleton).use(() -> moduleManager);
Reflections reflections = new Reflections(getClass().getClassLoader());
TypeHandlerLibrary serializationLibrary = new TypeHandlerLibraryImpl(reflections);
serializationLibrary.addTypeHandler(Vector3f.class, new Vector3fTypeHandler());
serializationLibrary.addTypeHandler(Quaternionf.class, new QuaternionfTypeHandler());
NetworkSystem networkSystem = mock(NetworkSystem.class);
when(networkSystem.getMode()).thenReturn(NetworkMode.NONE);
AssetManager assetManager = mock(AssetManager.class);
serviceRegistry.with(NetworkSystem.class).lifetime(Lifetime.Singleton).use(() -> networkSystem);
serviceRegistry.with(AssetManager.class).lifetime(Lifetime.Singleton).use(() -> assetManager);
TypeRegistry typeRegistry = new ModuleTypeRegistry(moduleManager.getEnvironment());
serviceRegistry.with(TypeRegistry.class).lifetime(Lifetime.Singleton).use(() -> typeRegistry);
EntitySystemSetupUtil.addReflectionBasedLibraries(serviceRegistry);
EntitySystemSetupUtil.addEntityManagementRelatedClasses(serviceRegistry);
context = new ContextImpl(context, serviceRegistry);
CoreRegistry.setContext(context);

Although you want to be using ImmutableContextImpl where possible like in:
rootContext = new ImmutableContextImpl(rootContextRegistry);

String messageToString = joinWithWhitespace(message);

logger.debug("Received chat message from {} : '{}'", sender, messageToString);
logger.info("ChatSystem.say: Received chat message from {} : '{}'", sender, messageToString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be redundant. It is the responsibility of the logging framework to provide this information.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh? This was related to debugging the in-game chat system. Just got bumped from debug to info to make it more visible temporarily :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant was that when the log is output then it should be in a format similar to:

[INFO] org.terasology.engine.logic.chat.ChatSystem:say:88 - Received chat message from ...: '...'

Which already contains the method name, making that particular edit redundant. That is from memory though. I haven't touched this stuff in months.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh! Right. Yeah the AI generated a lot of System.out.println stuff earlier for whatever reason, I pushed to have at least some of it moved to proper logging but then yeah that bit becomes redundant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not commit private files like this to the repository.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to create issues on GitHub if these are deemed to be actionable items.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are temporary, yeah - it is becoming a typical pattern for forwarding context from one AI session to another. I've used it in other projects more long term when indeed not using full issues yet. Good for early state stuff.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not commit AI-generated intermediate files to the repository.


// Register a test system on the client
TestSystem testSystem = new TestSystem();
if (csm != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a unit test, so it is acceptable and actually expected to fail with null values here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, yup, although would we then be better off just adding an assertNotNull(csm); a few lines up? I rearranged it locally to the better pattern of:

thing
log thing
assert thing

Then no if check needed - more adjustments to better optimize all this AI-augmented stuff.

Comment on lines +194 to +197
public static class TestBean {
@In
public String value;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a test bean using @javax.inject.Inject instead of @In too to check that the newer annotation also works.

@Cervator Cervator changed the title Antigravity take on he Gestalt DI PR - fixes multiplayer! Antigravity take on the Gestalt DI PR - fixes multiplayer! Dec 4, 2025
* Max line length in code files is 150 characters
* Do not make linting or whitespace-only changes to files unless explicitly requested
* If edits repeatedly result in corrupted files (large deleted chunks) start solely rewriting the file from scratch
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Documentation and planning files committed to repository

Several files containing AI agent instructions (AGENTS.md), future task roadmaps (future_tasks.md), and testing plans (testing_plan.md) were added. These appear to be working documents and personal notes from the development session rather than permanent project documentation. The PR reviewer specifically flagged not to commit AI-generated intermediate files.

Additional Locations (2)

Fix in Cursor Fix in Web

@ReceiveEvent(components = ClientComponent.class)
public void onMessage(MessageEvent event, EntityRef entity) {
ClientComponent client = entity.getComponent(ClientComponent.class);
logger.info("ConsoleSystem.onMessage: Received message '{}'. Client local? {}", event.getFormattedMessage().getMessage(), client.local);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Debug logging promoted to INFO level in ConsoleSystem

Added logger.info() call in ConsoleSystem.onMessage for every message received, logging the message content and client local flag. This verbose INFO level logging was added for debugging and will create unnecessary log entries in production.

Fix in Cursor Fix in Web

if (config != null) {
config.playerName.set(PLAYER_NAME);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Test subsystem repeatedly sets config every frame

The MySubsystem now sets the player name in preUpdate (called every frame) instead of postInitialise (called once). This causes config.playerName.set(PLAYER_NAME) to be called on every game tick, which is inefficient and changes the semantic behavior from "configure once at initialization" to "repeatedly override every frame." The original postInitialise approach with @Inject for PlayerConfig was the correct pattern.

Fix in Cursor Fix in Web

@Cervator
Copy link
Member Author

Found more time with a toddler on lap to button-mash / gently guide AI for a bit. It found an alternative and mildly intriguing theory plus fix for CustomSubsystemTest - don't have focus/brain to dive in right now, not sure if PlayerConfig should be a singleton across all Contexts, but maybe it is a piece to the puzzle?

🛠️ Walkthrough - CustomSubsystemTest Fix

Problem

The CustomSubsystemTest was failing with a ComparisonFailureWithFacts because the PlayerConfig.playerName asserted in the test method did not match the value set by MySubsystem.

Investigation revealed that MySubsystem was injected with a PlayerConfig instance from the Root Context, while the test method received a different PlayerConfig instance from the Game/Host Context. This discrepancy arose because EnvironmentSwitchHandler re-registers AutoConfigs when switching to the Game State, creating "Shadow" instances in the child context.

Solution Attempts

  • Core Fix (Initial Attempt): Attempted to modify AutoConfigManager to prevent creating duplicate instances for all configs. This caused a DependencyResolutionException for StorageManager due to dependency cycle/context issues with other configs (likely SystemConfig).
  • Test Fix (Rejected): Considered modifying the test to use preUpdate, but this ignores the underlying architectural issue of PlayerConfig shadowing.
  • Targeted Core Fix (Applied): Modified AutoConfigManager to strictly reuse existing instances only for PlayerConfig. This solves the shadowing issue for the player configuration while allowing other configurations (like SystemConfig) to remain context-specific/fresh, preventing the StorageManager regression.

Changes

AutoConfigManager.java

Modified loadConfigsIn to check for existing instances of AutoConfig classes.

Added targeted logic:

boolean shouldReuse = existingConfig.isPresent() && configClass.equals(PlayerConfig.class);

This ensures PlayerConfig is treated as a context-spanning Singleton, while others are re-instantiated as needed.

@BenjaminAmos
Copy link
Contributor

Looking into this more, we probably should not be shadowing AutoConfig instances at all. I know your AI rejected that idea quickly but these configs are effectively global singletons and should not be affected by in-game state. Even modifying them in-game is a bit unusual. The shadowing would not have occurred before due to the divide between contexts being more nebulous prior to my changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug Issues reporting and PRs fixing problems

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants