I have received a number of emails bringing up crashes and performance problems in the X-Plane 930 betas – some of the writers are concerned that 930 might be a lame patch, going final with crashes and lousy performance.
To assuage this concern, let me make a few comments on where we are in the beta process, the likely future schedule, and the problems themselves.
The Schedule
X-Plane 930 has been an absurdly long beta. Going into the beta I had the mindset that we should take the beta slowly to have time to discover driver bugs on a wide variety of hardware – why rush and miss something?
I think we took this too far. To run a “slow” beta we have run other development simultaneous to the beta, but that in turn has stretched the beta to epic lengths.
We are starting to try to clamp down and close out the beta now, but it is going to get interrupted again. Austin and I will be traveling to attend the
X-Plane conference in France, and from there we will spend two weeks working with Sergio in Italy. Given how rarely we go to Europe, we cannot pass up the opportunity to work with Sergio in person – we have a few problems in the sim where getting the three of us in one room is the best course of action.
Unfortunately our internet connectivity during the trip will be limited, and we can only bring some of our equipment, so closing out the beta while on the road is really not an option. Thus there will be yet another beta delay. Hopefully when we return, we can close the beta out for good.
Performance Problems
I have seen a number of emails regarding framerate with 930. A few notes on framerate and betas:
I try to save framerate for last in a beta. Most performance problems have two possible causes.
- We communicate with the video card driver in a way that is fast on our systems but astoundingly slow on other systems. We discover this from slow performance in a particular piece of the code on other hardware.
- The new beta does something new that is more expensive than what the old build did, and users have not figured out how to (or do not have a way to) turn this more expensive option off.
The solution to case 1 is to use another driver call; the solution to case 2 is to make sure the rendering options provide a way to turn the feature off. (We simply cannot guarantee that a new, nicer looking feature run without a fps penalty – we can only give you a choice between better visuals and faster fps.)
Either way, framerate work tends to be the last thing on my beta list for this reason: other bug fixes may cause framerate problems, typically in category 1 – that is, a bug fixes makes use of a new driver call that we find out has hurt performance. Thus I try to do all performance fixes at the end of beta when we won’t be adding new code.
This means that in practice, I have spent nearly zero time looking at performance. I am just starting that process this week, so it will be a little bit before I find problems.
Unfortunately often performance problems manifest only in the hardware I do not own – despite having a pile of computers in my office (a pile that seems to grow deeper and less manageable every year) there are just a ton of systems out there. So a lot of the performance bugs will get fixed by users trying experiments and reporting back to me – a slow process despite some of the really great efforts by our users.
Crashes
Crashes sometimes are manifestations of gross code defects, but often they fall into the category of driver problems too. I will be working to piece together the puzzle of strange behavior over the next few weeks; usually the solution is to not do some action that we thought was legal but fails in some hardware cases.
Don’t Panic
As always, my final message regarding the beta is: don’t panic. When it gets quiet over the next few weeks, it is because of travel, and even once Austin and I are back in the office, it will be slightly slow going to piece together problems on hardware other than our own.
Propsman caught something:
…is modifying the value of a batch of ATTR_light_level tris comparable [performance-wise] with toggling the state of a backlit generic instrument? Instinct tells me that you must have the latter more streamlined than the former, but maybe not?
He is right: in the current implementation, ATTR_light_level is probably a bit more expensive than using generic instruments. This may not be true in the future though.
- The generic instrument code is pretty tight.
- Right now ATTR_light_level sometimes has to adjust shaders, which can be expensive.
- In the future, ATTR_light_level has the potential to be very heavily optimized, while the generic instrument code will always be CPU based.
But to put it in perspective, all instrument drawing is slow compared to scenery drawing – in the scenery world we draw 50,000 triangles of identical OpenGL state in a row, and modern cards do that very, very well. In the panel, we have to put in a lot of CPU time to figure out how to draw each quad or tri-strip. Fortunately you probably don’t have 50,000 individually programmed flashing lights in your panel. Heck – there’s “only” 3608 datarefs published by the sim.
Perhaps other questions are important when picking ATTR_light_level vs. panel texture:
- Which is more useful: to be able to have several variant images and variant images that are not “lights” (this is only possible by generics) or the ability to vary the light level gradually and not just have on or off (this is only possible with ATTR_light_level)?
- Which is simpler to author given the rest of the panel?
In other words, it’s all pretty “slow”, but fortunately “slow” isn’t that slow. If your light has to blink, you may want to pick what looks best and is straightforward to author.
In my previous posts I have tried to explain the difference between commands and datarefs, and when you might use each. To review:
- A dataref represents information. You can always read it, and you might be able to change it.
- A command represents an action. You can always invoke the action, but you can’t tell if it worked without looking at a dataref.
So…why is there so much overlap and duplication?
Dataref Vs. Dataref
There is duplication in the datarefs because we don’t delete old datarefs when we add newer, improved ones. The old datarefs stay in place to keep old plugins working. Here are a few reasons why we’ve added new datarefs:
- The
cockpit2/
and flightmodel2/
sections were added as a new, simpler, easier to use interface for authors in version 9. (Read more here and here and here.)
- In some cases, the old dataref was a bit-field while the new one is a simple integer. While plugins can use bitfields, modelers cannot animate using bit fields.
- In some cases, the old dataref did not represent a clean view of the data. Some old datarefs exposed X-Plane internal structures that are not appropriate for long-term use.
To see this in action, let’s look at the autopilot. How many ways are there to set the autopilot mode?
sim/cockpit/autopilot/heading_mode
. This is the original heading mode, and it is marked deprecated, because it exposes a bunch of internal X-Plane autopilot values.
sim/cockpit/autopilot/autopilot_state
. This is the ideal autopilot dataref for plugins. It provides all functions, but since it is a bit-field it is not useful for authors.
sim/cockpit2/autopilot/heading_mode
. This is a clone of the original heading_mode into the cockpit2 domain. Honestly I am not sure how it got there – I know it was me who put it there, but it sure is a dumb idea; the original dataref is deprecated, so it was stupid of me to duplicate it!
sim/cockpit2/autopilot/heading_state
. This is coming in 930 and provides a heading-state enum set appropriate for authors…basically an enum that matches the two heading bits of the autopilot_state dataref that programmers were using.
How do you sort through this? Three rules of thumb:
- Try to use
sim/cockpit2
and sim/flightmodel2
when possible.
- More recent datarefs are usually better.
- Use the most useful dataref you can find.
Commands Vs. Commands
Sometimes there is some duplication of commands, e.g.
Here it’s a lot more obvious why there are multiple commands: they affect the carb heat in multiple ways. Typically this is done because commands are mapped to joysticks and other USB hardware; some hardware generates a button press when a command is toggled, but some hardware generates two commands, one for the off and one for the on position.
The rule of thumb is: use the command that gives you the action you want.
Commands Vs. Datarefs
Very often there will be a command and a writable dataref. Typically we need them both:
- The command is needed to let users set up their joystick and keyboard.
- The dataref predates version 9 – writing it was the only way to invoke an action.
Newer datarefs are more likely to be read-only, as we put new “changing the sim” functionality into commands. To go back to our autopilot example, we have on command: sim/autopilot/heading
that lets us arm heading mode. This command is probably preferable to any of the datarefs for changing the autopilot state.
My
previous post discusses writing to a dataref. vs. actuating a command in more detail.
It turns out that understanding what a command is doing gets really complicated.
The key idea to untangling it all is this:
- Commands have duration, and that duration is the amount of time you “hold down” the button or key that actuates the command.
- Not all commands do things for the entire duration.
An example will clarify this:
- When you press and hold down the ‘p’ key to pause the sim, the sim pauses (or unpauses) instantly the moment you press the p key. Holding the p key down for a long time does not change this. Pause is a “momentary” command.
- You have to keep the starter button/key held down for a few seconds to start an engine. If you press it and release it, the starter motor will only run for a fraction of a second, which is not enough time to start an aircraft engine. Engine start is a “duration” command.
Virtual Datarefs
A “virtual dataref” isn’t really a dataref at all – it’s a string you can enter into an OBJ or generic instrument that looks like a dataref, but actually is something else. There is one “set” of virtual datarefs in X-Plane right now.
In X-Plane, you can enter
into a generic instrument or obj animation – this creates a “virtual dataref” around the command’s “activation status”. The virtual dataref acts like a read-only integer-type dataref whose value is 0 if the command is not being pressed right now and 1 if it is.
For example, if you animate a push button using
CMND=sim/starters/engage_starter_1
(key framing the animation to be “out” when the virtual dataref is 0 and 1 when it is in) then you will have a button that appears to be pressed whenever the starters are engaged. This will happen
no matter how the starter is engaged – your animation will happen whether the user presses a joystick button, holds down a key, clicks on a manipulator, or a plugin runs the command.
Basically, virtual datarefs that provide “activation status” of commands exist so that you can animate the buttons in your virtual cockpit to match what the user is doing with the mouse, keyboard, etc. These datarefs are read-only; use trigger generic instruments and command manipulators to actually run the command.
For Programmers
In that last section I pointed out that a virtual dataref is not a real dataref at least 3 times. Why am I harping on this? Well, programmers, you cannot use
XPLMFindDataRef to access virtual datarefs yourself. Sorry.
Virtual datarefs exist to give authors of OBJs and panels access to command activation status, something they would not normally have.
Plugin programmers don’t need this – if you are programming a plugin you simply use
XPLMRegisterCommandHandler and you will be told when the command is being actuated and released. Using a command handler is a lot more efficient than reading a dataref because you will receive a call
only when the command is pressed, instead of having to check the dataref value every frame. If you need to monitor a lot of commands, the callbacks are a lot more efficient.
Command Activation Is Not The Same As System Activation
Let’s go back to the case of the “pause” command and review. Here’s what we know about the pause command.
- The command
sim/operation/pause_toggle
will change the sim’s pause state. The instant this command is pressed, the sim will pause (if it is runnning) or unpause (if it is paused).
- Holding down the
sim/operation/pause_toggle
command has no effect beyond its initial press. Hold it down for an hour, it doesn’t matter.
- The virtual dataref
CMND=sim/operation/pause_toggle
tells you if the pause command is being held down at any instant.
Here’s what we know about the pause dataref.
- The dataref sim/time/paused is 1 if the sim is paused, 0 if it is not.
- This dataref cannot be written!
So here’s what I mean when I say: command activation is not the same as system activation.
This becomes important when you start to model the buttons in airplanes. Take for example an autopilot button for altitude hold. In many planes, the button can be pushed in, and the moment you do, the altitude hold “arm” light will turn on. Keep the button held in and it doesn’t have any more effect. Altitude hold arm is a momentary command, but it has a system status indicator light.
I receive a number of questions about how to model this – and the answer is: take advantage of the fact that command activation (is the button pushed in) and system activation (is the light on) are not the same.
- You would use a read-only dataref for the autopilot state to turn on the indicator light. (There are two ways to do this: use ATTR_light_level to turn a _LIT texture on and off, or use panel texture and map a generic instrument like a rotary or an annunciator.)
- You would use a command to make the button work. Probably you’d use a command manipulator on the button mesh.
- You would key frame the button’s animation based on the virtual dataref wrapped around the command you are using with the manipulator.
The result will be a button that lights up when the AP is armed but pushes in while it is being pressed (whether via the mouse in the 3-d cockpit or a joystick button press or a keyboard button press).
In my final post, I’ll comment on how much overlap there is between datarefs and commands.
There is a lot of overlap between the datarefs and commands; very often there is both a dataref (telling information about some part of the sim) and a command (which takes action to change some part of the sim). Which should you use?
Here are some general guidelines:
- If you want to set up a joystick or keyboard, you have to use a command. The joystick and keyboard configuration dialog box lets you associate actions with a keystroke or button press, not information!
- If you need to show the status of a system (E.g. “is the landing gear down”) use a dataref. I will cover this issue in more detail in part 3, but basically only datarefs show you information.
The ambiguous case is whether to use a dataref write or a command to change a system when both exist.
- If there is a command that exactly does what you want to do, prefer the command over the dataref. For example, it is better to arm the autopilot using the commands than the datarefs. Changing the autopilot state often involves changing a lot of variables at once in complex ways. When you issue the command, that work is done for you, correctly, every time.
- If the command is not really suitable for your purpose, use a dataref. For example, to change the engine throttle position, do not use the command
sim/engines/throttle_up
to move it up “a little bit.” Use the dataref sim/cockpit2/engine/actuators/throttle_ratio
to set the throttle to the precise position you want. The throttle-up command exists so that users with no joystick or mouse wheel can fly with the keyboard by pressing the F1-F2 keys (bound to throttle-up, throttle-down). It is not meant to precisely control the throttle position!
(I will discuss why there are so much overlap between commands and datarefs in part 4.)
What is the difference between a dataref and a command? They serve different purposes in X-Plane, but it’s easy to get them confused, especially because the names can look so similar. If you only take one thing away from this comparison, it should be:
- Datarefs are information.
- Commands are actions.
Datarefs
A dataref is a single bit of published information. For example, the user’s indicated airspeed, as seen by the pilot, is a dataref, stored in:
sim/cockpit2/gauges/indicators/airspeed_kts_pilot
Datarefs have names that do not change. Datarefs made available by X-Plane start with sim/ while datarefs made available by plugins start with another prefix. Datarefs have been in X-Plane since the release of the plugin system in version 6.70.
You can always read a dataref, but sometimes you can change it. Trying to change a dataref usually has one of three actions:
- If the dataref is not writable at all, nothing happens.
- If the dataref is writable, it will change.
- Sometimes a dataref may be writable, but only after changing some other sim configuration. For example, you can only “write” to the control surface deflection datarefs after setting the control surface override dataref to 1. (If you don’t set this override, X-Plane will constantly write its own ideas of the control surface positions to the control surface datarefs and your changes will be lost.)
You can read and write datarefs:
Commands
A command is an action that the sim can take on your behalf. For example, the command
sim/autopilot/altitude_arm
arms the autopilot for altitude hold.
Like datarefs, commands have permanent names, starting with sim/ for X-Plane or other prefixes for plugins. Commands have been available in X-Plane since version 9.0.
You can always actuate a command, but there is no guarantee that it will do anything. For example, the engine starter command won’t start the engine if the plane has electrical starters and the battery is dead.
You can use commands by:
Plugins
Plugins can add both new datarefs and new commands to the sim. Plugins can also change the behavior of all built-in sim commands, and can change the information in some datarefs.
Where Do I Find Datarefs And Commands
X-Plane’s default commands and datarefs are listed in the text files Commands.txt and Datarefs.txt in the Resources/plugins folder. (Note: providing the command list is new to X-Plane 930.) The dataref list is also available on the X-Plane SDK Wiki.
Up next: when should I use a command and when should I use a dataref?
On the menu this morning: first a whiny rant, then a nerdy one.
Someone pointed me at this post. Before I go into my geeky diatribe about leveraging code, a quick note: it is true that at this point Austin is working heavily on the iPhone – probably more on the iPhone than the desktop. It is also true (but not mentioned) that Laminar is investing more man power into X-Plane for the desktop now than it ever has in the past. (With the iPhone we’ve grown our workload with a second “front” for products, but we’ve increased staffing a little bit too.)
It is also true that X-Plane 9 free updates have been less frequent. This doesn’t mean there’s less code going into them – it just means that we’re doing 4-5 months of coding and 3 months of beta instead of 2 months of codindg and 1 month of beta. I’m not sure if this is better or why it is happening (each release has to be individually planned for the circumstances) but one observation:
Given how many video cards and drivers are out there and how they react differently to the X-Plane code, I wouldn’t want a beta process less than 2-3 months, because I want to know that it’s had time to run on a wide variety of hardware. If our beta has to be at least 2-3 months, then a 3-month release cycle would have us in beta all the time!
My whiny rant (the short version) is this: Laminar Research isn’t in a position (as a company selling a product) to post all of the details of how our business operates internally. So posts that say “Laminar should do X” (where X is a business decision) drive me a bit nuts, because I can’t post a reasonable reply.
Now here’s my real point: development for the desktop and the iPhone are not a zero sum game. Clearly we leveraged the desktop sim to create the iPhone app. But it goes both ways; work on the iPhone also benefits the desktop.
Here is one example: when beta 8 comes out, Plane-Maker’s “export to object” will a much more complete OBJ, in OBJ8 format, with animations already in place for things like wing controls surfaces!
That’s a feature that people have been asking about for a while – both to make CSL objects* and as a way to save time when moving from a Plane-Maker drawn plane toa custom one. (You would first export an animated OBJ8, then start adding details in a 3-d modeling program. With beta 8, you won’t have to rebuild your control surfaces from scratch.)
But…it’s also a feature that is critical to the iPhone! The iPhone version of X-Plane uses animated OBJ8 files as well; this feature helped us internally to prepare planes for the iPhone version, but it’s also a requested feature from our authors.
So my response to those who say that the iPhone has taken away from X-Plane development is that it is not true – not only because we are developing more heavily for the desktop, but because sometimes iPhone development is for the desktop.
* I do not remember whether the current compiled versions of libXplaneMP use OBJ7 or OBJ8 objects. Since libXplaneMP uses the OBJ code from XPTools, it should be (relatively) straight forward to update libXplaneMP and the clients that use it to use OBJ8 directly. In the meantime, you can use ObjConverter to convert an exported OBJ8 back to OBJ7 for CSL use.
I receive a number of requests from authors for an attribute to tag an object as “needs maximum texture resolution” or “needs compression disabled” or “needs maximum anisotropic filtering”. The general idea is that the author wants to ensure a viewing environment that looks good.
For the most part, I am against these ideas – think of the two cases:
- If the attribute on the content is request for a relative improvement in resolution (e.g. set my object to one texture res higher than the rest of the world) then what we’ll have is an arms race – every author will set their content with this flag, and the result will be that the entire sim tends to run at one res setting higher than expected. The result: users without enough VRAM will turn their res settings down another notch and all the content will look like it did before.
- If the attribute on the content is a request for an absolute setting (e.g. load this texture at the highest resolution possible) some content will simply not run on some computers that do run X-Plane.
My general point is this: users run X-Plane with texture resolution, anisotropic filtering, and compression set to lower settings for a reason – because their hardware isn’t very fast! Forcing the sim to ignore the settings and run at a higher res won’t make the user’s video card any better – it will just take the framerate vs. visual quality tradeoff out of the hands of the user.
That’s a simplification of the issue – in fact I am sympathetic to the notion of differential settings – that is, we need to use more texture resolution for art elements that are closer to the viewer. The sim already improves airplane resolution a bit and cockpit resolution a lot. We set anisotropic filtering a bit higher on runways because they are viewed from a shallow view angle pretty much all the time during normal flight.
At this point I am looking at some more specific overrides for cockpit objects. In particular, modern cockpits are built out of many attached objects, and not just the “cockpit object” itself – reducing the resolution of these objects can make cockpit labels illegible.
If we do get extensions to improve resolution I can only say this: use them very, very sparingly! Adding the extension doesn’t improve the user’s hardware. If the user had the ability to run your airplane at extreme res without compression and 16x anisotropic filtering, he’d already be doing that!
First, we’re not going to get an OBJ materials model in 930. 930 is crammed full of stuff, and I don’t want to add anything more! I am not sure when we’ll get OBJ materials, or even if this is how it will go down. But this is what I am thinking:
- Materials will be controlled on the “batch” level, that is, by using ATTRibutes. You’ll be able to change materials mid-object.
- Materials will be grouped into “material classes” – the material class will be mutually exclusive. So if we have a metal material class and a “plastic” material class, you won’t be able to combine them.
- There will be some kind of material class that corresponds to the current behavior, and all of the existing attributes will keep working; probably there will be a newer, cleaner way to state in an OBJ what you can already do now.
- You will set all material class parameters at once. This simplifies the syntax and assures that we don’t have any strange mix & match combinations.
To this last point, consider that OBJ currently has a number of strange cases where you can set attributes that don’t have any effect. For example, you can set the shininess while drawing the cockpit texture (which is unlit). Code like this has historically been buggy, partly because it’s not necessarily obvious to a human reader what the object should do.
I do not yet know how normal and gloss maps fit in. Normal maps may be useful to multiple material classes; gloss maps are somewhat material class specific (in that if a material class has no concept of gloss, the gloss map is moot). It is definitely advantageous to pack a normal map and gloss map into a single texture (a la blender).
The main point of material classes is to create the infrastructure for complex extensions to the material system in a clean way. Adding additional attributes creates an NxN problem – for each new attribute I add, I have to consider how it interacts with every other attribute. By comparison, a new material class by definition shuts all other material classes off.
I have been trying to put documentation on the X-Plane Wiki, and use this blog for announcements and “the inside story”, rather than letting the blog turn into a poor-man’s users manual. An aircraft developer asked me via email whether there was a blog entry on some of the pitfalls of the v9 panel lighting system. There is not, and the lighting system is under-documented. I will be working on improving the documents over the next few weeks, but the point of this blog entry is: “how did we get here?”
I am a huge fan of incremental software improvement. That’s the subject of another blog post (perhaps on another blog), but for now I’ll say this: all changes to the rendering engine since version 8.0 have been incremental ones, and yet if you were to look at the code, you wouldn’t see a series of band-aids taped on top of each other. Each incremental change leaves the rendering code “fully updated”, as if it had been written yesterday. I start each new scenery feature by first reshaping the existing code into the most useful form for what we want to have in the future, and then coding the new feature is relatively simple.
But this strategy has an Achilles heal; if the code being refactored has a public interface (whether it is a file format or programming API), then all of the intermediate steps in the journey become requirements for future products in order to maintain backward compatibility.
This is not a problem as long as the programmer knows where he is going. The danger comes when one of the intermediate steps is actually a step in the wrong direction, and becomes dead weight around a future design.
A Reasonable Progression: OBJ
The OBJ 800 file format has had a reasonable progression* since its birth in version 8. It has gained a number of new features, but each one has generalized and made more powerful previous ideas, such that “legacy behaviors” are not so painful. Some examples:
- Hard surfaces may now be decks (e.g. you can fly under them) or not, and you can specify what kind of hard surface you have. The original hard surface command was simply “it’s hard” or “it’s not”. But viewed under the lense of the new scenery system, that old hard surface command implicitly implied “the surface is smooth” and “the hard surface is not a deck”. So the new hard surface command is a more general version of the old one, which continues to work under the new system.
- Animations in version 9 can be key framed; in version 8 you simply specify start and end values. But start and end values are just like having two key frames. So viewed under the lense of the new scenery system, all animations are key framed; older objects always just have two key frames. The new key framed commands are a more powerful, more general version of the old ones.
I can’t say that the relatively pain-free evolution of OBJ files over the last 4 years comes from good design or genius on my part – in truth it’s probably just good luck. But I think one thing has helped me keep the new OBJ extensions relatively sane: most of them are conceived several months before they make it into X-Plane.
I have a scenery system to-do list that will last me at least another four years; most of it is filled with things that Sergio has asked for. This to-do list acts as sort of a road map for future scenery system extensions; for any possible OBJ change, I can look at it relative to the other todo items and ask: “is this extension going to play nicely with things to come?”
(As a side note, this is one of the reasons why there are not light maps in any of the X-Plane modeling formats. Light maps don’t play well with a number of other scenery system extensions. I want to resolve the conflict between these future additions before they go into the sim.)
Wandering In the Desert
By comparison, the evolution of the panel system in version 9 has been more like wandering in the desert than a straight line toward a goal. Repeatedly, I put features into the panel system without a clear roadmap of where we would end up or how they would work together. The result is what you see now when looking over the panel documents: complexity and chaos.
Basically there are several major changes to the panel system that affect each other in strange ways:
- A more complex lighting model on the 2-d panel in version 920. (That is, the 3 2-d spot lights, and generic instruments with back-lit, mechanical, or glass lighting.)
- A more complex lighting model in the 3-d cockpit in version 930. (That is, 3-d spot lights, ATTR_cocpkit_region and generic instruments with back-lit, mechanical, or glass lighting.)
- A separate panel used only to provide texture to the 3-d cockpit. (That is, the 3-d panel.)
The problem is the order that they were invented: first ATTR_cockpit_region, then the 3-d cockpit, then back-lit generic instruments, then 2-d spot lights, and then 3-d spot lights.
The result is two sources of confusion:
- Some combinations of features simply don’t work together. Since all of the features appear to be independent, I sometimes get bug reports on these. For example, you can’t use the 2-d spot lights on the 3-d panel. This is not a bug, it is by design! I will explain why some of these limits exist in future blog posts.
- Among the remaining combinations that do work together, there are a lot of choices about how to structure a plane – too many choices!
This second point is a tricky one: X-Plane has to continue to support whatever set of features was available for any given release (864, 900, 920, 930) so that older planes continue to work. But some of those combinations (e.g. the ones that exist in version 900) don’t make a lot of sense for new planes made in 930.
I am open to ideas on how to solve this. I intend to document a “correct formula” for a modern plane, perhaps with tutorials, on the Wiki. I am also considering programming Plane-Maker to flag unusual combinations of features as a warning when saving 930 planes.
Either way, I fear I’ve learned my lesson from the panel system: incremental improvement of code is only a good idea if the programmer knows where he is going! Next time I will use Google Maps. 🙂
* I suppose that whether you think the OBJ 800’s evolution has been reasonable depends on your standards for file formats. OBJ 800 absolutely does show growing pains. I would only say: consider the number of revisions and the change in the hardware platform OBJ 800 feeds when you consider its stretch marks.