Monthly Archives: September 2008

VSTorqueScript #5.1

Sadly there isn't a lot new that I can post since I've hardly had time to work on my pet project during the last week - a few friends from Texas and California came over for the Oktoberfest and it suffices to say that I drank a lot of beer last week.

Nonetheless there is some progress and I've finished support for stepping in break mode (although I still have no idea what enum_STEPKIND.STEP_BACKWARDS is about, since I've never heard of stepping backwards before).

There is no screenshot this time because you wouldn't really see a difference, except if I did, before/after screenshots to show that the stepping works.

A few coding notes as always:

  • The current TorqueScript telnet debugger doesn't really tell you whether it breaks because of stepping or because it has hit a breakpoint, so I have to figure it myself. It is pretty straightforward though because stepping usually implies a certain callstack change (or the lack thereof) when finished:
    • "Step Into" implies that the new callstack height > old height
    • "Step Over" implies no change
    • "Step Out" implies of course: new callstack height < old height

    So it's pretty easy to figure out whether the stepping was finished or interrupted by a breakpoint.

  • The class diagram designer/viewer in Visual Studio 200x is awesome. If you haven't tried it, you've missed a really helpful feature of Visual Studio.
    Lately I've been using it to comment my public functions and parameters, etc. and for small refactorings (like name changes) in general.

Last but not least I think I've written a neat communication class for synchronous and asynchronous message processing which I'd like to share (if you're going to use it though, please tell me and/or send me some feedback):

Read more »

VSTorqueScript #5

It took me quite long to make progess in the last days. Partially this is due me being in Munich again and working in my uni job part-time again - and I'm stunned each time how tired I am when I come home.

It also is due to me experimenting with my TelnetDebuggerClient implementation that connects my debug engine to Torque's Telnet Debugger. I've tried quite a few different approaches for synchronous and asynchronous communication before I could settle for the simplest and the more advanced ones reside in the repository's history. I'll probably have to switch to them sooner or later because I only implement asynchronous communication at the moment which is fine for most things except when you need more advanced debugging info like for displaying objects properly in the debug window for example.

I've also had to read the "Definitive ANTLR Reference" in one night because I had to return it to uni's library the next day, so I was busy for 7 hours reading 350 pages of a parser reference. It was interesting though and tonight I'll try to write a QuakeC grammar - that's the plan at least.

However yesterday, I had time to work on the debug engine some more and here's a picture that tells it all:

VSTorqueScript now supports displaying the callstack, too

VSTorqueScript now supports displaying the callstack, too

As you see you can now break a program and it will retrieve the callstack from Torque and update the callstack window automatically and open the right file and jump to the correct file position, too - or rather as I've just noticed one-line off because VS line indicies have base 0 and TorqueScript uses base 1 >_>...

A few implementation notes as always:

  • I t seems, if your Debug Engine doesn't implement IDebugEngineLaunch2, your Program Provider has to implement IDebugProgramProvider2.WatchForProviderEvents
  • Moreover, if you don't implement IDebugEngineLaunch2, which in turn forces you to implement IDebugProcess2 (and maybe IDebugProcess3, too), suddenly some "deprecated" methods in your Program class (your IDebugProgram2 implementation) will be called, which, if you follow MSDN and the debug engine sampe, will result in failed asserts and the nice fail message "This function is not called by the debugger." :-)
    It's kind of logical that IDebugProgram2.Execute() is going to be called because the debugger has no IDebugProcess2 interface to call Execute on..
  • What is enum_FRAMEINFO_FLAGS_VALUES? It's undocumented and I suspect it's used for the undocumented m_dwFlags field in FRAMEINFO...
  • I'm totally stunned by how stable Visual Studio is. Most the interfaces I implement are only half implemented and return E_NOTIMPL on most calls and Visual Studio runs and runs well and only displays the information it can in a clean way.
    I guess this is one of the advantages of the COM programming/design paradigma and I think it's great. Being able to implement interfaces bit by bit and add functionality slowly makes debugging easier and also helps you understand things better - which is good because even though a lot is documented, you still need some practice and trial & error to get things right eventually.

Enough is enough though for now, and I'll head to uni in a bit.

Only one last remark: I've started reading the book "The Pragmatic Programmer" and so far it's really entertaining and, I guess, good.
I'll write more on it, when I've finished it and can actually assess its qualities..

Cheers,
Andreas

Weird Things Happen

Today I was at work for the first time since August and nobody seems to have logged into my shared workstation since then since my account name was still in the login field.
The first weird thing that happened was that suddenly the application I've been working on didn't run correctly anymore.
First let me tell you a bit about the app:
It renders a volume texture with some custom raytracing shaders in one window and allows editing of the transfer function - this is a function/texture that maps a scalar value in the volume texture to a color in another window. The transfer function is made up of primitives/shapes that you can colorize and stretch or deform the way you want to allow for easy editing. Look at this picture to see what a transfer function (editor) could look like - this isn't ours though.
The TF editor uses an FBO to render the transfer function into the transfer texture.

While the rest of the app was running mostly fine (that is AntTweakBar rendered fine and the background of the transfer function editor which is a histogram of the volume texture, too), the actual transfer function rendering was corrupted.
On my workstation you could move the primitive around and while you did so it would either totally disappear, invert its color or show small light green or purple blocks that stayed stationary inside the window as you moved the primitive around - the whole look liked corrupted memory that stayed corrupted and was only visible when the primitive rendered over it.

It's a pity I didn't take any screenshots but I didn't think of that back then.

I was totally at a loss with this behavior, so I rebooted which didn't fix it. Somehow I was hoping that the gfx card broke in the last month and it wasn't my code which caused this totally inexplicable behavior.
To determine this I logged into another workstation in our lab, checked out my code and run the program. To my horror it had similar problems... now quite the same but still strangely colored sprinkles all over the editor window when the primitive was rendered there. Both workstations are using graphics cards from Nvidia (8800 GTX if I recall correctly).

I tried to determine the revision which broke rendering with a binary search like trial & error through our revision history at work and found out that it broke when I changed the way the primitives were rendered to the texture.

It used to simply iterate through all primitives and render them once to the FBO and it was done.

The paper I'm implementing at the moment required  a special solution for overlapping primitives though, so I switched to a more complicated 2.5 pass rendering.

If $latex c_i $ is the color value of the ith primitive and $latex \alpha _ i $ its alpha value, then the resulting color and alpha are calculated as follows: $latex \left< c, \alpha \right> = \left< \frac{\displaystyle\sum_i^n{c_i \alpha_i}}{\displaystyle\sum_{i}^n{\alpha_i}},\max_{i=1..n}{\alpha_i} \right>$.

For this I do three passes and use two floating point textures:

  1. Render the primitives into a temp texture with
    glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE );

    to get $latex \left< {\displaystyle\sum_i^n{c_i \alpha_i}},{\displaystyle\sum_{i}^n{\alpha_i}} \right> $.

  2. Next I set the FBO to the real transfer function texture and use the temp texture as source and divide the color values by their alpha using a custom shader.
  3. Finally I use
    glBlendEquation( GL_MAX );

    and only masks all except the alpha channel and render all primitives again into the transfer function texture. This gets the correct term into the alpha channel of the texture.

This usually works and although it's a bit cumbersome it yields the correct result. Only now I identified it to be the source of all my trouble.

Here's the relevant part in our source code:

_checkGLErrors();
m_fbo.AttachTexture( GL_COLOR_ATTACHMENT0_EXT, m_tempTexture.glTarget, m_tempTexture.glTexID );
assert( m_fbo.IsValid() );
m_fbo.Bind();
_checkGLErrors();

glClearColor( 0.0, 0.0, 0.0, 0.0 );
glClear( GL_COLOR_BUFFER_BIT );

// C = srcC * srcA + dstC = C * A + C' * A' + ...
// A = srcA + dstA = A + A' + A'' + ...
glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE );
glBlendEquationSeparate( GL_FUNC_ADD, GL_FUNC_ADD );

for( Primitive::PList::const_iterator i = m_primitives.begin() ; i != m_primitives.end() ; i++ ) {
(*i)->bake();
}

m_fbo.Unattach( GL_COLOR_ATTACHMENT0_EXT );
m_fbo.Disable();
_checkGLErrors();

// divide color by alpha
glMatrixMode( GL_PROJECTION );
glLoadIdentity();

glBlendFunc( GL_ONE, GL_ZERO );

procInf.imageOp( m_tempTexture, m_transferFunction, m_program );

// render again this time to figure out the max alpha value

glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0.0, 1.0, 0.0, 1.0, -1.0, 1.0 );

m_fbo.AttachTexture( GL_COLOR_ATTACHMENT0_EXT, m_transferFunction.glTarget, m_transferFunction.glTexID );
assert( m_fbo.IsValid() );
m_fbo.Bind();
_checkGLErrors();

glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE );
glBlendEquation( GL_MAX );

for( Primitive::PList::const_iterator i = m_primitives.begin() ; i != m_primitives.end() ; i++ ) {
(*i)->bake();
}

Now I was ready to hunt the bug but the problem the revision was written more than a few days before I left in August and it was working back then. I refused to believe that it was broken and I went to my boss who had been using his rig in the last days and indeed it worked. I couldn't really believe my eyes. Two workstations appeared to be broken for some reason (driver issues or even the gfx cards? - who would know..).

I went back and somehow managed to change the appearance of the bug on one of the workstations - all the framebuffer corruptions went away and were replaced with what seemed like an unchanging alpha channel of one of the FBO textures with alpha being 0 on the initial position of the primitive and 1.0 everywhere else, so it masked it out at its initial position.

For some reason I thought that maybe using BlendFuncSeparate and the normal BlendFunc function together maybe left OGL's state machine in a mess for some reason so I added calls to disable and enable blending to code to render the call to glBlendFunc useless (see the code above and/or below).

_checkGLErrors();
m_fbo.AttachTexture( GL_COLOR_ATTACHMENT0_EXT, m_tempTexture.glTarget, m_tempTexture.glTexID );
assert( m_fbo.IsValid() );
m_fbo.Bind();
_checkGLErrors();

glClearColor( 0.0, 0.0, 0.0, 0.0 );
glClear( GL_COLOR_BUFFER_BIT );

// C = srcC * srcA + dstC = C * A + C' * A' + ...
// A = srcA + dstA = A + A' + A'' + ...
glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE );
glBlendEquationSeparate( GL_FUNC_ADD, GL_FUNC_ADD );

for( Primitive::PList::const_iterator i = m_primitives.begin() ; i != m_primitives.end() ; i++ ) {
(*i)->bake();
}

m_fbo.Unattach( GL_COLOR_ATTACHMENT0_EXT );
m_fbo.Disable();
_checkGLErrors();

// divide color by alpha
glMatrixMode( GL_PROJECTION );
glLoadIdentity();

---> glDisable(GL_BLEND); <---
glBlendFunc( GL_ONE, GL_ZERO );

procInf.imageOp( m_tempTexture, m_transferFunction, m_program );

// render again this time to figure out the max alpha value

glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0.0, 1.0, 0.0, 1.0, -1.0, 1.0 );

m_fbo.AttachTexture( GL_COLOR_ATTACHMENT0_EXT, m_transferFunction.glTarget, m_transferFunction.glTexID );
assert( m_fbo.IsValid() );
m_fbo.Bind();
_checkGLErrors();

---> glEnable( GL_BLEND ); <---
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE );
glBlendEquation( GL_MAX );

for( Primitive::PList::const_iterator i = m_primitives.begin() ; i != m_primitives.end() ; i++ ) {
(*i)->bake();
}

m_fbo.Disable();

When I ran the program, it decided to work normally and render everything the way it should. I was stunned once again.
Now the good thing with causality is that everything is supposed to be repeatable, so I removed the statements again and was expecting to see the bug appear again.. - but guess what it did not and suddenly the machine was working again as supposed.

I tried the same fix with the other workstation in our lab and it fixed it before I had time to take some screenshots of the persisting framebuffer corruption.

I'm still at a loss here and I guess I won't be able to come up with an explanation of my own for this, so if anyone has an idea, feel free to tell me :-)

Cheers,
Andreas

VSTorqueScript #4

My little pet project is actually called VSTorqueScript, so I've decided to use the correct title for my posts, too.

Today has been a somewhat productive day, although not as productive as I've hoped, because I was stuck with a very stupid issue for 3 or 4 hours. I'm not proud of it, but it made me go to the VSX forum and become active there.

I've worked more on the debug engine afterwards (well, actually I went jogging and swimming - in that order.. think about it ) and made some progress. Quite a bit of the stub stuff is implemented now and I think I'll start implementing the real thing tomorrow.

No screenshots this time, I'm sorry, but for the SDK users, I have a little helper file. If you look at the managed debug example, you'll find an AD7Events file that wraps the debug event interfaces.
I wasn't easily able to port it over, so I decided to write my own version of it (while keeping the comments).

I've appended it to the post, if you want to take a look.

On other news I'll probably switch this blog over to my own webspace soon to get some advantages (like expandable sourcecode amongst things), but I think I'll stick to wordpress.com for a few more weeks.
It's really a pity that you can't redirect from wordpress to your domain, but only vice-versa :(

Anyway, off to work some more on my code.

Cheers,
Andreas

Read more »

Visual TorqueScript #3.75

Another minor update, which I'm mostly using to publish a useful patch.
I've forgotten to mention that I've already created a custom settings page for my Visual TorqueScript projects. Yesterday night (before going to bed) I decided to add a configuration dependent page, too.
As you'll see it's very Torsion-esque, which is intentional and I plan to support .torsion files directly (as project files) later-on.

The main reason is that Torsion rocks and there is no reason to have yet another project file format for TorqueScript projects.

I hope that it will also be an incentive for people to give my plugin a try (if I ever release it).

A few notes (programatically so to speak :D ):

  • Again the managed project framework is awesome and once you figure out where to look for things you'll certainly find eternal bliss, too..
  • I generally derive from SettingsPage directly, because I don't want any of the regular faff in my project.
  • Don't forget to publish your pages via ProvideObject (or ProvideClass from my previous post)
  • You don't need to create your own GUIDs for everything. Classses are automatically provided with a unique GUID - try it and access typeof(SomeClassWithoutGuidAttribute).GUID and it'll still work.
    (If I said something wrong, please correct me..)
  • SettingsPage has an IsDirty property which you have to set, when a property is changed (to update the Apply button state).
    It results in pretty ugly copy'n'paste code, because you can't use default property accessors anymore.
    To fix this I spent an hour or two and I've come up with an elegant (in my opinion) patch to fix the issue:

    Index: ProjectBase/SettingsPage.cs
    ===================================================================
    --- ProjectBase/SettingsPage.cs	(revision 10)
    +++ ProjectBase/SettingsPage.cs	(working copy)
    @@ -27,7 +27,26 @@
    
     namespace Microsoft.VisualStudio.Package
     {
    +    // Added to make it easier to edit pages [9/10/2008 Andreas]
    
    +    class SettingsPagePropertyDescriptor : DesignPropertyDescriptor
    +    {
    +        public SettingsPagePropertyDescriptor(PropertyDescriptor prop)
    +            : base(prop)
    +        {
    +        }
    +
    +        public override void SetValue(object component, object value)
    +        {
    +            if (component is SettingsPage)
    +            {
    +                SettingsPage page = (SettingsPage)component;
    +                page.IsDirty = true;
    +            }
    +            base.SetValue(component, value);
    +        }
    +    }
    +
         /// <summary>
         /// The base class for property pages.
         /// </summary>
    @@ -78,7 +97,7 @@
                 get { return this.grid; }
             }
    
    -        protected bool IsDirty
    +        protected internal bool IsDirty
             {
                 get
                 {
    @@ -203,6 +222,13 @@
    
             #endregion
    
    +        #region overriden methods.
    +        public override DesignPropertyDescriptor CreateDesignPropertyDescriptor(PropertyDescriptor propertyDescriptor)
    +        {
    +            return new SettingsPagePropertyDescriptor(propertyDescriptor);
    +        }
    +        #endregion
    +
             #region IPropertyPage methods.
             public virtual void Activate(IntPtr parent, RECT[] pRect, int bModal)
             {
    @@ -410,6 +436,8 @@
                     try
                     {
                         Marshal.WriteIntPtr(ppUnk, p);
    +                    // Reset IsDirty before rebinding all properties [9/10/2008 Andreas]
    +                    IsDirty = false;
                         this.BindProperties();
                         // BUGBUG -- this is really bad casting a pointer to "int"...
                         this.grid.SetSelectedObjects(1, ppUnk.ToInt32());
    

    It also contains a one-line fix to reset IsDirty when reloading the page or switching configurations (ie. any time the properties are reset to their old value).

Cheers,
Black

Visual TorqueScript #3.5

Small update: After hours and hours of error and trial (and implementing a few interfaces by using Copy&Paste wisely), I've finally managed to get Visual Studio to load my debug engine.

This looks gorgeous, doesn't it?

A few notes for those interested in writing Debug Engines:

  • See my previous post for the ProvideDebugEngine helper attribute
  • There exists a really nice managed debug engine example, that isn't part of the SDK.
    You can find it here.
  • You are required to set a PortSupplier, even if the documentation says otherwise (this was a major source of frustration in the last hour).
  • Since ProvideDebugEngine requires a Type for PortSupplier, I've been using this stub class:
        /// <summary>
        /// The class is just a stub for use with ProvideDebugEngine.
        /// It seems it's mandatory to provide a PortSupplier GUID with a debug engine.
        /// </summary>
        [Guid("708C1ECA-FF48-11D2-904F-00C04FA302A1")]
        internal sealed class DefaultPortSupplier
        {
        }
    
  • I've also added another attribute (ProvideClass) for convenience to register a class directly (it's the same as using ProvideObject on the current class type).
    Since the code is quite short, I'm going to supply it directly:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Microsoft.VisualStudio.Shell
    {
        /// <summary>
        /// This is just a wrapper for ProvideObject to provide the class the attribute is used on
        /// </summary>
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        class ProvideClass : RegistrationAttribute
        {
            public override void Register(RegistrationAttribute.RegistrationContext context)
            {
                ProvideObjectAttribute objectProvider = new ProvideObjectAttribute(context.ComponentType);
                objectProvider.Register(context);
            }
    
            public override void Unregister(RegistrationAttribute.RegistrationContext context)
            {
                ProvideObjectAttribute objectProvider = new ProvideObjectAttribute(context.ComponentType);
                objectProvider.Unregister(context);
            }
        }
    }
    

And yes, I love the tag :-)
Cheers,
Andreas

Visual TorqueScript #3

I've spent a part of the last two days cursing the managed project framework (it's a bliss, too, and I'm thankful that it's available at the same time), because it is too different from what I need, and I'll probably end up rewriting most of it to use different approaches and not use MSBuild anymore.

I've spent more time, however, on reading up on Debug Engines in Visual Studio 2008 and I've started work on my own yesterday. To be honest, I haven't really started to work on it, I've rather spent the evening and most of today figuring out how to register a debug engine at all and write attributes for that, so that it becomes an automated process, just like it's done for Packages and Language Services by using attributes like ProvidePackage and ProvideLanguageService.

The code has turned out pretty nicely and now you can use the attributes like this, for example, to register a debg engine:

    [ProvideDebugEngine("{5887F58F-3B7F-401d-95CE-A7BADA1E4D7D}", "TorqueScript Debug Engine",
        Attach = true, ProgramProvider = typeof(VSTorqueScript_DebugEngine))]
    [ProvideIncompatibleEngineInfo( "{5887F58F-3B7F-401d-95CE-A7BADA1E4D7A}" )]
    [ProvideIncompatibleEngineInfo( "{5887F58F-3B7F-401d-95CE-A7BADA1E4D7F}" )]
    [ProvideAutoSelectIncompatibleEngineInfo( "{5887F58F-3B7F-401d-95CE-A7BADA1E4D7F}" )]
    [ProvideObject(VSTorqueScript_DebugEngine)
    [Guid(GuidList.guidVSTorqueScript_DebugEngineString)]
    public sealed class VSTorqueScript_DebugEngine
...

In my opinion this looks pretty neat - and it works.. \o/
I really love the whole concept of attributes and the way they make coding a lot easier (even though it's hard to find information about useful attributes, as you only find out they exist when you stumble over them or write them on your own).

You can find the code here: http://www.icculus.org/~black/weblog/VS_DebugEngine/

A few notes:

  • The implementation was done making heavy use of copy&paste and quick replace (I copied the table containing the help description of the metrics into the source file and reworked it using RE magic to look the way it does now)
  • At first I used Type.GetProperties to enumerate all properties and write them out into the registry but that bloated the resulting registry entry, of course. Because of this I later switched to the HashTable approach that I first found in SDK's implementation of ProvideLanguageServiceAttribute.
  • The restriction that all attribute parameters must be constant expressions forced me to make a few changes - at first I had Guid properties and for the incompatible engines list I expected a Guid[], which didn't work. I changed the Guids to Strings and added the Provide*EngineInfo attributes instead of the incompatible engines list (same for the autoselect one, of course).
  • The Provide*EngineInfo attributes contained their registry code at first. This was ugly because they kind of required a ProvideDebugEngine attribute to exist, too, which I couldn't enforce. Later I changed them to be pure value holders and moved all registry code into ProvideDebugEngine.
    Now nothing happens if you don't supply a ProvideDebugEngine attribute, too.

Now I'll try to start to work on the actual Debug Engine for TorqueScript.

Cheers,
Andreas

Visual TorqueScript #2

I haven't found time to add IntelliSense to the language service yet, so I've only added a basic parser and enabled brace highlighting/matching which was easier to do.

I've been told that a working debugger would be the most important feature, so I try to get started with it, but before I can do that I have to add a custom project type to support the standard .cs extension for TorqueScript and to offer a specialized debug options property page for Torque's telnet debugger.

Here you can see the progress I've made so far:
I've added a new custom project type and changed everything to support TorqueScript files that use the usual .cs extension. There is no conflict with C# whatsoever. (At least from what I know..)

Next I'll look into removing the MSBuild faff (if possible at all) and adding the debug configuration page.

Last but not least I've found another neat tool that I can only recommend and want to share with whoever might read this:
It's called VSSDK Assist and it's a so-called Guidance Package - it provides you with a neat assistance window that helps you do common tasks using the Visual Studio SDK in the preferred way (it supplies you with lots of dialog-based wizards for all kinds of things like custom pages/tools/editors/projects/...).

You can grab it here.

More to come.
Cheers,
Black

Visual TorqueScript #1

I've spent most of this week learning as much about Visual Studio's extensibility features as possible and I've decided to start a tiny project to see how easy it is to add a new language service to Visual Studio.

My conclusion so far is: it's not easy and that obvious but it certainly is easier than writing your own IDE. There is also the advantage that you have access to a nice debugging environment if you manage to write your own debugger service.

Anyway, I've started to work on adding basic support for TorqueScript to Visual Studio and after much learning, I've finished implementing the colorizer for TorqueScript today:

As you see I have to use the extension .tscript for now, because as you know .cs is already reserved for C#. I'm going to fix this later by adding a TorqueScript project type that overrides this link locally for your TorqueScript projects (I could live with that).

Creating your own language services is pretty easy, if you are ok with using C# and ManagedBabel. You just have to supply the specific grammar files for the managed lexer and parser generators and you're pretty much done with the basic stuff.

The Visual Studio SDK is pretty neat (you can grab it from here if you want) and another neat thing no one has probably heard about is that Microsoft gives away the Visual Studio Shell for free - yeah for free. That means you can build your own custom tools that have the Visual Studio look and feel and you don't have to pay Microsoft a dime for it.

A good place to start in general if you're interested is the VS Extensibility Center.

Stay tuned for more features (syntax parsing and debugging featues are the next thing on my plan).
Cheers,
BlackHC

Halo: Combat Evolved

Yay, I've finally found time to play through Halo 1 for the PC. To be honest I didn't even know that I owned the game a few days ago, but when I looked through one of my drawers, I found it together with Supreme Commander and GTA: San Andreas \o/

To make playing it look like work or at least like something worthier than just playing, I've taken notes occasionally and here they are:

  • The vehicles are very easy to steer: they always automatically try to drive towards your current view direction and you only use forward and backward to accelerate and slow down. I was used to ETQW's steering system and it took me some time to get used to, but it's pretty ok for the game.
  • I like to use my rifle as club. For the first half of the game I had a really closed-combat fighting style: start shooting while running on and when you need to reload or simply are near enough, hit the enemies with your gun before jumping to cover. It was very effective against Grunts and against Elites, too, since my timing was right most of the times and I was there, when their shield broke down.
    The ability to throw grenades and shooting at the same time (pretty much) also increased combat speed a lot and made everything more fluid, because I was able to react to everything always in an effective way.
    The Flood was pretty scary at first, because I had to learn a totally different combat technique and after getting used to it during the first few hours, I took me a bit to keep my distance again. After getting the shotgun and surviving the Library level fighting against the Flood became fun or at worst slightly boring and annoying, though I still have to watch out to prevent myself from becoming surrounded.
    I think, it is a pretty neat idea to make the Flood monsters get up again after being shot for the first time. It facilitates being surrounded and increases the risk of being overrun.
  • Level design is pretty weird sometimes. And with weird I mean repetitive - just look at the Library level to see the beauty of copy'n'paste. The last level, where you have to drive the buggy through a weird obstacle course that makes no sense whatsoever is pretty awkward, too - the buggy is a bit to wide to be driven through everything elegantly using the controls and I got stuck all the time. Moreover the buggy is too slow for all the little ramps you can find and I was left wondering why they put them there (maybe the buggy's speed was changed after the level was designed?)
  • I don't like checkpoints. Especially when they're used to tune the game difficulty by spreading them lighter in later levels >_<

So far, so good. I like Halo a lot and although some levels seemed a bit stretched (Library again), playing it was fun. I don't know, what I'm going to play next, but I think that maybe I should spend some of my time coding again - nah, just joking, next I'm going to play Call of Duty or more Sam & Max...

Cheers,
Black